Skip to content

Commit

Permalink
Support jsr77 j2ee statistics
Browse files Browse the repository at this point in the history
  • Loading branch information
amarziali committed Jun 26, 2024
1 parent 4d97846 commit 88848bb
Show file tree
Hide file tree
Showing 14 changed files with 453 additions and 0 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<dependency>
<groupId>javax.management.j2ee</groupId>
<artifactId>javax.management.j2ee-api</artifactId>
<version>1.1.2</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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<String> subAttributeList = new ArrayList<String>();

/** JmxComplexAttribute constructor. */
Expand Down Expand Up @@ -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));
}
}

Expand Down Expand Up @@ -96,6 +102,8 @@ private Object getValue(String subAttribute)
} else if (value instanceof java.util.Map) {
Map<String, Object> data = (Map<String, Object>) value;
return data.get(subAttribute);
} else if (value instanceof Statistic) {
return JeeStatisticsAttributes.dataFor(value, subAttribute);
}
throw new NumberFormatException();
}
Expand Down
140 changes: 140 additions & 0 deletions src/main/java/org/datadog/jmxfetch/util/JeeStatisticsAttributes.java
Original file line number Diff line number Diff line change
@@ -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<String> COUNT_ATTRIBUTES = Collections.singletonList("count");

/** Attributes for {@link javax.management.j2ee.statistics.BoundaryStatistic} */
public static final List<String> BOUNDARY_ATTRIBUTES =
Collections.unmodifiableList(Arrays.asList("upperBound", "lowerBound"));

/** Attributes for {@link javax.management.j2ee.statistics.TimeStatistic} */
public static final List<String> TIME_ATTRIBUTES =
Collections.unmodifiableList(Arrays.asList("count", "minTime", "maxTime", "totalTime"));

/** Attributes for {@link javax.management.j2ee.statistics.RangeStatistic} */
public static final List<String> RANGE_ATTRIBUTES =
Collections.unmodifiableList(Arrays.asList("highWaterMark", "lowWaterMark", "current"));

/** Attributes for {@link javax.management.j2ee.statistics.BoundedRangeStatistic} */
public static final List<String> 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<Class<? extends Statistic>, Map<String, MethodHandle>> METHOD_CACHE =
buildMethodCache();

private static Map<Class<? extends Statistic>, Map<String, MethodHandle>> buildMethodCache() {
Map<Class<? extends Statistic>, Map<String, MethodHandle>> 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<String, MethodHandle> buildMethodCacheFor(
final Class<? extends Statistic> cls, final List<String> attributes) {
final Map<String, MethodHandle> map = new HashMap<>();
AccessController.doPrivileged(
new PrivilegedAction<Void>() {
@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<String> 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<? extends Statistic> 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());
}
}
}
51 changes: 51 additions & 0 deletions src/test/java/org/datadog/jmxfetch/SimpleTestJavaApp.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -45,4 +46,10 @@ public interface SimpleTestJavaAppMBean {
TabularDataSupport getTabularDataSupport();

CompositeData getNestedCompositeData();
Statistic getJeeCounter();
Statistic getJeeRange();
Statistic getJeeTime();
Statistic getJeeBoundary();
Statistic getJeeBoundedRange();
Statistic getJeeUnsupported();
}
41 changes: 41 additions & 0 deletions src/test/java/org/datadog/jmxfetch/TestApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Map<String, Object>> metrics = getMetrics();

// 13 metrics from java.lang + 16 defined - 1 undefined
assertEquals(28, metrics.size());

List<String> 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();
Expand Down
36 changes: 36 additions & 0 deletions src/test/java/org/datadog/jmxfetch/jee/BaseStatistic.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit 88848bb

Please sign in to comment.