예제 #1
0
 /** Creates a tag list from an object name. */
 private TagList createTagList(ObjectName name) {
   Map<String, String> props = name.getKeyPropertyList();
   List<Tag> tags = Lists.newArrayList();
   for (Map.Entry<String, String> e : props.entrySet()) {
     String key = PROP_KEY_PREFIX + "." + e.getKey();
     tags.add(Tags.newTag(key, e.getValue()));
   }
   tags.add(Tags.newTag(DOMAIN_KEY, name.getDomain()));
   tags.add(CLASS_TAG);
   return SortedTagList.builder().withTags(tags).build();
 }
예제 #2
0
/** Generic poller for fetching simple data from JMX. */
public final class JmxMetricPoller implements MetricPoller {

  private static final Logger LOGGER = LoggerFactory.getLogger(JmxMetricPoller.class);

  private static final Tag CLASS_TAG =
      Tags.newTag(
          StandardTagKeys.CLASS_NAME.getKeyName(), JmxMetricPoller.class.getCanonicalName());

  private static final String DOMAIN_KEY = "JmxDomain";
  private static final String COMPOSITE_PATH_KEY = "JmxCompositePath";
  private static final String PROP_KEY_PREFIX = "Jmx";

  private final JmxConnector connector;
  private final List<ObjectName> queries;
  private final MetricFilter counters;

  /**
   * Creates a new instance that polls mbeans matching the provided object name pattern.
   *
   * @param connector used to get a connection to an MBeanServer
   * @param query object name pattern for selecting mbeans
   * @param counters metrics matching this filter will be treated as counters, all others will be
   *     gauges
   */
  public JmxMetricPoller(JmxConnector connector, ObjectName query, MetricFilter counters) {
    this.connector = connector;
    this.queries = Lists.newArrayList();
    queries.add(query);
    this.counters = counters;
  }

  /**
   * Creates a new instance that polls mbeans matching the provided object name patterns.
   *
   * @param connector used to get a connection to an MBeanServer
   * @param queries object name patterns for selecting mbeans
   * @param counters metrics matching this filter will be treated as counters, all others will be
   *     gauges
   */
  public JmxMetricPoller(JmxConnector connector, List<ObjectName> queries, MetricFilter counters) {
    this.connector = connector;
    this.queries = queries;
    this.counters = counters;
  }

  /** Creates a tag list from an object name. */
  private TagList createTagList(ObjectName name) {
    Map<String, String> props = name.getKeyPropertyList();
    List<Tag> tags = Lists.newArrayList();
    for (Map.Entry<String, String> e : props.entrySet()) {
      String key = PROP_KEY_PREFIX + "." + e.getKey();
      tags.add(Tags.newTag(key, e.getValue()));
    }
    tags.add(Tags.newTag(DOMAIN_KEY, name.getDomain()));
    tags.add(CLASS_TAG);
    return SortedTagList.builder().withTags(tags).build();
  }

  /** Create a new metric object and add it to the list. */
  private void addMetric(List<Metric> metrics, String name, TagList tags, Object value) {
    long now = System.currentTimeMillis();
    Number num = asNumber(value);
    if (num != null) {
      TagList newTags =
          counters.matches(MonitorConfig.builder(name).withTags(tags).build())
              ? SortedTagList.builder().withTags(tags).withTag(DataSourceType.COUNTER).build()
              : SortedTagList.builder().withTags(tags).withTag(DataSourceType.GAUGE).build();
      Metric m = new Metric(name, newTags, now, num);
      metrics.add(m);
    }
  }

  /**
   * Recursively extracts simple numeric values from composite data objects. The map {@code values}
   * will be populated with a path to the value as the key and the simple object as the value.
   */
  private void extractValues(String path, Map<String, Object> values, CompositeData obj) {
    for (String key : obj.getCompositeType().keySet()) {
      String newPath = (path == null) ? key : path + "." + key;
      Object value = obj.get(key);
      if (value instanceof CompositeData) {
        extractValues(newPath, values, (CompositeData) value);
      } else if (value != null) {
        values.put(newPath, value);
      }
    }
  }

