package org.datadog.jmxfetch; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.management.AttributeNotFoundException; import javax.management.InstanceNotFoundException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanException; import javax.management.ObjectName; import javax.management.openmbean.CompositeData; import javax.management.openmbean.InvalidKeyException; import javax.management.openmbean.TabularData; import javax.management.ReflectionException; public class JMXTabularAttribute extends JMXAttribute { private String instanceName; private HashMap<String, HashMap<String, HashMap<String, Object>>> subAttributeList; public JMXTabularAttribute(MBeanAttributeInfo attribute, ObjectName beanName, String instanceName, Connection connection, HashMap<String, String> instanceTags) { super(attribute, beanName, instanceName, connection, instanceTags, false); subAttributeList = new HashMap<String, HashMap<String, HashMap<String, Object>>>(); } private String getMultiKey(Collection keys) { StringBuilder sb = new StringBuilder(); boolean first = true; for (Object key : keys) { if (!first) { sb.append(","); } // I hope these have sane toString() methods sb.append(key.toString()); first = false; } return sb.toString(); } private void populateSubAttributeList(Object value) { TabularData data = (TabularData) value; for (Object rowKey : data.keySet()) { Collection keys = (Collection) rowKey; CompositeData compositeData = data.get(keys.toArray()); String pathKey = getMultiKey(keys); HashMap<String, HashMap<String, Object>> subAttributes = new HashMap<String, HashMap<String, Object>>(); for (String key : compositeData.getCompositeType().keySet()) { if (compositeData.get(key) instanceof CompositeData) { for (String subKey : ((CompositeData) compositeData.get(key)).getCompositeType().keySet()) { subAttributes.put(key + "." + subKey, new HashMap<String, Object>()); } } else { subAttributes.put(key, new HashMap<String, Object>()); } } subAttributeList.put(pathKey, subAttributes); } } protected String[] getTags(String key, String subAttribute) throws AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException, IOException { List<String> tagsList = new ArrayList<String>(); String fullMetricKey = getAttributeName() + "." + subAttribute; Map<String, ?> attributeParams = getAttributesFor(fullMetricKey); if (attributeParams != null) { Map<String, String> yamlTags = (Map) attributeParams.get("tags"); for (String tagName : yamlTags.keySet()) { String tag = tagName; String value = yamlTags.get(tagName); Object resolvedValue; if (value.startsWith("$")){ resolvedValue = getValue(key, value.substring(1)); if (resolvedValue != null){ value = (String) resolvedValue; } } tagsList.add(tag + ":" + value); } } String[] defaultTags = super.getTags(); tagsList.addAll(Arrays.asList(defaultTags)); String[] tags = new String[tagsList.size()]; tags = tagsList.toArray(tags); return tags; } private Map<String, ?> getAttributesFor(String key) { Filter include = getMatchingConf().getInclude(); if (include != null) { Object includeAttribute = include.getAttribute(); if (includeAttribute instanceof LinkedHashMap<?, ?>) { return (Map<String, ?>) ((Map)includeAttribute).get(key); } } return null; } @Override public LinkedList<HashMap<String, Object>> getMetrics() throws AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException, IOException { LinkedList<HashMap<String, Object>> metrics = new LinkedList<HashMap<String, Object>>(); HashMap<String, LinkedList<HashMap<String, Object>>> subMetrics = new HashMap<String, LinkedList<HashMap<String, Object>>>(); for (String dataKey : subAttributeList.keySet()) { HashMap<String, HashMap<String, Object>> subSub = subAttributeList.get(dataKey); for (String metricKey : subSub.keySet()) { String fullMetricKey = getAttributeName() + "." + metricKey; HashMap<String, Object> metric = subSub.get(metricKey); if (metric.get(ALIAS) == null) { metric.put(ALIAS, convertMetricName(getAlias(metricKey))); } if (metric.get(METRIC_TYPE) == null) { metric.put(METRIC_TYPE, getMetricType(metricKey)); } if (metric.get("tags") == null) { metric.put("tags", getTags(dataKey, metricKey)); } metric.put("value", castToDouble(getValue(dataKey, metricKey))); if(!subMetrics.containsKey(fullMetricKey)) { subMetrics.put(fullMetricKey, new LinkedList<HashMap<String, Object>>()); } subMetrics.get(fullMetricKey).add(metric); } } for (String key : subMetrics.keySet()) { // only add explicitly included metrics if (getAttributesFor(key) != null) { metrics.addAll(sortAndFilter(key, subMetrics.get(key))); } } return metrics; } private List<HashMap<String, Object>> sortAndFilter(String metricKey, LinkedList<HashMap<String, Object>> metrics) { Map<String, ?> attributes = getAttributesFor(metricKey); if (!attributes.containsKey("limit")) { return metrics; } Integer limit = (Integer) attributes.get("limit"); if (metrics.size() <= limit) { return metrics; } MetricComparator comp = new MetricComparator(); Collections.sort(metrics, comp); String sort = (String) attributes.get("sort"); if (sort == null || sort.equals("desc")) { metrics.subList(0, limit).clear(); } else { metrics.subList(metrics.size() - limit, metrics.size()).clear(); } return metrics; } private class MetricComparator implements Comparator<HashMap<String, Object>> { public int compare(HashMap<String, Object> o1, HashMap<String, Object> o2) { Double v1 = (Double) o1.get("value"); Double v2 = (Double) o2.get("value"); return v1.compareTo(v2); } } private Object getValue(String key, String subAttribute) throws AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException, IOException { try{ Object value = this.getJmxValue(); String attributeType = getAttribute().getType(); TabularData data = (TabularData) value; for (Object rowKey : data.keySet()) { Collection keys = (Collection) rowKey; String pathKey = getMultiKey(keys); if (key.equals(pathKey)) { CompositeData compositeData = data.get(keys.toArray()); if (subAttribute.contains(".")) { // walk down the path Object o; for (String subPathKey : subAttribute.split("\\.")) { o = compositeData.get(subPathKey); if (o instanceof CompositeData) { compositeData = (CompositeData) o; } else { return compositeData.get(subPathKey); } } } else { return compositeData.get(subAttribute); } } } } catch (InvalidKeyException e){ LOGGER.warn("`"+getAttribute().getName()+"` attribute does not have a `"+subAttribute+"` key."); return null; } throw new NumberFormatException(); } private Object getMetricType(String subAttribute) { String subAttributeName = getAttribute().getName() + "." + subAttribute; String metricType = null; Filter include = getMatchingConf().getInclude(); if (include.getAttribute() instanceof LinkedHashMap<?, ?>) { LinkedHashMap<String, LinkedHashMap<String, String>> attribute = (LinkedHashMap<String, LinkedHashMap<String, String>>) (include.getAttribute()); metricType = attribute.get(subAttributeName).get(METRIC_TYPE); if (metricType == null) { metricType = attribute.get(subAttributeName).get("type"); } } if (metricType == null) { metricType = "gauge"; } return metricType; } @Override public boolean match(Configuration configuration) { if (!matchDomain(configuration) || !matchBean(configuration) || excludeMatchDomain(configuration) || excludeMatchBean(configuration)) { return false; } try { populateSubAttributeList(getJmxValue()); } catch (Exception e) { return false; } return matchAttribute(configuration);//TODO && !excludeMatchAttribute(configuration); } private boolean matchSubAttribute(Filter params, String subAttributeName, boolean matchOnEmpty) { if ((params.getAttribute() instanceof LinkedHashMap<?, ?>) && ((LinkedHashMap<String, Object>) (params.getAttribute())).containsKey(subAttributeName)) { return true; } else if ((params.getAttribute() instanceof ArrayList<?> && ((ArrayList<String>) (params.getAttribute())).contains(subAttributeName))) { return true; } else if (params.getAttribute() == null) { return matchOnEmpty; } return false; } private boolean matchAttribute(Configuration configuration) { if (matchSubAttribute(configuration.getInclude(), getAttributeName(), true)) { return true; } Iterator<String> it1 = subAttributeList.keySet().iterator(); while (it1.hasNext()) { String key = it1.next(); HashMap<String, HashMap<String, Object>> subSub = subAttributeList.get(key); Iterator<String> it2 = subSub.keySet().iterator(); while (it2.hasNext()) { String subKey = it2.next(); if (!matchSubAttribute(configuration.getInclude(), getAttributeName() + "." + subKey, true)) { it2.remove(); } } if (subSub.size() <= 0) { it1.remove(); } } return subAttributeList.size() > 0; } }