package io.dropwizard.metrics; import org.junit.After; import org.junit.Before; import org.junit.Test; import io.dropwizard.metrics.Counter; import io.dropwizard.metrics.Gauge; import io.dropwizard.metrics.Histogram; import io.dropwizard.metrics.JmxReporter; import io.dropwizard.metrics.Meter; import io.dropwizard.metrics.MetricFilter; import io.dropwizard.metrics.MetricName; import io.dropwizard.metrics.MetricRegistry; import io.dropwizard.metrics.ObjectNameFactory; import io.dropwizard.metrics.Snapshot; import io.dropwizard.metrics.Timer; import javax.management.*; import java.lang.management.ManagementFactory; import java.util.SortedMap; import java.util.TreeMap; import java.util.UUID; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; public class JmxReporterTest { private final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); private final String name = UUID.randomUUID().toString().replaceAll("[{\\-}]", ""); private final MetricRegistry registry = new MetricRegistry(); private final JmxReporter reporter = JmxReporter.forRegistry(registry) .registerWith(mBeanServer) .inDomain(name) .convertDurationsTo(TimeUnit.MILLISECONDS) .convertRatesTo(TimeUnit.SECONDS) .filter(MetricFilter.ALL) .build(); private final Gauge gauge = mock(Gauge.class); private final Counter counter = mock(Counter.class); private final Histogram histogram = mock(Histogram.class); private final Meter meter = mock(Meter.class); private final Timer timer = mock(Timer.class); private final ObjectNameFactory mockObjectNameFactory = mock(ObjectNameFactory.class); private final ObjectNameFactory concreteObjectNameFactory = reporter.getObjectNameFactory(); @Before public void setUp() throws Exception { when(gauge.getValue()).thenReturn(1); when(counter.getCount()).thenReturn(100L); when(histogram.getCount()).thenReturn(1L); final Snapshot hSnapshot = mock(Snapshot.class); when(hSnapshot.getMax()).thenReturn(2L); when(hSnapshot.getMean()).thenReturn(3.0); when(hSnapshot.getMin()).thenReturn(4L); when(hSnapshot.getStdDev()).thenReturn(5.0); when(hSnapshot.getMedian()).thenReturn(6.0); when(hSnapshot.get75thPercentile()).thenReturn(7.0); when(hSnapshot.get95thPercentile()).thenReturn(8.0); when(hSnapshot.get98thPercentile()).thenReturn(9.0); when(hSnapshot.get99thPercentile()).thenReturn(10.0); when(hSnapshot.get999thPercentile()).thenReturn(11.0); when(histogram.getSnapshot()).thenReturn(hSnapshot); when(meter.getCount()).thenReturn(1L); when(meter.getMeanRate()).thenReturn(2.0); when(meter.getOneMinuteRate()).thenReturn(3.0); when(meter.getFiveMinuteRate()).thenReturn(4.0); when(meter.getFifteenMinuteRate()).thenReturn(5.0); when(timer.getCount()).thenReturn(1L); when(timer.getMeanRate()).thenReturn(2.0); when(timer.getOneMinuteRate()).thenReturn(3.0); when(timer.getFiveMinuteRate()).thenReturn(4.0); when(timer.getFifteenMinuteRate()).thenReturn(5.0); final Snapshot tSnapshot = mock(Snapshot.class); when(tSnapshot.getMax()).thenReturn(TimeUnit.MILLISECONDS.toNanos(100)); when(tSnapshot.getMean()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(200)); when(tSnapshot.getMin()).thenReturn(TimeUnit.MILLISECONDS.toNanos(300)); when(tSnapshot.getStdDev()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(400)); when(tSnapshot.getMedian()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(500)); when(tSnapshot.get75thPercentile()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(600)); when(tSnapshot.get95thPercentile()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(700)); when(tSnapshot.get98thPercentile()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(800)); when(tSnapshot.get99thPercentile()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(900)); when(tSnapshot.get999thPercentile()).thenReturn((double) TimeUnit.MILLISECONDS.toNanos(1000)); when(timer.getSnapshot()).thenReturn(tSnapshot); registry.register("gauge", gauge); registry.register("test.counter", counter); registry.register("test.histogram", histogram); registry.register("test.meter", meter); registry.register("test.another.timer", timer); reporter.start(); } @After public void tearDown() throws Exception { reporter.stop(); } @Test public void registersMBeansForMetricObjectsUsingProvidedObjectNameFactory() throws Exception { ObjectName n = new ObjectName(name + ":name=dummy"); try { String widgetName = "something"; when(mockObjectNameFactory.createName(any(String.class), any(String.class), any(MetricName.class))).thenReturn(n); Gauge aGauge = mock(Gauge.class); when(aGauge.getValue()).thenReturn(1); JmxReporter reporter = JmxReporter.forRegistry(registry) .registerWith(mBeanServer) .inDomain(name) .createsObjectNamesWith(mockObjectNameFactory) .build(); registry.register(widgetName, aGauge); reporter.start(); verify(mockObjectNameFactory).createName(eq("gauges"), any(String.class), eq(MetricName.build("something"))); //verifyNoMoreInteractions(mockObjectNameFactory); } finally { reporter.stop(); if(mBeanServer.isRegistered(n)) { mBeanServer.unregisterMBean(n); } } } @Test public void registersMBeansForGauges() throws Exception { final AttributeList attributes = getAttributes("gauge", "Value"); assertThat(values(attributes)) .contains(entry("Value", 1)); } @Test public void registersMBeansForCounters() throws Exception { final AttributeList attributes = getAttributes("test.counter", "Count"); assertThat(values(attributes)) .contains(entry("Count", 100L)); } @Test public void registersMBeansForHistograms() throws Exception { final AttributeList attributes = getAttributes("test.histogram", "Count", "Max", "Mean", "Min", "StdDev", "50thPercentile", "75thPercentile", "95thPercentile", "98thPercentile", "99thPercentile", "999thPercentile"); assertThat(values(attributes)) .contains(entry("Count", 1L)) .contains(entry("Max", 2L)) .contains(entry("Mean", 3.0)) .contains(entry("Min", 4L)) .contains(entry("StdDev", 5.0)) .contains(entry("50thPercentile", 6.0)) .contains(entry("75thPercentile", 7.0)) .contains(entry("95thPercentile", 8.0)) .contains(entry("98thPercentile", 9.0)) .contains(entry("99thPercentile", 10.0)) .contains(entry("999thPercentile", 11.0)); } @Test public void registersMBeansForMeters() throws Exception { final AttributeList attributes = getAttributes("test.meter", "Count", "MeanRate", "OneMinuteRate", "FiveMinuteRate", "FifteenMinuteRate", "RateUnit"); assertThat(values(attributes)) .contains(entry("Count", 1L)) .contains(entry("MeanRate", 2.0)) .contains(entry("OneMinuteRate", 3.0)) .contains(entry("FiveMinuteRate", 4.0)) .contains(entry("FifteenMinuteRate", 5.0)) .contains(entry("RateUnit", "events/second")); } @Test public void registersMBeansForTimers() throws Exception { final AttributeList attributes = getAttributes("test.another.timer", "Count", "MeanRate", "OneMinuteRate", "FiveMinuteRate", "FifteenMinuteRate", "Max", "Mean", "Min", "StdDev", "50thPercentile", "75thPercentile", "95thPercentile", "98thPercentile", "99thPercentile", "999thPercentile", "RateUnit", "DurationUnit"); assertThat(values(attributes)) .contains(entry("Count", 1L)) .contains(entry("MeanRate", 2.0)) .contains(entry("OneMinuteRate", 3.0)) .contains(entry("FiveMinuteRate", 4.0)) .contains(entry("FifteenMinuteRate", 5.0)) .contains(entry("Max", 100.0)) .contains(entry("Mean", 200.0)) .contains(entry("Min", 300.0)) .contains(entry("StdDev", 400.0)) .contains(entry("50thPercentile", 500.0)) .contains(entry("75thPercentile", 600.0)) .contains(entry("95thPercentile", 700.0)) .contains(entry("98thPercentile", 800.0)) .contains(entry("99thPercentile", 900.0)) .contains(entry("999thPercentile", 1000.0)) .contains(entry("RateUnit", "events/second")) .contains(entry("DurationUnit", "milliseconds")); } @Test public void cleansUpAfterItselfWhenStopped() throws Exception { reporter.stop(); try { getAttributes("gauge", "Value"); failBecauseExceptionWasNotThrown(InstanceNotFoundException.class); } catch (InstanceNotFoundException e) { } } @Test public void objectNameModifyingMBeanServer() throws Exception { MBeanServer mockedMBeanServer = mock(MBeanServer.class); // overwrite the objectName when(mockedMBeanServer.registerMBean(any(Object.class), any(ObjectName.class))).thenReturn(new ObjectInstance("DOMAIN:key=value","className")); MetricRegistry testRegistry = new MetricRegistry(); JmxReporter testJmxReporter = JmxReporter.forRegistry(testRegistry) .registerWith(mockedMBeanServer) .inDomain(name) .build(); testJmxReporter.start(); // should trigger a registerMBean testRegistry.timer("test"); // should trigger an unregisterMBean with the overwritten objectName = "DOMAIN:key=value" testJmxReporter.stop(); verify(mockedMBeanServer).unregisterMBean(new ObjectName("DOMAIN:key=value")); } @Test public void testJmxMetricNameWithAsterisk() { MetricRegistry metricRegistry = new MetricRegistry(); JmxReporter.forRegistry(metricRegistry).build().start(); metricRegistry.counter("test*"); } private AttributeList getAttributes(String name, String... attributeNames) throws JMException { ObjectName n = concreteObjectNameFactory.createName("only-for-logging-error", this.name, MetricName.build(name)); return mBeanServer.getAttributes(n, attributeNames); } private SortedMap<String, Object> values(AttributeList attributes) { final TreeMap<String, Object> values = new TreeMap<String, Object>(); for (Object o : attributes) { final Attribute attribute = (Attribute) o; values.put(attribute.getName(), attribute.getValue()); } return values; } }