  /**
   * Query the mbean connection and add all metrics that satisfy the filter to the list {@code
   * metrics}.
   */
  private void getMetrics(
      MBeanServerConnection con, MetricFilter filter, List<Metric> metrics, ObjectName name)
      throws JMException, IOException {
    // Create tags from the object name
    TagList tags = createTagList(name);
    MBeanInfo info = con.getMBeanInfo(name);
    MBeanAttributeInfo[] attrInfos = info.getAttributes();

    // Restrict to attributes that match the filter
    List<String> matchingNames = Lists.newArrayList();
    for (MBeanAttributeInfo attrInfo : attrInfos) {
      String attrName = attrInfo.getName();
      if (filter.matches(new MonitorConfig.Builder(attrName).withTags(tags).build())) {
        matchingNames.add(attrName);
      }
    }
    List<Attribute> attributeList = safelyLoadAttributes(con, name, matchingNames);

    for (Attribute attr : attributeList) {
      String attrName = attr.getName();
      Object obj = attr.getValue();
      if (obj instanceof CompositeData) {
        Map<String, Object> values = Maps.newHashMap();
        extractValues(null, values, (CompositeData) obj);
        for (Map.Entry<String, Object> e : values.entrySet()) {
          String key = e.getKey();
          TagList newTags =
              SortedTagList.builder().withTags(tags).withTag(COMPOSITE_PATH_KEY, key).build();
          if (filter.matches(MonitorConfig.builder(attrName).withTags(newTags).build())) {
            addMetric(metrics, attrName, newTags, e.getValue());
          }
        }
      } else {
        addMetric(metrics, attrName, tags, obj);
      }
    }
  }

  /**
   * Try to convert an object into a number. Boolean values will return 1 if true and 0 if false. If
   * the value is null or an unknown data type null will be returned.
   */
  private static Number asNumber(Object value) {
    Number num = null;
    if (value == null) {
      num = null;
    } else if (value instanceof Number) {
      num = (Number) value;
    } else if (value instanceof Boolean) {
      num = ((Boolean) value) ? 1 : 0;
    }
    return num;
  }

  /** {@inheritDoc} */
  public List<Metric> poll(MetricFilter filter) {
    return poll(filter, false);
  }

  /** {@inheritDoc} */
  public List<Metric> poll(MetricFilter filter, boolean reset) {
    List<Metric> metrics = Lists.newArrayList();
    try {
      MBeanServerConnection con = connector.getConnection();
      for (ObjectName query : queries) {
        Set<ObjectName> names = con.queryNames(query, null);
        for (ObjectName name : names) {
          try {
            getMetrics(con, filter, metrics, name);
          } catch (Exception e) {
            LOGGER.warn("failed to get metrics for: " + name, e);
          }
        }
      }
    } catch (Exception e) {
      LOGGER.warn("failed to collect jmx metrics.", e);
    }
    return metrics;
  }

  /**
   * There are issues loading some JMX attributes on some systems. This protects us from a single
   * bad attribute stopping us reading any metrics (or just a random sampling) out of the system.
   */
  private static List<Attribute> safelyLoadAttributes(
      MBeanServerConnection server, ObjectName objectName, List<String> matchingNames) {
    try {
      // first try batch loading all attributes as this is faster
      return batchLoadAttributes(server, objectName, matchingNames);
    } catch (Exception e) {
      // JBOSS ticket: https://issues.jboss.org/browse/AS7-4404

      LOGGER.info("Error batch loading attributes for {} : {}", objectName, e.getMessage());
      // some containers (jboss I am looking at you) fail the entire getAttributes request
      // if one is broken we can get the working attributes if we ask for them individually
      return individuallyLoadAttributes(server, objectName, matchingNames);
    }
  }

  private static List<Attribute> batchLoadAttributes(
      MBeanServerConnection server, ObjectName objectName, List<String> matchingNames)
      throws InstanceNotFoundException, ReflectionException, IOException {
    final String[] namesArray = matchingNames.toArray(new String[matchingNames.size()]);
    return server.getAttributes(objectName, namesArray).asList();
  }

  private static List<Attribute> individuallyLoadAttributes(
      MBeanServerConnection server, ObjectName objectName, List<String> matchingNames) {
    List<Attribute> attributes = Lists.newArrayList();
    for (String attrName : matchingNames) {
      try {
        Object value = server.getAttribute(objectName, attrName);
        attributes.add(new Attribute(attrName, value));
      } catch (Exception e) {
        LOGGER.info(
            "Couldn't load attribute {} for {} : {}",
            new Object[] {attrName, objectName, e.getMessage()},
            e);
      }
    }
    return attributes;
  }
}