From d8b68bc0c65675831a5c2c1349dab9f8cb0185c3 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Thu, 27 Jun 2024 16:04:51 +0200 Subject: [PATCH] Add classloader weak cache --- .../datadog/jmxfetch/JmxComplexAttribute.java | 12 +- .../util/JeeStatisticsAttributes.java | 314 +++++++++++------- 2 files changed, 189 insertions(+), 137 deletions(-) diff --git a/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java index aa4b231b..e57cb84f 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java @@ -72,11 +72,9 @@ private void populateSubAttributeList(Object attributeValue) { for (String key : data.keySet()) { this.subAttributeList.add(key); } - } else if (JeeStatisticsAttributes.CLASS_STATISTIC != null - && JeeStatisticsAttributes.CLASS_STATISTIC.isInstance(attributeValue)) { + } else if (JeeStatisticsAttributes.isJeeStatistic(attributeValue)) { this.subAttributeList.addAll(JeeStatisticsAttributes.attributesFor(attributeValue)); - } else if (JeeStatisticsAttributes.CLASS_STATS != null - && JeeStatisticsAttributes.CLASS_STATS.isInstance(attributeValue)) { + } else if (JeeStatisticsAttributes.isJeeStat(attributeValue)) { this.subAttributeList.addAll(JeeStatisticsAttributes.getStatisticNames(attributeValue)); } } @@ -106,11 +104,9 @@ private Object getValue(String subAttribute) } else if (value instanceof java.util.Map) { Map data = (Map) value; return data.get(subAttribute); - } else if (JeeStatisticsAttributes.CLASS_STATISTIC != null - && JeeStatisticsAttributes.CLASS_STATISTIC.isInstance(value)) { + } else if (JeeStatisticsAttributes.isJeeStatistic(value)) { return JeeStatisticsAttributes.dataFor(value, subAttribute); - } else if (JeeStatisticsAttributes.CLASS_STATS != null - && JeeStatisticsAttributes.CLASS_STATS.isInstance(value)) { + } else if (JeeStatisticsAttributes.isJeeStat(value)) { return JeeStatisticsAttributes.getStatisticDataFor(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 index 828ed2e0..d5741432 100644 --- a/src/main/java/org/datadog/jmxfetch/util/JeeStatisticsAttributes.java +++ b/src/main/java/org/datadog/jmxfetch/util/JeeStatisticsAttributes.java @@ -7,6 +7,7 @@ import java.lang.invoke.MethodHandles; import java.security.AccessController; import java.security.PrivilegedAction; +import java.sql.Ref; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -14,166 +15,191 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.WeakHashMap; import javax.management.AttributeNotFoundException; import javax.management.ReflectionException; public class JeeStatisticsAttributes { - /** - * Attributes for @see javax.management.j2ee.statistics.CountStatistic - * */ + /** Attributes for @see javax.management.j2ee.statistics.CountStatistic */ private static final List COUNT_ATTRIBUTES = Collections.singletonList("count"); - /** - * Attributes for @see javax.management.j2ee.statistics.BoundaryStatistic - * */ + /** Attributes for @see javax.management.j2ee.statistics.BoundaryStatistic */ private static final List BOUNDARY_ATTRIBUTES = Collections.unmodifiableList(Arrays.asList("upperBound", "lowerBound")); - /** - * Attributes for @see javax.management.j2ee.statistics.TimeStatistic - * */ + /** Attributes for @see javax.management.j2ee.statistics.TimeStatistic */ private static final List TIME_ATTRIBUTES = Collections.unmodifiableList(Arrays.asList("count", "minTime", "maxTime", "totalTime")); - /** - * Attributes for @see javax.management.j2ee.statistics.RangeStatistic - * */ + /** Attributes for @see javax.management.j2ee.statistics.RangeStatistic */ private static final List RANGE_ATTRIBUTES = Collections.unmodifiableList(Arrays.asList("highWaterMark", "lowWaterMark", "current")); - /** - * Attributes for @see javax.management.j2ee.statistics.BoundedRangeStatistic - * */ + /** Attributes for @see javax.management.j2ee.statistics.BoundedRangeStatistic */ private static final List BOUNDED_RANGE_ATTRIBUTES = Collections.unmodifiableList( Arrays.asList("upperBound", "lowerBound", "highWaterMark", "lowWaterMark", "current")); private static final Logger LOGGER = LoggerFactory.getLogger(JeeStatisticsAttributes.class); - public static final Class CLASS_STATS = - maybeLookupClass("javax.management.j2ee.statistics.Stats"); - public static final MethodHandle MH_STATS_GET_STATISTIC_NAMES = - maybeFindMethodHandleFor(CLASS_STATS, "getStatisticNames"); - public static final MethodHandle MH_STATS_GET_STATISTIC = - maybeFindMethodHandleFor(CLASS_STATS, "getStatistic", String.class); - public static final Class CLASS_STATISTIC = - maybeLookupClass("javax.management.j2ee.statistics.Statistic"); - private static final Class CLASS_COUNT_STATISTIC = - maybeLookupClass("javax.management.j2ee.statistics.CountStatistic"); - private static final Class CLASS_TIME_STATISTIC = - maybeLookupClass("javax.management.j2ee.statistics.TimeStatistic"); - private static final Class CLASS_RANGE_STATISTIC = - maybeLookupClass("javax.management.j2ee.statistics.RangeStatistic"); - private static final Class CLASS_BOUNDARY_STATISTIC = - maybeLookupClass("javax.management.j2ee.statistics.BoundaryStatistic"); - private static final Class CLASS_BOUNDED_RANGE_STATISTIC = - maybeLookupClass("javax.management.j2ee.statistics.BoundedRangeStatistic"); + private static final WeakHashMap REFLECTION_CACHE = + new WeakHashMap<>(); - private static final Map, Map> METHOD_CACHE = - buildMethodCache(); + private static class ReflectionHolder { + public final Class classStat; + public final MethodHandle mhStatGetStatisticNames; + public final MethodHandle mhStatGetStatistic; + public final Class classStatistic; + private final Class classCountStatistic; + private final Class classTimeStatistic; + private final Class classRangeStatistic; + private final Class classBoundaryStatistic; + private final Class classBoundedRangeStatistic; - private static Class maybeLookupClass(final String name) { - try { - return Class.forName(name); - } catch (Throwable t) { - LOGGER.warn("Class {} is unavailable. J2ee statistics won't be extracted", name); - } - return null; - } + private final Map, Map> methodCache; - private static Map, Map> buildMethodCache() { - Map, Map> map = new HashMap<>(); - if (CLASS_STATISTIC != null) { - map.put(CLASS_COUNT_STATISTIC, - buildMethodCacheFor(CLASS_COUNT_STATISTIC, COUNT_ATTRIBUTES)); - } - if (CLASS_TIME_STATISTIC != null) { - map.put(CLASS_TIME_STATISTIC, - buildMethodCacheFor(CLASS_TIME_STATISTIC, TIME_ATTRIBUTES)); - } - if (CLASS_BOUNDARY_STATISTIC != null) { - map.put(CLASS_BOUNDARY_STATISTIC, buildMethodCacheFor(CLASS_BOUNDARY_STATISTIC, - BOUNDARY_ATTRIBUTES)); + ReflectionHolder(final ClassLoader classLoader) { + classStat = maybeLookupClass("javax.management.j2ee.statistics.Stats", classLoader); + mhStatGetStatisticNames = maybeFindMethodHandleFor(classStat, "getStatisticNames"); + mhStatGetStatistic = maybeFindMethodHandleFor(classStat, "getStatistic", String.class); + classStatistic = maybeLookupClass("javax.management.j2ee.statistics.Statistic", + classLoader); + classCountStatistic = + maybeLookupClass("javax.management.j2ee.statistics.CountStatistic", classLoader); + classTimeStatistic = + maybeLookupClass("javax.management.j2ee.statistics.TimeStatistic", classLoader); + classRangeStatistic = + maybeLookupClass("javax.management.j2ee.statistics.RangeStatistic", classLoader); + classBoundaryStatistic = + maybeLookupClass("javax.management.j2ee.statistics.BoundaryStatistic", classLoader); + classBoundedRangeStatistic = + maybeLookupClass("javax.management.j2ee.statistics.BoundedRangeStatistic", + classLoader); + methodCache = buildMethodCache(); } - if (CLASS_RANGE_STATISTIC != null) { - map.put(CLASS_RANGE_STATISTIC, - buildMethodCacheFor(CLASS_RANGE_STATISTIC, RANGE_ATTRIBUTES)); + + private Map, Map> buildMethodCache() { + Map, Map> map = new HashMap<>(); + if (classCountStatistic != null) { + map.put(classCountStatistic, + buildMethodCacheFor(classCountStatistic, COUNT_ATTRIBUTES)); + } + if (classTimeStatistic != null) { + map.put(classTimeStatistic, + buildMethodCacheFor(classTimeStatistic, TIME_ATTRIBUTES)); + } + if (classBoundaryStatistic != null) { + map.put( + classBoundaryStatistic, + buildMethodCacheFor(classBoundaryStatistic, BOUNDARY_ATTRIBUTES)); + } + if (classRangeStatistic != null) { + map.put(classRangeStatistic, + buildMethodCacheFor(classRangeStatistic, RANGE_ATTRIBUTES)); + } + if (classBoundedRangeStatistic != null) { + map.put( + classBoundedRangeStatistic, + buildMethodCacheFor(classBoundedRangeStatistic, BOUNDED_RANGE_ATTRIBUTES)); + } + return map; } - if (CLASS_BOUNDED_RANGE_STATISTIC != null) { - map.put( - CLASS_BOUNDED_RANGE_STATISTIC, - buildMethodCacheFor(CLASS_BOUNDED_RANGE_STATISTIC, BOUNDED_RANGE_ATTRIBUTES)); + + private static MethodHandle maybeFindMethodHandleFor( + final Class cls, final String name, final Class... parameterTypes) { + if (cls == null) { + return null; + } + return AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public MethodHandle run() { + try { + return MethodHandles.lookup() + .unreflect(cls.getMethod(name, parameterTypes)); + } catch (Throwable t) { + LOGGER.warn("Unable to find method {} for class {}: {}", name, cls, + t.getMessage()); + } + return null; + } + }); } - return map; - } + private static Map buildMethodCacheFor( + final Class cls, final List attributes) { + final Map map = new HashMap<>(); + for (String attribute : attributes) { + MethodHandle methodHandle = + maybeFindMethodHandleFor(cls, getterMethodName(attribute)); + if (methodHandle != null) { + map.put(attribute, methodHandle); + } + } + return map; + } - private static MethodHandle maybeFindMethodHandleFor(final Class cls, final String name, - final Class... parameterTypes) { - if (cls == null) { + private static Class maybeLookupClass(final String name, final ClassLoader classLoader) { + try { + return Class.forName(name, false, classLoader); + } catch (Throwable t) { + LOGGER.debug( + "Class {} is unavailable for classloader {}. JEE statistics won't be extracted", + name, + classLoader); + } return null; } - return AccessController.doPrivileged( - new PrivilegedAction() { - @Override - public MethodHandle run() { - try { - return MethodHandles.lookup().unreflect( - cls.getMethod(name, parameterTypes)); - } catch (Throwable t) { - LOGGER.warn("Unable to find method {} for class {}: {}", name, cls, - t.getMessage()); - } - return null; - } - }); - } - private static Map buildMethodCacheFor(final Class cls, - final List attributes) { - final Map map = new HashMap<>(); - for (String attribute : attributes) { - MethodHandle methodHandle = maybeFindMethodHandleFor(cls, getterMethodName(attribute)); - if (methodHandle != null) { - map.put(attribute, methodHandle); - } + private static String getterMethodName(String attribute) { + // inspired from JavaBean PropertyDescriptor + return "get" + attribute.substring(0, 1).toUpperCase(Locale.ROOT) + + attribute.substring(1); } - return map; } - private static String getterMethodName(String attribute) { - // inspired from JavaBean PropertyDescriptor - return "get" + attribute.substring(0, 1).toUpperCase(Locale.ROOT) + attribute.substring(1); + private static ReflectionHolder getOrCreateReflectionHolder(final ClassLoader classLoader) { + // no need to lock here. At worst, we'll do it more time if there is contention. + ReflectionHolder holder = REFLECTION_CACHE.get(classLoader); + if (holder != null) { + return holder; + } + holder = new ReflectionHolder(classLoader); + REFLECTION_CACHE.put(classLoader, holder); + return holder; } /** * Get the list of attributes for a @see javax.management.j2ee.statistics.Statistic object. + * * @param instance a @see javax.management.j2ee.statistics.Statistic instance * @return the list of attributes */ public static List attributesFor(Object instance) { - if (CLASS_COUNT_STATISTIC != null && CLASS_COUNT_STATISTIC.isInstance(instance)) { + ReflectionHolder rh = getOrCreateReflectionHolder(instance.getClass().getClassLoader()); + if (rh.classCountStatistic != null && rh.classCountStatistic.isInstance(instance)) { return COUNT_ATTRIBUTES; } - if (CLASS_TIME_STATISTIC != null && CLASS_TIME_STATISTIC.isInstance(instance)) { + if (rh.classTimeStatistic != null && rh.classTimeStatistic.isInstance(instance)) { return TIME_ATTRIBUTES; } - if (CLASS_BOUNDED_RANGE_STATISTIC != null - && CLASS_BOUNDED_RANGE_STATISTIC.isInstance(instance)) { + if (rh.classBoundedRangeStatistic != null + && rh.classBoundedRangeStatistic.isInstance(instance)) { return BOUNDED_RANGE_ATTRIBUTES; } - if (CLASS_RANGE_STATISTIC != null && CLASS_RANGE_STATISTIC.isInstance(instance)) { + if (rh.classRangeStatistic != null && rh.classRangeStatistic.isInstance(instance)) { return RANGE_ATTRIBUTES; } - if (CLASS_BOUNDARY_STATISTIC != null && CLASS_BOUNDARY_STATISTIC.isInstance(instance)) { + if (rh.classBoundaryStatistic != null && rh.classBoundaryStatistic.isInstance(instance)) { return BOUNDARY_ATTRIBUTES; } return Collections.emptyList(); } /** - * Fetch the data for a @see javax.management.j2ee.statistics.Statistic instance given - * an attribute name. + * Fetch the data for a @see javax.management.j2ee.statistics.Statistic instance given an + * attribute name. + * * @param instance the @see javax.management.j2ee.statistics.Statistic object * @param attribute the attribute name * @return the data if any @@ -183,24 +209,25 @@ public static List attributesFor(Object instance) { public static long dataFor(Object instance, String attribute) throws ReflectionException, AttributeNotFoundException { Class cls = null; - if (CLASS_COUNT_STATISTIC != null && CLASS_COUNT_STATISTIC.isInstance(instance)) { - cls = CLASS_COUNT_STATISTIC; - } else if (CLASS_TIME_STATISTIC != null && CLASS_TIME_STATISTIC.isInstance(instance)) { - cls = CLASS_TIME_STATISTIC; - } else if (CLASS_BOUNDED_RANGE_STATISTIC != null - && CLASS_BOUNDED_RANGE_STATISTIC.isInstance(instance)) { - cls = CLASS_BOUNDED_RANGE_STATISTIC; - } else if (CLASS_RANGE_STATISTIC != null && CLASS_RANGE_STATISTIC.isInstance(instance)) { - cls = CLASS_RANGE_STATISTIC; - } else if (CLASS_BOUNDARY_STATISTIC != null - && CLASS_BOUNDARY_STATISTIC.isInstance(instance)) { - cls = CLASS_BOUNDARY_STATISTIC; + ReflectionHolder rh = getOrCreateReflectionHolder(instance.getClass().getClassLoader()); + if (rh.classCountStatistic != null && rh.classCountStatistic.isInstance(instance)) { + cls = rh.classCountStatistic; + } else if (rh.classTimeStatistic != null && rh.classTimeStatistic.isInstance(instance)) { + cls = rh.classTimeStatistic; + } else if (rh.classBoundedRangeStatistic != null + && rh.classBoundedRangeStatistic.isInstance(instance)) { + cls = rh.classBoundedRangeStatistic; + } else if (rh.classRangeStatistic != null && rh.classRangeStatistic.isInstance(instance)) { + cls = rh.classRangeStatistic; + } else if (rh.classBoundaryStatistic != null + && rh.classBoundaryStatistic.isInstance(instance)) { + cls = rh.classBoundaryStatistic; } if (cls == null) { - throw new AttributeNotFoundException("Not supported JSR-77 class: " - + instance.getClass().getName()); + throw new AttributeNotFoundException( + "Not supported JSR-77 class: " + instance.getClass().getName()); } - MethodHandle methodHandle = METHOD_CACHE.get(cls).get(attribute); + MethodHandle methodHandle = rh.methodCache.get(cls).get(attribute); if (methodHandle == null) { throw new AttributeNotFoundException( "Unable to find getter for attribute " + attribute + " on class " + cls.getName()); @@ -216,19 +243,21 @@ public static long dataFor(Object instance, String attribute) /** * Get names for a Stat complex object. + * * @param instance a Stat instance * @return the names of inner statistics it holds. */ public static List getStatisticNames(Object instance) { - if (MH_STATS_GET_STATISTIC_NAMES == null || MH_STATS_GET_STATISTIC == null) { + ReflectionHolder rh = getOrCreateReflectionHolder(instance.getClass().getClassLoader()); + if (rh.mhStatGetStatisticNames == null || rh.mhStatGetStatistic == null) { return Collections.emptyList(); } try { - String[] names = (String[]) MH_STATS_GET_STATISTIC_NAMES.invoke(instance); + String[] names = (String[]) rh.mhStatGetStatisticNames.invoke(instance); if (names != null) { List ret = new ArrayList<>(); for (String name : names) { - Object stat = MH_STATS_GET_STATISTIC.invoke(instance, name); + Object stat = rh.mhStatGetStatistic.invoke(instance, name); for (String attr : attributesFor(stat)) { ret.add(name + "." + attr); } @@ -236,14 +265,17 @@ public static List getStatisticNames(Object instance) { return ret; } } catch (Throwable t) { - LOGGER.warn("Unable to get statistic names from jee stat class {}: {}", - instance.getClass(), t.getMessage()); + LOGGER.warn( + "Unable to get statistic names from jee stat class {}: {}", + instance.getClass(), + t.getMessage()); } return Collections.emptyList(); } /** * Get the statistic data for a complex Stat object. + * * @param instance the Stat instance * @param name the name (dot separated) of the statistic * @return the value. @@ -251,9 +283,10 @@ public static List getStatisticNames(Object instance) { */ public static long getStatisticDataFor(Object instance, String name) throws AttributeNotFoundException { - if (MH_STATS_GET_STATISTIC == null) { - throw new IllegalStateException("Cannot fetch statistic data for instance type " - + instance.getClass()); + ReflectionHolder rh = getOrCreateReflectionHolder(instance.getClass().getClassLoader()); + if (rh.mhStatGetStatistic == null) { + throw new IllegalStateException( + "Cannot fetch statistic data for instance type " + instance.getClass()); } int idx = name.indexOf("."); if (idx == -1) { @@ -262,11 +295,34 @@ public static long getStatisticDataFor(Object instance, String name) String statName = name.substring(0, idx); String attrName = name.substring(idx + 1); try { - Object stat = MH_STATS_GET_STATISTIC.invoke(instance, statName); + Object stat = rh.mhStatGetStatistic.invoke(instance, statName); return dataFor(stat, attrName); } catch (Throwable t) { - throw new AttributeNotFoundException("Unable to get statistic with name " + name - + "from jee stat class " + instance.getClass()); + throw new AttributeNotFoundException( + "Unable to get statistic with name " + + name + + "from jee stat class " + + instance.getClass()); } } + + /** + * Check that's instance is instance of Stat. + * @param instance the instance + * @return a boolean + */ + public static boolean isJeeStat(Object instance) { + ReflectionHolder rh = getOrCreateReflectionHolder(instance.getClass().getClassLoader()); + return rh.classStat != null && rh.classStat.isInstance(instance); + } + + /** + * Check that's instance is instance of Statistic. + * @param instance the instance + * @return a boolean + */ + public static boolean isJeeStatistic(Object instance) { + ReflectionHolder rh = getOrCreateReflectionHolder(instance.getClass().getClassLoader()); + return rh.classStatistic != null && rh.classStatistic.isInstance(instance); + } }