Skip to content

Commit

Permalink
[attribute] add TabularData support (#128)
Browse files Browse the repository at this point in the history
* Added JMX TabularData support

* Added unit tests for TabularData/JMXMultiAttribute

* Only including multi-attribute metrics if they're explicitly defined to avoid unintended metric growth

* Expanded range in TestApp for counter attribute rate values

* [tests] `sort`, `limit` for tabular data

Rename CompositeType components.
Modify the `jmx.yaml` YAML configuration test to use `sort` and `limit`
for tabular type MBeans.

* [attribute] rename `JMXMultiAttribute`

* [attribute][tabular] tag substitution on any field

Support tag substitution for any field of a CompositeData attribute.
  • Loading branch information
yannmh authored Mar 3, 2017
1 parent dfc5698 commit 2f58003
Show file tree
Hide file tree
Showing 10 changed files with 401 additions and 21 deletions.
6 changes: 5 additions & 1 deletion src/main/java/org/datadog/jmxfetch/Instance.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class Instance {
"java.util.concurrent.atomic.AtomicInteger", "java.util.concurrent.atomic.AtomicLong",
"java.lang.Object", "java.lang.Boolean", "boolean", "java.lang.Number");
private final static List<String> COMPOSED_TYPES = Arrays.asList("javax.management.openmbean.CompositeData", "java.util.HashMap", "java.util.Map");
private final static List<String> MULTI_TYPES = Arrays.asList("javax.management.openmbean.TabularData");
private final static int MAX_RETURNED_METRICS = 350;
private final static int DEFAULT_REFRESH_BEANS_PERIOD = 600;
public static final String PROCESS_NAME_REGEX = "process_name_regex";
Expand Down Expand Up @@ -248,8 +249,11 @@ private void getMatchingAttributes() {
LOGGER.debug(ATTRIBUTE + beanName + " : " + attributeInfo + " has attributeInfo simple type");
jmxAttribute = new JMXSimpleAttribute(attributeInfo, beanName, instanceName, connection, tags, cassandraAliasing);
} else if (COMPOSED_TYPES.contains(attributeType)) {
LOGGER.debug(ATTRIBUTE + beanName + " : " + attributeInfo + " has attributeInfo complex type");
LOGGER.debug(ATTRIBUTE + beanName + " : " + attributeInfo + " has attributeInfo composite type");
jmxAttribute = new JMXComplexAttribute(attributeInfo, beanName, instanceName, connection, tags);
} else if (MULTI_TYPES.contains(attributeType)) {
LOGGER.debug(ATTRIBUTE + beanName + " : " + attributeInfo + " has attributeInfo tabular type");
jmxAttribute = new JMXTabularAttribute(attributeInfo, beanName, instanceName, connection, tags);
} else {
try {
LOGGER.debug(ATTRIBUTE + beanName + " : " + attributeInfo + " has an unsupported type: " + attributeType);
Expand Down
5 changes: 2 additions & 3 deletions src/main/java/org/datadog/jmxfetch/JMXAttribute.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public abstract class JMXAttribute {

protected static final String ALIAS = "alias";
protected static final String METRIC_TYPE = "metric_type";
private final static Logger LOGGER = Logger.getLogger(JMXAttribute.class.getName());
protected final static Logger LOGGER = Logger.getLogger(JMXAttribute.class.getName());
private static final List<String> EXCLUDED_BEAN_PARAMS = Arrays.asList("domain", "domain_regex", "bean_name", "bean",
"bean_regex", "attribute", "exclude_tags", "tags");
private static final String FIRST_CAP_PATTERN = "(.)([A-Z][a-z]+)";
Expand Down Expand Up @@ -98,7 +98,6 @@ private void applyTagsBlackList() {
private void addAdditionalTags() {
Filter include = this.matchingConf.getInclude();
if (include != null) {

for (Map.Entry<String, String> tag : include.getAdditionalTags().entrySet()) {
this.defaultTagsList.add(tag.getKey() + ":" + this.replaceByAlias(tag.getValue()));
}
Expand Down Expand Up @@ -254,7 +253,7 @@ Object convertMetricValue(Object metricValue) {
return converted;
}

double getValueAsDouble(Object metricValue) {
double castToDouble(Object metricValue) {
Object value = convertMetricValue(metricValue);

if (value instanceof String) {
Expand Down
9 changes: 4 additions & 5 deletions src/main/java/org/datadog/jmxfetch/JMXComplexAttribute.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,27 +65,26 @@ public LinkedList<HashMap<String, Object>> getMetrics()
metric.put("tags", getTags());
}

metric.put("value", getValue(subAttribute));
metric.put("value", castToDouble(getValue(subAttribute)));
metrics.add(metric);

}
return metrics;

}

private double getValue(String subAttribute) throws AttributeNotFoundException, InstanceNotFoundException,
private Object getValue(String subAttribute) throws AttributeNotFoundException, InstanceNotFoundException,
MBeanException, ReflectionException, IOException {

Object value = this.getJmxValue();
String attributeType = getAttribute().getType();

if ("javax.management.openmbean.CompositeData".equals(attributeType)) {
CompositeData data = (CompositeData) value;
return getValueAsDouble(data.get(subAttribute));

return data.get(subAttribute);
} else if (("java.util.HashMap".equals(attributeType)) || ("java.util.Map".equals(attributeType))) {
Map<String, Object> data = (Map<String, Object>) value;
return getValueAsDouble(data.get(subAttribute));
return data.get(subAttribute);
}
throw new NumberFormatException();
}
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/org/datadog/jmxfetch/JMXSimpleAttribute.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public LinkedList<HashMap<String, Object>> getMetrics() throws AttributeNotFound
HashMap<String, Object> metric = new HashMap<String, Object>();

metric.put("alias", getAlias());
metric.put("value", getValue());
metric.put("value", castToDouble(getValue()));
metric.put("tags", getTags());
metric.put("metric_type", getMetricType());
LinkedList<HashMap<String, Object>> metrics = new LinkedList<HashMap<String, Object>>();
Expand Down Expand Up @@ -98,8 +98,8 @@ private String getMetricType() {
return metricType;
}

private double getValue() throws AttributeNotFoundException, InstanceNotFoundException, MBeanException,
private Object getValue() throws AttributeNotFoundException, InstanceNotFoundException, MBeanException,
ReflectionException, IOException, NumberFormatException {
return getValueAsDouble(this.getJmxValue());
return this.getJmxValue();
}
}
296 changes: 296 additions & 0 deletions src/main/java/org/datadog/jmxfetch/JMXTabularAttribute.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
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;
}
}
Loading

0 comments on commit 2f58003

Please sign in to comment.