From 88848bbbe163865b4e1625a81ca67a17161e8e30 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Wed, 26 Jun 2024 17:38:10 +0200 Subject: [PATCH] Support jsr77 j2ee statistics --- pom.xml | 6 + .../datadog/jmxfetch/JmxComplexAttribute.java | 8 + .../util/JeeStatisticsAttributes.java | 140 ++++++++++++++++++ .../datadog/jmxfetch/SimpleTestJavaApp.java | 51 +++++++ .../jmxfetch/SimpleTestJavaAppMBean.java | 7 + .../java/org/datadog/jmxfetch/TestApp.java | 41 +++++ .../datadog/jmxfetch/jee/BaseStatistic.java | 36 +++++ .../jmxfetch/jee/BoundaryStatisticImpl.java | 24 +++ .../jee/BoundedRangeStatisticImpl.java | 25 ++++ .../jmxfetch/jee/CountStatisticImpl.java | 17 +++ .../jmxfetch/jee/RangeStatisticImpl.java | 31 ++++ .../jmxfetch/jee/TimeStatisticImpl.java | 38 +++++ .../jee/UnsupportedStatisticImpl.java | 12 ++ src/test/resources/jmx_jee_data.yaml | 17 +++ 14 files changed, 453 insertions(+) create mode 100644 src/main/java/org/datadog/jmxfetch/util/JeeStatisticsAttributes.java create mode 100644 src/test/java/org/datadog/jmxfetch/jee/BaseStatistic.java create mode 100644 src/test/java/org/datadog/jmxfetch/jee/BoundaryStatisticImpl.java create mode 100644 src/test/java/org/datadog/jmxfetch/jee/BoundedRangeStatisticImpl.java create mode 100644 src/test/java/org/datadog/jmxfetch/jee/CountStatisticImpl.java create mode 100644 src/test/java/org/datadog/jmxfetch/jee/RangeStatisticImpl.java create mode 100644 src/test/java/org/datadog/jmxfetch/jee/TimeStatisticImpl.java create mode 100644 src/test/java/org/datadog/jmxfetch/jee/UnsupportedStatisticImpl.java create mode 100644 src/test/resources/jmx_jee_data.yaml diff --git a/pom.xml b/pom.xml index 3b6c8c295..c660f8292 100644 --- a/pom.xml +++ b/pom.xml @@ -185,6 +185,12 @@ snakeyaml ${snakeyaml.version} + + javax.management.j2ee + javax.management.j2ee-api + 1.1.2 + + junit junit diff --git a/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java index 6849116fa..eed702ed4 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java @@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j; import org.datadog.jmxfetch.service.ServiceNameProvider; +import org.datadog.jmxfetch.util.JeeStatisticsAttributes; import java.io.IOException; import java.util.ArrayList; @@ -17,6 +18,7 @@ import javax.management.MBeanException; import javax.management.ObjectName; import javax.management.ReflectionException; +import javax.management.j2ee.statistics.Statistic; import javax.management.openmbean.CompositeData; @Slf4j @@ -26,9 +28,11 @@ public class JmxComplexAttribute extends JmxSubAttribute { Arrays.asList( "javax.management.openmbean.CompositeData", "javax.management.openmbean.CompositeDataSupport", + "javax.management.j2ee.statistics.Statistic", "java.util.HashMap", "java.util.Map"); + private List subAttributeList = new ArrayList(); /** JmxComplexAttribute constructor. */ @@ -68,6 +72,8 @@ private void populateSubAttributeList(Object attributeValue) { for (String key : data.keySet()) { this.subAttributeList.add(key); } + } else if (attributeValue instanceof Statistic) { + this.subAttributeList.addAll(JeeStatisticsAttributes.attributesFor(attributeValue)); } } @@ -96,6 +102,8 @@ private Object getValue(String subAttribute) } else if (value instanceof java.util.Map) { Map data = (Map) value; return data.get(subAttribute); + } else if (value instanceof Statistic) { + return JeeStatisticsAttributes.dataFor(value, subAttribute); } throw new NumberFormatException(); } diff --git a/src/main/java/org/datadog/jmxfetch/util/JeeStatisticsAttributes.java b/src/main/java/org/datadog/jmxfetch/util/JeeStatisticsAttributes.java new file mode 100644 index 000000000..f753bea4e --- /dev/null +++ b/src/main/java/org/datadog/jmxfetch/util/JeeStatisticsAttributes.java @@ -0,0 +1,140 @@ +package org.datadog.jmxfetch.util; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import javax.management.AttributeNotFoundException; +import javax.management.ReflectionException; +import javax.management.j2ee.statistics.BoundaryStatistic; +import javax.management.j2ee.statistics.BoundedRangeStatistic; +import javax.management.j2ee.statistics.CountStatistic; +import javax.management.j2ee.statistics.RangeStatistic; +import javax.management.j2ee.statistics.Statistic; +import javax.management.j2ee.statistics.TimeStatistic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JeeStatisticsAttributes { + /** Attributes for {@link javax.management.j2ee.statistics.CountStatistic} */ + public static final List COUNT_ATTRIBUTES = Collections.singletonList("count"); + + /** Attributes for {@link javax.management.j2ee.statistics.BoundaryStatistic} */ + public static final List BOUNDARY_ATTRIBUTES = + Collections.unmodifiableList(Arrays.asList("upperBound", "lowerBound")); + + /** Attributes for {@link javax.management.j2ee.statistics.TimeStatistic} */ + public static final List TIME_ATTRIBUTES = + Collections.unmodifiableList(Arrays.asList("count", "minTime", "maxTime", "totalTime")); + + /** Attributes for {@link javax.management.j2ee.statistics.RangeStatistic} */ + public static final List RANGE_ATTRIBUTES = + Collections.unmodifiableList(Arrays.asList("highWaterMark", "lowWaterMark", "current")); + + /** Attributes for {@link javax.management.j2ee.statistics.BoundedRangeStatistic} */ + public static final List BOUNDED_RANGE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList("upperBound", "lowerBound", "highWaterMark", "lowWaterMark", "current")); + + private static final Logger LOGGER = LoggerFactory.getLogger(JeeStatisticsAttributes.class); + private static final Map, Map> METHOD_CACHE = + buildMethodCache(); + + private static Map, Map> buildMethodCache() { + Map, Map> map = new HashMap<>(); + map.put(CountStatistic.class, buildMethodCacheFor(CountStatistic.class, COUNT_ATTRIBUTES)); + map.put(TimeStatistic.class, buildMethodCacheFor(TimeStatistic.class, TIME_ATTRIBUTES)); + map.put( + BoundaryStatistic.class, buildMethodCacheFor(BoundaryStatistic.class, BOUNDARY_ATTRIBUTES)); + map.put(RangeStatistic.class, buildMethodCacheFor(RangeStatistic.class, RANGE_ATTRIBUTES)); + map.put( + BoundedRangeStatistic.class, + buildMethodCacheFor(BoundedRangeStatistic.class, BOUNDED_RANGE_ATTRIBUTES)); + return map; + } + + private static Map buildMethodCacheFor( + final Class cls, final List attributes) { + final Map map = new HashMap<>(); + AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Void run() { + for (String attribute : attributes) { + try { + Method getter = cls.getMethod(getterMethodName(attribute)); + map.put(attribute, MethodHandles.lookup().unreflect(getter)); + } catch (Throwable t) { + LOGGER.warn( + "Unable to find getter for attribute {}: {}", attribute, t.getMessage()); + } + } + return null; + } + }); + + return map; + } + + private static String getterMethodName(String attribute) { + // inspired from JavaBean PropertyDescriptor + return "get" + attribute.substring(0, 1).toUpperCase(Locale.ROOT) + attribute.substring(1); + } + + public static List attributesFor(Object s) { + if (s instanceof CountStatistic) { + return COUNT_ATTRIBUTES; + } + if (s instanceof TimeStatistic) { + return TIME_ATTRIBUTES; + } + if (s instanceof BoundedRangeStatistic) { + return BOUNDED_RANGE_ATTRIBUTES; + } + if (s instanceof RangeStatistic) { + return RANGE_ATTRIBUTES; + } + if (s instanceof BoundaryStatistic) { + return BOUNDARY_ATTRIBUTES; + } + return Collections.emptyList(); + } + + public static long dataFor(Object s, String attribute) + throws ReflectionException, AttributeNotFoundException { + Class cls = null; + if (s instanceof CountStatistic) { + cls = CountStatistic.class; + } else if (s instanceof TimeStatistic) { + cls = TimeStatistic.class; + } else if (s instanceof BoundedRangeStatistic) { + cls = BoundedRangeStatistic.class; + } else if (s instanceof RangeStatistic) { + cls = RangeStatistic.class; + } else if (s instanceof BoundaryStatistic) { + cls = BoundaryStatistic.class; + } + if (cls == null) { + throw new AttributeNotFoundException("Not supported JSR-77 class: " + s.getClass().getName()); + } + MethodHandle methodHandle = METHOD_CACHE.get(cls).get(attribute); + if (methodHandle == null) { + throw new AttributeNotFoundException( + "Unable to find getter for attribute " + attribute + " on class " + cls.getName()); + } + try { + return (long) methodHandle.invoke(s); + } catch (Throwable t) { + throw new ReflectionException( + new Exception(t), + "Unable to invoke getter for attribute" + attribute + " on class " + cls.getName()); + } + } +} diff --git a/src/test/java/org/datadog/jmxfetch/SimpleTestJavaApp.java b/src/test/java/org/datadog/jmxfetch/SimpleTestJavaApp.java index d245cb183..c72f76a8f 100644 --- a/src/test/java/org/datadog/jmxfetch/SimpleTestJavaApp.java +++ b/src/test/java/org/datadog/jmxfetch/SimpleTestJavaApp.java @@ -1,10 +1,23 @@ package org.datadog.jmxfetch; +import org.datadog.jmxfetch.jee.BoundaryStatisticImpl; +import org.datadog.jmxfetch.jee.BoundedRangeStatisticImpl; +import org.datadog.jmxfetch.jee.CountStatisticImpl; +import org.datadog.jmxfetch.jee.RangeStatisticImpl; +import org.datadog.jmxfetch.jee.TimeStatisticImpl; +import org.datadog.jmxfetch.jee.UnsupportedStatisticImpl; + import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import javax.management.j2ee.statistics.BoundaryStatistic; +import javax.management.j2ee.statistics.BoundedRangeStatistic; +import javax.management.j2ee.statistics.CountStatistic; +import javax.management.j2ee.statistics.RangeStatistic; +import javax.management.j2ee.statistics.Statistic; +import javax.management.j2ee.statistics.TimeStatistic; import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.CompositeType; @@ -45,6 +58,14 @@ public class SimpleTestJavaApp implements SimpleTestJavaAppMBean { private final CompositeData nestedCompositeData; + // JEE Stats + private final CountStatistic countStatistic = new CountStatisticImpl("Sample Counter", long42424242); + private final TimeStatistic timeStatistic = new TimeStatisticImpl("Sample Time", 0, Long.MAX_VALUE, long42424242, 1); + private final RangeStatistic rangeStatistic = new RangeStatisticImpl("Sample Range", Long.MIN_VALUE, Long.MAX_VALUE, long42424242); + private final BoundaryStatistic boundaryStatistic = new BoundaryStatisticImpl("Sample Boundary", Long.MIN_VALUE, Long.MAX_VALUE); + private final BoundedRangeStatistic boundedRangeStatistic = new BoundedRangeStatisticImpl("Sample BoundedRange", Long.MIN_VALUE, Long.MAX_VALUE, 0, -1, +1); + private final Statistic unsupportedStatistic = new UnsupportedStatisticImpl("Sample Unsupported Statistic"); + SimpleTestJavaApp() { hashmap.put("thisis0", 0); hashmap.put("thisis10", 10); @@ -203,6 +224,36 @@ public CompositeData getNestedCompositeData() { return this.nestedCompositeData; } + @Override + public Statistic getJeeCounter() { + return countStatistic; + } + + @Override + public Statistic getJeeRange() { + return rangeStatistic; + } + + @Override + public Statistic getJeeTime() { + return timeStatistic; + } + + @Override + public Statistic getJeeBoundary() { + return boundaryStatistic; + } + + @Override + public Statistic getJeeBoundedRange() { + return boundedRangeStatistic; + } + + @Override + public Statistic getJeeUnsupported() { + return unsupportedStatistic; + } + private CompositeData buildCompositeData(Integer i) { try { return new CompositeDataSupport( diff --git a/src/test/java/org/datadog/jmxfetch/SimpleTestJavaAppMBean.java b/src/test/java/org/datadog/jmxfetch/SimpleTestJavaAppMBean.java index 161724721..1f2abb683 100644 --- a/src/test/java/org/datadog/jmxfetch/SimpleTestJavaAppMBean.java +++ b/src/test/java/org/datadog/jmxfetch/SimpleTestJavaAppMBean.java @@ -4,6 +4,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import javax.management.j2ee.statistics.Statistic; import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.TabularData; @@ -45,4 +46,10 @@ public interface SimpleTestJavaAppMBean { TabularDataSupport getTabularDataSupport(); CompositeData getNestedCompositeData(); + Statistic getJeeCounter(); + Statistic getJeeRange(); + Statistic getJeeTime(); + Statistic getJeeBoundary(); + Statistic getJeeBoundedRange(); + Statistic getJeeUnsupported(); } diff --git a/src/test/java/org/datadog/jmxfetch/TestApp.java b/src/test/java/org/datadog/jmxfetch/TestApp.java index 2f39e5840..cd8d5a73b 100644 --- a/src/test/java/org/datadog/jmxfetch/TestApp.java +++ b/src/test/java/org/datadog/jmxfetch/TestApp.java @@ -1126,6 +1126,47 @@ public void testTabularDataTagged() throws Exception { assertCoverage(); } + @Test + public void testJeeStatistics() throws Exception { + // We expose a few metrics through JMX + SimpleTestJavaApp testApp = new SimpleTestJavaApp(); + registerMBean(testApp, "org.datadog.jmxfetch.test:type=SimpleTestJavaApp"); + + // We do a first collection + when(appConfig.isTargetDirectInstances()).thenReturn(true); + initApplication("jmx_jee_data.yaml"); + + run(); + List> metrics = getMetrics(); + + // 13 metrics from java.lang + 16 defined - 1 undefined + assertEquals(28, metrics.size()); + + List tags = Arrays.asList( + "instance:jmx_test_instance", + "jmx_domain:org.datadog.jmxfetch.test", + "type:SimpleTestJavaApp" + ); + final String prefix = "jmx.org.datadog.jmxfetch.test."; + + assertMetric(prefix + "jee_counter.count", testApp.getLong42424242(), tags, -1); + assertMetric(prefix + "jee_time.count", 1, tags, -1); + assertMetric(prefix + "jee_time.min_time", 0, tags, -1); + assertMetric(prefix + "jee_time.max_time", Long.MAX_VALUE, tags, -1); + assertMetric(prefix + "jee_time.total_time", testApp.getLong42424242(), tags, -1); + assertMetric(prefix + "jee_range.low_water_mark", Long.MIN_VALUE, tags, -1); + assertMetric(prefix + "jee_range.high_water_mark", Long.MAX_VALUE, tags, -1); + assertMetric(prefix + "jee_range.current", testApp.getLong42424242(), tags, -1); + assertMetric(prefix + "jee_boundary.lower_bound", Long.MIN_VALUE, tags, -1); + assertMetric(prefix + "jee_boundary.upper_bound", Long.MAX_VALUE, tags, -1); + assertMetric(prefix + "jee_bounded_range.low_water_mark", Long.MIN_VALUE, tags, -1); + assertMetric(prefix + "jee_bounded_range.high_water_mark", Long.MAX_VALUE, tags, -1); + assertMetric(prefix + "jee_bounded_range.current", 0, tags, -1); + assertMetric(prefix + "jee_bounded_range.lower_bound", -1, tags, -1); + assertMetric(prefix + "jee_bounded_range.upper_bound", 1, tags, -1); + assertCoverage(); + } + @Test public void testNestedCompositeData() throws Exception { SimpleTestJavaApp testApp = new SimpleTestJavaApp(); diff --git a/src/test/java/org/datadog/jmxfetch/jee/BaseStatistic.java b/src/test/java/org/datadog/jmxfetch/jee/BaseStatistic.java new file mode 100644 index 000000000..1f45600d0 --- /dev/null +++ b/src/test/java/org/datadog/jmxfetch/jee/BaseStatistic.java @@ -0,0 +1,36 @@ +package org.datadog.jmxfetch.jee; + +import javax.management.j2ee.statistics.Statistic; + +public abstract class BaseStatistic implements Statistic { + private final String name; + + public BaseStatistic(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getUnit() { + return ""; + } + + @Override + public String getDescription() { + return ""; + } + + @Override + public long getStartTime() { + return 0; + } + + @Override + public long getLastSampleTime() { + return 0; + } +} diff --git a/src/test/java/org/datadog/jmxfetch/jee/BoundaryStatisticImpl.java b/src/test/java/org/datadog/jmxfetch/jee/BoundaryStatisticImpl.java new file mode 100644 index 000000000..dbac88422 --- /dev/null +++ b/src/test/java/org/datadog/jmxfetch/jee/BoundaryStatisticImpl.java @@ -0,0 +1,24 @@ +package org.datadog.jmxfetch.jee; + +import javax.management.j2ee.statistics.BoundaryStatistic; + +public class BoundaryStatisticImpl extends BaseStatistic implements BoundaryStatistic { + private final long low; + private final long high; + + public BoundaryStatisticImpl(String name, long low, long high) { + super(name); + this.low = low; + this.high = high; + } + + @Override + public long getUpperBound() { + return high; + } + + @Override + public long getLowerBound() { + return low; + } +} diff --git a/src/test/java/org/datadog/jmxfetch/jee/BoundedRangeStatisticImpl.java b/src/test/java/org/datadog/jmxfetch/jee/BoundedRangeStatisticImpl.java new file mode 100644 index 000000000..a6cfad715 --- /dev/null +++ b/src/test/java/org/datadog/jmxfetch/jee/BoundedRangeStatisticImpl.java @@ -0,0 +1,25 @@ +package org.datadog.jmxfetch.jee; + +import javax.management.j2ee.statistics.BoundedRangeStatistic; + +public class BoundedRangeStatisticImpl extends RangeStatisticImpl implements BoundedRangeStatistic { + private final long low; + private final long high; + + public BoundedRangeStatisticImpl( + String name, long min, long max, long current, long low, long high) { + super(name, min, max, current); + this.low = low; + this.high = high; + } + + @Override + public long getUpperBound() { + return high; + } + + @Override + public long getLowerBound() { + return low; + } +} diff --git a/src/test/java/org/datadog/jmxfetch/jee/CountStatisticImpl.java b/src/test/java/org/datadog/jmxfetch/jee/CountStatisticImpl.java new file mode 100644 index 000000000..0946a2326 --- /dev/null +++ b/src/test/java/org/datadog/jmxfetch/jee/CountStatisticImpl.java @@ -0,0 +1,17 @@ +package org.datadog.jmxfetch.jee; + +import javax.management.j2ee.statistics.CountStatistic; + +public class CountStatisticImpl extends BaseStatistic implements CountStatistic { + private final long count; + + public CountStatisticImpl(String name, long count) { + super(name); + this.count = count; + } + + @Override + public long getCount() { + return count; + } +} diff --git a/src/test/java/org/datadog/jmxfetch/jee/RangeStatisticImpl.java b/src/test/java/org/datadog/jmxfetch/jee/RangeStatisticImpl.java new file mode 100644 index 000000000..6eacb8353 --- /dev/null +++ b/src/test/java/org/datadog/jmxfetch/jee/RangeStatisticImpl.java @@ -0,0 +1,31 @@ +package org.datadog.jmxfetch.jee; + +import javax.management.j2ee.statistics.RangeStatistic; + +public class RangeStatisticImpl extends BaseStatistic implements RangeStatistic { + private final long current; + private final long max; + private final long min; + + public RangeStatisticImpl(String name, long min, long max, long current) { + super(name); + this.current = current; + this.max = max; + this.min = min; + } + + @Override + public long getHighWaterMark() { + return max; + } + + @Override + public long getLowWaterMark() { + return min; + } + + @Override + public long getCurrent() { + return current; + } +} diff --git a/src/test/java/org/datadog/jmxfetch/jee/TimeStatisticImpl.java b/src/test/java/org/datadog/jmxfetch/jee/TimeStatisticImpl.java new file mode 100644 index 000000000..a2979dff2 --- /dev/null +++ b/src/test/java/org/datadog/jmxfetch/jee/TimeStatisticImpl.java @@ -0,0 +1,38 @@ +package org.datadog.jmxfetch.jee; + +import javax.management.j2ee.statistics.TimeStatistic; + +public class TimeStatisticImpl extends BaseStatistic implements TimeStatistic { + private final long count; + private final long min; + private final long max; + private final long total; + + public TimeStatisticImpl(String name, long min, long max, long total, long count) { + super(name); + this.count = count; + this.min = min; + this.max = max; + this.total = total; + } + + @Override + public long getCount() { + return count; + } + + @Override + public long getMaxTime() { + return max; + } + + @Override + public long getMinTime() { + return min; + } + + @Override + public long getTotalTime() { + return total; + } +} diff --git a/src/test/java/org/datadog/jmxfetch/jee/UnsupportedStatisticImpl.java b/src/test/java/org/datadog/jmxfetch/jee/UnsupportedStatisticImpl.java new file mode 100644 index 000000000..a7959432b --- /dev/null +++ b/src/test/java/org/datadog/jmxfetch/jee/UnsupportedStatisticImpl.java @@ -0,0 +1,12 @@ +package org.datadog.jmxfetch.jee; + +public class UnsupportedStatisticImpl extends BaseStatistic { + + public UnsupportedStatisticImpl(String name) { + super(name); + } + + public long getUnsupportedValue() { + return 0; + } +} diff --git a/src/test/resources/jmx_jee_data.yaml b/src/test/resources/jmx_jee_data.yaml new file mode 100644 index 000000000..a357ab3ae --- /dev/null +++ b/src/test/resources/jmx_jee_data.yaml @@ -0,0 +1,17 @@ +--- +init_config: + +instances: + - jvm_direct: true + refresh_beans: 4 + name: jmx_test_instance + conf: + - include: + domain: org.datadog.jmxfetch.test + attribute: + - JeeCounter # 1 metric + - JeeTime # 4 metrics + - JeeRange # 3 metrics + - JeeBoundary # 2 metrics + - JeeBoundedRange # 5 metrics + - JeeUnsupported # 0 metrics