diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
commit | 72231250ed81e10d66bfe70701e64fa5fe50f712 (patch) | |
tree | 2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /jdisc_jmx_metrics |
Publish
Diffstat (limited to 'jdisc_jmx_metrics')
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()); + } +} |