From 763e0e195b86202ef5d63674c8a6d2aca825d5e6 Mon Sep 17 00:00:00 2001 From: Zheng-Xian Li Date: Wed, 20 Oct 2021 09:36:59 +0800 Subject: [PATCH 01/14] WIP --- .../org/astraea/metrics/MetricExplorer.java | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 app/src/main/java/org/astraea/metrics/MetricExplorer.java diff --git a/app/src/main/java/org/astraea/metrics/MetricExplorer.java b/app/src/main/java/org/astraea/metrics/MetricExplorer.java new file mode 100644 index 0000000000..6c87ad9050 --- /dev/null +++ b/app/src/main/java/org/astraea/metrics/MetricExplorer.java @@ -0,0 +1,135 @@ +package org.astraea.metrics; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.IStringConverter; +import com.beust.jcommander.Parameter; +import com.beust.jcommander.ParameterException; +import java.net.MalformedURLException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import javax.management.remote.JMXServiceURL; +import org.astraea.argument.ArgumentUtil; +import org.astraea.metrics.jmx.BeanObject; +import org.astraea.metrics.jmx.BeanQuery; +import org.astraea.metrics.jmx.MBeanClient; + +public class MetricExplorer { + + static void execute(MBeanClient mBeanClient, Argument args) { + + BeanQuery.BeanQueryBuilder builder = + BeanQuery.builder( + args.fromDomainName, + args.properties.stream() + .map(Argument.CorrectPropertyFormat::toEntry) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + + if(!args.strictMatch) + builder.usePropertyListPattern(); + + Collection beanObjects = mBeanClient.queryBeans(builder.build()); + + for (BeanObject beanObject : beanObjects) { + System.out.println(beanObject); + } + + } + + public static void main(String[] args) throws Exception { + var arguments = ArgumentUtil.parseArgument(new Argument(), args); + + try (MBeanClient mBeanClient = new MBeanClient(arguments.jmxServer)) { + execute(mBeanClient, arguments); + } + } + + static class Argument { + @Parameter( + names = {"--jmx.server"}, + description = + "The JMX server address to connect to, support [hostname:port] style or JMX URI format, former style assume no security and no password used", + validateWith = ArgumentUtil.NotEmptyString.class, + converter = Argument.JmxServerUrlConverter.class, + required = true) + JMXServiceURL jmxServer; + + @Parameter( + names = {"--from-domain"}, + description = + "Show Mbeans from specific domain pattern, support wildcard(*) and any char(?)") + String fromDomainName = "*"; + + @Parameter( + names = {"--property"}, + description = "Fetch mbeans with specific property, support wildcard(*) and any char(?)", + validateWith = CorrectPropertyFormat.class) + List properties; + + @Parameter( + names = {"--strict-match"}, + description = + "Only MBeans metrics with properties completely match the given criteria shows") + boolean strictMatch = false; + + static class JmxServerUrlConverter implements IStringConverter { + + static Pattern patternOfJmxUrlStart = Pattern.compile("^service:jmx:rmi"); + static Pattern patternOfHostnameIp = + Pattern.compile("^(?[[^\\p{Punct}]&&[.]]+):(?[0-9]{1,6})$"); + + @Override + public JMXServiceURL convert(String value) { + + if (patternOfJmxUrlStart.matcher(value).find()) { + try { + return new JMXServiceURL(value); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Cannot parse given URL: " + value, e); + } + } else { + Matcher matcher = patternOfHostnameIp.matcher(value); + if (matcher.find()) { + String hostname = matcher.group("hostname"); + String ip = matcher.group("ip"); + String url = + String.format( + "service:jmx:rmi:///jndi/rmi://" + "%s" + ":" + "%s" + "/jmxrmi", hostname, ip); + try { + return new JMXServiceURL(url); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Illegal JMX url: " + url, e); + } + } else { + throw new IllegalArgumentException("Illegal hostname:ip pair: " + value); + } + } + } + } + + static class CorrectPropertyFormat implements IParameterValidator { + + static Pattern propertyPattern = Pattern.compile("^(?.+)=(?.+)$"); + + @Override + public void validate(String name, String value) throws ParameterException { + if (!propertyPattern.matcher(value).find()) + throw new ParameterException( + name + + "with value [" + + value + + "] should match format: " + + propertyPattern.pattern()); + } + + static Map.Entry toEntry(String property) { + Matcher matcher = propertyPattern.matcher(property); + if (!matcher.find()) throw new IllegalArgumentException("Not property format: " + property); + return Map.entry(matcher.group("key"), matcher.group("value")); + } + } + } +} From fdc8a46d7dbed1623420ee136cc7c4e914682087 Mon Sep 17 00:00:00 2001 From: Zheng-Xian Li Date: Mon, 1 Nov 2021 16:06:52 +0800 Subject: [PATCH 02/14] WIP 2 --- app/src/main/java/org/astraea/App.java | 3 +- .../org/astraea/metrics/MetricExplorer.java | 176 ++++++++++++++---- .../astraea/metrics/MetricExplorerTest.java | 56 ++++++ 3 files changed, 195 insertions(+), 40 deletions(-) create mode 100644 app/src/test/java/org/astraea/metrics/MetricExplorerTest.java diff --git a/app/src/main/java/org/astraea/App.java b/app/src/main/java/org/astraea/App.java index 758a068aeb..9a3c158436 100644 --- a/app/src/main/java/org/astraea/App.java +++ b/app/src/main/java/org/astraea/App.java @@ -7,6 +7,7 @@ import java.util.Map; import org.astraea.metrics.KafkaMetricClientApp; import org.astraea.performance.Performance; +import org.astraea.metrics.MetricExplorer; import org.astraea.topic.ReplicaCollie; import org.astraea.topic.TopicExplorer; @@ -14,7 +15,7 @@ public class App { private static final Map> MAIN_CLASSES = Map.of( "offset", TopicExplorer.class, - "metrics", KafkaMetricClientApp.class, + "metrics", MetricExplorer.class, "replica", ReplicaCollie.class, "performance", Performance.class); diff --git a/app/src/main/java/org/astraea/metrics/MetricExplorer.java b/app/src/main/java/org/astraea/metrics/MetricExplorer.java index 6c87ad9050..47cf86dfaa 100644 --- a/app/src/main/java/org/astraea/metrics/MetricExplorer.java +++ b/app/src/main/java/org/astraea/metrics/MetricExplorer.java @@ -5,12 +5,14 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterException; import java.net.MalformedURLException; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; import javax.management.remote.JMXServiceURL; import org.astraea.argument.ArgumentUtil; import org.astraea.metrics.jmx.BeanObject; @@ -20,23 +22,36 @@ public class MetricExplorer { static void execute(MBeanClient mBeanClient, Argument args) { - + // compile query BeanQuery.BeanQueryBuilder builder = BeanQuery.builder( args.fromDomainName, args.properties.stream() .map(Argument.CorrectPropertyFormat::toEntry) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + if (!args.strictMatch) builder.usePropertyListPattern(); - if(!args.strictMatch) - builder.usePropertyListPattern(); - + // execute query Collection beanObjects = mBeanClient.queryBeans(builder.build()); + // display result + display(beanObjects); + } + + static void display(Collection beanObjects) { for (BeanObject beanObject : beanObjects) { - System.out.println(beanObject); - } + String properties = + beanObject.getProperties().entrySet().stream() + .map(entry -> entry.getKey() + "=" + entry.getValue()) + .sorted() + .collect(Collectors.joining(",")); + System.out.printf("[%s:%s]\n", beanObject.domainName(), properties); + + Stream strings = DataUtils.elaborateData(beanObject.getAttributes()); + strings = DataUtils.streamAppendWith(" ", 4, strings); + strings.forEach(System.out::println); + } } public static void main(String[] args) throws Exception { @@ -67,7 +82,7 @@ static class Argument { names = {"--property"}, description = "Fetch mbeans with specific property, support wildcard(*) and any char(?)", validateWith = CorrectPropertyFormat.class) - List properties; + List properties = List.of(); @Parameter( names = {"--strict-match"}, @@ -75,44 +90,53 @@ static class Argument { "Only MBeans metrics with properties completely match the given criteria shows") boolean strictMatch = false; - static class JmxServerUrlConverter implements IStringConverter { - - static Pattern patternOfJmxUrlStart = Pattern.compile("^service:jmx:rmi"); - static Pattern patternOfHostnameIp = - Pattern.compile("^(?[[^\\p{Punct}]&&[.]]+):(?[0-9]{1,6})$"); - + public static class JmxServerUrlConverter implements IStringConverter { + + /** This regex used to test if a string look like a JMX URL */ + static Pattern patternOfJmxUrlStart = Pattern.compile("^service:jmx:rmi://"); + + /** + * Convert the given string to a {@link JMXServiceURL}. + * + *

Following string formats are supported + * + *

    + *
  1. From hostname and port {@code "kafka1.example.com:9875"} + *
  2. From JMX Service URL string {@code "service:jmx:rmi:///kafka1.example.come ..."} + *
+ */ @Override public JMXServiceURL convert(String value) { + Matcher matchJmxUrlStart = patternOfJmxUrlStart.matcher(value); - if (patternOfJmxUrlStart.matcher(value).find()) { - try { - return new JMXServiceURL(value); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Cannot parse given URL: " + value, e); - } + if (matchJmxUrlStart.find()) { + return convertFromJmxUrl(value); } else { - Matcher matcher = patternOfHostnameIp.matcher(value); - if (matcher.find()) { - String hostname = matcher.group("hostname"); - String ip = matcher.group("ip"); - String url = - String.format( - "service:jmx:rmi:///jndi/rmi://" + "%s" + ":" + "%s" + "/jmxrmi", hostname, ip); - try { - return new JMXServiceURL(url); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Illegal JMX url: " + url, e); - } - } else { - throw new IllegalArgumentException("Illegal hostname:ip pair: " + value); - } + return convertFromAddress(value); + } + } + + private JMXServiceURL convertFromJmxUrl(String jmxUrlString) { + try { + return new JMXServiceURL(jmxUrlString); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Cannot parse given URL: " + jmxUrlString, e); + } + } + + private JMXServiceURL convertFromAddress(String addressOfJmxServer) { + String url = String.format("service:jmx:rmi:///jndi/rmi://%s/jmxrmi", addressOfJmxServer); + try { + return new JMXServiceURL(url); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Illegal JMX Url: " + url, e); } } } - static class CorrectPropertyFormat implements IParameterValidator { + public static class CorrectPropertyFormat implements IParameterValidator { - static Pattern propertyPattern = Pattern.compile("^(?.+)=(?.+)$"); + static Pattern propertyPattern = Pattern.compile("^(?[^=]+)=(?.+)$"); @Override public void validate(String name, String value) throws ParameterException { @@ -132,4 +156,78 @@ static Map.Entry toEntry(String property) { } } } + + static class DataUtils { + + static Stream elaborateData(Object target) { + if (target == null) { + return Stream.of("null"); + } else if (target instanceof Map) { + return elaborateMap((Map) target); + } else if (target instanceof CompositeDataSupport) { + return elaborateCompositeDataSupport(((CompositeDataSupport) target)); + } else if (target instanceof CompositeData) { + return elaborateCompositeData(((CompositeData) target)); + } else if (target.getClass().isArray()) { + if (target.getClass() == long[].class) return elaborateArray((long[]) target); + else return elaborateArray((Object[]) target); + } + + // I have no idea how to display this object, just toString it. + return Stream.of(target.toString()); + } + + static Stream elaborateMap(Map data) { + return data.entrySet().stream() + .sorted(Comparator.comparing(Object::toString)) + .flatMap(DataUtils::elaborateMapEntry) + .limit(50); + } + + static Stream elaborateMapEntry(Map.Entry entry) { + List key = elaborateData(entry.getKey()).collect(Collectors.toList()); + List value = elaborateData(entry.getValue()).collect(Collectors.toList()); + + if (key.size() == 1 && value.size() > 1) + return Stream.concat( + Stream.of(String.format("\"%s\":", key.get(0))), + streamAppendWith(" ", 4, value.stream())); + + return Stream.of(String.format("\"%s\": %s", key.get(0), value.get(0))); + } + + static Stream elaborateArray(Object[] array) { + return IntStream.range(0, array.length) + .boxed() + .flatMap( + i -> { + List collect = elaborateData(array[i]).collect(Collectors.toList()); + if (collect.size() == 1) + return Stream.of(String.format("%d: %s", i, collect.get(0))); + else + return Stream.concat( + Stream.of(i + ":"), streamAppendWith(" ", 4, collect.stream())); + }); + } + + static Stream elaborateArray(long[] array) { + return IntStream.range(0, array.length).mapToObj(x -> x + ": " + array[x]).limit(20); + } + + static Stream elaborateCompositeDataSupport(CompositeDataSupport data) { + if (data.getCompositeType().getTypeName().startsWith("java.util.Map")) + return Stream.of(data.get("value").toString()); + else return elaborateArray(data.values().toArray()); + } + + static Stream elaborateCompositeData(CompositeData data) { + data.values(); + return Stream.of(); + } + + static Stream streamAppendWith(String s, int size, Stream source) { + String append = String.join("", Collections.nCopies(size, s)); + return source.map(x -> append + x); + } + } } diff --git a/app/src/test/java/org/astraea/metrics/MetricExplorerTest.java b/app/src/test/java/org/astraea/metrics/MetricExplorerTest.java new file mode 100644 index 0000000000..3f6128e0ed --- /dev/null +++ b/app/src/test/java/org/astraea/metrics/MetricExplorerTest.java @@ -0,0 +1,56 @@ +package org.astraea.metrics; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class MetricExplorerTest { + + @ParameterizedTest + @CsvSource( + delimiterString = "->", + value = { + "service:jmx:rmi://localhost:1234/jndi/rmi://localhost:2344/jmxrmi -> match", + "service:jmx:rmi:///jndi/rmi://:9999/jmxrmi -> match", + "service:jmx:rmi://myhost/jndi/rmi://myhost:1099/myhost/myjmxconnector -> match", + "service:jmx:rmi:///jndi/rmi://localhost:9987/jmxrmi -> match", + "aaaaaaaaaaaaaaaaaaa -> no-match", + "bbbbbbbbbbbbbbbbbbb -> no-match", + }) + void testPatternJmxUrlStart(String url, String expected) { + // arrange + Pattern pattern = MetricExplorer.Argument.JmxServerUrlConverter.patternOfJmxUrlStart; + + // act + Matcher matcher = pattern.matcher(url); + + // assert + assertEquals(expected, matcher.find() ? "match" : "no-match"); + } + + @ParameterizedTest + @CsvSource( + delimiterString = "->", + value = { + "type=Memory -> type -> Memory", + "type=OperatingSystem -> type -> OperatingSystem", + "key=value -> key -> value", + "logDirectory=\"/tmp/kafka-logs\" -> logDirectory -> \"/tmp/kafka-logs\"", + "face=\"= =\" -> face -> \"= =\"", + }) + void testPatternProperty(String property, String key, String value) { + // arrange + Pattern pattern = MetricExplorer.Argument.CorrectPropertyFormat.propertyPattern; + + // act + Matcher matcher = pattern.matcher(property); + + // assert + assertTrue(matcher.find()); + assertEquals(key, matcher.group("key")); + assertEquals(value, matcher.group("value")); + } +} From 1784fe47105dabfa03d8ff3e762987040806055f Mon Sep 17 00:00:00 2001 From: Zheng-Xian Li Date: Mon, 1 Nov 2021 16:03:00 +0800 Subject: [PATCH 03/14] WIP 3 --- .../org/astraea/metrics/MetricExplorer.java | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/astraea/metrics/MetricExplorer.java b/app/src/main/java/org/astraea/metrics/MetricExplorer.java index 47cf86dfaa..39e627db3e 100644 --- a/app/src/main/java/org/astraea/metrics/MetricExplorer.java +++ b/app/src/main/java/org/astraea/metrics/MetricExplorer.java @@ -162,12 +162,12 @@ static class DataUtils { static Stream elaborateData(Object target) { if (target == null) { return Stream.of("null"); + } else if (target instanceof List) { + return elaborateList((List) target); } else if (target instanceof Map) { return elaborateMap((Map) target); } else if (target instanceof CompositeDataSupport) { return elaborateCompositeDataSupport(((CompositeDataSupport) target)); - } else if (target instanceof CompositeData) { - return elaborateCompositeData(((CompositeData) target)); } else if (target.getClass().isArray()) { if (target.getClass() == long[].class) return elaborateArray((long[]) target); else return elaborateArray((Object[]) target); @@ -214,15 +214,24 @@ static Stream elaborateArray(long[] array) { return IntStream.range(0, array.length).mapToObj(x -> x + ": " + array[x]).limit(20); } - static Stream elaborateCompositeDataSupport(CompositeDataSupport data) { - if (data.getCompositeType().getTypeName().startsWith("java.util.Map")) - return Stream.of(data.get("value").toString()); - else return elaborateArray(data.values().toArray()); + static Stream elaborateList(List list) { + return IntStream.range(0, list.size()) + .boxed() + .flatMap(i -> { + List collect = elaborateData(list.get(i)).collect(Collectors.toList()); + if (collect.size() == 1) + return Stream.of(String.format("%d: %s", i, collect.get(0))); + else + return Stream.concat( + Stream.of(i + ":"), streamAppendWith(" ", 4, collect.stream())); + }); } - static Stream elaborateCompositeData(CompositeData data) { - data.values(); - return Stream.of(); + static Stream elaborateCompositeDataSupport(CompositeDataSupport data) { + return data.getCompositeType().keySet().stream() + .sorted() + .flatMap(key -> DataUtils.elaborateMapEntry(Map.entry(key, data.get(key)))) + .limit(50); } static Stream streamAppendWith(String s, int size, Stream source) { From 6d00274202d7d09b6fe0a0752f6475216775bfed Mon Sep 17 00:00:00 2001 From: Zheng-Xian Li Date: Tue, 2 Nov 2021 22:58:28 +0800 Subject: [PATCH 04/14] spotless --- app/src/main/java/org/astraea/App.java | 3 +-- .../java/org/astraea/metrics/MetricExplorer.java | 14 +++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/astraea/App.java b/app/src/main/java/org/astraea/App.java index 9a3c158436..a6ef0c4393 100644 --- a/app/src/main/java/org/astraea/App.java +++ b/app/src/main/java/org/astraea/App.java @@ -5,9 +5,8 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.astraea.metrics.KafkaMetricClientApp; -import org.astraea.performance.Performance; import org.astraea.metrics.MetricExplorer; +import org.astraea.performance.Performance; import org.astraea.topic.ReplicaCollie; import org.astraea.topic.TopicExplorer; diff --git a/app/src/main/java/org/astraea/metrics/MetricExplorer.java b/app/src/main/java/org/astraea/metrics/MetricExplorer.java index 39e627db3e..66a93e46c2 100644 --- a/app/src/main/java/org/astraea/metrics/MetricExplorer.java +++ b/app/src/main/java/org/astraea/metrics/MetricExplorer.java @@ -11,7 +11,6 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; -import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeDataSupport; import javax.management.remote.JMXServiceURL; import org.astraea.argument.ArgumentUtil; @@ -216,22 +215,23 @@ static Stream elaborateArray(long[] array) { static Stream elaborateList(List list) { return IntStream.range(0, list.size()) - .boxed() - .flatMap(i -> { + .boxed() + .flatMap( + i -> { List collect = elaborateData(list.get(i)).collect(Collectors.toList()); if (collect.size() == 1) return Stream.of(String.format("%d: %s", i, collect.get(0))); else return Stream.concat( - Stream.of(i + ":"), streamAppendWith(" ", 4, collect.stream())); + Stream.of(i + ":"), streamAppendWith(" ", 4, collect.stream())); }); } static Stream elaborateCompositeDataSupport(CompositeDataSupport data) { return data.getCompositeType().keySet().stream() - .sorted() - .flatMap(key -> DataUtils.elaborateMapEntry(Map.entry(key, data.get(key)))) - .limit(50); + .sorted() + .flatMap(key -> DataUtils.elaborateMapEntry(Map.entry(key, data.get(key)))) + .limit(50); } static Stream streamAppendWith(String s, int size, Stream source) { From f1e0b94fa4a7a7c6b6288845b4989a8e28e6f1e2 Mon Sep 17 00:00:00 2001 From: Zheng-Xian Li Date: Sat, 6 Nov 2021 13:34:21 +0800 Subject: [PATCH 05/14] able to view object name as list --- .../org/astraea/metrics/MetricExplorer.java | 122 +++++++++++++++++- 1 file changed, 118 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/astraea/metrics/MetricExplorer.java b/app/src/main/java/org/astraea/metrics/MetricExplorer.java index 66a93e46c2..2922b998a8 100644 --- a/app/src/main/java/org/astraea/metrics/MetricExplorer.java +++ b/app/src/main/java/org/astraea/metrics/MetricExplorer.java @@ -34,15 +34,19 @@ static void execute(MBeanClient mBeanClient, Argument args) { Collection beanObjects = mBeanClient.queryBeans(builder.build()); // display result - display(beanObjects); + if (args.viewObjectNameList) displayObjectNameList(beanObjects); + else displayBeanObjects(beanObjects); } - static void display(Collection beanObjects) { - for (BeanObject beanObject : beanObjects) { + static void displayBeanObjects(Collection beanObjects) { + var propertyOrderMap = propertyOrder(beanObjects); + var collect = sortBeanObjects(beanObjects, propertyOrderMap); + + for (BeanObject beanObject : collect) { String properties = beanObject.getProperties().entrySet().stream() + .sorted(Comparator.comparing(x -> propertyOrderMap.get(x.getKey()))) .map(entry -> entry.getKey() + "=" + entry.getValue()) - .sorted() .collect(Collectors.joining(",")); System.out.printf("[%s:%s]\n", beanObject.domainName(), properties); @@ -53,6 +57,111 @@ static void display(Collection beanObjects) { } } + static void displayObjectNameList(Collection beanObjects) { + var propertyOrderMap = propertyOrder(beanObjects); + var collect = sortBeanObjects(beanObjects, propertyOrderMap); + + collect.forEach( + x -> + System.out.printf( + "%s:%s\n", + x.domainName(), + x.getProperties().entrySet().stream() + .sorted(Comparator.comparingLong(z -> propertyOrderMap.get(z.getKey()))) + .map(z -> z.getKey() + "=" + z.getValue()) + .collect(Collectors.joining(",")))); + } + + /** + * generate the order of property entry, the lower number the higher priority + * + * @param beanObjects Mbeans objects, the order is decided by some property or statistics based on + * the given Mbeans. + * @return a {@link Map} describe the priority of each property key + */ + static Map propertyOrder(Collection beanObjects) { + + // count the frequency of each property key name + // property who has higher frequency will close to the tree root when printing result + Map propertyFrequencyMap = + beanObjects.stream() + .flatMap(x -> x.getProperties().entrySet().stream()) + .map(Map.Entry::getKey) + .collect(Collectors.groupingBy(x -> x, Collectors.counting())); + + // the order for sorting properties + Map propertyOrderMap = + propertyFrequencyMap.entrySet().stream() + .map(entry -> Map.entry(entry.getKey(), beanObjects.size() + 1 - entry.getValue())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + // the property key "name" will always has the lowest priority, just like how JMC does it. + propertyOrderMap.put("name", beanObjects.size() + 1L); + + return propertyOrderMap; + } + + /** + * sort the given bean objects based on the given property order. The order is first decide by the + * alphabet order of domain name, then decided by the given property key order. + * + * @param beanObjects the Mbeans to be sorted. + * @param propertyOrderMap the order of property key, the lower number the higher priority. + * @return a new {@link List} consist of all the given Mbeans, all Mbeans are sort by given + * property order. + */ + static List sortBeanObjects( + Collection beanObjects, Map propertyOrderMap) { + return beanObjects.stream() + .sorted( + Comparator.comparing(BeanObject::domainName) + .thenComparing( + (o1, o2) -> { + List> order1 = + o1.getProperties().entrySet().stream() + .sorted(Comparator.comparing(x -> propertyOrderMap.get(x.getKey()))) + .collect(Collectors.toList()); + List> order2 = + o2.getProperties().entrySet().stream() + .sorted(Comparator.comparing(x -> propertyOrderMap.get(x.getKey()))) + .collect(Collectors.toList()); + + // how the comparison works: + // 1. sort all properties, now property key/value with higher key frequency + // will show first. + // 2. sort by property key/value amount + // 3. compare the 1'st property key + // 4. compare the 2'nd property key + // 5. etc... + // 6. compare the 1'st property value + // 7. compare the 2'nd property value + // 8. etc... + // 9. object name equal + + if (order1.size() != order2.size()) { + return -Integer.compare(order1.size(), order2.size()); + } else { + var key = + Comparator.comparing( + (Map.Entry x) -> propertyOrderMap.get(x.getKey())); + var value = Map.Entry.comparingByValue(); + + for (int i = 0; i < order1.size(); i++) { + int result = key.compare(order1.get(i), order2.get(i)); + if (result != 0) return result; + } + + for (int i = 0; i < order1.size(); i++) { + int result = value.compare(order1.get(i), order2.get(i)); + if (result != 0) return result; + } + + return 0; + } + })) + .collect(Collectors.toList()); + } + public static void main(String[] args) throws Exception { var arguments = ArgumentUtil.parseArgument(new Argument(), args); @@ -89,6 +198,11 @@ static class Argument { "Only MBeans metrics with properties completely match the given criteria shows") boolean strictMatch = false; + @Parameter( + names = {"--view-object-name-list"}, + description = "Show the list view of MBeans' domain name & properties") + boolean viewObjectNameList = false; + public static class JmxServerUrlConverter implements IStringConverter { /** This regex used to test if a string look like a JMX URL */ From 5af26d163110b17b443819d7d6316272945d68e2 Mon Sep 17 00:00:00 2001 From: Zheng-Xian Li Date: Sat, 6 Nov 2021 14:37:41 +0800 Subject: [PATCH 06/14] add unit tests --- .../org/astraea/metrics/MetricExplorer.java | 4 +- .../astraea/metrics/MetricExplorerTest.java | 71 ++++++++++++++++++- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/astraea/metrics/MetricExplorer.java b/app/src/main/java/org/astraea/metrics/MetricExplorer.java index 2922b998a8..0078c00cbf 100644 --- a/app/src/main/java/org/astraea/metrics/MetricExplorer.java +++ b/app/src/main/java/org/astraea/metrics/MetricExplorer.java @@ -38,7 +38,7 @@ static void execute(MBeanClient mBeanClient, Argument args) { else displayBeanObjects(beanObjects); } - static void displayBeanObjects(Collection beanObjects) { + private static void displayBeanObjects(Collection beanObjects) { var propertyOrderMap = propertyOrder(beanObjects); var collect = sortBeanObjects(beanObjects, propertyOrderMap); @@ -57,7 +57,7 @@ static void displayBeanObjects(Collection beanObjects) { } } - static void displayObjectNameList(Collection beanObjects) { + private static void displayObjectNameList(Collection beanObjects) { var propertyOrderMap = propertyOrder(beanObjects); var collect = sortBeanObjects(beanObjects, propertyOrderMap); diff --git a/app/src/test/java/org/astraea/metrics/MetricExplorerTest.java b/app/src/test/java/org/astraea/metrics/MetricExplorerTest.java index 3f6128e0ed..4b9b4d7968 100644 --- a/app/src/test/java/org/astraea/metrics/MetricExplorerTest.java +++ b/app/src/test/java/org/astraea/metrics/MetricExplorerTest.java @@ -1,11 +1,22 @@ package org.astraea.metrics; +import static org.astraea.metrics.MetricExplorer.*; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.astraea.argument.ArgumentUtil; +import org.astraea.metrics.jmx.BeanObject; +import org.astraea.metrics.jmx.MBeanClient; +import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; class MetricExplorerTest { @@ -22,7 +33,7 @@ class MetricExplorerTest { }) void testPatternJmxUrlStart(String url, String expected) { // arrange - Pattern pattern = MetricExplorer.Argument.JmxServerUrlConverter.patternOfJmxUrlStart; + Pattern pattern = Argument.JmxServerUrlConverter.patternOfJmxUrlStart; // act Matcher matcher = pattern.matcher(url); @@ -43,7 +54,7 @@ void testPatternJmxUrlStart(String url, String expected) { }) void testPatternProperty(String property, String key, String value) { // arrange - Pattern pattern = MetricExplorer.Argument.CorrectPropertyFormat.propertyPattern; + Pattern pattern = Argument.CorrectPropertyFormat.propertyPattern; // act Matcher matcher = pattern.matcher(property); @@ -53,4 +64,60 @@ void testPatternProperty(String property, String key, String value) { assertEquals(key, matcher.group("key")); assertEquals(value, matcher.group("value")); } + + @ParameterizedTest + @ValueSource( + strings = { + "--jmx.server localhost:5566 --from-domain example.com", + "--jmx.server localhost:5566 --from-domain example.com --view-object-name-list", + }) + void executeDoesPrintSomething(String args) { + // arrange + var argument = ArgumentUtil.parseArgument(new Argument(), args.split(" ")); + var mockMBeanClient = mock(MBeanClient.class); + when(mockMBeanClient.queryBeans(any())) + .thenReturn(List.of(new BeanObject("example.com", Map.of("key", "value"), Map.of()))); + var stdout = System.out; + var mockOutput = new ByteArrayOutputStream(); + System.setOut(new PrintStream(mockOutput)); + + // act + Executable executable = () -> MetricExplorer.execute(mockMBeanClient, argument); + + // assert + assertDoesNotThrow(executable); + assertTrue(mockOutput.toString().contains("example.com")); + assertTrue(mockOutput.toString().contains("key")); + assertTrue(mockOutput.toString().contains("value")); + + // restore + System.setOut(stdout); + } + + @ParameterizedTest + @CsvSource( + delimiterString = "(is", + value = { + "--jmx.server localhost:5566 (is ok", + "--jmx.server localhost:5566 --from-domain kafka.log (is ok", + "--jmx.server localhost:5566 --property type=Memory (is ok", + "--jmx.server localhost:5566 --strict-match --property type=Memory (is ok", + "--jmx.server localhost:5566 --view-object-name-list (is ok", + "wuewuewuewue (is not ok", + "--view-object-name-list (is not ok", + }) + void ensureArgumentWorking(String argumentString, String outcome) { + // arrange + String[] arguments = argumentString.split(" "); + + // act + Executable doParsing = () -> ArgumentUtil.parseArgument(new Argument(), arguments); + + // assert + if (outcome.equals("ok")) { + assertDoesNotThrow(doParsing); + } else if (outcome.equals("not ok")) { + assertThrows(Exception.class, doParsing); + } + } } From ac392a21b51bf768e55aa98127f959e689e3aa30 Mon Sep 17 00:00:00 2001 From: Zheng-Xian Li Date: Sat, 6 Nov 2021 14:55:49 +0800 Subject: [PATCH 07/14] update README.md --- README.md | 13 +++++++++++-- .../java/org/astraea/metrics/MetricExplorer.java | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f45af32a61..2423463650 100644 --- a/README.md +++ b/README.md @@ -136,17 +136,26 @@ Run the tool from source code ```shell ./gradlew run --args="metrics --jmx.server 192.168.50.178:1099" +./gradlew run --args="metrics --jmx.server 192.168.50.178:1099 --property type=Memory" +./gradlew run --args="metrics --jmx.server 192.168.50.178:1099 --view-object-name-list" +./gradlew run --args="metrics --jmx.server 192.168.50.178:1099 --from-domain kafka.network --property request=Metadata --property name=LocalTimeMs" ``` Run the tool from release ```shell java -jar app-0.0.1-SNAPSHOT-all.jar metrics --jmx.server 192.168.50.178:1099 +java -jar app-0.0.1-SNAPSHOT-all.jar metrics --jmx.server 192.168.50.178:1099 --property type=Memory +java -jar app-0.0.1-SNAPSHOT-all.jar metrics --jmx.server 192.168.50.178:1099 --view-object-name-list +java -jar app-0.0.1-SNAPSHOT-all.jar metrics --jmx.server 192.168.50.178:1099 --from-domain kafka.network --property request=Metadata --property name=LocalTimeMs ``` ### Metric Client Configurations -1. --jmx.server: the address to connect to Kafka JMX remote server -2. --metrics: the Mbean metric to fetch. Default: All metrics +1. --jmx.server: the address to connect to Kafka JMX remote server. +2. --from-domain: query Mbeans from the specific domain name (support wildcard "\*" and "?"). Default: "\*". +3. --property: query mbeans with the specific property (support wildcard "\*" and "?"). You can specify this argument multiple times. Default: []. +4. --strict-match: only Mbeans with its object name completely match the given criteria shows. Default: false. +5. --view-object-name-list: show the list view of MBeans' domain name & properties. Default: false. --- diff --git a/app/src/main/java/org/astraea/metrics/MetricExplorer.java b/app/src/main/java/org/astraea/metrics/MetricExplorer.java index 0078c00cbf..991d867a20 100644 --- a/app/src/main/java/org/astraea/metrics/MetricExplorer.java +++ b/app/src/main/java/org/astraea/metrics/MetricExplorer.java @@ -174,7 +174,7 @@ static class Argument { @Parameter( names = {"--jmx.server"}, description = - "The JMX server address to connect to, support [hostname:port] style or JMX URI format, former style assume no security and no password used", + "The JMX server address to connect to, support [hostname:port] style or JMX URI format", validateWith = ArgumentUtil.NotEmptyString.class, converter = Argument.JmxServerUrlConverter.class, required = true) From ed06e238504e8c690e17bfef105e2f5895ac50cb Mon Sep 17 00:00:00 2001 From: Zheng-Xian Li Date: Sat, 6 Nov 2021 15:18:20 +0800 Subject: [PATCH 08/14] remove KafkaMetricClientApp --- .../astraea/metrics/KafkaMetricClientApp.java | 88 ------------------- .../kafka/KafkaMetricClientAppTest.java | 33 ------- 2 files changed, 121 deletions(-) delete mode 100644 app/src/main/java/org/astraea/metrics/KafkaMetricClientApp.java delete mode 100644 app/src/test/java/org/astraea/metrics/kafka/KafkaMetricClientAppTest.java diff --git a/app/src/main/java/org/astraea/metrics/KafkaMetricClientApp.java b/app/src/main/java/org/astraea/metrics/KafkaMetricClientApp.java deleted file mode 100644 index 64ed43df31..0000000000 --- a/app/src/main/java/org/astraea/metrics/KafkaMetricClientApp.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.astraea.metrics; - -import com.beust.jcommander.Parameter; -import java.net.MalformedURLException; -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; -import java.util.Arrays; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import javax.management.remote.JMXServiceURL; -import org.astraea.argument.ArgumentUtil; -import org.astraea.metrics.jmx.MBeanClient; -import org.astraea.metrics.kafka.BrokerTopicMetricsResult; -import org.astraea.metrics.kafka.KafkaMetrics; - -public final class KafkaMetricClientApp { - - public static String JMX_URI_FORMAT = "service:jmx:rmi:///jndi/rmi://" + "%s" + "/jmxrmi"; - - public static String createJmxUrl(String address) { - return String.format(JMX_URI_FORMAT, address); - } - - public static void main(String[] args) throws MalformedURLException { - var parameters = ArgumentUtil.parseArgument(new Argument(), args); - - String argumentJmxServerNetworkAddress = parameters.address; - Set argumentTargetMetrics = parameters.metrics; - - JMXServiceURL serviceURL = new JMXServiceURL(createJmxUrl(argumentJmxServerNetworkAddress)); - try (MBeanClient mBeanClient = new MBeanClient(serviceURL)) { - - // find the actual metrics to fetch. - List metrics = - argumentTargetMetrics.stream() - .map(KafkaMetrics.BrokerTopic::of) - .collect(Collectors.toUnmodifiableList()); - - while (!Thread.interrupted()) { - // fetch - List collect = - metrics.stream() - .map(x -> x.fetch(mBeanClient)) - .collect(Collectors.toUnmodifiableList()); - - // output - System.out.println( - "[" - + LocalTime.now().format(DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM)) - + "]"); - for (BrokerTopicMetricsResult result : collect) System.out.println(result); - System.out.println(); - - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - break; - } - } - - } catch (Exception e) { - e.printStackTrace(); - } - } - - static class Argument { - @Parameter( - names = {"--jmx.server"}, - description = "The address to connect to JMX remote server", - validateWith = ArgumentUtil.NotEmptyString.class, - required = true) - String address; - - @Parameter( - names = {"--metrics"}, - variableArity = true, - converter = ArgumentUtil.StringSetConverter.class, - description = - "The metric names you want. If no metric name specified in argument, all metrics will be selected.") - Set metrics = - Arrays.stream(KafkaMetrics.BrokerTopic.values()) - .map(KafkaMetrics.BrokerTopic::metricName) - .collect(Collectors.toUnmodifiableSet()); - } -} diff --git a/app/src/test/java/org/astraea/metrics/kafka/KafkaMetricClientAppTest.java b/app/src/test/java/org/astraea/metrics/kafka/KafkaMetricClientAppTest.java deleted file mode 100644 index a8d7ac5ce4..0000000000 --- a/app/src/test/java/org/astraea/metrics/kafka/KafkaMetricClientAppTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.astraea.metrics.kafka; - -import static org.junit.jupiter.api.Assertions.*; - -import com.beust.jcommander.ParameterException; -import java.io.OutputStream; -import java.io.PrintStream; -import org.astraea.metrics.KafkaMetricClientApp; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class KafkaMetricClientAppTest { - - private final PrintStream stderr = System.err; - - @BeforeEach - void beforeEach() { - // swallow stderr output to null stream, avoid help() info from KafkaMetricClientApp - System.setErr(new PrintStream(OutputStream.nullOutputStream())); - } - - @AfterEach - void afterEach() { - // restore stderr stream - System.setErr(stderr); - } - - @Test - void testBadArguments() { - assertThrows(ParameterException.class, () -> KafkaMetricClientApp.main(new String[0])); - } -} From 057ced656d14a6b114112982f35982add6e8a735 Mon Sep 17 00:00:00 2001 From: Zheng-Xian Li Date: Tue, 9 Nov 2021 23:39:47 +0800 Subject: [PATCH 09/14] rename flag --from-domain to --domain --- README.md | 6 +++--- app/src/main/java/org/astraea/metrics/MetricExplorer.java | 2 +- .../test/java/org/astraea/metrics/MetricExplorerTest.java | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2423463650..921b3a6ed9 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ Run the tool from source code ./gradlew run --args="metrics --jmx.server 192.168.50.178:1099" ./gradlew run --args="metrics --jmx.server 192.168.50.178:1099 --property type=Memory" ./gradlew run --args="metrics --jmx.server 192.168.50.178:1099 --view-object-name-list" -./gradlew run --args="metrics --jmx.server 192.168.50.178:1099 --from-domain kafka.network --property request=Metadata --property name=LocalTimeMs" +./gradlew run --args="metrics --jmx.server 192.168.50.178:1099 --domain kafka.network --property request=Metadata --property name=LocalTimeMs" ``` Run the tool from release @@ -146,13 +146,13 @@ Run the tool from release java -jar app-0.0.1-SNAPSHOT-all.jar metrics --jmx.server 192.168.50.178:1099 java -jar app-0.0.1-SNAPSHOT-all.jar metrics --jmx.server 192.168.50.178:1099 --property type=Memory java -jar app-0.0.1-SNAPSHOT-all.jar metrics --jmx.server 192.168.50.178:1099 --view-object-name-list -java -jar app-0.0.1-SNAPSHOT-all.jar metrics --jmx.server 192.168.50.178:1099 --from-domain kafka.network --property request=Metadata --property name=LocalTimeMs +java -jar app-0.0.1-SNAPSHOT-all.jar metrics --jmx.server 192.168.50.178:1099 --domain kafka.network --property request=Metadata --property name=LocalTimeMs ``` ### Metric Client Configurations 1. --jmx.server: the address to connect to Kafka JMX remote server. -2. --from-domain: query Mbeans from the specific domain name (support wildcard "\*" and "?"). Default: "\*". +2. --domain: query Mbeans from the specific domain name (support wildcard "\*" and "?"). Default: "\*". 3. --property: query mbeans with the specific property (support wildcard "\*" and "?"). You can specify this argument multiple times. Default: []. 4. --strict-match: only Mbeans with its object name completely match the given criteria shows. Default: false. 5. --view-object-name-list: show the list view of MBeans' domain name & properties. Default: false. diff --git a/app/src/main/java/org/astraea/metrics/MetricExplorer.java b/app/src/main/java/org/astraea/metrics/MetricExplorer.java index 991d867a20..9770f120ce 100644 --- a/app/src/main/java/org/astraea/metrics/MetricExplorer.java +++ b/app/src/main/java/org/astraea/metrics/MetricExplorer.java @@ -181,7 +181,7 @@ static class Argument { JMXServiceURL jmxServer; @Parameter( - names = {"--from-domain"}, + names = {"--domain"}, description = "Show Mbeans from specific domain pattern, support wildcard(*) and any char(?)") String fromDomainName = "*"; diff --git a/app/src/test/java/org/astraea/metrics/MetricExplorerTest.java b/app/src/test/java/org/astraea/metrics/MetricExplorerTest.java index 4b9b4d7968..7aa324c682 100644 --- a/app/src/test/java/org/astraea/metrics/MetricExplorerTest.java +++ b/app/src/test/java/org/astraea/metrics/MetricExplorerTest.java @@ -68,8 +68,8 @@ void testPatternProperty(String property, String key, String value) { @ParameterizedTest @ValueSource( strings = { - "--jmx.server localhost:5566 --from-domain example.com", - "--jmx.server localhost:5566 --from-domain example.com --view-object-name-list", + "--jmx.server localhost:5566 --domain example.com", + "--jmx.server localhost:5566 --domain example.com --view-object-name-list", }) void executeDoesPrintSomething(String args) { // arrange @@ -99,7 +99,7 @@ void executeDoesPrintSomething(String args) { delimiterString = "(is", value = { "--jmx.server localhost:5566 (is ok", - "--jmx.server localhost:5566 --from-domain kafka.log (is ok", + "--jmx.server localhost:5566 --domain kafka.log (is ok", "--jmx.server localhost:5566 --property type=Memory (is ok", "--jmx.server localhost:5566 --strict-match --property type=Memory (is ok", "--jmx.server localhost:5566 --view-object-name-list (is ok", From cf23894692894a435a56503064996d87dfbb9d02 Mon Sep 17 00:00:00 2001 From: Zheng-Xian Li Date: Tue, 9 Nov 2021 23:42:41 +0800 Subject: [PATCH 10/14] update README.md --- README.md | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 921b3a6ed9..5b54a8bf42 100644 --- a/README.md +++ b/README.md @@ -128,28 +128,44 @@ This project offers a way to run kafka official tool by container. For example: --- -## Kafka Metric Client +## Kafka Metric Explorer This tool can be used to access Kafka's MBean metrics via JMX. Run the tool from source code ```shell +# fetch every Mbeans from specific JMX server. ./gradlew run --args="metrics --jmx.server 192.168.50.178:1099" + +# fetch any Mbean that its object name contains property "type=Memory". ./gradlew run --args="metrics --jmx.server 192.168.50.178:1099 --property type=Memory" -./gradlew run --args="metrics --jmx.server 192.168.50.178:1099 --view-object-name-list" + +# fetch any Mbean that belongs to "kafka.network" domain name, +# and it's object name contains two properties "request=Metadata" and "name=LocalTimeMs". ./gradlew run --args="metrics --jmx.server 192.168.50.178:1099 --domain kafka.network --property request=Metadata --property name=LocalTimeMs" + +# list all Mbeans' object name on specific JMX server. +./gradlew run --args="metrics --jmx.server 192.168.50.178:1099 --view-object-name-list" ``` Run the tool from release ```shell +# fetch every Mbeans from specific JMX server. java -jar app-0.0.1-SNAPSHOT-all.jar metrics --jmx.server 192.168.50.178:1099 + +# fetch any Mbean that its object name contains property "type=Memory". java -jar app-0.0.1-SNAPSHOT-all.jar metrics --jmx.server 192.168.50.178:1099 --property type=Memory -java -jar app-0.0.1-SNAPSHOT-all.jar metrics --jmx.server 192.168.50.178:1099 --view-object-name-list + +# fetch any Mbean that belongs to "kafka.network" domain name, +# and it's object name contains two properties "request=Metadata" and "name=LocalTimeMs". java -jar app-0.0.1-SNAPSHOT-all.jar metrics --jmx.server 192.168.50.178:1099 --domain kafka.network --property request=Metadata --property name=LocalTimeMs + +# list all Mbeans' object name on specific JMX server. +java -jar app-0.0.1-SNAPSHOT-all.jar metrics --jmx.server 192.168.50.178:1099 --view-object-name-list ``` -### Metric Client Configurations +### Metric Explorer Configurations 1. --jmx.server: the address to connect to Kafka JMX remote server. 2. --domain: query Mbeans from the specific domain name (support wildcard "\*" and "?"). Default: "\*". From c2323639d9031324a3a6975818ecc2a70ab29bdb Mon Sep 17 00:00:00 2001 From: Zheng-Xian Li Date: Wed, 10 Nov 2021 00:20:44 +0800 Subject: [PATCH 11/14] remove output hard limit Some hard limit has been apply to the DataUtil#elaborate* series function. This commit remove them. --- app/src/main/java/org/astraea/metrics/MetricExplorer.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/astraea/metrics/MetricExplorer.java b/app/src/main/java/org/astraea/metrics/MetricExplorer.java index 9770f120ce..92f670f3a1 100644 --- a/app/src/main/java/org/astraea/metrics/MetricExplorer.java +++ b/app/src/main/java/org/astraea/metrics/MetricExplorer.java @@ -293,8 +293,7 @@ static Stream elaborateData(Object target) { static Stream elaborateMap(Map data) { return data.entrySet().stream() .sorted(Comparator.comparing(Object::toString)) - .flatMap(DataUtils::elaborateMapEntry) - .limit(50); + .flatMap(DataUtils::elaborateMapEntry); } static Stream elaborateMapEntry(Map.Entry entry) { @@ -324,7 +323,7 @@ static Stream elaborateArray(Object[] array) { } static Stream elaborateArray(long[] array) { - return IntStream.range(0, array.length).mapToObj(x -> x + ": " + array[x]).limit(20); + return IntStream.range(0, array.length).mapToObj(x -> x + ": " + array[x]); } static Stream elaborateList(List list) { @@ -344,8 +343,7 @@ static Stream elaborateList(List list) { static Stream elaborateCompositeDataSupport(CompositeDataSupport data) { return data.getCompositeType().keySet().stream() .sorted() - .flatMap(key -> DataUtils.elaborateMapEntry(Map.entry(key, data.get(key)))) - .limit(50); + .flatMap(key -> DataUtils.elaborateMapEntry(Map.entry(key, data.get(key)))); } static Stream streamAppendWith(String s, int size, Stream source) { From d0faca57eb81cd11496ab35f923d700e4c276f5a Mon Sep 17 00:00:00 2001 From: Zheng-Xian Li Date: Wed, 10 Nov 2021 00:27:00 +0800 Subject: [PATCH 12/14] Revert "remove output hard limit" This reverts commit c2323639d9031324a3a6975818ecc2a70ab29bdb. --- app/src/main/java/org/astraea/metrics/MetricExplorer.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/astraea/metrics/MetricExplorer.java b/app/src/main/java/org/astraea/metrics/MetricExplorer.java index 92f670f3a1..9770f120ce 100644 --- a/app/src/main/java/org/astraea/metrics/MetricExplorer.java +++ b/app/src/main/java/org/astraea/metrics/MetricExplorer.java @@ -293,7 +293,8 @@ static Stream elaborateData(Object target) { static Stream elaborateMap(Map data) { return data.entrySet().stream() .sorted(Comparator.comparing(Object::toString)) - .flatMap(DataUtils::elaborateMapEntry); + .flatMap(DataUtils::elaborateMapEntry) + .limit(50); } static Stream elaborateMapEntry(Map.Entry entry) { @@ -323,7 +324,7 @@ static Stream elaborateArray(Object[] array) { } static Stream elaborateArray(long[] array) { - return IntStream.range(0, array.length).mapToObj(x -> x + ": " + array[x]); + return IntStream.range(0, array.length).mapToObj(x -> x + ": " + array[x]).limit(20); } static Stream elaborateList(List list) { @@ -343,7 +344,8 @@ static Stream elaborateList(List list) { static Stream elaborateCompositeDataSupport(CompositeDataSupport data) { return data.getCompositeType().keySet().stream() .sorted() - .flatMap(key -> DataUtils.elaborateMapEntry(Map.entry(key, data.get(key)))); + .flatMap(key -> DataUtils.elaborateMapEntry(Map.entry(key, data.get(key)))) + .limit(50); } static Stream streamAppendWith(String s, int size, Stream source) { From 14b03d00e61dfb4cc6dd942986fc96af6663f356 Mon Sep 17 00:00:00 2001 From: Zheng-Xian Li Date: Wed, 10 Nov 2021 21:57:50 +0800 Subject: [PATCH 13/14] improve output visibility 1. Some Mbeans from the JVM contain extremely long string, this commit implement some code to truncated those string. 2. Some Mbeans will throwing error during access, this commit wrap all the DataUtil#* routine with a try-catch block. 3. remove hard limit on collection output size. --- .../org/astraea/metrics/MetricExplorer.java | 152 +++++++++++------- 1 file changed, 97 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/org/astraea/metrics/MetricExplorer.java b/app/src/main/java/org/astraea/metrics/MetricExplorer.java index 9770f120ce..0ea1167cc6 100644 --- a/app/src/main/java/org/astraea/metrics/MetricExplorer.java +++ b/app/src/main/java/org/astraea/metrics/MetricExplorer.java @@ -272,85 +272,127 @@ static Map.Entry toEntry(String property) { static class DataUtils { + static int stringLengthLimit = 500; + static Stream elaborateData(Object target) { - if (target == null) { - return Stream.of("null"); - } else if (target instanceof List) { - return elaborateList((List) target); - } else if (target instanceof Map) { - return elaborateMap((Map) target); - } else if (target instanceof CompositeDataSupport) { - return elaborateCompositeDataSupport(((CompositeDataSupport) target)); - } else if (target.getClass().isArray()) { - if (target.getClass() == long[].class) return elaborateArray((long[]) target); - else return elaborateArray((Object[]) target); + try { + if (target == null) { + return Stream.of("null"); + } else if (target instanceof List) { + return elaborateList((List) target); + } else if (target instanceof Map) { + return elaborateMap((Map) target); + } else if (target instanceof CompositeDataSupport) { + return elaborateCompositeDataSupport(((CompositeDataSupport) target)); + } else if (target.getClass().isArray()) { + if (target.getClass() == long[].class) return elaborateArray((long[]) target); + else return elaborateArray((Object[]) target); + } + + // I have no idea how to display this object, just toString it. + return Stream.of(stringify(target)); + } catch (Exception e) { + return Stream.of(e.toString()); } + } - // I have no idea how to display this object, just toString it. - return Stream.of(target.toString()); + static String stringify(Object object) { + String string = object.toString(); + if(string.length() > stringLengthLimit) { + return string.substring(0, stringLengthLimit) + + String.format("%n... (%d characters truncated)", string.length() - stringLengthLimit); + } else { + return string; + } } static Stream elaborateMap(Map data) { - return data.entrySet().stream() - .sorted(Comparator.comparing(Object::toString)) - .flatMap(DataUtils::elaborateMapEntry) - .limit(50); + try { + return data.entrySet().stream() + .sorted(Comparator.comparing(Object::toString)) + .flatMap(DataUtils::elaborateMapEntry); + } catch (Exception e) { + return Stream.of(e.toString()); + } } static Stream elaborateMapEntry(Map.Entry entry) { - List key = elaborateData(entry.getKey()).collect(Collectors.toList()); - List value = elaborateData(entry.getValue()).collect(Collectors.toList()); - - if (key.size() == 1 && value.size() > 1) - return Stream.concat( - Stream.of(String.format("\"%s\":", key.get(0))), - streamAppendWith(" ", 4, value.stream())); - - return Stream.of(String.format("\"%s\": %s", key.get(0), value.get(0))); + try { + List key = elaborateData(entry.getKey()).collect(Collectors.toList()); + List value = elaborateData(entry.getValue()).collect(Collectors.toList()); + + if (key.size() == 1 && value.size() > 1) + return Stream.concat( + Stream.of(String.format("\"%s\":", key.get(0))), + streamAppendWith(" ", 4, value.stream())); + + return Stream.of(String.format("\"%s\": %s", key.get(0), value.get(0))); + } catch (Exception e) { + return Stream.of(e.toString()); + } } static Stream elaborateArray(Object[] array) { - return IntStream.range(0, array.length) - .boxed() - .flatMap( - i -> { - List collect = elaborateData(array[i]).collect(Collectors.toList()); - if (collect.size() == 1) - return Stream.of(String.format("%d: %s", i, collect.get(0))); - else - return Stream.concat( - Stream.of(i + ":"), streamAppendWith(" ", 4, collect.stream())); - }); + try { + return IntStream.range(0, array.length) + .boxed() + .flatMap( + i -> { + List collect = elaborateData(array[i]).collect(Collectors.toList()); + if (collect.size() == 1) + return Stream.of(String.format("%d: %s", i, collect.get(0))); + else + return Stream.concat( + Stream.of(i + ":"), streamAppendWith(" ", 4, collect.stream())); + }); + } catch (Exception e) { + return Stream.of(e.toString()); + } } static Stream elaborateArray(long[] array) { - return IntStream.range(0, array.length).mapToObj(x -> x + ": " + array[x]).limit(20); + try { + return IntStream.range(0, array.length).mapToObj(x -> x + ": " + array[x]); + } catch (Exception e) { + return Stream.of(e.toString()); + } } static Stream elaborateList(List list) { - return IntStream.range(0, list.size()) - .boxed() - .flatMap( - i -> { - List collect = elaborateData(list.get(i)).collect(Collectors.toList()); - if (collect.size() == 1) - return Stream.of(String.format("%d: %s", i, collect.get(0))); - else - return Stream.concat( - Stream.of(i + ":"), streamAppendWith(" ", 4, collect.stream())); - }); + try { + return IntStream.range(0, list.size()) + .boxed() + .flatMap( + i -> { + List collect = elaborateData(list.get(i)).collect(Collectors.toList()); + if (collect.size() == 1) + return Stream.of(String.format("%d: %s", i, collect.get(0))); + else + return Stream.concat( + Stream.of(i + ":"), streamAppendWith(" ", 4, collect.stream())); + }); + } catch (Exception e) { + return Stream.of(e.toString()); + } } static Stream elaborateCompositeDataSupport(CompositeDataSupport data) { - return data.getCompositeType().keySet().stream() - .sorted() - .flatMap(key -> DataUtils.elaborateMapEntry(Map.entry(key, data.get(key)))) - .limit(50); + try { + return data.getCompositeType().keySet().stream() + .sorted() + .flatMap(key -> DataUtils.elaborateMapEntry(Map.entry(key, data.get(key)))); + } catch (Exception e) { + return Stream.of(e.toString()); + } } static Stream streamAppendWith(String s, int size, Stream source) { - String append = String.join("", Collections.nCopies(size, s)); - return source.map(x -> append + x); + try { + String append = String.join("", Collections.nCopies(size, s)); + return source.map(x -> append + x); + } catch (Exception e) { + return Stream.of(e.toString()); + } } } } From fc31230413246a6d01759db3b2619c047ca71a35 Mon Sep 17 00:00:00 2001 From: Zheng-Xian Li Date: Wed, 10 Nov 2021 22:02:48 +0800 Subject: [PATCH 14/14] spotless --- .../org/astraea/metrics/MetricExplorer.java | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/org/astraea/metrics/MetricExplorer.java b/app/src/main/java/org/astraea/metrics/MetricExplorer.java index 0ea1167cc6..26ac4cd173 100644 --- a/app/src/main/java/org/astraea/metrics/MetricExplorer.java +++ b/app/src/main/java/org/astraea/metrics/MetricExplorer.java @@ -297,20 +297,20 @@ static Stream elaborateData(Object target) { } static String stringify(Object object) { - String string = object.toString(); - if(string.length() > stringLengthLimit) { - return string.substring(0, stringLengthLimit) + - String.format("%n... (%d characters truncated)", string.length() - stringLengthLimit); - } else { - return string; - } + String string = object.toString(); + if (string.length() > stringLengthLimit) { + return string.substring(0, stringLengthLimit) + + String.format("%n... (%d characters truncated)", string.length() - stringLengthLimit); + } else { + return string; + } } static Stream elaborateMap(Map data) { try { return data.entrySet().stream() - .sorted(Comparator.comparing(Object::toString)) - .flatMap(DataUtils::elaborateMapEntry); + .sorted(Comparator.comparing(Object::toString)) + .flatMap(DataUtils::elaborateMapEntry); } catch (Exception e) { return Stream.of(e.toString()); } @@ -323,30 +323,30 @@ static Stream elaborateMapEntry(Map.Entry entry) { if (key.size() == 1 && value.size() > 1) return Stream.concat( - Stream.of(String.format("\"%s\":", key.get(0))), - streamAppendWith(" ", 4, value.stream())); + Stream.of(String.format("\"%s\":", key.get(0))), + streamAppendWith(" ", 4, value.stream())); return Stream.of(String.format("\"%s\": %s", key.get(0), value.get(0))); } catch (Exception e) { - return Stream.of(e.toString()); + return Stream.of(e.toString()); } } static Stream elaborateArray(Object[] array) { try { return IntStream.range(0, array.length) - .boxed() - .flatMap( - i -> { - List collect = elaborateData(array[i]).collect(Collectors.toList()); - if (collect.size() == 1) - return Stream.of(String.format("%d: %s", i, collect.get(0))); - else - return Stream.concat( - Stream.of(i + ":"), streamAppendWith(" ", 4, collect.stream())); - }); + .boxed() + .flatMap( + i -> { + List collect = elaborateData(array[i]).collect(Collectors.toList()); + if (collect.size() == 1) + return Stream.of(String.format("%d: %s", i, collect.get(0))); + else + return Stream.concat( + Stream.of(i + ":"), streamAppendWith(" ", 4, collect.stream())); + }); } catch (Exception e) { - return Stream.of(e.toString()); + return Stream.of(e.toString()); } } @@ -354,23 +354,23 @@ static Stream elaborateArray(long[] array) { try { return IntStream.range(0, array.length).mapToObj(x -> x + ": " + array[x]); } catch (Exception e) { - return Stream.of(e.toString()); + return Stream.of(e.toString()); } } static Stream elaborateList(List list) { try { return IntStream.range(0, list.size()) - .boxed() - .flatMap( - i -> { - List collect = elaborateData(list.get(i)).collect(Collectors.toList()); - if (collect.size() == 1) - return Stream.of(String.format("%d: %s", i, collect.get(0))); - else - return Stream.concat( - Stream.of(i + ":"), streamAppendWith(" ", 4, collect.stream())); - }); + .boxed() + .flatMap( + i -> { + List collect = elaborateData(list.get(i)).collect(Collectors.toList()); + if (collect.size() == 1) + return Stream.of(String.format("%d: %s", i, collect.get(0))); + else + return Stream.concat( + Stream.of(i + ":"), streamAppendWith(" ", 4, collect.stream())); + }); } catch (Exception e) { return Stream.of(e.toString()); } @@ -379,8 +379,8 @@ static Stream elaborateList(List list) { static Stream elaborateCompositeDataSupport(CompositeDataSupport data) { try { return data.getCompositeType().keySet().stream() - .sorted() - .flatMap(key -> DataUtils.elaborateMapEntry(Map.entry(key, data.get(key)))); + .sorted() + .flatMap(key -> DataUtils.elaborateMapEntry(Map.entry(key, data.get(key)))); } catch (Exception e) { return Stream.of(e.toString()); }