From 0706aadbc6662e5ba2807f2d464ce2a06848a536 Mon Sep 17 00:00:00 2001 From: Alejandro Rivera Date: Thu, 18 Feb 2016 11:36:18 +0800 Subject: [PATCH 1/4] Added a new Profiler for CPU/JVM CPU monitoring through `com.sun.management.OperatingSystemMXBean` --- README.md | 60 +++++++-- .../com/etsy/statsd/profiler/Profiler.java | 10 +- .../profiler/profilers/JVMCPUProfiler.java | 114 ++++++++++++++++++ .../profiler/reporter/InfluxDBReporter.java | 18 ++- .../statsd/profiler/reporter/Reporter.java | 7 +- .../profiler/reporter/StatsDReporter.java | 20 ++- .../etsy/statsd/profiler/util/CPUTraces.java | 6 +- .../etsy/statsd/profiler/util/MapUtil.java | 20 ++- .../profiler/reporter/MockReporter.java | 21 +++- .../statsd/profiler/util/MapUtilTest.java | 2 +- 10 files changed, 243 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/etsy/statsd/profiler/profilers/JVMCPUProfiler.java diff --git a/README.md b/README.md index 871c2b0..bfc7945 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ port | The port number for the server to which the reporter should s prefix | The prefix for metrics (optional, defaults to statsd-jvm-profiler) packageWhitelist | Colon-delimited whitelist for packages to include (optional, defaults to include everything) packageBlacklist | Colon-delimited whitelist for packages to exclude (optional, defaults to exclude nothing) -profilers | Colon-delimited list of profiler class names (optional, defaults to CPUProfiler and MemoryProfiler) +profilers | Colon-delimited list of profiler class names (optional, defaults to `CPUProfiler` and `MemoryProfiler`) reporter | Class name of the reporter to use (optional, defaults to StatsDReporter) httpServerEnabled| Determines if the embedded HTTP server should be started. (optional, defaults to `true`) httpPort | The port on which to bind the embedded HTTP server (optional, defaults to 5005). If this port is already in use, the next free port will be taken. @@ -100,28 +100,64 @@ If the `tagMapping` argument is not defined, only the `prefix` tag will be added If you do not want to include a component of `prefix` as a tag, use the special name `SKIP` in `tagMapping` for that position. -## Metrics +## Profilers -`statsd-jvm-profiler` will profile the following: +`statsd-jvm-profiler` offers 3 profilers: `MemoryProfiler`, `CPUProfiler` and `JVMCPUProfiler`. + +The metrics for all these profilers will prefixed with the value from the `prefix` argument or it's default value: `statsd-jvm-profiler`. + +You can enable specific profilers through the `profilers` argument like so: +1. Memory metrics only: `profilers=MemoryProfiler` +2. CPU Tracing metrics only: `profilers=CPUProfiler` +3. JVM/System CPU metrics only: `profilers=JVMCPUProfiler` + +Default value: `profilers=MemoryProfiler:CPUProfiler` + +### Garbage Collector and Memory Profiler: `MemoryProfiler` +This profiler will record: 1. Heap and non-heap memory usage 2. Number of GC pauses and GC time -3. Time spent in each function -Assuming you use the default prefix of `statsd-jvm-profiler`, the memory usage metrics will be under `statsd-jvm-profiler.heap` and `statsd-jvm-profiler.nonheap`, the GC metrics will be under `statsd-jvm-profiler.gc`, and the CPU time metrics will be under `statsd-jvm-profiler.cpu.trace`. +Assuming you use the default prefix of `statsd-jvm-profiler`, +the memory usage metrics will be under `statsd-jvm-profiler.heap` and `statsd-jvm-profiler.nonheap`, +the GC metrics will be under `statsd-jvm-profiler.gc`. -Memory and GC metrics are reported once every 10 seconds. The CPU time is sampled every millisecond, but only reported every 10 seconds. The CPU time metrics represent the total time spent in that function. +Memory and GC metrics are reported once every 10 seconds. -Profiling a long-running process or a lot of processes simultaneously will produce a lot of data, so be careful with the capacity of your StatsD instance. The `packageWhitelist` and `packageBlacklist` arguments can be used to limit the number of functions that are reported. Any function whose stack trace contains a function in one of the whitelisted packages will be included. +### CPU Tracing Profiler: `CPUProfiler` +This profiler records the time spent in each function across all Threads. -You can disable either the memory or CPU metrics using the `profilers` argument: +Assuming you use the default prefix of `statsd-jvm-profiler`, the the CPU time metrics will be under `statsd-jvm-profiler.cpu.trace`. -1. Memory metrics only: `profilers=MemoryProfiler` -2. CPU metrics only: `profilers=CPUProfiler` +The CPU time is sampled every millisecond, but only reported every 10 seconds. +The CPU time metrics represent the total time spent in that function. + +Profiling a long-running process or a lot of processes simultaneously will produce a lot of data, so be careful with the +capacity of your StatsD instance. The `packageWhitelist` and `packageBlacklist` arguments can be used to limit the number +of functions that are reported. Any function whose stack trace contains a function in one of the whitelisted packages will be included. + +The `visualization` directory contains some utilities for visualizing the output of this profiler. + +### JVM And System CPU Profiler: `JVMCPUProfiler` + +This profiler will record the JVM's and the overall system's CPU load, if the JVM is capable of providing this information. + +Assuming you use the default prefix of `statsd-jvm-profiler`, the JVM CPU load metrics will be under `statsd-jvm-profiler.cpu.jvm`, +and the System CPU load wil be under `statsd-jvm-profiler.cpu.system`. + +The reported metrics will be percentages in the range of [0, 100] with 1 decimal precision. -## Visualization +CPU load metrics are sampled and reported once every 10 seconds. -The `visualization` directory contains some utilities for visualizing the output of the profiler. +Important notes: +* This Profiler is not enabled by default. To enable use the argument `profilers=JVMCPUProfiler` +* This Profiler relies on Sun/Oracle-specific JVM implementations that offer a JMX bean that might not be available in other JVMs. + Even if you are using the right JVM, there's no guarantee this JMX bean will remain there in the future. +* The minimum required JVM version that offers support for this is for Java 7. +* See [com.sun.management.OperatingSystemMXBean](https://docs.oracle.com/javase/7/docs/jre/api/management/extension/com/sun/management/OperatingSystemMXBean.html#getProcessCpuLoad()) + for more information. +* If the JVM doesn't support the required operations, the metrics above won't be reported at all. ## Dynamic Loading of Agent diff --git a/src/main/java/com/etsy/statsd/profiler/Profiler.java b/src/main/java/com/etsy/statsd/profiler/Profiler.java index 4f0d237..53e76b4 100644 --- a/src/main/java/com/etsy/statsd/profiler/Profiler.java +++ b/src/main/java/com/etsy/statsd/profiler/Profiler.java @@ -76,13 +76,21 @@ protected void recordGaugeValue(String key, long value) { reporter.recordGaugeValue(key, value); } + /** + * @see #recordGaugeValue(String, long) + */ + protected void recordGaugeValue(String key, double value) { + recordedStats++; + reporter.recordGaugeValue(key, value); + } + /** * Record multiple gauge values * This is useful for reporters that can send points in batch * * @param gauges A map of gauge names to values */ - protected void recordGaugeValues(Map gauges) { + protected void recordGaugeValues(Map gauges) { recordedStats++; reporter.recordGaugeValues(gauges); } diff --git a/src/main/java/com/etsy/statsd/profiler/profilers/JVMCPUProfiler.java b/src/main/java/com/etsy/statsd/profiler/profilers/JVMCPUProfiler.java new file mode 100644 index 0000000..67492cf --- /dev/null +++ b/src/main/java/com/etsy/statsd/profiler/profilers/JVMCPUProfiler.java @@ -0,0 +1,114 @@ +package com.etsy.statsd.profiler.profilers; + +import com.etsy.statsd.profiler.Arguments; +import com.etsy.statsd.profiler.Profiler; +import com.etsy.statsd.profiler.reporter.Reporter; + +import java.lang.management.ManagementFactory; +import java.util.concurrent.TimeUnit; +import javax.management.Attribute; +import javax.management.AttributeList; +import javax.management.InstanceNotFoundException; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import javax.management.ReflectionException; + +/** + * This profiler retrieves CPU values for the JVM and System from the "OperatingSystem" JMX Bean. + *

