diff --git a/common/pom.xml b/common/pom.xml index a8fdd27..09136a4 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -199,10 +199,17 @@ jackson-databind ${jackson.new.version} - + + + + org.mockito + mockito-all + ${mockito-all.version} + test diff --git a/common/src/java/org/apache/hadoop/hive/common/metrics/metrics2/CodahaleMetrics.java b/common/src/java/org/apache/hadoop/hive/common/metrics/metrics2/CodahaleMetrics.java index 4c43367..1b08735 100644 --- a/common/src/java/org/apache/hadoop/hive/common/metrics/metrics2/CodahaleMetrics.java +++ b/common/src/java/org/apache/hadoop/hive/common/metrics/metrics2/CodahaleMetrics.java @@ -34,7 +34,7 @@ import com.codahale.metrics.jvm.MemoryUsageGaugeSet; import com.codahale.metrics.jvm.ThreadStatesGaugeSet; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.joshelser.dropwizard.metrics.hadoop.HadoopMetrics2Reporter; +import org.apache.hive.common.util.HadoopMetrics2Reporter; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; import com.google.common.cache.CacheBuilder; diff --git a/common/src/java/org/apache/hive/common/util/HadoopMetrics2Reporter.java b/common/src/java/org/apache/hive/common/util/HadoopMetrics2Reporter.java new file mode 100644 index 0000000..270be97 --- /dev/null +++ b/common/src/java/org/apache/hive/common/util/HadoopMetrics2Reporter.java @@ -0,0 +1,452 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hive.common.util; + +import org.apache.hadoop.metrics2.MetricsCollector; +import org.apache.hadoop.metrics2.MetricsInfo; +import org.apache.hadoop.metrics2.MetricsRecordBuilder; +import org.apache.hadoop.metrics2.MetricsSource; +import org.apache.hadoop.metrics2.MetricsSystem; +import org.apache.hadoop.metrics2.lib.Interns; +import org.apache.hadoop.metrics2.lib.MetricsRegistry; + +import com.codahale.metrics.Counter; +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Histogram; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricFilter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.ScheduledReporter; +import com.codahale.metrics.Snapshot; +import com.codahale.metrics.Timer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.SortedMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; + +/** + * Local code copy of com.github.joshelser.dropwizard.metrics.hadoop.HadoopMetrics2Reporter + * which allows CodaHale metrics to bridge to Hadoop Metrics2. + * + * Note that the presence of this class inside the hive project is + * explicitly intended to be temporary and is being introduced as Deprecated. + * It should not be modified here. If you have any contributions to make to this, + * please contribute directly to the original project. The reason this is being + * introduced here is to fix an issue where the aforementioned class which is a + * hive dependency generates an exceptionally large number of INFO level logs due + * to the way we use it. Fixing this in a log4j.properties is trivial, but manual + * for a lot of people, and thus, having this copy here is a temporary fix till a + * newer version of the Metrics2Reporter exists which we can depend on instead. + * + * Please do *NOT* depend on this from code other than from CodahaleMetrics, this + * will be removed soon. + */ + +@Deprecated +public class HadoopMetrics2Reporter extends ScheduledReporter implements MetricsSource { + private static final Logger LOG = LoggerFactory.getLogger(HadoopMetrics2Reporter.class); + private static final String EMPTY_STRING = ""; + + public static final MetricsInfo RATE_UNIT_LABEL = + Interns.info("rate_unit", "The unit of measure for rate metrics"); + public static final MetricsInfo DURATION_UNIT_LABEL = + Interns.info("duration_unit", "The unit of measure of duration metrics"); + + /** + * Returns a new {@link Builder} for {@link HadoopMetrics2Reporter}. + * + * @param registry the registry to report + * @return a {@link Builder} instance for a {@link HadoopMetrics2Reporter} + */ + public static Builder forRegistry(MetricRegistry registry) { + return new Builder(registry); + } + + /** + * A builder to create {@link HadoopMetrics2Reporter} instances. + */ + public static class Builder { + private final MetricRegistry registry; + private MetricFilter filter; + private TimeUnit rateUnit; + private TimeUnit durationUnit; + private String recordContext; + + private Builder(MetricRegistry registry) { + this.registry = registry; + this.filter = MetricFilter.ALL; + this.rateUnit = TimeUnit.SECONDS; + this.durationUnit = TimeUnit.MILLISECONDS; + } + + /** + * Convert rates to the given time unit. Defaults to {@link TimeUnit#SECONDS}. + * + * @param rateUnit a unit of time + * @return {@code this} + */ + public Builder convertRatesTo(TimeUnit rateUnit) { + this.rateUnit = Objects.requireNonNull(rateUnit); + return this; + } + + /** + * Convert durations to the given time unit. Defaults to {@link TimeUnit#MILLISECONDS}. + * + * @param durationUnit a unit of time + * @return {@code this} + */ + public Builder convertDurationsTo(TimeUnit durationUnit) { + this.durationUnit = Objects.requireNonNull(durationUnit); + return this; + } + + /** + * Only report metrics which match the given filter. Defaults to {@link MetricFilter#ALL}. + * + * @param filter a {@link MetricFilter} + * @return {@code this} + */ + public Builder filter(MetricFilter filter) { + this.filter = Objects.requireNonNull(filter); + return this; + } + + /** + * A "context" name that will be added as a tag on each emitted metric record. Defaults to + * no "context" attribute on each record. + * + * @param recordContext The "context" tag + * @return {@code this} + */ + public Builder recordContext(String recordContext) { + this.recordContext = Objects.requireNonNull(recordContext); + return this; + } + + /** + * Builds a {@link HadoopMetrics2Reporter} with the given properties, making metrics available + * to the Hadoop Metrics2 framework (any configured {@link MetricsSource}s. + * + * @param metrics2System The Hadoop Metrics2 system instance. + * @param jmxContext The JMX "path", e.g. {@code "MyServer,sub=Requests"}. + * @param description A description these metrics. + * @param recordName A suffix included on each record to identify it. + * + * @return a {@link HadoopMetrics2Reporter} + */ + public HadoopMetrics2Reporter build(MetricsSystem metrics2System, String jmxContext, + String description, String recordName) { + return new HadoopMetrics2Reporter(registry, + rateUnit, + durationUnit, + filter, + metrics2System, + Objects.requireNonNull(jmxContext), + description, + recordName, + recordContext); + } + } + + private final MetricsRegistry metrics2Registry; + private final MetricsSystem metrics2System; + private final String recordName; + private final String context; + + @SuppressWarnings("rawtypes") + private final ConcurrentLinkedQueue> dropwizardGauges; + private final ConcurrentLinkedQueue> dropwizardCounters; + private final ConcurrentLinkedQueue> dropwizardHistograms; + private final ConcurrentLinkedQueue> dropwizardMeters; + private final ConcurrentLinkedQueue> dropwizardTimers; + private HadoopMetrics2Reporter(MetricRegistry registry, TimeUnit rateUnit, TimeUnit durationUnit, + MetricFilter filter, MetricsSystem metrics2System, String jmxContext, String description, + String recordName, String context) { + super(registry, "hadoop-metrics2-reporter", filter, rateUnit, durationUnit); + this.metrics2Registry = new MetricsRegistry(Interns.info(jmxContext, description)); + this.metrics2System = metrics2System; + this.recordName = recordName; + this.context = context; + + this.dropwizardGauges = new ConcurrentLinkedQueue<>(); + this.dropwizardCounters = new ConcurrentLinkedQueue<>(); + this.dropwizardHistograms = new ConcurrentLinkedQueue<>(); + this.dropwizardMeters = new ConcurrentLinkedQueue<>(); + this.dropwizardTimers = new ConcurrentLinkedQueue<>(); + + // Register this source with the Metrics2 system. + // Make sure this is the last thing done as getMetrics() can be called at any time after. + this.metrics2System.register(Objects.requireNonNull(jmxContext), + Objects.requireNonNull(description), this); + } + + @Override public void getMetrics(MetricsCollector collector, boolean all) { + MetricsRecordBuilder builder = collector.addRecord(recordName); + if (null != context) { + builder.setContext(context); + } + + snapshotAllMetrics(builder); + + metrics2Registry.snapshot(builder, all); + } + /** + * Consumes the current metrics collected by dropwizard and adds them to the {@code builder}. + * + * @param builder A record builder + */ + void snapshotAllMetrics(MetricsRecordBuilder builder) { + // Pass through the gauges + @SuppressWarnings("rawtypes") + Iterator> gaugeIterator = dropwizardGauges.iterator(); + while (gaugeIterator.hasNext()) { + @SuppressWarnings("rawtypes") + Entry gauge = gaugeIterator.next(); + final MetricsInfo info = Interns.info(gauge.getKey(), EMPTY_STRING); + final Object o = gauge.getValue().getValue(); + + // Figure out which gauge types metrics2 supports and call the right method + if (o instanceof Integer) { + builder.addGauge(info, (int) o); + } else if (o instanceof Long) { + builder.addGauge(info, (long) o); + } else if (o instanceof Float) { + builder.addGauge(info, (float) o); + } else if (o instanceof Double) { + builder.addGauge(info, (double) o); + } else { + LOG.debug("Ignoring Gauge ({}) with unhandled type: {}", gauge.getKey(), o.getClass()); + } + + gaugeIterator.remove(); + } + + // Pass through the counters + Iterator> counterIterator = dropwizardCounters.iterator(); + while (counterIterator.hasNext()) { + Entry counter = counterIterator.next(); + MetricsInfo info = Interns.info(counter.getKey(), EMPTY_STRING); + LOG.debug("Adding counter {} {}", info, counter.getValue().getCount()); + builder.addCounter(info, counter.getValue().getCount()); + counterIterator.remove(); + } + // Pass through the histograms + Iterator> histogramIterator = dropwizardHistograms.iterator(); + while (histogramIterator.hasNext()) { + final Entry entry = histogramIterator.next(); + final String name = entry.getKey(); + final Histogram histogram = entry.getValue(); + + addSnapshot(builder, name, EMPTY_STRING, histogram.getSnapshot(), histogram.getCount()); + + histogramIterator.remove(); + } + + // Pass through the meter values + Iterator> meterIterator = dropwizardMeters.iterator(); + while (meterIterator.hasNext()) { + final Entry meterEntry = meterIterator.next(); + final String name = meterEntry.getKey(); + final Meter meter = meterEntry.getValue(); + + addMeter(builder, name, EMPTY_STRING, meter.getCount(), meter.getMeanRate(), + meter.getOneMinuteRate(), meter.getFiveMinuteRate(), meter.getFifteenMinuteRate()); + + meterIterator.remove(); + } + + // Pass through the timers (meter + histogram) + Iterator> timerIterator = dropwizardTimers.iterator(); + while (timerIterator.hasNext()) { + final Entry timerEntry = timerIterator.next(); + final String name = timerEntry.getKey(); + final Timer timer = timerEntry.getValue(); + final Snapshot snapshot = timer.getSnapshot(); + + // Add the meter info (mean rate and rate over time windows) + addMeter(builder, name, EMPTY_STRING, timer.getCount(), timer.getMeanRate(), + timer.getOneMinuteRate(), timer.getFiveMinuteRate(), timer.getFifteenMinuteRate()); + + // Count was already added via the meter + addSnapshot(builder, name, EMPTY_STRING, snapshot); + + timerIterator.remove(); + } + + // Add in metadata about what the units the reported metrics are displayed using. + builder.tag(RATE_UNIT_LABEL, getRateUnit()); + builder.tag(DURATION_UNIT_LABEL, getDurationUnit()); + } + /** + * Add Dropwizard-Metrics rate information to a Hadoop-Metrics2 record builder, converting the + * rates to the appropriate unit. + * + * @param builder A Hadoop-Metrics2 record builder. + * @param name A base name for this record. + * @param desc A description for the record. + * @param count The number of measured events. + * @param meanRate The average measured rate. + * @param oneMinuteRate The measured rate over the past minute. + * @param fiveMinuteRate The measured rate over the past five minutes + * @param fifteenMinuteRate The measured rate over the past fifteen minutes. + */ + private void addMeter(MetricsRecordBuilder builder, String name, String desc, long count, + double meanRate, double oneMinuteRate, double fiveMinuteRate, double fifteenMinuteRate) { + builder.addGauge(Interns.info(name + "_count", EMPTY_STRING), count); + builder.addGauge(Interns.info(name + "_mean_rate", EMPTY_STRING), convertRate(meanRate)); + builder.addGauge(Interns.info(name + "_1min_rate", EMPTY_STRING), convertRate(oneMinuteRate)); + builder.addGauge(Interns.info(name + "_5min_rate", EMPTY_STRING), convertRate(fiveMinuteRate)); + builder.addGauge(Interns.info(name + "_15min_rate", EMPTY_STRING), + convertRate(fifteenMinuteRate)); + } + + /** + * Add Dropwizard-Metrics value-distribution data to a Hadoop-Metrics2 record building, converting + * the durations to the appropriate unit. + * + * @param builder A Hadoop-Metrics2 record builder. + * @param name A base name for this record. + * @param desc A description for this record. + * @param snapshot The distribution of measured values. + * @param count The number of values which were measured. + */ + private void addSnapshot(MetricsRecordBuilder builder, String name, String desc, + Snapshot snapshot, long count) { + builder.addGauge(Interns.info(name + "_count", desc), count); + addSnapshot(builder, name, desc, snapshot); + } + + /** + * Add Dropwizard-Metrics value-distribution data to a Hadoop-Metrics2 record building, converting + * the durations to the appropriate unit. + * + * @param builder A Hadoop-Metrics2 record builder. + * @param name A base name for this record. + * @param desc A description for this record. + * @param snapshot The distribution of measured values. + */ + private void addSnapshot(MetricsRecordBuilder builder, String name, String desc, + Snapshot snapshot) { + builder.addGauge(Interns.info(name + "_mean", desc), convertDuration(snapshot.getMean())); + builder.addGauge(Interns.info(name + "_min", desc), convertDuration(snapshot.getMin())); + builder.addGauge(Interns.info(name + "_max", desc), convertDuration(snapshot.getMax())); + builder.addGauge(Interns.info(name + "_median", desc), convertDuration(snapshot.getMedian())); + builder.addGauge(Interns.info(name + "_stddev", desc), convertDuration(snapshot.getStdDev())); + + builder.addGauge(Interns.info(name + "_75thpercentile", desc), + convertDuration(snapshot.get75thPercentile())); + builder.addGauge(Interns.info(name + "_95thpercentile", desc), + convertDuration(snapshot.get95thPercentile())); + builder.addGauge(Interns.info(name + "_98thpercentile", desc), + convertDuration(snapshot.get98thPercentile())); + builder.addGauge(Interns.info(name + "_99thpercentile", desc), + convertDuration(snapshot.get99thPercentile())); + builder.addGauge(Interns.info(name + "_999thpercentile", desc), + convertDuration(snapshot.get999thPercentile())); + } + @SuppressWarnings("rawtypes") + @Override public void report(SortedMap gauges, + SortedMap counters, SortedMap histograms, + SortedMap meters, SortedMap timers) { + for (Entry gauge : gauges.entrySet()) { + dropwizardGauges.add(gauge); + } + + for (Entry counter : counters.entrySet()) { + dropwizardCounters.add(counter); + } + + for (Entry histogram : histograms.entrySet()) { + dropwizardHistograms.add(histogram); + } + + for (Entry meter : meters.entrySet()) { + dropwizardMeters.add(meter); + } + + for (Entry timer : timers.entrySet()) { + dropwizardTimers.add(timer); + } + } + + @Override protected String getRateUnit() { + // Make it "events per rate_unit" to be accurate. + return "events/" + super.getRateUnit(); + } + + @Override protected String getDurationUnit() { + // Make it visible to the tests + return super.getDurationUnit(); + } + + @Override protected double convertDuration(double duration) { + // Make it visible to the tests + return super.convertDuration(duration); + } + + @Override protected double convertRate(double rate) { + // Make it visible to the tests + return super.convertRate(rate); + } + + // Getters visible for testing + + MetricsRegistry getMetrics2Registry() { + return metrics2Registry; + } + + MetricsSystem getMetrics2System() { + return metrics2System; + } + + String getRecordName() { + return recordName; + } + + String getContext() { + return context; + } + + @SuppressWarnings("rawtypes") ConcurrentLinkedQueue> getDropwizardGauges() { + return dropwizardGauges; + } + + ConcurrentLinkedQueue> getDropwizardCounters() { + return dropwizardCounters; + } + + ConcurrentLinkedQueue> getDropwizardHistograms() { + return dropwizardHistograms; + } + + ConcurrentLinkedQueue> getDropwizardMeters() { + return dropwizardMeters; + } + + ConcurrentLinkedQueue> getDropwizardTimers() { + return dropwizardTimers; + } +} diff --git a/common/src/test/org/apache/hive/common/util/HadoopMetrics2ReporterTest.java b/common/src/test/org/apache/hive/common/util/HadoopMetrics2ReporterTest.java new file mode 100644 index 0000000..5c6ac99 --- /dev/null +++ b/common/src/test/org/apache/hive/common/util/HadoopMetrics2ReporterTest.java @@ -0,0 +1,336 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hive.common.util; + +import org.apache.hive.common.util.HadoopMetrics2Reporter; +import org.apache.hive.common.util.HadoopMetrics2Reporter.Builder; + +import com.codahale.metrics.Counter; +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Histogram; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Snapshot; +import com.codahale.metrics.Timer; + +import org.apache.hadoop.metrics2.MetricsCollector; +import org.apache.hadoop.metrics2.MetricsRecordBuilder; +import org.apache.hadoop.metrics2.MetricsSystem; +import org.apache.hadoop.metrics2.lib.Interns; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.AbstractMap.SimpleEntry; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link HadoopMetrics2Reporter}. + * + * Please do not modify, this class is here only temporarily, and is being introduced + * in a Deprecated state for + */ +@Deprecated +public class HadoopMetrics2ReporterTest { + + private MetricRegistry mockRegistry; + private MetricsSystem mockMetricsSystem; + private String recordName = "myserver"; + private HadoopMetrics2Reporter metrics2Reporter; + + @Before public void setup() { + mockRegistry = Mockito.mock(MetricRegistry.class); + mockMetricsSystem = Mockito.mock(MetricsSystem.class); + + recordName = "myserver"; + metrics2Reporter = HadoopMetrics2Reporter.forRegistry(mockRegistry) + .convertDurationsTo(TimeUnit.MILLISECONDS) + .convertRatesTo(TimeUnit.SECONDS) + .build(mockMetricsSystem, "MyServer", "My Cool Server", recordName); + } + + private void verifyRecordBuilderUnits(MetricsRecordBuilder recordBuilder) { + Mockito.verify(recordBuilder).tag(HadoopMetrics2Reporter.RATE_UNIT_LABEL, + metrics2Reporter.getRateUnit()); + Mockito.verify(recordBuilder).tag(HadoopMetrics2Reporter.DURATION_UNIT_LABEL, + metrics2Reporter.getDurationUnit()); + } + + @Test public void testBuilderDefaults() { + Builder builder = HadoopMetrics2Reporter.forRegistry(mockRegistry); + + final String jmxContext = "MyJmxContext;sub=Foo"; + final String desc = "Description"; + final String recordName = "Metrics"; + + HadoopMetrics2Reporter reporter = + builder.build(mockMetricsSystem, jmxContext, desc, recordName); + + assertEquals(mockMetricsSystem, reporter.getMetrics2System()); + // The Context "tag", not the jmx context + assertEquals(null, reporter.getContext()); + assertEquals(recordName, reporter.getRecordName()); + } + + @Test public void testGaugeReporting() { + final AtomicLong gaugeValue = new AtomicLong(0L); + @SuppressWarnings("rawtypes") + final Gauge gauge = new Gauge() { + @Override public Long getValue() { + return gaugeValue.get(); + } + }; + + // Add the metrics objects to the internal "queues" by hand + metrics2Reporter.getDropwizardGauges().add(new SimpleEntry<>("my_gauge", gauge)); + + // Set some values + gaugeValue.set(5L); + + MetricsCollector collector = Mockito.mock(MetricsCollector.class); + MetricsRecordBuilder recordBuilder = Mockito.mock(MetricsRecordBuilder.class); + + Mockito.when(collector.addRecord(recordName)).thenReturn(recordBuilder); + + // Make sure a value of 5 gets reported + metrics2Reporter.getMetrics(collector, true); + + Mockito.verify(recordBuilder).addGauge(Interns.info("my_gauge", ""), gaugeValue.get()); + verifyRecordBuilderUnits(recordBuilder); + } + + @Test public void testCounterReporting() { + final Counter counter = new Counter(); + + // Add the metrics objects to the internal "queues" by hand + metrics2Reporter.getDropwizardCounters().add(new SimpleEntry<>("my_counter", counter)); + + // Set some values + counter.inc(5L); + + MetricsCollector collector = Mockito.mock(MetricsCollector.class); + MetricsRecordBuilder recordBuilder = Mockito.mock(MetricsRecordBuilder.class); + + Mockito.when(collector.addRecord(recordName)).thenReturn(recordBuilder); + + metrics2Reporter.getMetrics(collector, true); + + Mockito.verify(recordBuilder).addCounter(Interns.info("my_counter", ""), 5L); + verifyRecordBuilderUnits(recordBuilder); + } + + @Test public void testHistogramReporting() { + final String metricName = "my_histogram"; + final Histogram histogram = Mockito.mock(Histogram.class); + final Snapshot snapshot = Mockito.mock(Snapshot.class); + + long count = 10L; + double percentile75 = 75; + double percentile95 = 95; + double percentile98 = 98; + double percentile99 = 99; + double percentile999 = 999; + double median = 50; + double mean = 60; + long min = 1L; + long max = 100L; + double stddev = 10; + + Mockito.when(snapshot.get75thPercentile()).thenReturn(percentile75); + Mockito.when(snapshot.get95thPercentile()).thenReturn(percentile95); + Mockito.when(snapshot.get98thPercentile()).thenReturn(percentile98); + Mockito.when(snapshot.get99thPercentile()).thenReturn(percentile99); + Mockito.when(snapshot.get999thPercentile()).thenReturn(percentile999); + Mockito.when(snapshot.getMedian()).thenReturn(median); + Mockito.when(snapshot.getMean()).thenReturn(mean); + Mockito.when(snapshot.getMin()).thenReturn(min); + Mockito.when(snapshot.getMax()).thenReturn(max); + Mockito.when(snapshot.getStdDev()).thenReturn(stddev); + + Mockito.when(histogram.getCount()).thenReturn(count); + Mockito.when(histogram.getSnapshot()).thenReturn(snapshot); + + MetricsCollector collector = Mockito.mock(MetricsCollector.class); + MetricsRecordBuilder recordBuilder = Mockito.mock(MetricsRecordBuilder.class); + + Mockito.when(collector.addRecord(recordName)).thenReturn(recordBuilder); + + // Add the metrics objects to the internal "queues" by hand + metrics2Reporter.getDropwizardHistograms().add(new SimpleEntry<>(metricName, histogram)); + + metrics2Reporter.getMetrics(collector, true); + + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_max", ""), + metrics2Reporter.convertDuration(max)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_min", ""), + metrics2Reporter.convertDuration(min)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_median", ""), + metrics2Reporter.convertDuration(median)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_count", ""), count); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_stddev", ""), + metrics2Reporter.convertDuration(stddev)); + + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_75thpercentile", ""), + metrics2Reporter.convertDuration(percentile75)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_95thpercentile", ""), + metrics2Reporter.convertDuration(percentile95)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_98thpercentile", ""), + metrics2Reporter.convertDuration(percentile98)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_99thpercentile", ""), + metrics2Reporter.convertDuration(percentile99)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_999thpercentile", ""), + metrics2Reporter.convertDuration(percentile999)); + + verifyRecordBuilderUnits(recordBuilder); + } + + @Test public void testTimerReporting() { + final String metricName = "my_timer"; + final Timer timer = Mockito.mock(Timer.class); + final Snapshot snapshot = Mockito.mock(Snapshot.class); + + // Add the metrics objects to the internal "queues" by hand + metrics2Reporter.getDropwizardTimers().add(new SimpleEntry<>(metricName, timer)); + + long count = 10L; + double meanRate = 1.0; + double oneMinRate = 2.0; + double fiveMinRate = 5.0; + double fifteenMinRate = 10.0; + + Mockito.when(timer.getCount()).thenReturn(count); + Mockito.when(timer.getMeanRate()).thenReturn(meanRate); + Mockito.when(timer.getOneMinuteRate()).thenReturn(oneMinRate); + Mockito.when(timer.getFiveMinuteRate()).thenReturn(fiveMinRate); + Mockito.when(timer.getFifteenMinuteRate()).thenReturn(fifteenMinRate); + Mockito.when(timer.getSnapshot()).thenReturn(snapshot); + + double percentile75 = 75; + double percentile95 = 95; + double percentile98 = 98; + double percentile99 = 99; + double percentile999 = 999; + double median = 50; + double mean = 60; + long min = 1L; + long max = 100L; + double stddev = 10; + + Mockito.when(snapshot.get75thPercentile()).thenReturn(percentile75); + Mockito.when(snapshot.get95thPercentile()).thenReturn(percentile95); + Mockito.when(snapshot.get98thPercentile()).thenReturn(percentile98); + Mockito.when(snapshot.get99thPercentile()).thenReturn(percentile99); + Mockito.when(snapshot.get999thPercentile()).thenReturn(percentile999); + Mockito.when(snapshot.getMedian()).thenReturn(median); + Mockito.when(snapshot.getMean()).thenReturn(mean); + Mockito.when(snapshot.getMin()).thenReturn(min); + Mockito.when(snapshot.getMax()).thenReturn(max); + Mockito.when(snapshot.getStdDev()).thenReturn(stddev); + + MetricsCollector collector = Mockito.mock(MetricsCollector.class); + MetricsRecordBuilder recordBuilder = Mockito.mock(MetricsRecordBuilder.class); + + Mockito.when(collector.addRecord(recordName)).thenReturn(recordBuilder); + + metrics2Reporter.getMetrics(collector, true); + + // We get the count from the meter and histogram + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_count", ""), count); + + // Verify the rates + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_mean_rate", ""), + metrics2Reporter.convertRate(meanRate)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_1min_rate", ""), + metrics2Reporter.convertRate(oneMinRate)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_5min_rate", ""), + metrics2Reporter.convertRate(fiveMinRate)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_15min_rate", ""), + metrics2Reporter.convertRate(fifteenMinRate)); + + // Verify the histogram + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_max", ""), + metrics2Reporter.convertDuration(max)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_min", ""), + metrics2Reporter.convertDuration(min)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_median", ""), + metrics2Reporter.convertDuration(median)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_stddev", ""), + metrics2Reporter.convertDuration(stddev)); + + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_75thpercentile", ""), + metrics2Reporter.convertDuration(percentile75)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_95thpercentile", ""), + metrics2Reporter.convertDuration(percentile95)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_98thpercentile", ""), + metrics2Reporter.convertDuration(percentile98)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_99thpercentile", ""), + metrics2Reporter.convertDuration(percentile99)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_999thpercentile", ""), + metrics2Reporter.convertDuration(percentile999)); + + verifyRecordBuilderUnits(recordBuilder); + } + + @Test public void testMeterReporting() { + final String metricName = "my_meter"; + final Meter meter = Mockito.mock(Meter.class); + + // Add the metrics objects to the internal "queues" by hand + metrics2Reporter.getDropwizardMeters().add(new SimpleEntry<>(metricName, meter)); + + // Set some values + long count = 10L; + double meanRate = 1.0; + double oneMinRate = 2.0; + double fiveMinRate = 5.0; + double fifteenMinRate = 10.0; + + Mockito.when(meter.getCount()).thenReturn(count); + Mockito.when(meter.getMeanRate()).thenReturn(meanRate); + Mockito.when(meter.getOneMinuteRate()).thenReturn(oneMinRate); + Mockito.when(meter.getFiveMinuteRate()).thenReturn(fiveMinRate); + Mockito.when(meter.getFifteenMinuteRate()).thenReturn(fifteenMinRate); + + MetricsCollector collector = Mockito.mock(MetricsCollector.class); + MetricsRecordBuilder recordBuilder = Mockito.mock(MetricsRecordBuilder.class); + + Mockito.when(collector.addRecord(recordName)).thenReturn(recordBuilder); + + metrics2Reporter.getMetrics(collector, true); + + // Verify the rates + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_count", ""), count); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_mean_rate", ""), + metrics2Reporter.convertRate(meanRate)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_1min_rate", ""), + metrics2Reporter.convertRate(oneMinRate)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_5min_rate", ""), + metrics2Reporter.convertRate(fiveMinRate)); + Mockito.verify(recordBuilder).addGauge(Interns.info(metricName + "_15min_rate", ""), + metrics2Reporter.convertRate(fifteenMinRate)); + + // Verify the units + verifyRecordBuilderUnits(recordBuilder); + } +}