/* * Copyright (C) 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.facebook.stats.mx; import com.google.common.collect.ImmutableSet; import javax.management.JMException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanInfo; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeType; import javax.management.openmbean.OpenMBeanAttributeInfo; import javax.management.openmbean.OpenType; import javax.management.openmbean.SimpleType; import java.lang.management.ManagementFactory; import java.util.Optional; import java.util.Set; import java.util.concurrent.Callable; import java.util.regex.Pattern; /** * Exports JVM stats so that they are available as counter values in fb303 stats. * */ public class JVMStatsExporter { private final Stats stats; private StatsNameBuilder statsNameBuilder; private static final String NAME_PREFIX = "jvm"; private static final Set<? extends OpenType> NUMERIC_TYPES = new ImmutableSet.Builder<SimpleType>() .add(SimpleType.BYTE) .add(SimpleType.SHORT) .add(SimpleType.INTEGER) .add(SimpleType.LONG) .add(SimpleType.FLOAT) .add(SimpleType.DOUBLE) .build(); /** * Creates an instance and exports the counters from the beans matching the supplied name * pattern. * * * @param stats The stats instance to which all the dynamic counters will be registered. * @param statNamePattern A regex {@link java.util.regex.Pattern} that is matched against all * generated counter key names. Only the counters that match the supplied pattern are added to * Stats. Note that all stats have the prefix <code>"jvm."</code>. * @param beanNamePatterns the object name patterns used to discover the Mbeans. * * @throws JMException if the beanNamePattern is not a valid bean name pattern. * @throws java.util.regex.PatternSyntaxException if the syntax of statNamePattern is invalid * * @see ObjectName for details on the syntax of beanNamePattern */ public JVMStatsExporter( Stats stats, String statNamePattern, String... beanNamePatterns ) throws JMException { this(stats, patternFilter(Pattern.compile(statNamePattern)), beanNamePatterns); } /** * Creates an instance and exports the counters from the beans matching the supplied name * pattern. * * * @param stats The stats instance to which all the dynamic counters will be registered. * @param statNameBuilder A name builder {@link StatsNameBuilder} that is applied to all * discovered MBeans. * @param beanNamePatterns the object name patterns used to discover the Mbeans. * * @throws JMException if the beanNamePattern is not a valid bean name pattern. * * @see ObjectName for details on the syntax of beanNamePattern */ public JVMStatsExporter( Stats stats, StatsNameBuilder statsNameBuilder, String... beanNamePatterns ) throws JMException { this.stats = stats; this.statsNameBuilder = statsNameBuilder; for (String beanNamePattern : beanNamePatterns) { exportNumericAttributes(new ObjectName(beanNamePattern)); } } /** * Creates an instance and exports all the counters from all the platform beans. * * @param stats The stats instance to which all dynamic counters will be registered. * @throws JMException unexpected, indicates a coding/error bug. */ public JVMStatsExporter(Stats stats) throws JMException { this(stats, ".*", "java.lang:type=*,*"); } @SuppressWarnings("ResultOfObjectAllocationIgnored") public static Stats createAndBindTo(Stats stats) throws JMException { new JVMStatsExporter(stats); return stats; } /** * Exports all the numeric attributes of beans matching the supplied MXBean name pattern. * Also, discovers the numeric properties of the attributes of type CompositeData and registers * them as well. * * @param beanNamePattern the bean name pattern used to discover the beans. * @see javax.management.ObjectName for bean name pattern syntax * @see java.lang.management for the list of java platform mbeans * * @throws JMException if there are errors querying MBeans or information on them */ public void exportNumericAttributes(ObjectName beanNamePattern) throws JMException { MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer(); Set<ObjectName> beanNames = beanServer.queryNames(beanNamePattern, null); // Iterate through all beans and register their numeric properties with Stats for (ObjectName beanName : beanNames) { MBeanInfo beanInfo = beanServer.getMBeanInfo(beanName); // Iterate through all bean attributes for (MBeanAttributeInfo attributeInfo : beanInfo.getAttributes()) { if (attributeInfo.isReadable()) { // Figure out the open type for the attribute OpenType<?> openType = null; if (attributeInfo instanceof OpenMBeanAttributeInfo) { openType = ((OpenMBeanAttributeInfo) attributeInfo).getOpenType(); } else { // Sometimes the open mbean info is available in the descriptor Object obj = attributeInfo.getDescriptor().getFieldValue("openType"); if (obj != null && obj instanceof OpenType) { openType = (OpenType) obj; } } // once the open type is found, figure out if it's a numeric or composite type if (openType != null) { if (NUMERIC_TYPES.contains(openType)){ // numeric attribute types are registered with callbacks that simply // return their value Optional<String> name = statsNameBuilder.name( beanName, attributeInfo.getName(), null ); if (name.isPresent()) { stats.addDynamicCounter( name.get(), new MBeanLongAttributeFetcher(beanServer, beanName, attributeInfo.getName())); } } else if (openType instanceof CompositeType) { // for composite types, we figure out which properties of the composite type // are numeric and register callbacks to fetch those composite type attributes CompositeType compositeType = (CompositeType) openType; for (String key : compositeType.keySet()) { if (NUMERIC_TYPES.contains(compositeType.getType(key))) { Optional<String> name = statsNameBuilder.name( beanName, attributeInfo.getName(), key ); if (name.isPresent()) { stats.addDynamicCounter( name.get(), new MBeanLongCompositeValueFetcher( beanServer, beanName, attributeInfo.getName(), key ) ); } } } } } } } } } private static StatsNameBuilder patternFilter(Pattern pattern) { return (bean, attribute, key) -> { String name = getStatName(bean, attribute, key); return pattern.matcher(name).matches() ? Optional.of(name) : Optional.empty(); }; } /** * Returns a stat name for the given set of parameters. * * @param beanName the MBean name to which the stat belongs * @param attributeNames the mbean attribute name followed by any nested attribute names for * composite types * * @return the stat name */ private static String getStatName(ObjectName beanName, String attributeName, String key) { StringBuilder builder = new StringBuilder(NAME_PREFIX); String value = beanName.getKeyProperty("type"); if (value != null) { builder.append('.').append(value); } value = beanName.getKeyProperty("name"); if (value != null) { builder.append('.').append(value); } if (attributeName != null) { builder.append('.').append(attributeName); } if (key != null) { builder.append('.').append(key); } return builder.toString().replace(' ', '_'); } /** * A base class that fetches an mbean attribute value. */ private static class MBeanAttributeFetcher { private final MBeanServer mBeanServer; private final ObjectName beanName; private final String attribute; protected MBeanAttributeFetcher(MBeanServer mBeanServer, ObjectName beanName, String attribute) { this.mBeanServer = mBeanServer; this.beanName = beanName; this.attribute = attribute; } protected Object getAttributeValue() throws Exception { return mBeanServer.getAttribute(beanName, attribute); } } /** * Fetches attribute value as long */ private static class MBeanLongAttributeFetcher extends MBeanAttributeFetcher implements Callable<Long> { private MBeanLongAttributeFetcher( MBeanServer mBeanServer, ObjectName beanName, String attribute) { super(mBeanServer, beanName, attribute); } @Override public Long call() throws Exception { Object obj = getAttributeValue(); if(obj instanceof Number) { return ((Number)obj).longValue(); } return -1L; } } /** * Fetches an attribute of an mbean attribute that is of composite type. */ private static class MBeanLongCompositeValueFetcher extends MBeanAttributeFetcher implements Callable<Long> { private final String itemName; private MBeanLongCompositeValueFetcher( MBeanServer mBeanServer, ObjectName beanName, String attribute, String itemName) { super(mBeanServer, beanName, attribute); this.itemName = itemName; } @Override public Long call() throws Exception { Object obj = getAttributeValue(); if(obj instanceof CompositeData) { obj = ((CompositeData)obj).get(itemName); if (obj instanceof Number) { return ((Number)obj).longValue(); } } return -1L; } } }