+ * This profiler relies on a JMX bean that might not be available in all JVM implementations. + * We know for sure it's available in Sun/Oracle's JRE 7+, but there are no guarantees it + * will remain there for the foreseeable future. + * + * @see StackOverflow post + * + * @author Alejandro Rivera + */ +public class JVMCPUProfiler extends Profiler { + public static final long PERIOD = 10; + public static final double INVALID_VALUE = Double.NaN; + private final MBeanServer mbs; + + public JVMCPUProfiler(Reporter reporter, Arguments arguments) { + super(reporter, arguments); + mbs = ManagementFactory.getPlatformMBeanServer(); + } + + /** + * Profile memory usage and GC statistics + */ + @Override + public void profile() { + recordStats(); + } + + @Override + public void flushData() { + recordStats(); + } + + @Override + public long getPeriod() { + return PERIOD; + } + + @Override + public TimeUnit getTimeUnit() { + return TimeUnit.SECONDS; + } + + @Override + protected void handleArguments(Arguments arguments) { /* No arguments needed */ } + + /** + * Records all memory statistics + */ + private void recordStats() { + ObjectName os = getOperatingsystemObject(); + if (os != null) { + recordStat(os, "ProcessCpuLoad", "cpu.jvm"); + recordStat(os, "SystemCpuLoad", "cpu.system"); + } + } + + private void recordStat(ObjectName os, String attribute, String metricKey) { + double value = getValue(mbs, os, attribute); + if (value != INVALID_VALUE) { + recordGaugeValue(metricKey, value); + } + } + + private ObjectName getOperatingsystemObject() { + try { + return ObjectName.getInstance("java.lang:type=OperatingSystem"); + } catch (MalformedObjectNameException e) { + return null; + } + } + + public static double getValue(MBeanServer mbs, ObjectName name, String attribute) { + + AttributeList list; + try { + list = mbs.getAttributes(name, new String[]{attribute}); + } catch (InstanceNotFoundException e) { + return INVALID_VALUE; + } catch (ReflectionException e) { + return INVALID_VALUE; + } + + if (list.isEmpty()) { + return INVALID_VALUE; + } + + Attribute att = (Attribute) list.get(0); + Double value = (Double) att.getValue(); + + if (value == -1.0) { + return INVALID_VALUE; + } + + return ((int) (value * 1000)) / 10.0d; // 0-100 with 1-decimal precision + } +} diff --git a/src/main/java/com/etsy/statsd/profiler/reporter/InfluxDBReporter.java b/src/main/java/com/etsy/statsd/profiler/reporter/InfluxDBReporter.java index 6a50325..626cee5 100644 --- a/src/main/java/com/etsy/statsd/profiler/reporter/InfluxDBReporter.java +++ b/src/main/java/com/etsy/statsd/profiler/reporter/InfluxDBReporter.java @@ -50,17 +50,25 @@ public void recordGaugeValue(String key, long value) { recordGaugeValues(gauges); } + /** + * @see #recordGaugeValue(String, long) + */ + @Override + public void recordGaugeValue(String key, double value) { + Map gauges = ImmutableMap.of(key, value); + recordGaugeValues(gauges); + } + /** * Record multiple gauge values in InfluxDB * * @param gauges A map of gauge names to values */ @Override - public void recordGaugeValues(Map gauges) { + public void recordGaugeValues(Map gauges) { long time = System.currentTimeMillis(); - BatchPoints batchPoints = BatchPoints.database(database) - .build(); - for (Map.Entry gauge: gauges.entrySet()) { + BatchPoints batchPoints = BatchPoints.database(database).build(); + for (Map.Entry gauge: gauges.entrySet()) { batchPoints.point(constructPoint(time, gauge.getKey(), gauge.getValue())); } client.write(batchPoints); @@ -106,7 +114,7 @@ protected void handleArguments(Arguments arguments) { Preconditions.checkNotNull(database); } - private Point constructPoint(long time, String key, long value) { + private Point constructPoint(long time, String key, Number value) { Point.Builder builder = Point.measurement(key) .time(time, TimeUnit.MILLISECONDS) .field(VALUE_COLUMN, value); diff --git a/src/main/java/com/etsy/statsd/profiler/reporter/Reporter.java b/src/main/java/com/etsy/statsd/profiler/reporter/Reporter.java index bc0dd9b..5b052ff 100644 --- a/src/main/java/com/etsy/statsd/profiler/reporter/Reporter.java +++ b/src/main/java/com/etsy/statsd/profiler/reporter/Reporter.java @@ -32,13 +32,18 @@ public Reporter(Arguments arguments) { */ public abstract void recordGaugeValue(String key, long value); + /** + * @see #recordGaugeValue(String, long) + */ + public abstract void recordGaugeValue(String key, double value); + /** * Record multiple gauge values * This is useful for reporters that can send points in batch * * @param gauges A map of gauge names to values */ - public abstract void recordGaugeValues(Map gauges); + public abstract void recordGaugeValues(Map gauges); /** * CPUProfiler can emit some metrics that indicate the upper and lower bound on the length of stack traces diff --git a/src/main/java/com/etsy/statsd/profiler/reporter/StatsDReporter.java b/src/main/java/com/etsy/statsd/profiler/reporter/StatsDReporter.java index 651b12e..28326ac 100644 --- a/src/main/java/com/etsy/statsd/profiler/reporter/StatsDReporter.java +++ b/src/main/java/com/etsy/statsd/profiler/reporter/StatsDReporter.java @@ -27,6 +27,14 @@ public void recordGaugeValue(String key, long value) { client.recordGaugeValue(key, value); } + /** + * @see #recordGaugeValue(String, long) + */ + @Override + public void recordGaugeValue(String key, double value) { + client.recordGaugeValue(key, value); + } + /** * Record multiple gauge values in StatsD * This simply loops over calling recordGaugeValue @@ -34,9 +42,15 @@ public void recordGaugeValue(String key, long value) { * @param gauges A map of gauge names to values */ @Override - public void recordGaugeValues(Map gauges) { - for (Map.Entry gauge : gauges.entrySet()) { - recordGaugeValue(gauge.getKey(), gauge.getValue()); + public void recordGaugeValues(Map gauges) { + for (Map.Entry gauge : gauges.entrySet()) { + if (gauge.getValue() instanceof Long) { + client.recordGaugeValue(gauge.getKey(), gauge.getValue().longValue()); + } else if (gauge.getValue() instanceof Double) { + client.recordGaugeValue(gauge.getKey(), gauge.getValue().doubleValue()); + } else { + throw new IllegalArgumentException("Unexpected Number type: " + gauge.getValue().getClass().getSimpleName()); + } } } diff --git a/src/main/java/com/etsy/statsd/profiler/util/CPUTraces.java b/src/main/java/com/etsy/statsd/profiler/util/CPUTraces.java index 6d9a28f..6acfcc1 100644 --- a/src/main/java/com/etsy/statsd/profiler/util/CPUTraces.java +++ b/src/main/java/com/etsy/statsd/profiler/util/CPUTraces.java @@ -9,7 +9,7 @@ * @author Andrew Johnson */ public class CPUTraces { - private Map traces; + private Map traces; private int max = Integer.MIN_VALUE; private int min = Integer.MAX_VALUE; @@ -33,8 +33,8 @@ public void increment(String traceKey, long inc) { * It only returns traces that have been updated since the last flush * */ - public Map getDataToFlush() { - Map result = traces; + public Map getDataToFlush() { + Map result = traces; traces = new HashMap<>(); return result; } diff --git a/src/main/java/com/etsy/statsd/profiler/util/MapUtil.java b/src/main/java/com/etsy/statsd/profiler/util/MapUtil.java index 505e564..a6c30a2 100644 --- a/src/main/java/com/etsy/statsd/profiler/util/MapUtil.java +++ b/src/main/java/com/etsy/statsd/profiler/util/MapUtil.java @@ -17,12 +17,24 @@ private MapUtil() { } * @param key The key for the map * @param inc The new value or increment for the given key */ - public static void setOrIncrementMap(Map map, String key, long inc) { - Long val = map.get(key); + public static void setOrIncrementMap(Map map, String key, Number inc) { + Number val = map.get(key); if (val == null) { - map.put(key, inc); + if (inc instanceof Double) { + map.put(key, inc.doubleValue()); + } else if (inc instanceof Long || inc instanceof Integer) { + map.put(key, inc.longValue()); + } else { + throw new IllegalArgumentException("Unexpected Number type: " + inc.getClass().getSimpleName()); + } } else { - map.put(key, val + inc); + if (val instanceof Double) { + map.put(key, val.doubleValue() + inc.doubleValue()); + } else if (val instanceof Long || val instanceof Integer) { + map.put(key, val.longValue() + inc.longValue()); + } else { + throw new IllegalArgumentException("Unexpected Number type: " + val.getClass().getSimpleName()); + } } } } diff --git a/src/test/java/com/etsy/statsd/profiler/reporter/MockReporter.java b/src/test/java/com/etsy/statsd/profiler/reporter/MockReporter.java index 64cf0c1..055e8f2 100644 --- a/src/test/java/com/etsy/statsd/profiler/reporter/MockReporter.java +++ b/src/test/java/com/etsy/statsd/profiler/reporter/MockReporter.java @@ -13,7 +13,7 @@ * @author Andrew Johnson */ public class MockReporter extends Reporter { - private Map output; + private Map output; public MockReporter() { super(MockArguments.BASIC); @@ -26,12 +26,23 @@ public void recordGaugeValue(String key, long value) { } @Override - public void recordGaugeValues(Map gauges) { - for (Map.Entry gauge : gauges.entrySet()) { - recordGaugeValue(gauge.getKey(), gauge.getValue()); + public void recordGaugeValues(Map gauges) { + for (Map.Entry gauge : gauges.entrySet()) { + if (gauge.getValue() instanceof Long) { + recordGaugeValue(gauge.getKey(), gauge.getValue().longValue()); + } else if (gauge.getValue() instanceof Double) { + recordGaugeValue(gauge.getKey(), gauge.getValue().doubleValue()); + } else { + throw new IllegalArgumentException("Unexpected Number type: " + gauge.getValue().getClass().getSimpleName()); + } } } + @Override + public void recordGaugeValue(String key, double value) { + MapUtil.setOrIncrementMap(output, key, value); + } + @Override protected String createClient(String server, int port, String prefix) { return ""; @@ -40,7 +51,7 @@ protected String createClient(String server, int port, String prefix) { @Override protected void handleArguments(Arguments arguments) { } - public Map getOutput() { + public Map getOutput() { return output; } } diff --git a/src/test/java/com/etsy/statsd/profiler/util/MapUtilTest.java b/src/test/java/com/etsy/statsd/profiler/util/MapUtilTest.java index 072d502..37e7de2 100644 --- a/src/test/java/com/etsy/statsd/profiler/util/MapUtilTest.java +++ b/src/test/java/com/etsy/statsd/profiler/util/MapUtilTest.java @@ -11,7 +11,7 @@ public class MapUtilTest { @Test public void testSetOrIncrementMap() { - Map map = new HashMap<>(); + Map map = new HashMap<>(); MapUtil.setOrIncrementMap(map, "key", 1); assertEquals(new Long(1L), map.get("key")); From 3e2f1067157deb38c9cbc8235a9190dab74c9049 Mon Sep 17 00:00:00 2001 From: Alejandro Rivera Date: Fri, 19 Feb 2016 13:09:58 +0800 Subject: [PATCH 2/4] Reusing the OperatingSystem JXM bean instance. --- .../profiler/profilers/JVMCPUProfiler.java | 77 ++++++++++--------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/etsy/statsd/profiler/profilers/JVMCPUProfiler.java b/src/main/java/com/etsy/statsd/profiler/profilers/JVMCPUProfiler.java index 67492cf..f88ea1d 100644 --- a/src/main/java/com/etsy/statsd/profiler/profilers/JVMCPUProfiler.java +++ b/src/main/java/com/etsy/statsd/profiler/profilers/JVMCPUProfiler.java @@ -1,10 +1,13 @@ package com.etsy.statsd.profiler.profilers; +import com.google.common.collect.ImmutableMap; + import com.etsy.statsd.profiler.Arguments; import com.etsy.statsd.profiler.Profiler; import com.etsy.statsd.profiler.reporter.Reporter; import java.lang.management.ManagementFactory; +import java.util.Map; import java.util.concurrent.TimeUnit; import javax.management.Attribute; import javax.management.AttributeList; @@ -26,13 +29,28 @@ * @author Alejandro Rivera */ public class JVMCPUProfiler extends Profiler { + public static final long PERIOD = 10; - public static final double INVALID_VALUE = Double.NaN; + private static final Map ATTRIBUTES_MAP = ImmutableMap.of("ProcessCpuLoad", "cpu.jvm", + "SystemCpuLoad", "cpu.system"); + + private static final String[] ATTRIBUTES = ATTRIBUTES_MAP.keySet().toArray(new String[ATTRIBUTES_MAP.size()]); + private final MBeanServer mbs; + private final ObjectName os; public JVMCPUProfiler(Reporter reporter, Arguments arguments) { super(reporter, arguments); mbs = ManagementFactory.getPlatformMBeanServer(); + os = getOperatingSystemObject(); + } + + private ObjectName getOperatingSystemObject() { + try { + return ObjectName.getInstance("java.lang:type=OperatingSystem"); + } catch (MalformedObjectNameException e) { + return null; + } } /** @@ -65,50 +83,35 @@ protected void handleArguments(Arguments arguments) { /* No arguments needed */ * Records all memory statistics */ private void recordStats() { - ObjectName os = getOperatingsystemObject(); - if (os != null) { - recordStat(os, "ProcessCpuLoad", "cpu.jvm"); - recordStat(os, "SystemCpuLoad", "cpu.system"); - } - } - - private void recordStat(ObjectName os, String attribute, String metricKey) { - double value = getValue(mbs, os, attribute); - if (value != INVALID_VALUE) { - recordGaugeValue(metricKey, value); + if (os == null) { + return; } - } - - private ObjectName getOperatingsystemObject() { - try { - return ObjectName.getInstance("java.lang:type=OperatingSystem"); - } catch (MalformedObjectNameException e) { - return null; - } - } - - public static double getValue(MBeanServer mbs, ObjectName name, String attribute) { AttributeList list; try { - list = mbs.getAttributes(name, new String[]{attribute}); - } catch (InstanceNotFoundException e) { - return INVALID_VALUE; - } catch (ReflectionException e) { - return INVALID_VALUE; + list = mbs.getAttributes(os, ATTRIBUTES); + } catch (InstanceNotFoundException | ReflectionException e) { + return; } - if (list.isEmpty()) { - return INVALID_VALUE; - } + Attribute att; + Double value; + String metric; + for (Object o : list) { + att = (Attribute) o; + value = (Double) att.getValue(); - Attribute att = (Attribute) list.get(0); - Double value = (Double) att.getValue(); + if (value == null || value == -1.0) { + continue; + } - if (value == -1.0) { - return INVALID_VALUE; - } + metric = ATTRIBUTES_MAP.get(att.getName()); + if (metric == null) { + continue; + } - return ((int) (value * 1000)) / 10.0d; // 0-100 with 1-decimal precision + value = ((int) (value * 1000)) / 10.0d; // 0-100 with 1-decimal precision + recordGaugeValue(metric, value); + } } } From 50c202854decc4be5587e71f2d2ed9a693c1cdfd Mon Sep 17 00:00:00 2001 From: Alejandro Rivera Date: Fri, 19 Feb 2016 13:29:16 +0800 Subject: [PATCH 3/4] Reusing the Attributes from the OperatingSystem JXM bean instance. --- .../profiler/profilers/JVMCPUProfiler.java | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/etsy/statsd/profiler/profilers/JVMCPUProfiler.java b/src/main/java/com/etsy/statsd/profiler/profilers/JVMCPUProfiler.java index f88ea1d..0a4e2b5 100644 --- a/src/main/java/com/etsy/statsd/profiler/profilers/JVMCPUProfiler.java +++ b/src/main/java/com/etsy/statsd/profiler/profilers/JVMCPUProfiler.java @@ -34,23 +34,18 @@ public class JVMCPUProfiler extends Profiler { private static final Map ATTRIBUTES_MAP = ImmutableMap.of("ProcessCpuLoad", "cpu.jvm", "SystemCpuLoad", "cpu.system"); - private static final String[] ATTRIBUTES = ATTRIBUTES_MAP.keySet().toArray(new String[ATTRIBUTES_MAP.size()]); - - private final MBeanServer mbs; - private final ObjectName os; + private AttributeList list; public JVMCPUProfiler(Reporter reporter, Arguments arguments) { super(reporter, arguments); - mbs = ManagementFactory.getPlatformMBeanServer(); - os = getOperatingSystemObject(); - } - - private ObjectName getOperatingSystemObject() { try { - return ObjectName.getInstance("java.lang:type=OperatingSystem"); - } catch (MalformedObjectNameException e) { - return null; + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + ObjectName os = ObjectName.getInstance("java.lang:type=OperatingSystem"); + list = mbs.getAttributes(os, ATTRIBUTES_MAP.keySet().toArray(new String[ATTRIBUTES_MAP.size()])); + } catch (InstanceNotFoundException | ReflectionException | MalformedObjectNameException e) { + list = null; } + } /** @@ -83,14 +78,7 @@ protected void handleArguments(Arguments arguments) { /* No arguments needed */ * Records all memory statistics */ private void recordStats() { - if (os == null) { - return; - } - - AttributeList list; - try { - list = mbs.getAttributes(os, ATTRIBUTES); - } catch (InstanceNotFoundException | ReflectionException e) { + if (list == null) { return; } From feafbc63a1276b8a4053bf1ddad3952c1b8f6a81 Mon Sep 17 00:00:00 2001 From: Alejandro Rivera Date: Fri, 19 Feb 2016 13:35:03 +0800 Subject: [PATCH 4/4] Renamed `CPUProfiler`->`CPUTracingProfiler` and `JVMCPUProfiler`->`CPULoadProfiler` --- README.md | 16 ++++++++-------- pom.xml | 2 +- .../java/com/etsy/statsd/profiler/Arguments.java | 4 ++-- .../java/com/etsy/statsd/profiler/Profiler.java | 2 +- ...{JVMCPUProfiler.java => CPULoadProfiler.java} | 4 ++-- ...{CPUProfiler.java => CPUTracingProfiler.java} | 4 ++-- .../profiler/reporter/InfluxDBReporter.java | 2 +- .../etsy/statsd/profiler/reporter/Reporter.java | 2 +- .../com/etsy/statsd/profiler/ArgumentsTest.java | 12 ++++++------ .../profiler/util/StackTraceFilterTest.java | 4 ++-- 10 files changed, 26 insertions(+), 26 deletions(-) rename src/main/java/com/etsy/statsd/profiler/profilers/{JVMCPUProfiler.java => CPULoadProfiler.java} (96%) rename src/main/java/com/etsy/statsd/profiler/profilers/{CPUProfiler.java => CPUTracingProfiler.java} (97%) diff --git a/README.md b/README.md index bfc7945..7a8a71e 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ port | The port number for the server to which the reporter should s prefix | The prefix for metrics (optional, defaults to statsd-jvm-profiler) packageWhitelist | Colon-delimited whitelist for packages to include (optional, defaults to include everything) packageBlacklist | Colon-delimited whitelist for packages to exclude (optional, defaults to exclude nothing) -profilers | Colon-delimited list of profiler class names (optional, defaults to `CPUProfiler` and `MemoryProfiler`) +profilers | Colon-delimited list of profiler class names (optional, defaults to `CPUTracingProfiler` and `MemoryProfiler`) reporter | Class name of the reporter to use (optional, defaults to StatsDReporter) httpServerEnabled| Determines if the embedded HTTP server should be started. (optional, defaults to `true`) httpPort | The port on which to bind the embedded HTTP server (optional, defaults to 5005). If this port is already in use, the next free port will be taken. @@ -102,16 +102,16 @@ If you do not want to include a component of `prefix` as a tag, use the special ## Profilers -`statsd-jvm-profiler` offers 3 profilers: `MemoryProfiler`, `CPUProfiler` and `JVMCPUProfiler`. +`statsd-jvm-profiler` offers 3 profilers: `MemoryProfiler`, `CPUTracingProfiler` and `CPULoadProfiler`. The metrics for all these profilers will prefixed with the value from the `prefix` argument or it's default value: `statsd-jvm-profiler`. You can enable specific profilers through the `profilers` argument like so: 1. Memory metrics only: `profilers=MemoryProfiler` -2. CPU Tracing metrics only: `profilers=CPUProfiler` -3. JVM/System CPU metrics only: `profilers=JVMCPUProfiler` +2. CPU Tracing metrics only: `profilers=CPUTracingProfiler` +3. JVM/System CPU load metrics only: `profilers=CPULoadProfiler` -Default value: `profilers=MemoryProfiler:CPUProfiler` +Default value: `profilers=MemoryProfiler:CPUTracingProfiler` ### Garbage Collector and Memory Profiler: `MemoryProfiler` This profiler will record: @@ -125,7 +125,7 @@ the GC metrics will be under `statsd-jvm-profiler.gc`. Memory and GC metrics are reported once every 10 seconds. -### CPU Tracing Profiler: `CPUProfiler` +### CPU Tracing Profiler: `CPUTracingProfiler` This profiler records the time spent in each function across all Threads. Assuming you use the default prefix of `statsd-jvm-profiler`, the the CPU time metrics will be under `statsd-jvm-profiler.cpu.trace`. @@ -139,7 +139,7 @@ of functions that are reported. Any function whose stack trace contains a functi The `visualization` directory contains some utilities for visualizing the output of this profiler. -### JVM And System CPU Profiler: `JVMCPUProfiler` +### JVM And System CPU Load Profiler: `CPULoadProfiler` This profiler will record the JVM's and the overall system's CPU load, if the JVM is capable of providing this information. @@ -151,7 +151,7 @@ The reported metrics will be percentages in the range of [0, 100] with 1 decimal CPU load metrics are sampled and reported once every 10 seconds. Important notes: -* This Profiler is not enabled by default. To enable use the argument `profilers=JVMCPUProfiler` +* This Profiler is not enabled by default. To enable use the argument `profilers=CPULoadProfiler` * This Profiler relies on Sun/Oracle-specific JVM implementations that offer a JMX bean that might not be available in other JVMs. Even if you are using the right JVM, there's no guarantee this JMX bean will remain there in the future. * The minimum required JVM version that offers support for this is for Java 7. diff --git a/pom.xml b/pom.xml index 33762dc..ba1e6cc 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.etsy statsd-jvm-profiler - 1.0.3-SNAPSHOT + 2.0.0-SNAPSHOT jar statsd-jvm-profiler diff --git a/src/main/java/com/etsy/statsd/profiler/Arguments.java b/src/main/java/com/etsy/statsd/profiler/Arguments.java index 61dfbee..889ec95 100644 --- a/src/main/java/com/etsy/statsd/profiler/Arguments.java +++ b/src/main/java/com/etsy/statsd/profiler/Arguments.java @@ -1,6 +1,6 @@ package com.etsy.statsd.profiler; -import com.etsy.statsd.profiler.profilers.CPUProfiler; +import com.etsy.statsd.profiler.profilers.CPUTracingProfiler; import com.etsy.statsd.profiler.profilers.MemoryProfiler; import com.etsy.statsd.profiler.reporter.Reporter; import com.etsy.statsd.profiler.reporter.StatsDReporter; @@ -97,7 +97,7 @@ private Class> parserReporterArg(String reporterArg) { private Set> parseProfilerArg(String profilerArg) { Set> parsedProfilers = new HashSet<>(); if (profilerArg == null) { - parsedProfilers.add(CPUProfiler.class); + parsedProfilers.add(CPUTracingProfiler.class); parsedProfilers.add(MemoryProfiler.class); } else { for (String p : profilerArg.split(":")) { diff --git a/src/main/java/com/etsy/statsd/profiler/Profiler.java b/src/main/java/com/etsy/statsd/profiler/Profiler.java index 53e76b4..a306c41 100644 --- a/src/main/java/com/etsy/statsd/profiler/Profiler.java +++ b/src/main/java/com/etsy/statsd/profiler/Profiler.java @@ -48,7 +48,7 @@ public Profiler(Reporter reporter, Arguments arguments) { public abstract TimeUnit getTimeUnit(); /** - * CPUProfiler can emit some metrics that indicate the upper and lower bound on the length of stack traces + * CPUTracingProfiler can emit some metrics that indicate the upper and lower bound on the length of stack traces * This is helpful for querying this data for some backends (such as Graphite) that do not have rich query languages * Reporters can override this to disable these metrics * diff --git a/src/main/java/com/etsy/statsd/profiler/profilers/JVMCPUProfiler.java b/src/main/java/com/etsy/statsd/profiler/profilers/CPULoadProfiler.java similarity index 96% rename from src/main/java/com/etsy/statsd/profiler/profilers/JVMCPUProfiler.java rename to src/main/java/com/etsy/statsd/profiler/profilers/CPULoadProfiler.java index 0a4e2b5..2e2cee6 100644 --- a/src/main/java/com/etsy/statsd/profiler/profilers/JVMCPUProfiler.java +++ b/src/main/java/com/etsy/statsd/profiler/profilers/CPULoadProfiler.java @@ -28,7 +28,7 @@ * * @author Alejandro Rivera */ -public class JVMCPUProfiler extends Profiler { +public class CPULoadProfiler extends Profiler { public static final long PERIOD = 10; private static final Map ATTRIBUTES_MAP = ImmutableMap.of("ProcessCpuLoad", "cpu.jvm", @@ -36,7 +36,7 @@ public class JVMCPUProfiler extends Profiler { private AttributeList list; - public JVMCPUProfiler(Reporter reporter, Arguments arguments) { + public CPULoadProfiler(Reporter reporter, Arguments arguments) { super(reporter, arguments); try { MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); diff --git a/src/main/java/com/etsy/statsd/profiler/profilers/CPUProfiler.java b/src/main/java/com/etsy/statsd/profiler/profilers/CPUTracingProfiler.java similarity index 97% rename from src/main/java/com/etsy/statsd/profiler/profilers/CPUProfiler.java rename to src/main/java/com/etsy/statsd/profiler/profilers/CPUTracingProfiler.java index ffd1cce..02ccf32 100644 --- a/src/main/java/com/etsy/statsd/profiler/profilers/CPUProfiler.java +++ b/src/main/java/com/etsy/statsd/profiler/profilers/CPUTracingProfiler.java @@ -21,7 +21,7 @@ * * @author Andrew Johnson */ -public class CPUProfiler extends Profiler { +public class CPUTracingProfiler extends Profiler { private static final String PACKAGE_WHITELIST_ARG = "packageWhitelist"; private static final String PACKAGE_BLACKLIST_ARG = "packageBlacklist"; @@ -35,7 +35,7 @@ public class CPUProfiler extends Profiler { private final long reportingFrequency; - public CPUProfiler(Reporter reporter, Arguments arguments) { + public CPUTracingProfiler(Reporter reporter, Arguments arguments) { super(reporter, arguments); traces = new CPUTraces(); profileCount = 0; diff --git a/src/main/java/com/etsy/statsd/profiler/reporter/InfluxDBReporter.java b/src/main/java/com/etsy/statsd/profiler/reporter/InfluxDBReporter.java index 626cee5..bfab404 100644 --- a/src/main/java/com/etsy/statsd/profiler/reporter/InfluxDBReporter.java +++ b/src/main/java/com/etsy/statsd/profiler/reporter/InfluxDBReporter.java @@ -75,7 +75,7 @@ public void recordGaugeValues(Map gauges) { } /** - * InfluxDB has a rich query language and does not need the bounds metrics emitted by CPUProfiler + * InfluxDB has a rich query language and does not need the bounds metrics emitted by CPUTracingProfiler * As such we can disable emitting these metrics * * @return false diff --git a/src/main/java/com/etsy/statsd/profiler/reporter/Reporter.java b/src/main/java/com/etsy/statsd/profiler/reporter/Reporter.java index 5b052ff..24ce519 100644 --- a/src/main/java/com/etsy/statsd/profiler/reporter/Reporter.java +++ b/src/main/java/com/etsy/statsd/profiler/reporter/Reporter.java @@ -46,7 +46,7 @@ public Reporter(Arguments arguments) { public abstract void recordGaugeValues(Map gauges); /** - * CPUProfiler can emit some metrics that indicate the upper and lower bound on the length of stack traces + * CPUTracingProfiler can emit some metrics that indicate the upper and lower bound on the length of stack traces * This is helpful for querying this data for some backends (such as Graphite) that do not have rich query languages * Reporters can override this to disable these metrics * diff --git a/src/test/java/com/etsy/statsd/profiler/ArgumentsTest.java b/src/test/java/com/etsy/statsd/profiler/ArgumentsTest.java index beceb64..647a688 100644 --- a/src/test/java/com/etsy/statsd/profiler/ArgumentsTest.java +++ b/src/test/java/com/etsy/statsd/profiler/ArgumentsTest.java @@ -1,6 +1,6 @@ package com.etsy.statsd.profiler; -import com.etsy.statsd.profiler.profilers.CPUProfiler; +import com.etsy.statsd.profiler.profilers.CPUTracingProfiler; import com.etsy.statsd.profiler.profilers.MemoryProfiler; import com.etsy.statsd.profiler.reporter.InfluxDBReporter; import com.etsy.statsd.profiler.reporter.StatsDReporter; @@ -69,7 +69,7 @@ public void testDefaultProfilers() { Arguments arguments = Arguments.parseArgs(args); Set> expected = new HashSet<>(); - expected.add(CPUProfiler.class); + expected.add(CPUTracingProfiler.class); expected.add(MemoryProfiler.class); assertEquals(expected, arguments.profilers); @@ -77,11 +77,11 @@ public void testDefaultProfilers() { @Test public void testProfilerWithPackage() { - String args = "server=localhost,port=8125,profilers=com.etsy.statsd.profiler.profilers.CPUProfiler"; + String args = "server=localhost,port=8125,profilers=com.etsy.statsd.profiler.profilers.CPUTracingProfiler"; Arguments arguments = Arguments.parseArgs(args); Set> expected = new HashSet<>(); - expected.add(CPUProfiler.class); + expected.add(CPUTracingProfiler.class); assertEquals(expected, arguments.profilers); } @@ -99,11 +99,11 @@ public void testProfilerWithoutPackage() { @Test public void testMultipleProfilers() { - String args = "server=localhost,port=8125,profilers=CPUProfiler:MemoryProfiler"; + String args = "server=localhost,port=8125,profilers=CPUTracingProfiler:MemoryProfiler"; Arguments arguments = Arguments.parseArgs(args); Set> expected = new HashSet<>(); - expected.add(CPUProfiler.class); + expected.add(CPUTracingProfiler.class); expected.add(MemoryProfiler.class); assertEquals(expected, arguments.profilers); diff --git a/src/test/java/com/etsy/statsd/profiler/util/StackTraceFilterTest.java b/src/test/java/com/etsy/statsd/profiler/util/StackTraceFilterTest.java index 547b63a..1972d34 100644 --- a/src/test/java/com/etsy/statsd/profiler/util/StackTraceFilterTest.java +++ b/src/test/java/com/etsy/statsd/profiler/util/StackTraceFilterTest.java @@ -1,6 +1,6 @@ package com.etsy.statsd.profiler.util; -import com.etsy.statsd.profiler.profilers.CPUProfiler; +import com.etsy.statsd.profiler.profilers.CPUTracingProfiler; import org.junit.BeforeClass; import org.junit.Test; @@ -22,7 +22,7 @@ public class StackTraceFilterTest { @BeforeClass public static void setup() { includePackages = Arrays.asList("com.etsy", "com.twitter.scalding"); - filter = new StackTraceFilter(includePackages, CPUProfiler.EXCLUDE_PACKAGES); + filter = new StackTraceFilter(includePackages, CPUTracingProfiler.EXCLUDE_PACKAGES); excludedTraces = Arrays.asList("com-etsy-statsd-profiler-profiler-util-StackTraceFormatter-formatStackTraceElement", "com-timgroup-statsd-StatsDClient-send", "com-etsy-statsd-profiler-profiler-util-StackTraceFormatter-formatStackTraceElement.com-etsy-Foo-fooTest"); includedTraces = Collections.singletonList("com-etsy-foo-fooTest"); otherTraces = Collections.singletonList("com-google-guava-Foo-helloWorld");