summaryrefslogtreecommitdiffstats
path: root/jdisc_jmx_metrics
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /jdisc_jmx_metrics
Publish
Diffstat (limited to 'jdisc_jmx_metrics')
-rw-r--r--jdisc_jmx_metrics/.gitignore2
-rw-r--r--jdisc_jmx_metrics/OWNERS1
-rw-r--r--jdisc_jmx_metrics/pom.xml90
-rw-r--r--jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/ComponentMetricMBean.java123
-rw-r--r--jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/ConsumerContextMetric.java59
-rw-r--r--jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/ConsumerContextMetricReader.java20
-rw-r--r--jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/JmxMetricConsumer.java120
-rw-r--r--jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/JmxMetricContext.java56
-rw-r--r--jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/MultiSourceComponentMetric.java88
-rw-r--r--jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/cloud/package-info.java4
-rw-r--r--jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/core/AbstractDynamicMBean.java24
-rw-r--r--jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/core/CounterMetricUnit.java37
-rw-r--r--jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/core/GaugeMetricUnit.java117
-rw-r--r--jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/core/MetricUnit.java34
-rw-r--r--jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/package-info.java4
-rw-r--r--jdisc_jmx_metrics/src/main/resources/configdefinitions/jmx-metric.def8
-rw-r--r--jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/ComponentMetricMBeanTestCase.java103
-rw-r--r--jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/ConsumerContextMetricTestCase.java100
-rw-r--r--jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/IntegrationTestCase.java459
-rw-r--r--jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/JmxMetricConsumerTestCase.java189
-rw-r--r--jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/JmxMetricContextTestCase.java84
-rw-r--r--jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/MultiSourceComponentMetricTestCase.java97
-rw-r--r--jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/core/CounterMetricUnitTestCase.java78
-rw-r--r--jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/core/GaugeMetricUnitTestCase.java79
24 files changed, 1976 insertions, 0 deletions
diff --git a/jdisc_jmx_metrics/.gitignore b/jdisc_jmx_metrics/.gitignore
new file mode 100644
index 00000000000..12251442258
--- /dev/null
+++ b/jdisc_jmx_metrics/.gitignore
@@ -0,0 +1,2 @@
+/target
+/pom.xml.build
diff --git a/jdisc_jmx_metrics/OWNERS b/jdisc_jmx_metrics/OWNERS
new file mode 100644
index 00000000000..3b2ba1ede81
--- /dev/null
+++ b/jdisc_jmx_metrics/OWNERS
@@ -0,0 +1 @@
+gjoranv
diff --git a/jdisc_jmx_metrics/pom.xml b/jdisc_jmx_metrics/pom.xml
new file mode 100644
index 00000000000..75970944d7e
--- /dev/null
+++ b/jdisc_jmx_metrics/pom.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0"?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+ http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>parent</artifactId>
+ <version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
+ </parent>
+ <artifactId>jdisc_jmx_metrics</artifactId>
+ <version>6-SNAPSHOT</version>
+ <packaging>container-plugin</packaging>
+ <name>${project.artifactId}</name>
+ <dependencies>
+ <dependency>
+ <groupId>com.google.inject</groupId>
+ <artifactId>guice</artifactId>
+ <classifier>no_aop</classifier>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.googlecode.jmockit</groupId>
+ <artifactId>jmockit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>config-lib</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>jdisc_core</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <properties>
+ <property>
+ <name>listener</name>
+ <value>mockit.integration.testng.Initializer</value>
+ </property>
+ </properties>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <compilerArgs>
+ <arg>-Xlint:rawtypes</arg>
+ <arg>-Xlint:unchecked</arg>
+ <arg>-Xlint:deprecation</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-site-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/ComponentMetricMBean.java b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/ComponentMetricMBean.java
new file mode 100644
index 00000000000..1029f328ed9
--- /dev/null
+++ b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/ComponentMetricMBean.java
@@ -0,0 +1,123 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx;
+
+import com.yahoo.jdisc.Timer;
+import com.yahoo.jdisc.metrics.jmx.cloud.JmxMetricConfig;
+import com.yahoo.jdisc.metrics.jmx.core.AbstractDynamicMBean;
+import com.yahoo.jdisc.metrics.jmx.core.MetricUnit;
+
+import javax.management.*;
+import java.util.*;
+
+/**
+ * <p>This class provides a read-only implementation of a {@link DynamicMBean} that exposes JDisc metrics.
+ * It is backed internally by a {@link MultiSourceComponentMetric}</p>
+ *
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+public final class ComponentMetricMBean extends AbstractDynamicMBean implements ConsumerContextMetricReader {
+
+ private final String description;
+ private final MultiSourceComponentMetric componentMetric;
+ private Map<String, MetricUnit> snapshot = Collections.emptyMap();
+ private long nextSnapshotTime;
+ private final long snapshotInterval;
+ private final Timer timer;
+
+ public ComponentMetricMBean(JmxMetricConfig metricConfig, ConsumerContextMetric contextMetric, Timer timer) {
+ metricConfig.getClass(); // throws NullPointerException
+ contextMetric.getClass();
+
+ this.componentMetric = new MultiSourceComponentMetric(contextMetric);
+ description = metricConfig.beanDescription();
+ snapshotInterval = metricConfig.minSnapshotIntervalMillis();
+ this.timer = timer;
+ nextSnapshotTime = timer.currentTimeMillis() + snapshotInterval;
+ }
+
+ @Override
+ public synchronized Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException {
+ attribute.getClass(); // throws NullPointerException
+ MetricUnit metricUnit = snapshot().get(attribute);
+ if (metricUnit == null) {
+ // If attribute name not recognized
+ throw new AttributeNotFoundException("Can not find " + attribute + " attribute in " + getClass().getName() + ".");
+ }
+ return metricUnit.getValue();
+ }
+
+ @Override
+ public synchronized AttributeList getAttributes(String[] attributes) {
+ attributes.getClass(); // throws NullPointerException
+ Map<String, MetricUnit> snapshot = snapshot();
+ AttributeList list = new AttributeList();
+ for (String name : attributes) {
+ MetricUnit metricUnit = snapshot.get(name);
+ if (metricUnit != null) {
+ list.add(new Attribute(name, metricUnit.getValue()));
+ }
+ }
+ return list;
+ }
+
+ @Override
+ public synchronized MBeanInfo getMBeanInfo() {
+
+ Set<String> jDiscStatsKeys = snapshot().keySet();
+ MBeanAttributeInfo[] attributes = new MBeanAttributeInfo[jDiscStatsKeys.size()];
+ Iterator<String> itr = jDiscStatsKeys.iterator();
+ for (int i=0; i < attributes.length; i++ ) {
+ String name = itr.next();
+ attributes[i] = new MBeanAttributeInfo(
+ name, // name
+ "java.lang.Number", // type
+ "JDisc stat for: " + name, // description
+ true, // readable
+ false, // writable
+ false // isIs
+ );
+ }
+ return new MBeanInfo(getClass().getName(),
+ description,
+ attributes,
+ null, // just default constructor
+ null, // no operations
+ null); // no notifications
+ }
+
+ @Override
+ public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException {
+ if (actionName.equals("addDataSource") && params.length == 1) {
+ return addDataSource((ConsumerContextMetric)params[0]);
+ }
+ if (actionName.equals("dataSourceCount")) {
+ return dataSourceCount();
+ }
+ if (actionName.equals("snapshot")) {
+ return snapshot();
+ }
+ throw new ReflectionException(new NoSuchMethodException(actionName));
+ }
+
+ @Override
+ public synchronized boolean addDataSource(ConsumerContextMetric contextMetric) {
+ return componentMetric.addConsumerContextMetric(contextMetric);
+ }
+
+ // Used for testing
+ @Override
+ public synchronized int dataSourceCount() {
+ return componentMetric.getSourceCount();
+ }
+
+ @Override
+ public synchronized Map<String, MetricUnit> snapshot() {
+ long currentTime = timer.currentTimeMillis();
+ if (currentTime >= nextSnapshotTime) {
+ snapshot = componentMetric.snapshot();
+ nextSnapshotTime = currentTime + snapshotInterval;
+ }
+ return snapshot;
+ }
+
+}
diff --git a/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/ConsumerContextMetric.java b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/ConsumerContextMetric.java
new file mode 100644
index 00000000000..d7946023761
--- /dev/null
+++ b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/ConsumerContextMetric.java
@@ -0,0 +1,59 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx;
+
+import com.yahoo.jdisc.metrics.jmx.core.CounterMetricUnit;
+import com.yahoo.jdisc.metrics.jmx.core.GaugeMetricUnit;
+import com.yahoo.jdisc.metrics.jmx.core.MetricUnit;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * <p>This class contains a map of metrics. It is used by {@link JmxMetricConsumer} to keep track of the metrics
+ * for the current thread. There is one instance of this class for each new {@link JmxMetricContext}</p>
+ *
+ * <p>{@link #snapshot} can be potentially called by another thread which is consuming a snapshot of the data.
+ * In so doing, it is possible that either {@link #incrementMetric} or {@link #setMetric} are accessing the {@link MetricUnit}
+ * data of the snapshot. If the thread calling {@link #snapshot} starts processing {@link MetricUnit} data at the
+ * same time, we might end up in an inconsistent state. The {@link MetricUnit} implementations take care of this case.
+ *
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+class ConsumerContextMetric {
+
+ private volatile Map<String, MetricUnit> metrics = new HashMap<String, MetricUnit>();
+ private final int gaugeDepth;
+
+ public ConsumerContextMetric(int gaugeDepth) {
+ this.gaugeDepth = gaugeDepth;
+ }
+
+ public void incrementMetric(String key, Number value) {
+ key.getClass(); // throws NullPointerException
+ value.getClass();
+
+ MetricUnit unit = metrics.get(key);
+ if (unit == null) {
+ metrics.put(key, unit=new CounterMetricUnit());
+ }
+ unit.addValue(value);
+ }
+
+ public void setMetric(String key, Number value) {
+ key.getClass(); // throws NullPointerException
+ value.getClass();
+
+ MetricUnit unit = metrics.get(key);
+ if (unit == null) {
+ metrics.put(key, unit=new GaugeMetricUnit(gaugeDepth));
+ }
+ unit.addValue(value);
+ }
+
+ public Map<String, MetricUnit> snapshot() {
+ Map<String, MetricUnit> snapshot = metrics;
+ metrics = new HashMap<String, MetricUnit>();
+ return snapshot;
+ }
+
+}
diff --git a/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/ConsumerContextMetricReader.java b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/ConsumerContextMetricReader.java
new file mode 100644
index 00000000000..d40a75e21aa
--- /dev/null
+++ b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/ConsumerContextMetricReader.java
@@ -0,0 +1,20 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx;
+
+import java.util.Map;
+import com.yahoo.jdisc.metrics.jmx.core.MetricUnit;
+
+/**
+ * <p>This interface is necessary so that we can retrieve MBeans in real-time through an MBean proxy and add
+ * new data sources to those existing MBeans. It is implemented by {@link ComponentMetricMBean}</p>
+ *
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+public interface ConsumerContextMetricReader {
+
+ public boolean addDataSource(ConsumerContextMetric contextMetric);
+
+ public int dataSourceCount();
+
+ public Map<String, MetricUnit> snapshot();
+}
diff --git a/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/JmxMetricConsumer.java b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/JmxMetricConsumer.java
new file mode 100644
index 00000000000..ebebe6fd940
--- /dev/null
+++ b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/JmxMetricConsumer.java
@@ -0,0 +1,120 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx;
+
+import com.google.inject.Inject;
+import com.yahoo.jdisc.Metric;
+import com.yahoo.jdisc.Timer;
+import com.yahoo.jdisc.application.MetricConsumer;
+import com.yahoo.jdisc.metrics.jmx.cloud.JmxMetricConfig;
+
+import javax.management.*;
+import java.lang.management.ManagementFactory;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+/**
+ * <p>This class implements a {@link MetricConsumer} interface that exposes the metrics through a
+ * {@link javax.management.DynamicMBean}, in our case a {@link ComponentMetricMBean}.
+ * It is <i>not</i> thread-safe. Instances of this class are created through {@link Provider}</p>
+ *
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+public class JmxMetricConsumer implements MetricConsumer {
+
+ private final static Logger logger = Logger.getLogger(JmxMetricConsumer.class.getName());
+
+ // Each entry corresponds to one MBean with a unique ObjectName
+ private final Map<Metric.Context, ConsumerContextMetric> componentMetrics =
+ new HashMap<Metric.Context, ConsumerContextMetric>();
+
+ // If there is no context, default to the null context component metric
+ private final ConsumerContextMetric nullContextMetricInstance;
+ private final JmxMetricConfig config;
+ private final Timer timer;
+
+ public JmxMetricConsumer(JmxMetricConfig config, Timer timer) {
+ this.config = config;
+ this.timer = timer;
+ nullContextMetricInstance = new ConsumerContextMetric(config.gaugeQueueDepth());
+
+ // Attach the default component metric instance to an MBean
+ createAndRegisterMBean(createContext(null), nullContextMetricInstance, config, timer);
+ }
+
+ @Override
+ public void add(String key, Number val, Metric.Context ctx) {
+ getComponentMetric(ctx).incrementMetric(key, val);
+ }
+
+ @Override
+ public void set(String key, Number val, Metric.Context ctx) {
+ getComponentMetric(ctx).setMetric(key, val);
+ }
+
+ @Override
+ public Metric.Context createContext(Map<String, ?> properties) {
+ return new JmxMetricContext(config, properties != null ? properties : Collections.<String, Object>emptyMap());
+ }
+
+ // package private for testing purposes
+ static synchronized void createAndRegisterMBean(Metric.Context context,
+ ConsumerContextMetric dataSource,
+ JmxMetricConfig metricConfig, Timer timer) {
+ context.getClass(); // throws NullPointerException
+ if (! (context instanceof JmxMetricContext)) {
+ throw new IllegalArgumentException("Expected " + JmxMetricContext.class.getName() + ", got " + context.getClass().getName() + ".");
+ }
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ ObjectName objectName = ((JmxMetricContext)context).getObjectName();
+ if (! mbs.isRegistered(objectName)) {
+ logger.info("Registering new MBean with name: "+objectName + ".");
+ try {
+ mbs.registerMBean(new ComponentMetricMBean(metricConfig, dataSource, timer), objectName);
+ } catch (JMException e) {
+ throw new RuntimeException("Exception thrown by MBeanServer.registerMBean().", e);
+ }
+ } else {
+ // If the MBean with this ObjectName already exists, register the new data source with it
+ JMX.newMBeanProxy(ManagementFactory.getPlatformMBeanServer(),
+ objectName,
+ ConsumerContextMetricReader.class).addDataSource(dataSource);
+ }
+ }
+
+ private ConsumerContextMetric getComponentMetric(Metric.Context context) {
+ if (context == null) {
+ return nullContextMetricInstance;
+ }
+ ConsumerContextMetric componentMetric = componentMetrics.get(context);
+ if (componentMetric == null) {
+ componentMetric = new ConsumerContextMetric(config.gaugeQueueDepth());
+ createAndRegisterMBean(context, componentMetric, config, timer);
+ componentMetrics.put(context, componentMetric);
+ }
+ return componentMetric;
+ }
+
+ /**
+ * <p>This class is a {@link com.google.inject.Provider} of {@link JmxMetricConsumer} objects. There should be
+ * only 1 instance of {@link JmxMetricConsumer} per thread since it is not thread-safe</p>
+ */
+ public static class Provider implements com.google.inject.Provider<MetricConsumer> {
+
+ private final JmxMetricConfig metricConfig;
+ private final Timer timer;
+
+ @Inject
+ public Provider(JmxMetricConfig metricConfig, Timer timer) {
+ // Config is immutable
+ this.metricConfig = metricConfig;
+ this.timer = timer;
+ }
+
+ @Override
+ public MetricConsumer get() {
+ return new JmxMetricConsumer(metricConfig, timer);
+ }
+ }
+}
diff --git a/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/JmxMetricContext.java b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/JmxMetricContext.java
new file mode 100644
index 00000000000..2df55791fc0
--- /dev/null
+++ b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/JmxMetricContext.java
@@ -0,0 +1,56 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx;
+
+import com.yahoo.jdisc.Metric;
+import com.yahoo.jdisc.metrics.jmx.cloud.JmxMetricConfig;
+
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import java.util.Hashtable;
+import java.util.Map;
+
+/**
+ * <p>An instance of this class should be created by calling {@link JmxMetricConsumer#createContext(java.util.Map)}</p>
+ *
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+public class JmxMetricContext implements Metric.Context {
+
+ private final ObjectName objectName;
+
+ public JmxMetricContext(JmxMetricConfig metricConfig, Map<String, ?> dimensions) {
+ metricConfig.getClass(); // throws NullPointerException
+ dimensions.getClass();
+
+ Hashtable<String, String> dimensionsTable = new Hashtable<String, String>();
+ for (Map.Entry<String, ?> entry : dimensions.entrySet()) {
+ dimensionsTable.put(entry.getKey(), String.valueOf(entry.getValue()));
+ }
+ if (dimensionsTable.isEmpty()) {
+ dimensionsTable.put("name", "JDisc");
+ }
+ try {
+ objectName = new ObjectName(metricConfig.objectNameDomain(), dimensionsTable);
+ } catch (MalformedObjectNameException e) {
+ throw new IllegalArgumentException("Could not create ObjectName.", e);
+ }
+ }
+
+ public ObjectName getObjectName() {
+ return objectName;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if(obj == null || ! (obj instanceof JmxMetricContext)) {
+ return false;
+ }
+ return objectName.equals(((JmxMetricContext)obj).objectName);
+ }
+
+ @Override
+ public int hashCode() {
+ return objectName.hashCode();
+ }
+
+}
diff --git a/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/MultiSourceComponentMetric.java b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/MultiSourceComponentMetric.java
new file mode 100644
index 00000000000..8816b826ed9
--- /dev/null
+++ b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/MultiSourceComponentMetric.java
@@ -0,0 +1,88 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx;
+
+import com.yahoo.jdisc.metrics.jmx.core.MetricUnit;
+
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * <p>A multi-source class which can be periodically scheduled to poll the data from the sources</p>
+ *
+ * <p>Note: It is possible that multiple {@link ConsumerContextMetric}s are simultaneously added as data sources in a
+ * multi-threaded environment. However, the corresponding setter in {@link ComponentMetricMBean} is synchronized.</p>
+ *
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+class MultiSourceComponentMetric {
+
+ private final static Logger logger = Logger.getLogger(MultiSourceComponentMetric.class.getName());
+
+ // Accessors / setters are synchronized
+ private final List<ConsumerContextMetric> consumerContextMetrics = new LinkedList<ConsumerContextMetric>();
+
+ // We need to preserve some values e.g. Counters after each snapshot.
+ private final Map<String, MetricUnit> persistentMetrics = new HashMap<String, MetricUnit>();
+
+ public MultiSourceComponentMetric(ConsumerContextMetric contextMetric) {
+ addConsumerContextMetric(contextMetric);
+ }
+
+ /**
+ * Add a data source
+ *
+ * @return true if added successfully, false otherwise
+ */
+ public boolean addConsumerContextMetric(ConsumerContextMetric contextMetric) {
+ return consumerContextMetrics.add(contextMetric);
+
+ }
+
+ /**
+ * Number of data sources
+ *
+ * @return data source count
+ */
+ public int getSourceCount() {
+ return consumerContextMetrics.size();
+ }
+
+ /**
+ * Merge the data sources if the data type is persistent
+ */
+ public Map<String, MetricUnit> snapshot() {
+ Map<String, MetricUnit> snapshot = new HashMap<String, MetricUnit>();
+ for (ConsumerContextMetric contextMetric : consumerContextMetrics) {
+ Map<String, MetricUnit> contextMetricSnapshot = contextMetric.snapshot();
+ for (Map.Entry<String, MetricUnit> entry : contextMetricSnapshot.entrySet()) {
+ String key = entry.getKey();
+ MetricUnit value = entry.getValue();
+ if (value.isPersistent()) {
+ MetricUnit prev = persistentMetrics.get(key);
+ if (prev == null) {
+ persistentMetrics.put(key, value);
+ } else {
+ value = addMetric(prev, value);
+ }
+ } else {
+ MetricUnit snapshotValue = snapshot.get(key);
+ if (snapshotValue != null) {
+ value = addMetric(snapshotValue, value);
+ }
+ }
+ snapshot.put(key, value);
+ }
+ }
+ return snapshot;
+ }
+
+ private MetricUnit addMetric(MetricUnit to, MetricUnit from) {
+ try {
+ to.addMetric(from);
+ } catch (RuntimeException e) {
+ logger.warning("Can not merge context metric: " + e.getLocalizedMessage() + ".");
+ }
+ return to;
+ }
+
+}
diff --git a/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/cloud/package-info.java b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/cloud/package-info.java
new file mode 100644
index 00000000000..f55526c2dae
--- /dev/null
+++ b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/cloud/package-info.java
@@ -0,0 +1,4 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.jdisc.metrics.jmx.cloud;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/core/AbstractDynamicMBean.java b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/core/AbstractDynamicMBean.java
new file mode 100644
index 00000000000..e614c947b38
--- /dev/null
+++ b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/core/AbstractDynamicMBean.java
@@ -0,0 +1,24 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx.core;
+
+import javax.management.*;
+
+/**
+ * <p>This abstract class provides default implementations for {@link DynamicMBean} methods we do not require</p>
+ *
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+public abstract class AbstractDynamicMBean implements DynamicMBean {
+
+ @Override
+ public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException,
+ MBeanException, ReflectionException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public AttributeList setAttributes(AttributeList attributes) {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/core/CounterMetricUnit.java b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/core/CounterMetricUnit.java
new file mode 100644
index 00000000000..0d282aabd46
--- /dev/null
+++ b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/core/CounterMetricUnit.java
@@ -0,0 +1,37 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx.core;
+
+/**
+ * <p>This class represents a counter value</p>
+ *
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+public final class CounterMetricUnit implements MetricUnit {
+
+ private volatile long value = 0;
+
+ public CounterMetricUnit() {}
+
+ @Override
+ public final Number getValue() {
+ return value;
+ }
+
+ @Override
+ public void addValue(Number value) {
+ this.value += value.longValue();
+ }
+
+ @Override
+ public void addMetric(MetricUnit metricUnit) {
+ if (! (metricUnit instanceof CounterMetricUnit)) {
+ throw new IllegalArgumentException(metricUnit.getClass().getName());
+ }
+ this.value += ((CounterMetricUnit)metricUnit).value;
+ }
+
+ @Override
+ public boolean isPersistent() {
+ return true;
+ }
+}
diff --git a/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/core/GaugeMetricUnit.java b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/core/GaugeMetricUnit.java
new file mode 100644
index 00000000000..1f35a221f99
--- /dev/null
+++ b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/core/GaugeMetricUnit.java
@@ -0,0 +1,117 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx.core;
+
+import java.util.*;
+
+/**
+ * <p>This class represents a gauge value that can be safely written to by one thread, and read from another thread.
+ * Every time that the gauge value is requested, the average value of all the {@link Number}s in the list is returned.
+ * This approach is preferred over keeping a single {@link Number} to avoid overflowing.</p>
+ *
+ * <p>It is heavily based off the Vespa implementation of ThreadRobustList, which does not involve any locking or
+ * use of volatile. It does not support multiple writes from multiple threads, but that is fine in our case.
+ *
+ * <p>Note: If an instance of the iterator is created and another thread calls {@link #addValue}, the iterator might
+ * <i>not</i> see the new value. However, we guarantee that all elements up to the exit of the Iterator constructor
+ * will be seen.</p>
+ *
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+public final class GaugeMetricUnit implements MetricUnit {
+
+ private Number[] items;
+
+ // Index of next item
+ private int next = 0;
+
+ public GaugeMetricUnit(int initialCapacity) {
+ items = new Number[initialCapacity];
+ }
+
+ @Override
+ public Number getValue() {
+ GaugeMetricIterator iterator = new GaugeMetricIterator(items);
+ double sum = 0; // overflow should not be an issue for reasonable intervals
+ int count = 0;
+ while (iterator.hasNext()) {
+ sum += iterator.next().doubleValue();
+ count++;
+ }
+ if (count <= 0) {
+ return null;
+ }
+ return sum / count;
+ }
+
+ @Override
+ public void addValue(Number value) {
+ value.getClass(); // throws NullPointerException
+ Number[] curItems = items;
+ if (next >= items.length) {
+ final int newLength = 20 + items.length * 2;
+ curItems = Arrays.copyOf(curItems, newLength);
+ curItems[next++] = value;
+ items = curItems;
+ } else {
+ curItems[next++] = value;
+ }
+ }
+
+ /**
+ * Copies the elements from the argument. Does not remove any element from the argument
+ */
+ @Override
+ public void addMetric(MetricUnit metricUnit) {
+ if (! (metricUnit instanceof GaugeMetricUnit)) {
+ throw new IllegalArgumentException(metricUnit.getClass().getName());
+ }
+ GaugeMetricIterator iterator = new GaugeMetricIterator(((GaugeMetricUnit)metricUnit).items);
+ while (iterator.hasNext()) {
+ addValue(iterator.next());
+ }
+ }
+
+ @Override
+ public boolean isPersistent() {
+ return false;
+ }
+
+ private class GaugeMetricIterator implements Iterator<Number> {
+
+ // 'final' ensures safe publication and to be up-to-date up to the point where constructor exits
+ private final Number[] items;
+
+ private int nextIndex = 0;
+
+ private GaugeMetricIterator(final Number[] items) {
+ items.getClass(); // throws NullPointerException
+ this.items = items;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (nextIndex >= items.length) {
+ return false;
+ }
+ if (items[nextIndex] == null) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public Number next() {
+ if (! hasNext()) {
+ throw new NoSuchElementException();
+ }
+ return items[nextIndex++];
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+
+}
diff --git a/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/core/MetricUnit.java b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/core/MetricUnit.java
new file mode 100644
index 00000000000..091fe857dcb
--- /dev/null
+++ b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/core/MetricUnit.java
@@ -0,0 +1,34 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx.core;
+
+/**
+ * <p>This interface represents a single metric. Implementations need to ensure that instances within a snapshot of
+ * com.yahoo.jdisc.metrics.jmx.ConsumerContextMetric are in a consistent state</p>
+ *
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+public interface MetricUnit {
+
+ /**
+ * Returns a Number representation of the stored value(s)
+ *
+ * @return The stored value, or a single representation of the stored values
+ */
+ public Number getValue();
+
+ /**
+ * Adds the input value
+ */
+ public void addValue(Number value);
+
+ /**
+ * Copies data from the input {@link MetricUnit} and merges with the
+ * current {@link MetricUnit}
+ */
+ public void addMetric(MetricUnit metricUnit);
+
+ /**
+ * Is this {@link MetricUnit} persistent
+ */
+ public boolean isPersistent();
+}
diff --git a/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/package-info.java b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/package-info.java
new file mode 100644
index 00000000000..0e24f7ba2a6
--- /dev/null
+++ b/jdisc_jmx_metrics/src/main/java/com/yahoo/jdisc/metrics/jmx/package-info.java
@@ -0,0 +1,4 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.jdisc.metrics.jmx;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/jdisc_jmx_metrics/src/main/resources/configdefinitions/jmx-metric.def b/jdisc_jmx_metrics/src/main/resources/configdefinitions/jmx-metric.def
new file mode 100644
index 00000000000..265c9d49bdd
--- /dev/null
+++ b/jdisc_jmx_metrics/src/main/resources/configdefinitions/jmx-metric.def
@@ -0,0 +1,8 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=jdisc.metrics.jmx.cloud
+
+applicationName string default="JDisc"
+objectNameDomain string default="com.yahoo.jdisc.metrics.jmx"
+beanDescription string default="JDisc Stats dynamic MBean"
+minSnapshotIntervalMillis long default=60000
+gaugeQueueDepth int default=100000
diff --git a/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/ComponentMetricMBeanTestCase.java b/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/ComponentMetricMBeanTestCase.java
new file mode 100644
index 00000000000..daca094d024
--- /dev/null
+++ b/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/ComponentMetricMBeanTestCase.java
@@ -0,0 +1,103 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx;
+
+import com.google.inject.Guice;
+import com.yahoo.jdisc.Timer;
+import com.yahoo.jdisc.metrics.jmx.cloud.JmxMetricConfig;
+import org.testng.annotations.Test;
+
+import javax.management.AttributeList;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanFeatureInfo;
+import javax.management.MBeanInfo;
+import javax.management.ReflectionException;
+
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.testng.AssertJUnit.assertEquals;
+
+/**
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+public class ComponentMetricMBeanTestCase {
+
+ private final Timer timer = Guice.createInjector().getInstance(Timer.class);
+
+ @Test(expectedExceptions = RuntimeException.class)
+ public void requireThatConstructorThrowsException() {
+ ComponentMetricMBean componentMetricMBean = new ComponentMetricMBean(null, null, timer);
+ componentMetricMBean.toString();
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void requireThatGetAttributeThrowsException() throws Exception {
+ ComponentMetricMBean componentMetricMBean = new ComponentMetricMBean(new JmxMetricConfig(new JmxMetricConfig.Builder()),
+ new ConsumerContextMetric(10000), timer);
+ componentMetricMBean.getAttribute(null);
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void requireThatGetAttributesThrowsException() throws Exception {
+ ComponentMetricMBean componentMetricMBean = new ComponentMetricMBean(new JmxMetricConfig(new JmxMetricConfig.Builder()),
+ new ConsumerContextMetric(10000), timer);
+ componentMetricMBean.getAttributes(null);
+ }
+
+ @Test(expectedExceptions = ReflectionException.class)
+ public void requireThatInvokeThrowsException() throws Exception {
+ ComponentMetricMBean componentMetricMBean = new ComponentMetricMBean(new JmxMetricConfig(new JmxMetricConfig.Builder()),
+ new ConsumerContextMetric(Integer.MAX_VALUE), timer);
+ componentMetricMBean.invoke("blah", null, null);
+ }
+
+ @Test
+ public void requireThatGetAttributesSupportsNonFoundValues() {
+ ConsumerContextMetric componentMetric = new ConsumerContextMetric(10000);
+ componentMetric.setMetric("key1", 10);
+ componentMetric.setMetric("key2", 15);
+ JmxMetricConfig.Builder builder = new JmxMetricConfig.Builder();
+ builder.minSnapshotIntervalMillis(0);
+ JmxMetricConfig config = new JmxMetricConfig(builder);
+ ComponentMetricMBean componentMetricMBean = new ComponentMetricMBean(config, componentMetric, timer);
+ AttributeList list = componentMetricMBean.getAttributes(new String[]{ "key1", "key2", "key3" });
+ assertEquals(2, list.size());
+ }
+
+ @Test
+ public void requireThatGetMBeanInfoWorks() throws Exception {
+ ConsumerContextMetric componentMetric = new ConsumerContextMetric(10000);
+ componentMetric.setMetric("key1", 10);
+ componentMetric.setMetric("key2", 15);
+ JmxMetricConfig.Builder builder = new JmxMetricConfig.Builder();
+ builder.minSnapshotIntervalMillis(0);
+ JmxMetricConfig config = new JmxMetricConfig(builder);
+ ComponentMetricMBean componentMetricMBean = new ComponentMetricMBean(config, componentMetric, timer);
+ //componentMetricMBean.poll(); // TODO: Add if we have a timer
+ MBeanInfo info = componentMetricMBean.getMBeanInfo();
+ MBeanAttributeInfo[] attributes = info.getAttributes();
+
+ List<String> keys = Arrays.stream(attributes).map(MBeanFeatureInfo::getName).collect(Collectors.toList());
+ assertEquals(keys, Arrays.asList("key1", "key2"));
+ }
+
+ @Test(expectedExceptions = RuntimeException.class)
+ public void requireThatSetAttributeIsNotImplemented() throws Exception {
+ ConsumerContextMetric componentMetric = new ConsumerContextMetric(10000);
+ ComponentMetricMBean componentMetricMBean = new ComponentMetricMBean(new JmxMetricConfig(new JmxMetricConfig.Builder()),
+ componentMetric, timer);
+ componentMetricMBean.setAttribute(null);
+ }
+
+ @Test(expectedExceptions = RuntimeException.class)
+ public void requireThatSetAttributesIsNotImplemented() throws Exception {
+ ConsumerContextMetric componentMetric = new ConsumerContextMetric(10000);
+ ComponentMetricMBean componentMetricMBean = new ComponentMetricMBean(new JmxMetricConfig(new JmxMetricConfig.Builder()),
+ componentMetric, timer);
+ componentMetricMBean.setAttributes(null);
+ }
+
+}
diff --git a/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/ConsumerContextMetricTestCase.java b/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/ConsumerContextMetricTestCase.java
new file mode 100644
index 00000000000..3e172e5b632
--- /dev/null
+++ b/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/ConsumerContextMetricTestCase.java
@@ -0,0 +1,100 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx;
+
+import com.yahoo.jdisc.metrics.jmx.core.MetricUnit;
+import org.testng.annotations.Test;
+
+import java.util.Map;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertNull;
+
+
+/**
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+public class ConsumerContextMetricTestCase {
+
+ @Test
+ public void requireThatMetricCanBeIncremented() {
+ ConsumerContextMetric componentMetric = newContextMetric();
+ componentMetric.incrementMetric("key1", 123);
+ componentMetric.incrementMetric("key1", 1);
+ assertEquals(124, componentMetric.snapshot().get("key1").getValue().longValue());
+ }
+
+ @Test
+ public void requireThatMetricCanBeIncrementedWithVariousKeys() {
+ ConsumerContextMetric componentMetric = newContextMetric();
+ componentMetric.incrementMetric("key1", 123);
+ componentMetric.incrementMetric("key1", 1);
+ componentMetric.incrementMetric("key2", 1);
+ componentMetric.incrementMetric("key2", 1);
+ componentMetric.incrementMetric("key3", 1);
+ Map<String, MetricUnit> snapshot = componentMetric.snapshot();
+ assertEquals(124, snapshot.get("key1").getValue().longValue());
+ assertEquals(2, snapshot.get("key2").getValue().longValue());
+ assertEquals(1, snapshot.get("key3").getValue().longValue());
+ }
+
+ @Test
+ public void requireThatMetricCanBeSet() {
+ ConsumerContextMetric componentMetric = newContextMetric();
+ componentMetric.setMetric("key1", 123);
+ componentMetric.setMetric("key1", 1);
+ assertEquals(62.0, componentMetric.snapshot().get("key1").getValue());
+ }
+
+ @Test
+ public void requireThatMetricCanBeSetWithVariousKeys() {
+ ConsumerContextMetric componentMetric = newContextMetric();
+ componentMetric.setMetric("key1", 123);
+ componentMetric.setMetric("key1", 1);
+ componentMetric.setMetric("key2", 1);
+ componentMetric.setMetric("key2", 1);
+ componentMetric.setMetric("key3", 1);
+ Map<String, MetricUnit> snapshot = componentMetric.snapshot();
+ assertEquals(62.0, snapshot.get("key1").getValue());
+ assertEquals(1.0, snapshot.get("key2").getValue());
+ assertEquals(1.0, snapshot.get("key3").getValue());
+ }
+
+ @Test
+ public void requireThatMetricCanBeSetAndIncrementedWithVariousKeys() {
+ ConsumerContextMetric componentMetric = newContextMetric();
+ componentMetric.setMetric("key1", 123);
+ componentMetric.setMetric("key1", 1);
+ componentMetric.incrementMetric("key2", 1);
+ componentMetric.incrementMetric("key2", 1);
+ componentMetric.setMetric("key3", 1);
+ componentMetric.incrementMetric("key4", 1);
+ Map<String, MetricUnit> snapshot = componentMetric.snapshot();
+ assertEquals(62.0, snapshot.get("key1").getValue());
+ assertEquals(2, snapshot.get("key2").getValue().longValue());
+ assertEquals(1.0, snapshot.get("key3").getValue());
+ assertEquals(1, snapshot.get("key4").getValue().longValue());
+ }
+
+ @Test
+ public void requireThatAllSourcesAreEmptyAfterSnapshot() {
+ ConsumerContextMetric componentMetric = newContextMetric();
+ componentMetric.setMetric("key1", 121);
+ componentMetric.setMetric("key1", 1);
+ componentMetric.setMetric("key2", 1);
+ componentMetric.incrementMetric("key3", 2);
+ componentMetric.setMetric("key4", 10);
+ Map<String, MetricUnit> snapshot = componentMetric.snapshot();
+ componentMetric.incrementMetric("key3", 2);
+ componentMetric.setMetric("key4", 10);
+ snapshot = componentMetric.snapshot();
+ assertEquals(2, snapshot.get("key3").getValue().longValue());
+ assertEquals(10.0, snapshot.get("key4").getValue());
+ assertNull(snapshot.get("key1"));
+ assertNull(snapshot.get("key2"));
+ }
+
+ private ConsumerContextMetric newContextMetric() {
+ return new ConsumerContextMetric(100);
+ }
+
+}
diff --git a/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/IntegrationTestCase.java b/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/IntegrationTestCase.java
new file mode 100644
index 00000000000..191664cba09
--- /dev/null
+++ b/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/IntegrationTestCase.java
@@ -0,0 +1,459 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx;
+
+import com.google.inject.Guice;
+import com.yahoo.jdisc.Timer;
+import com.yahoo.jdisc.application.MetricConsumer;
+import com.yahoo.jdisc.metrics.jmx.cloud.JmxMetricConfig;
+import org.testng.annotations.Test;
+
+import javax.management.*;
+import java.lang.management.ManagementFactory;
+import java.util.HashMap;
+import java.util.Hashtable;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+
+/**
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+public class IntegrationTestCase {
+
+ Timer timer = Guice.createInjector().getInstance(Timer.class);
+
+ @Test
+ public void requireThatACounterCanBeCapturedWithNullContext() throws Exception {
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ ObjectName objectName = new ObjectName("com.jdisc.jmx:name=JDisc");
+ try {
+ JmxMetricConsumer.Provider provider = new JmxMetricConsumer.Provider(config(), timer);
+ MetricConsumer consumer = provider.get();
+ consumer.add("key1", 100, null);
+ assertEquals(Long.valueOf(100), mbs.getAttribute(objectName, "key1"));
+ // Make sure to call this every time
+ } finally {
+ mbs.unregisterMBean(objectName);
+ }
+ }
+
+ @Test
+ public void requireThatGaugesCanBeCapturedWithNullContext() throws Exception {
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ ObjectName objectName = new ObjectName("com.jdisc.jmx:name=JDisc");
+ try {
+ JmxMetricConsumer.Provider provider = new JmxMetricConsumer.Provider(config(), timer);
+ MetricConsumer consumer = provider.get();
+ consumer.set("key1", 100, null);
+ consumer.set("key1", 50, null);
+ consumer.set("key2", 50, null);
+ AttributeList list = mbs.getAttributes(objectName, new String[]{"key1", "key2"});
+ assertEquals(75.0, ((Attribute) list.get(0)).getValue());
+ assertEquals(50.0, ((Attribute)list.get(1)).getValue());
+
+ } finally {
+ // Make sure to call this every time
+ mbs.unregisterMBean(objectName);
+ }
+ }
+
+ @Test(expectedExceptions = AttributeNotFoundException.class)
+ public void requireThatANonExistentAttributeThrowsException() throws Exception {
+ JmxMetricConsumer.Provider provider = new JmxMetricConsumer.Provider(config(), timer);
+ MetricConsumer consumer = provider.get();
+ consumer.add("key1", 100, null);
+ ObjectName objectName = new ObjectName("com.jdisc.jmx:name=JDisc");
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ try {
+ assertEquals(100.0, mbs.getAttribute(objectName, "key2"));
+ } finally {
+ // Make sure to call this every time
+ mbs.unregisterMBean(objectName);
+ }
+ }
+
+ @Test
+ public void requireThatACounterCanBeCapturedWithEmptyContext() throws Exception {
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ ObjectName objectName = new ObjectName("com.jdisc.jmx:name=JDisc");
+ try {
+ JmxMetricContext context = new JmxMetricContext(config(), new HashMap<String, Object>());
+ JmxMetricConsumer.Provider provider = new JmxMetricConsumer.Provider(config(), timer);
+ MetricConsumer consumer = provider.get();
+ consumer.add("key1", 100, context);
+ assertEquals(Long.valueOf(100), mbs.getAttribute(objectName, "key1"));
+ } finally {
+ // Make sure to call this every time
+ mbs.unregisterMBean(objectName);
+ }
+ }
+
+ @Test
+ public void requireThatACounterCanBeCapturedWithOneDimension() throws Exception {
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ Hashtable<String, String> contextMap = new Hashtable<String, String>();
+ contextMap.put("dimension1", "8080");
+ ObjectName objectName = new ObjectName("com.jdisc.jmx", contextMap);
+ try {
+ JmxMetricContext context = new JmxMetricContext(config(), contextMap);
+ JmxMetricConsumer.Provider provider = new JmxMetricConsumer.Provider(config(), timer);
+ MetricConsumer consumer = provider.get();
+ consumer.add("key2", 101, context);
+ assertEquals(Long.valueOf(101), mbs.getAttribute(objectName, "key2"));
+ } finally {
+ // Make sure to call this every time
+ mbs.unregisterMBean(objectName);
+ }
+ }
+
+ @Test
+ public void requireThatCountersCanBeCapturedWithMultipleDimensionsOneConsumerOneMBean() throws Exception {
+ JmxMetricContext context = new JmxMetricContext(config(), context());
+
+ JmxMetricConsumer.Provider provider = new JmxMetricConsumer.Provider(config(), timer);
+ MetricConsumer consumer = provider.get();
+ consumer.add("bytesSent", 101, context);
+ consumer.add("bytesReceived", 97, context);
+ consumer.add("timeout", 1, context);
+ consumer.add("timeout", 2, context);
+ consumer.add("bytesSent", 1, context);
+ consumer.add("bytesSent", 10, context);
+ consumer.add("bytesReceived", 99, context);
+ consumer.add("bytesReceived", 98, context);
+ ObjectName objectName = new ObjectName("com.jdisc.jmx", context());
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ try {
+ //doFlush(objectName);
+ AttributeList results = mbs.getAttributes(objectName, new String[]{"timeout", "bytesSent", "bytesReceived"});
+
+ assertEquals(3, results.size());
+
+ Attribute attribute1 = (Attribute) results.get(0);
+ Attribute attribute2 = (Attribute) results.get(1);
+ Attribute attribute3 = (Attribute) results.get(2);
+
+ assertEquals("timeout", attribute1.getName());
+ assertEquals(Long.valueOf(3), attribute1.getValue());
+
+ assertEquals("bytesSent", attribute2.getName());
+ assertEquals(Long.valueOf(112), attribute2.getValue());
+
+ assertEquals("bytesReceived", attribute3.getName());
+ assertEquals(Long.valueOf(294), attribute3.getValue());
+ } finally {
+ // Make sure to call this every time
+ mbs.unregisterMBean(objectName);
+ }
+ }
+
+ @Test
+ public void requireThatCountersAndGaugesCanBeCapturedWithMultipleDimensionsOneConsumerOneMBean() throws Exception {
+ JmxMetricContext context = new JmxMetricContext(config(), context());
+
+ JmxMetricConsumer.Provider provider = new JmxMetricConsumer.Provider(config(), timer);
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ ObjectName objectName = new ObjectName("com.jdisc.jmx", context());
+ try {
+ MetricConsumer consumer = provider.get();
+ consumer.add("bytesSent", 101, context);
+ consumer.add("bytesReceived", 97, context);
+ consumer.add("timeout", 1, context);
+ consumer.set("responseTime", 100.0, context); // gauge
+ consumer.add("timeout", 2, context);
+ consumer.add("bytesSent", 1, context);
+ consumer.add("bytesSent", 10, context);
+ consumer.add("bytesReceived", 99, context);
+ consumer.add("bytesReceived", 98, context);
+ consumer.set("processTime", 100.0, context); // gauge
+ consumer.set("responseTime", 200.0, context); // gauge
+ consumer.set("responseTime", 300.0, context); // gauge
+ AttributeList results = mbs.getAttributes(objectName, new String[]{"timeout", "bytesSent", "bytesReceived",
+ "processTime", "responseTime"});
+ assertEquals(5, results.size());
+
+ Attribute attribute1 = (Attribute) results.get(0);
+ Attribute attribute2 = (Attribute) results.get(1);
+ Attribute attribute3 = (Attribute) results.get(2);
+ Attribute attribute4 = (Attribute) results.get(3);
+ Attribute attribute5 = (Attribute) results.get(4);
+
+ assertEquals("timeout", attribute1.getName());
+ assertEquals(Long.valueOf(3), attribute1.getValue());
+
+ assertEquals("bytesSent", attribute2.getName());
+ assertEquals(Long.valueOf(112), attribute2.getValue());
+
+ assertEquals("bytesReceived", attribute3.getName());
+ assertEquals(Long.valueOf(294), attribute3.getValue());
+
+ assertEquals("processTime", attribute4.getName());
+ assertEquals(100.0, attribute4.getValue());
+
+ assertEquals("responseTime", attribute5.getName());
+ assertEquals(200.0, attribute5.getValue());
+
+ assertEquals(1, dataSourceCount(objectName));
+ } finally {
+ // Make sure to call this every time
+ mbs.unregisterMBean(objectName);
+ }
+ }
+
+ private static int TRY_COUNT=10;
+
+ @Test
+ public void requireThatCountersAndGaugesCanBeCapturedInAThreadedEnv() throws Exception {
+
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ ObjectName objectName = new ObjectName("com.jdisc.jmx", context());
+ ObjectName objectName2 = new ObjectName("com.jdisc.jmx", context2());
+ try {
+ RunnableConsumerBase rcb1 = new RunnableConsumer1();
+ RunnableConsumerBase rcb2 = new RunnableConsumer2();
+ RunnableConsumerBase rcb3 = new RunnableConsumer3();
+
+ RunnableConsumerBase2 rcb1_2 = new RunnableConsumer1_2();
+ RunnableConsumerBase2 rcb2_2 = new RunnableConsumer2_2();
+ RunnableConsumerBase2 rcb3_2 = new RunnableConsumer3_2();
+
+ for (int i=1;i<=TRY_COUNT; i++) {
+ Thread t1 = new Thread(rcb1);
+ Thread t2 = new Thread(rcb2);
+ Thread t3 = new Thread(rcb3);
+ Thread t1_2 = new Thread(rcb1_2);
+ Thread t2_2 = new Thread(rcb2_2);
+ Thread t3_2 = new Thread(rcb3_2);
+
+ t1.start();t2.start();t3.start();
+ t1_2.start();t2_2.start();t3_2.start();
+ t1.join();t2.join();t3.join();
+ t1_2.join();t2_2.join();t3_2.join();
+
+ verifyOnce(objectName, i);
+ verifyOnce(objectName2, i);
+ }
+ } finally {
+ // Make sure to call this every time
+ mbs.unregisterMBean(objectName);
+ mbs.unregisterMBean(objectName2);
+ }
+ }
+
+ private void verifyOnce(ObjectName objectName, int idx) throws Exception {
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ AttributeList results = mbs.getAttributes(objectName, new String[]{"timeout", "bytesSent", "bytesReceived",
+ "processTime", "responseTime", "responseTime2", "bytesBlocked", "cycleTime"});
+ assertEquals(8, results.size());
+
+ Attribute attribute1 = (Attribute) results.get(0);
+ Attribute attribute2 = (Attribute) results.get(1);
+ Attribute attribute3 = (Attribute) results.get(2);
+ Attribute attribute4 = (Attribute) results.get(3);
+ Attribute attribute5 = (Attribute) results.get(4);
+ Attribute attribute6 = (Attribute) results.get(5);
+ Attribute attribute7 = (Attribute) results.get(6);
+ Attribute attribute8 = (Attribute) results.get(7);
+
+ assertEquals("timeout", attribute1.getName());
+ assertEquals(Long.valueOf(6*idx), attribute1.getValue());
+
+ assertEquals("bytesSent", attribute2.getName());
+ assertEquals(Long.valueOf(223*idx), attribute2.getValue());
+
+ assertEquals("bytesReceived", attribute3.getName());
+ assertEquals(Long.valueOf(587*idx), attribute3.getValue());
+
+ assertEquals("processTime", attribute4.getName());
+ assertEquals(75.0, attribute4.getValue());
+
+ assertEquals("responseTime", attribute5.getName());
+ assertEquals(180.0, attribute5.getValue());
+
+ assertEquals("responseTime2", attribute6.getName());
+ assertEquals(300.0, attribute6.getValue());
+
+ assertEquals("bytesBlocked", attribute7.getName());
+ assertEquals(Long.valueOf(33*idx), attribute7.getValue());
+
+ assertEquals("cycleTime", attribute8.getName());
+ assertEquals(10.0, attribute8.getValue());
+
+ assertEquals(3, dataSourceCount(objectName));
+ }
+
+
+ abstract class RunnableConsumerBase implements Runnable {
+
+ protected MetricConsumer consumer;
+ protected JmxMetricContext context = new JmxMetricContext(config(), context());
+
+ public RunnableConsumerBase() {
+ JmxMetricConsumer.Provider provider = new JmxMetricConsumer.Provider(config(), timer);
+ consumer = provider.get();
+ }
+ }
+
+ abstract class RunnableConsumerBase2 implements Runnable {
+
+ protected MetricConsumer consumer;
+ protected JmxMetricContext context = new JmxMetricContext(config(), context2());
+
+ public RunnableConsumerBase2() {
+ JmxMetricConsumer.Provider provider = new JmxMetricConsumer.Provider(config(), timer);
+ consumer = provider.get();
+ }
+ }
+
+ class RunnableConsumer1 extends RunnableConsumerBase {
+
+ public void run() {
+ try {
+ consumer.add("bytesSent", 101, context);
+ consumer.add("bytesReceived", 97, context);
+ consumer.add("timeout", 1, context);
+ consumer.set("responseTime", 100.0, context); // gauge
+ consumer.add("timeout", 2, context);
+ consumer.add("bytesSent", 1, context);
+ consumer.add("bytesSent", 10, context);
+ consumer.add("bytesReceived", 99, context);
+ consumer.add("bytesReceived", 98, context);
+ consumer.set("processTime", 100.0, context); // gauge
+ consumer.set("responseTime", 200.0, context); // gauge
+ consumer.set("responseTime", 300.0, context); // gauge
+ } catch (Exception e) {
+ System.out.println(e.getLocalizedMessage());
+ assertFalse(true);
+ }
+ }
+ }
+
+ class RunnableConsumer2 extends RunnableConsumerBase {
+
+ public void run() {
+ try {
+ consumer.add("bytesSent", 101, context);
+ consumer.add("bytesReceived", 97, context);
+ consumer.add("timeout", 1, context);
+ consumer.set("responseTime", 100.0, context); // gauge
+ consumer.add("timeout", 2, context);
+ consumer.add("bytesSent", 1, context);
+ consumer.add("bytesSent", 9, context);
+ consumer.add("bytesReceived", 99, context);
+ consumer.add("bytesReceived", 97, context);
+ consumer.set("processTime", 50.0, context); // gauge
+ consumer.set("responseTime", 200.0, context); // gauge
+ consumer.set("responseTime2", 300.0, context); // gauge
+ consumer.add("bytesBlocked", 13.0, context);
+ } catch (Exception e) {
+ System.out.println(e.getLocalizedMessage());
+ assertFalse(true);
+ }
+ }
+ }
+
+ class RunnableConsumer3 extends RunnableConsumerBase {
+
+ public void run() {
+ try {
+ consumer.add("bytesBlocked", 11.0, context);
+ consumer.add("bytesBlocked", 9.0, context);
+ consumer.set("cycleTime", 10.0, context); // gauge
+ } catch (Exception e) {
+ System.out.println(e.getLocalizedMessage());
+ assertFalse(true);
+ }
+ }
+ }
+
+ class RunnableConsumer1_2 extends RunnableConsumerBase2 {
+
+ public void run() {
+ try {
+ consumer.add("bytesSent", 101, context);
+ consumer.add("bytesReceived", 97, context);
+ consumer.add("timeout", 1, context);
+ consumer.set("responseTime", 100.0, context); // gauge
+ consumer.add("timeout", 2, context);
+ consumer.add("bytesSent", 1, context);
+ consumer.add("bytesSent", 10, context);
+ consumer.add("bytesReceived", 99, context);
+ consumer.add("bytesReceived", 98, context);
+ consumer.set("processTime", 100.0, context); // gauge
+ consumer.set("responseTime", 200.0, context); // gauge
+ consumer.set("responseTime", 300.0, context); // gauge
+ } catch (Exception e) {
+ System.out.println(e.getLocalizedMessage());
+ assertFalse(true);
+ }
+ }
+ }
+
+ class RunnableConsumer2_2 extends RunnableConsumerBase2 {
+
+ public void run() {
+ try {
+ consumer.add("bytesSent", 101, context);
+ consumer.add("bytesReceived", 97, context);
+ consumer.add("timeout", 1, context);
+ consumer.set("responseTime", 100.0, context); // gauge
+ consumer.add("timeout", 2, context);
+ consumer.add("bytesSent", 1, context);
+ consumer.add("bytesSent", 9, context);
+ consumer.add("bytesReceived", 99, context);
+ consumer.add("bytesReceived", 97, context);
+ consumer.set("processTime", 50.0, context); // gauge
+ consumer.set("responseTime", 200.0, context); // gauge
+ consumer.set("responseTime2", 300.0, context); // gauge
+ consumer.add("bytesBlocked", 13.0, context);
+ } catch (Exception e) {
+ System.out.println(e.getLocalizedMessage());
+ assertFalse(true);
+ }
+ }
+ }
+
+ class RunnableConsumer3_2 extends RunnableConsumerBase2 {
+
+ public void run() {
+ try {
+ consumer.add("bytesBlocked", 11.0, context);
+ consumer.add("bytesBlocked", 9.0, context);
+ consumer.set("cycleTime", 10.0, context); // gauge
+ } catch (Exception e) {
+ System.out.println(e.getLocalizedMessage());
+ assertFalse(true);
+ }
+ }
+ }
+
+ protected Hashtable<String, String> context() {
+ Hashtable<String, String> contextMap = new Hashtable<String, String>();
+ contextMap.put("port", "8080");
+ contextMap.put("server", "http");
+ contextMap.put("bundle", "http_service");
+ return contextMap;
+ }
+
+ protected Hashtable<String, String> context2() {
+ Hashtable<String, String> contextMap = new Hashtable<String, String>();
+ contextMap.put("port", "8081");
+ contextMap.put("server", "https");
+ contextMap.put("bundle", "http_service");
+ return contextMap;
+ }
+
+ protected int dataSourceCount(ObjectName objectName) {
+ ConsumerContextMetricReader componentMetricMBean = JMX.newMBeanProxy(ManagementFactory.getPlatformMBeanServer(),
+ objectName,
+ ConsumerContextMetricReader.class);
+ return componentMetricMBean.dataSourceCount();
+ }
+
+ protected JmxMetricConfig config() {
+ JmxMetricConfig.Builder builder = new JmxMetricConfig.Builder();
+ builder.objectNameDomain("com.jdisc.jmx");
+ builder.minSnapshotIntervalMillis(0);
+ JmxMetricConfig config = new JmxMetricConfig(builder);
+ return config;
+ }
+}
diff --git a/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/JmxMetricConsumerTestCase.java b/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/JmxMetricConsumerTestCase.java
new file mode 100644
index 00000000000..ac9e8e9789b
--- /dev/null
+++ b/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/JmxMetricConsumerTestCase.java
@@ -0,0 +1,189 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx;
+
+import com.google.inject.Guice;
+import com.yahoo.jdisc.Metric;
+import com.yahoo.jdisc.Timer;
+import com.yahoo.jdisc.metrics.jmx.cloud.JmxMetricConfig;
+import org.testng.annotations.Test;
+
+import javax.management.JMException;
+import javax.management.JMX;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import java.lang.management.ManagementFactory;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertTrue;
+
+/**
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+public class JmxMetricConsumerTestCase {
+
+ Timer timer = Guice.createInjector().getInstance(Timer.class);
+
+ @Test(expectedExceptions = RuntimeException.class)
+ public void requireThatConstructorCanThrowException() {
+ JmxMetricConfig.Builder builder = new JmxMetricConfig.Builder();
+ builder.objectNameDomain(":::");
+ JmxMetricConfig config = new JmxMetricConfig(builder);
+ JmxMetricConsumer consumer = new JmxMetricConsumer(config, timer);
+ consumer.toString();
+ }
+
+ @Test(expectedExceptions = RuntimeException.class)
+ public void requireThatCreateContextCanThrowException() {
+ JmxMetricConfig.Builder builder = new JmxMetricConfig.Builder();
+ builder.objectNameDomain(":::");
+ JmxMetricConfig config = new JmxMetricConfig(builder);
+ JmxMetricConsumer consumer = new JmxMetricConsumer(config, timer);
+ consumer.createContext(null);
+ consumer.toString();
+ }
+
+ ConsumerContextMetric dataSource = new ConsumerContextMetric(Integer.MAX_VALUE);
+
+ @Test
+ public void requireThatNewMBeanCanBeRegisteredWithNoDimensions() {
+
+ JmxMetricConfig config = new JmxMetricConfig(new JmxMetricConfig.Builder());
+
+ try {
+ new JmxMetricConsumer(config, timer).createAndRegisterMBean(createContext(), dataSource, config, timer);
+ } catch (Exception e) {
+ assertTrue(false);
+ }
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ ObjectName objectName = null;
+ try {
+ objectName = new ObjectName("test.jdisc.metrics.jmx:name=JDisc");
+ } catch (Exception e) {
+ } finally {
+ assertTrue(mbs.isRegistered(objectName));
+ try {
+ mbs.unregisterMBean(objectName);
+ } catch (Exception e) {
+ assertFalse(true);
+ }
+ }
+ }
+
+ @Test
+ public void requireThatNewMBeanCanBeRegisteredWithEmptyContext() {
+
+ JmxMetricConfig config = new JmxMetricConfig(new JmxMetricConfig.Builder());
+
+ try {
+ new JmxMetricConsumer(config, timer).createAndRegisterMBean(createContext(), dataSource, config, timer);
+ } catch (Exception e) {
+ assertTrue(false);
+ }
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ ObjectName objectName = null;
+ try {
+ objectName = new ObjectName("test.jdisc.metrics.jmx:name=JDisc");
+ } catch (Exception e) {
+ } finally {
+ assertTrue(mbs.isRegistered(objectName));
+ try {
+ mbs.unregisterMBean(objectName);
+ } catch (Exception e) {
+ assertFalse(true);
+ }
+ }
+ }
+
+ @Test
+ public void requireThatNewMBeanCanBeRegisteredWithDimensions() {
+ Map<String, Object> contextMap = new HashMap<String, Object>();
+ contextMap.put("key1", "value1");
+ contextMap.put("key2", "value2");
+ JmxMetricConfig.Builder builder = new JmxMetricConfig.Builder();
+ builder.objectNameDomain("test.jdisc.metrics.jmx");
+ JmxMetricConfig config = new JmxMetricConfig(builder);
+ JmxMetricContext context = new JmxMetricContext(config, contextMap);
+
+ try {
+ new JmxMetricConsumer(config, timer).createAndRegisterMBean(context, dataSource, config, timer);
+ } catch (Exception e) {
+ assertTrue(false);
+ }
+ Hashtable<String, String> expectedDimensions = new Hashtable<String, String>();
+ expectedDimensions.put("key1", "value1");
+ expectedDimensions.put("key2", "value2");
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ ObjectName objectName = null;
+ try {
+ objectName = new ObjectName("test.jdisc.metrics.jmx", expectedDimensions);
+ } catch (Exception e) {
+ assertFalse(true);
+ } finally {
+ assertTrue(mbs.isRegistered(objectName));
+ try {
+ mbs.unregisterMBean(objectName);
+ } catch (Exception e) {
+ assertFalse(true);
+ }
+ }
+ }
+
+ @Test(expectedExceptions = RuntimeException.class)
+ public void requireThatExceptionCanBeThrown() throws JMException {
+ JmxMetricConfig config = new JmxMetricConfig(new JmxMetricConfig.Builder());
+
+ new JmxMetricConsumer(config, timer).createAndRegisterMBean(new DummyContext(), dataSource, config, timer);
+ assertTrue(false);
+ }
+
+ @Test
+ public void requireThatDataSourceCanBeRegisteredWithExistingMBean() {
+
+ // Register MBean first
+ JmxMetricConfig config = new JmxMetricConfig(new JmxMetricConfig.Builder());
+
+ try {
+ new JmxMetricConsumer(config, timer).createAndRegisterMBean(createContext(), dataSource, config, timer);
+ } catch (Exception e) {
+ assertTrue(false);
+ }
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ ObjectName objectName = null;
+ try {
+ objectName = new ObjectName("test.jdisc.metrics.jmx:name=JDisc");
+ } catch (Exception e) {
+ } finally {
+ assertTrue(mbs.isRegistered(objectName));
+ }
+
+ ConsumerContextMetric dataSource2 = new ConsumerContextMetric(Integer.MAX_VALUE);
+ // Register same MBean, different data source
+ try {
+ new JmxMetricConsumer(config, timer).createAndRegisterMBean(createContext(), dataSource2, config, timer);
+ } catch (Exception e) {
+ assertTrue(false);
+ }
+ ConsumerContextMetricReader componentMetricMBean = JMX.newMBeanProxy(mbs,
+ objectName,
+ ConsumerContextMetricReader.class);
+ assertEquals(2, componentMetricMBean.dataSourceCount());
+
+ }
+
+ private JmxMetricContext createContext() {
+ Map<String, String> dimensions = new HashMap<String, String>();
+ JmxMetricConfig.Builder builder = new JmxMetricConfig.Builder();
+ builder.objectNameDomain("test.jdisc.metrics.jmx");
+ JmxMetricConfig config = new JmxMetricConfig(builder);
+ return new JmxMetricContext(config, dimensions);
+ }
+
+ class DummyContext implements Metric.Context {
+
+ }
+
+}
diff --git a/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/JmxMetricContextTestCase.java b/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/JmxMetricContextTestCase.java
new file mode 100644
index 00000000000..e04918d2329
--- /dev/null
+++ b/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/JmxMetricContextTestCase.java
@@ -0,0 +1,84 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx;
+
+import com.yahoo.jdisc.metrics.jmx.cloud.JmxMetricConfig;
+import org.testng.annotations.Test;
+
+import javax.management.ObjectName;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import static org.testng.AssertJUnit.*;
+
+/**
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+public class JmxMetricContextTestCase {
+
+ @Test
+ public void requireThatEqualsWorks() {
+ JmxMetricContext context = createContext();
+ JmxMetricContext context2 = createContext();
+ assertTrue(context.equals(context2));
+ }
+
+ @Test
+ public void requireThatEqualsWorksOnlyForRightType() {
+ JmxMetricContext context = createContext();
+ assertFalse(context.equals(null));
+ assertFalse(context.equals(new Integer(0)));
+ }
+
+ @Test
+ public void requireThatGetObjectNameWorks() throws Exception {
+ JmxMetricContext context = createContext();
+ Hashtable<String, String> dimensions = new Hashtable<String, String>();
+ dimensions.put("key1", "value1");
+ dimensions.put("key2", "value2");
+
+ assertTrue(context.getObjectName().equals(new ObjectName(new JmxMetricConfig(new JmxMetricConfig.Builder()).objectNameDomain(),
+ dimensions)));
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void requireThatNullParamIsInvalid() {
+ JmxMetricContext context = new JmxMetricContext(new JmxMetricConfig(new JmxMetricConfig.Builder()), null);
+ context.getObjectName();
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void requireThatInvalidDomainNameThrowsException() {
+ JmxMetricConfig.Builder builder = new JmxMetricConfig.Builder();
+ builder.objectNameDomain("abc--86^%#@:");
+ JmxMetricConfig config = new JmxMetricConfig(builder);
+ JmxMetricContext context = new JmxMetricContext(config, new HashMap<String, String>());
+ context.getObjectName();
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void requireThatInvalidDomainNameThrowsExceptionWithDimensions() {
+ JmxMetricConfig.Builder builder = new JmxMetricConfig.Builder();
+ builder.objectNameDomain("abc--86^%#@:");
+ JmxMetricConfig config = new JmxMetricConfig(builder);
+ Hashtable<String, String> dimensions = new Hashtable<String, String>();
+ dimensions.put("key1", "value1");
+ dimensions.put("key2", "value2");
+ JmxMetricContext context = new JmxMetricContext(config, dimensions);
+ context.getObjectName();
+ }
+
+ @Test
+ public void requireThatHashCodeWorks() {
+ JmxMetricContext context = createContext();
+ assertEquals(-218969104, context.hashCode());
+ }
+
+ private JmxMetricContext createContext() {
+ Map<String, String> dimensions = new HashMap<String, String>();
+ dimensions.put("key1", "value1");
+ dimensions.put("key2", "value2");
+ return new JmxMetricContext(new JmxMetricConfig(new JmxMetricConfig.Builder()), dimensions);
+ }
+
+}
diff --git a/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/MultiSourceComponentMetricTestCase.java b/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/MultiSourceComponentMetricTestCase.java
new file mode 100644
index 00000000000..e10af285241
--- /dev/null
+++ b/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/MultiSourceComponentMetricTestCase.java
@@ -0,0 +1,97 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx;
+
+import org.testng.annotations.Test;
+
+import java.util.Map;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertNull;
+import com.yahoo.jdisc.metrics.jmx.core.MetricUnit;
+
+/**
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+public class MultiSourceComponentMetricTestCase {
+
+ @Test
+ public void requireThatOneComponentMetricIsSupported() {
+ ConsumerContextMetric source1 = new ConsumerContextMetric(10000);
+ source1.setMetric("key1", 100);
+ source1.setMetric("key2", 101);
+ source1.setMetric("key3", 108);
+ source1.setMetric("key3", 100);
+ MultiSourceComponentMetric componentMetric = new MultiSourceComponentMetric(source1);
+ Map<String, MetricUnit> snapshot = componentMetric.snapshot();
+ assertEquals(100.0, snapshot.get("key1").getValue());
+ assertEquals(101.0, snapshot.get("key2").getValue());
+ assertEquals(104.0, snapshot.get("key3").getValue());
+ assertNull(snapshot.get("key4"));
+ }
+
+ @Test
+ public void requireThatNComponentMetricsAreSupported() {
+ ConsumerContextMetric source1 = new ConsumerContextMetric(10000);
+ ConsumerContextMetric source2 = new ConsumerContextMetric(10000);
+ ConsumerContextMetric source3 = new ConsumerContextMetric(10000);
+ source1.setMetric("key1", 100);
+ source2.setMetric("key1", 200);
+ source3.setMetric("key1", 300);
+ MultiSourceComponentMetric componentMetric = new MultiSourceComponentMetric(source1);
+ componentMetric.addConsumerContextMetric(source2);
+ componentMetric.addConsumerContextMetric(source3);
+ Map<String, MetricUnit> snapshot = componentMetric.snapshot();
+ assertEquals(200.0, snapshot.get("key1").getValue());
+ assertEquals(3, componentMetric.getSourceCount());
+ }
+
+ @Test
+ public void requireThatNComponentMetricsAreSupportedWithMultipleKeys() {
+ ConsumerContextMetric source1 = new ConsumerContextMetric(1);
+ ConsumerContextMetric source2 = new ConsumerContextMetric(2);
+ ConsumerContextMetric source3 = new ConsumerContextMetric(1);
+ source1.setMetric("key1", 100);
+ source1.incrementMetric("key4", 100);
+ source1.setMetric("key2", 100);
+ source1.setMetric("key2", 100);
+ source2.setMetric("key1", 200);
+ source2.setMetric("key2", 100);
+ source3.setMetric("key1", 300);
+ source3.incrementMetric("key4", 300);
+ MultiSourceComponentMetric componentMetric = new MultiSourceComponentMetric(source1);
+ componentMetric.addConsumerContextMetric(source2);
+ componentMetric.addConsumerContextMetric(source3);
+ Map<String, MetricUnit> snapshot = componentMetric.snapshot();
+ assertEquals(200.0, snapshot.get("key1").getValue());
+ assertEquals(100.0, snapshot.get("key2").getValue());
+ assertEquals(400, snapshot.get("key4").getValue().longValue());
+ assertEquals(200.0, snapshot.get("key1").getValue());
+ assertEquals(100.0, snapshot.get("key2").getValue());
+ assertEquals(400, snapshot.get("key4").getValue().longValue());
+ snapshot = componentMetric.snapshot();
+ assertNull(snapshot.get("key1"));
+ assertNull(snapshot.get("key2"));
+ assertNull(snapshot.get("key4"));
+ }
+
+ @Test
+ public void requireThatNComponentMetricsReturnCorrectKeysWithMultipleKeys() {
+ ConsumerContextMetric source1 = new ConsumerContextMetric(1);
+ ConsumerContextMetric source2 = new ConsumerContextMetric(4);
+ ConsumerContextMetric source3 = new ConsumerContextMetric(2);
+ source1.setMetric("key1", 100);
+ source1.incrementMetric("key4", 100);
+ source1.setMetric("key2", 100);
+ source1.setMetric("key2", 100);
+ source2.setMetric("key1", 200);
+ source2.setMetric("key2", 100);
+ source3.setMetric("key1", 300);
+ source3.incrementMetric("key4", 300);
+ MultiSourceComponentMetric componentMetric = new MultiSourceComponentMetric(source1);
+ componentMetric.addConsumerContextMetric(source2);
+ componentMetric.addConsumerContextMetric(source3);
+ Map<String, MetricUnit> snapshot = componentMetric.snapshot();
+ assertEquals(3, snapshot.keySet().size());
+ }
+
+}
diff --git a/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/core/CounterMetricUnitTestCase.java b/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/core/CounterMetricUnitTestCase.java
new file mode 100644
index 00000000000..0654e337881
--- /dev/null
+++ b/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/core/CounterMetricUnitTestCase.java
@@ -0,0 +1,78 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx.core;
+
+import org.testng.annotations.Test;
+
+import static org.testng.AssertJUnit.*;
+
+/**
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+public class CounterMetricUnitTestCase {
+
+ @Test
+ public void requireThatACounterMetricOfTypeIntegerIsProperlyStored() {
+ CounterMetricUnit counterMetricUnit = new CounterMetricUnit();
+ counterMetricUnit.addValue(100);
+ Number expected = counterMetricUnit.getValue();
+ assertEquals(100, expected.intValue());
+ }
+
+ @Test
+ public void requireThatACounterMetricOfTypeIntegerCanBeAddedTo() {
+ CounterMetricUnit counterMetricUnit = new CounterMetricUnit();
+ counterMetricUnit.addValue(100);
+ counterMetricUnit.addValue(50);
+ Number expected = counterMetricUnit.getValue();
+ assertEquals(150, expected.intValue());
+ }
+
+ @Test
+ public void requireThatACounterMetricOfTypeDoubleIsProperlyStored() {
+ CounterMetricUnit counterMetricUnit = new CounterMetricUnit();
+ counterMetricUnit.addValue(100.0);
+ Number expected = counterMetricUnit.getValue();
+ assertEquals(100.0, expected.doubleValue());
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void requireThatAddToNullThrowsException() {
+ CounterMetricUnit counterMetricUnit = new CounterMetricUnit();
+ counterMetricUnit.addValue(null);
+ }
+
+ @Test
+ public void requireThatValueCanBeTakenFromAnotherCounter() {
+ CounterMetricUnit takeTo = new CounterMetricUnit();
+ takeTo.addValue(100);
+ CounterMetricUnit takeFrom = new CounterMetricUnit();
+ takeFrom.addValue(200);
+ takeTo.addMetric(takeFrom);
+ assertEquals(300, takeTo.getValue().longValue());
+ assertEquals(200, takeFrom.getValue().longValue()); // unchanged
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void requireThatValueCannotBeTakenFromNull() {
+ CounterMetricUnit takeTo = new CounterMetricUnit();
+ takeTo.addMetric(null);
+ assertEquals(100.0, takeTo.getValue());
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void requireThatValueCannotBeTakenFromAGauge() {
+ CounterMetricUnit takeTo = new CounterMetricUnit();
+ takeTo.addValue(100);
+ GaugeMetricUnit takeFrom = new GaugeMetricUnit(100);
+ takeFrom.addValue(200);
+ takeTo.addMetric(takeFrom);
+ assertEquals(100.0, takeTo.getValue());
+ assertEquals(200.0, takeFrom.getValue()); // unchanged
+ }
+
+ @Test
+ public void requireThatCountersShouldBePersistent() {
+ CounterMetricUnit metricUnit = new CounterMetricUnit();
+ assertTrue(metricUnit.isPersistent());
+ }
+}
diff --git a/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/core/GaugeMetricUnitTestCase.java b/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/core/GaugeMetricUnitTestCase.java
new file mode 100644
index 00000000000..ff3508e422c
--- /dev/null
+++ b/jdisc_jmx_metrics/src/test/java/com/yahoo/jdisc/metrics/jmx/core/GaugeMetricUnitTestCase.java
@@ -0,0 +1,79 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.metrics.jmx.core;
+
+import org.testng.annotations.Test;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+
+/**
+ * @author <a href="mailto:alain@yahoo-inc.com">Alain Wan Buen Cheong</a>
+ */
+public class GaugeMetricUnitTestCase {
+
+ private final int DEPTH=100;
+
+ @Test
+ public void requireThatGaugeReturnsAverageValue() {
+ GaugeMetricUnit gaugeMetricUnit = new GaugeMetricUnit(DEPTH);
+ gaugeMetricUnit.addValue(100);
+ gaugeMetricUnit.addValue(50.0);
+ Number expected = gaugeMetricUnit.getValue();
+ assertEquals(75.0, expected.doubleValue());
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void requireThatAddToNullThrowsException() {
+ GaugeMetricUnit gaugeMetricUnit = new GaugeMetricUnit(DEPTH);
+ gaugeMetricUnit.addValue(100);
+ gaugeMetricUnit.addValue(null);
+ }
+
+ @Test
+ public void requireThatGaugeValueCanBeTaken() {
+ GaugeMetricUnit gaugeMetricUnit = new GaugeMetricUnit(DEPTH);
+ gaugeMetricUnit.addValue(60);
+ gaugeMetricUnit.addValue(40.0);
+ GaugeMetricUnit gaugeMetricUnit2 = new GaugeMetricUnit(DEPTH);
+ gaugeMetricUnit2.addValue(200);
+ gaugeMetricUnit2.addMetric(gaugeMetricUnit);
+ assertEquals(100.0, gaugeMetricUnit2.getValue());
+ assertEquals(50.0, gaugeMetricUnit.getValue());
+ }
+
+ @Test
+ public void requireThatGaugeDoesNotRunOutOfSpace() {
+ GaugeMetricUnit drainFrom = new GaugeMetricUnit(DEPTH);
+ drainFrom.addValue(60.0);
+ drainFrom.addValue(10);
+ drainFrom.addValue(2);
+ GaugeMetricUnit drainTo = new GaugeMetricUnit(2);
+ drainTo.addMetric(drainFrom);
+ assertEquals(24.0, drainTo.getValue());
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void requireThatValueCannotBeTakenFromNull() {
+ GaugeMetricUnit takeTo = new GaugeMetricUnit(DEPTH);
+ takeTo.addValue(100);
+ takeTo.addMetric(null);
+ assertEquals(100.0, takeTo.getValue());
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void requireThatValueCannotBeTakenFromACounter() {
+ GaugeMetricUnit takeTo = new GaugeMetricUnit(DEPTH);
+ takeTo.addValue(100);
+ CounterMetricUnit takeFrom = new CounterMetricUnit();
+ takeFrom.addValue(200);
+ takeTo.addMetric(takeFrom);
+ assertEquals(100.0, takeTo.getValue());
+ assertEquals(200.0, takeFrom.getValue()); // unchanged
+ }
+
+ @Test
+ public void requireThatGaugesShouldBePersistent() {
+ GaugeMetricUnit metricUnit = new GaugeMetricUnit(DEPTH);
+ assertFalse(metricUnit.isPersistent());
+ }
+}