package io.github.azagniotov.metrics.reporter.cloudwatch; import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient; import software.amazon.awssdk.services.cloudwatch.model.Dimension; import software.amazon.awssdk.services.cloudwatch.model.MetricDatum; import software.amazon.awssdk.services.cloudwatch.model.PutMetricDataRequest; import software.amazon.awssdk.services.cloudwatch.model.PutMetricDataResponse; import software.amazon.awssdk.services.cloudwatch.model.StandardUnit; import com.codahale.metrics.EWMA; import com.codahale.metrics.Gauge; import com.codahale.metrics.Histogram; import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.SlidingWindowReservoir; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static com.google.common.truth.Truth.assertThat; import static io.github.azagniotov.metrics.reporter.cloudwatch.CloudWatchReporter.DIMENSION_COUNT; import static io.github.azagniotov.metrics.reporter.cloudwatch.CloudWatchReporter.DIMENSION_GAUGE; import static io.github.azagniotov.metrics.reporter.cloudwatch.CloudWatchReporter.DIMENSION_NAME_TYPE; import static io.github.azagniotov.metrics.reporter.cloudwatch.CloudWatchReporter.DIMENSION_SNAPSHOT_MEAN; import static io.github.azagniotov.metrics.reporter.cloudwatch.CloudWatchReporter.DIMENSION_SNAPSHOT_STD_DEV; import static io.github.azagniotov.metrics.reporter.cloudwatch.CloudWatchReporter.DIMENSION_SNAPSHOT_SUMMARY; import static org.mockito.Matchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class CloudWatchReporterTest { private static final String NAMESPACE = "namespace"; private static final String ARBITRARY_COUNTER_NAME = "TheCounter"; private static final String ARBITRARY_METER_NAME = "TheMeter"; private static final String ARBITRARY_HISTOGRAM_NAME = "TheHistogram"; private static final String ARBITRARY_TIMER_NAME = "TheTimer"; private static final String ARBITRARY_GAUGE_NAME = "TheGauge"; @Mock private CloudWatchAsyncClient mockAmazonCloudWatchAsyncClient; @Mock private CompletableFuture<PutMetricDataResponse> mockPutMetricDataResultFuture; @Captor private ArgumentCaptor<PutMetricDataRequest> metricDataRequestCaptor; private MetricRegistry metricRegistry; private CloudWatchReporter.Builder reporterBuilder; @BeforeClass public static void beforeClass() throws Exception { reduceMeterDefaultTickInterval(); } @Before public void setUp() throws Exception { metricRegistry = new MetricRegistry(); reporterBuilder = CloudWatchReporter.forRegistry(metricRegistry, mockAmazonCloudWatchAsyncClient, NAMESPACE); when(mockAmazonCloudWatchAsyncClient.putMetricData(metricDataRequestCaptor.capture())).thenReturn(mockPutMetricDataResultFuture); } @Test public void shouldNotInvokeCloudWatchClientInDryRunMode() throws Exception { metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); reporterBuilder.withDryRun().build().report(); verify(mockAmazonCloudWatchAsyncClient, never()).putMetricData(any(PutMetricDataRequest.class)); } @Test public void notSettingHighResolutionGeneratesMetricsWithStorageResolutionSetToSixty() throws Exception { metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); reporterBuilder.build().report(); final MetricDatum firstMetricDatum = firstMetricDatumFromCapturedRequest(); assertThat(firstMetricDatum.storageResolution()).isEqualTo(60); } @Test public void settingHighResolutionGeneratesMetricsWithStorageResolutionSetToOne() throws Exception { metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); reporterBuilder.withHighResolution().build().report(); final MetricDatum firstMetricDatum = firstMetricDatumFromCapturedRequest(); assertThat(firstMetricDatum.storageResolution()).isEqualTo(1); } @Test public void shouldReportWithoutGlobalDimensionsWhenGlobalDimensionsNotConfigured() throws Exception { metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); reporterBuilder.build().report(); // When 'withGlobalDimensions' was not called final List<Dimension> dimensions = firstMetricDatumDimensionsFromCapturedRequest(); assertThat(dimensions).hasSize(1); assertThat(dimensions).contains(Dimension.builder().name(DIMENSION_NAME_TYPE).value(DIMENSION_COUNT).build()); } @Test public void reportedCounterShouldContainExpectedDimension() throws Exception { metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); reporterBuilder.build().report(); final List<Dimension> dimensions = firstMetricDatumDimensionsFromCapturedRequest(); assertThat(dimensions).contains(Dimension.builder().name(DIMENSION_NAME_TYPE).value(DIMENSION_COUNT).build()); } @Test public void reportedGaugesAreInvokedOnlyOnce() { Gauge<Long> theGauge = spy(new Gauge<Long>() { @Override public Long getValue() { return 1L; } }); metricRegistry.register(ARBITRARY_GAUGE_NAME, theGauge); reporterBuilder.build().report(); verify(theGauge).getValue(); } @Test public void reportedGaugeShouldContainExpectedDimension() throws Exception { metricRegistry.register(ARBITRARY_GAUGE_NAME, (Gauge<Long>) () -> 1L); reporterBuilder.build().report(); final List<Dimension> dimensions = firstMetricDatumDimensionsFromCapturedRequest(); assertThat(dimensions).contains(Dimension.builder().name(DIMENSION_NAME_TYPE).value(DIMENSION_GAUGE).build()); } @Test public void shouldNotReportGaugeWhenMetricValueNotOfTypeNumber() throws Exception { metricRegistry.register(ARBITRARY_GAUGE_NAME, (Gauge<String>) () -> "bad value type"); reporterBuilder.build().report(); verify(mockAmazonCloudWatchAsyncClient, never()).putMetricData(any(PutMetricDataRequest.class)); } @Test public void neverReportMetersCountersGaugesWithZeroValues() throws Exception { metricRegistry.register(ARBITRARY_GAUGE_NAME, (Gauge<Long>) () -> 0L); metricRegistry.meter(ARBITRARY_METER_NAME).mark(0); metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(0); buildReportWithSleep(reporterBuilder .withArithmeticMean() .withOneMinuteMeanRate() .withFiveMinuteMeanRate() .withFifteenMinuteMeanRate() .withMeanRate()); verify(mockAmazonCloudWatchAsyncClient, never()).putMetricData(any(PutMetricDataRequest.class)); } @Test public void reportMetersCountersGaugesWithZeroValuesOnlyWhenConfigured() throws Exception { metricRegistry.register(ARBITRARY_GAUGE_NAME, (Gauge<Long>) () -> 0L); metricRegistry.meter(ARBITRARY_METER_NAME).mark(0); metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(0); metricRegistry.timer(ARBITRARY_TIMER_NAME).update(-1L, TimeUnit.NANOSECONDS); buildReportWithSleep(reporterBuilder .withArithmeticMean() .withOneMinuteMeanRate() .withFiveMinuteMeanRate() .withFifteenMinuteMeanRate() .withZeroValuesSubmission() .withMeanRate()); verify(mockAmazonCloudWatchAsyncClient, times(1)).putMetricData(metricDataRequestCaptor.capture()); final PutMetricDataRequest putMetricDataRequest = metricDataRequestCaptor.getValue(); final List<MetricDatum> metricData = putMetricDataRequest.metricData(); for (final MetricDatum metricDatum : metricData) { assertThat(metricDatum.value()).isEqualTo(0.0); } } @Test public void reportedMeterShouldContainExpectedOneMinuteMeanRateDimension() throws Exception { metricRegistry.meter(ARBITRARY_METER_NAME).mark(1); buildReportWithSleep(reporterBuilder.withOneMinuteMeanRate()); final List<Dimension> dimensions = allDimensionsFromCapturedRequest(); assertThat(dimensions).contains(Dimension.builder().name(DIMENSION_NAME_TYPE).value("1-min-mean-rate [per-second]").build()); } @Test public void reportedMeterShouldContainExpectedFiveMinuteMeanRateDimension() throws Exception { metricRegistry.meter(ARBITRARY_METER_NAME).mark(1); buildReportWithSleep(reporterBuilder.withFiveMinuteMeanRate()); final List<Dimension> dimensions = allDimensionsFromCapturedRequest(); assertThat(dimensions).contains(Dimension.builder().name(DIMENSION_NAME_TYPE).value("5-min-mean-rate [per-second]").build()); } @Test public void reportedMeterShouldContainExpectedFifteenMinuteMeanRateDimension() throws Exception { metricRegistry.meter(ARBITRARY_METER_NAME).mark(1); buildReportWithSleep(reporterBuilder.withFifteenMinuteMeanRate()); final List<Dimension> dimensions = allDimensionsFromCapturedRequest(); assertThat(dimensions).contains(Dimension.builder().name(DIMENSION_NAME_TYPE).value("15-min-mean-rate [per-second]").build()); } @Test public void reportedMeterShouldContainExpectedMeanRateDimension() throws Exception { metricRegistry.meter(ARBITRARY_METER_NAME).mark(1); reporterBuilder.withMeanRate().build().report(); final List<Dimension> dimensions = allDimensionsFromCapturedRequest(); assertThat(dimensions).contains(Dimension.builder().name(DIMENSION_NAME_TYPE).value("mean-rate [per-second]").build()); } @Test public void reportedMeterShouldHaveChangedUnit() throws Exception { metricRegistry.meter(ARBITRARY_METER_NAME).mark(1); CloudWatchReporter.Builder builder = reporterBuilder.withMeanRate().withMeterUnitSentToCW(StandardUnit.TERABYTES); CloudWatchReporter cloudWatchReporter = builder.build(); cloudWatchReporter.report(); final List<MetricDatum> metricData = allMetricDataFromCapturedRequests(); List<MetricDatum> filtered = metricData.stream().filter(x -> !x.unit().equals(StandardUnit.COUNT)).collect(Collectors.toList()); assertThat(filtered.size()).isEqualTo(1); assertThat(filtered.get(0).unit()).isEqualTo(StandardUnit.TERABYTES); } @Test public void reportedHistogramShouldContainExpectedArithmeticMeanDimension() throws Exception { metricRegistry.histogram(ARBITRARY_HISTOGRAM_NAME).update(1); reporterBuilder.withArithmeticMean().build().report(); final List<Dimension> dimensions = allDimensionsFromCapturedRequest(); assertThat(dimensions).contains(Dimension.builder().name(DIMENSION_NAME_TYPE).value(DIMENSION_SNAPSHOT_MEAN).build()); } @Test public void reportedHistogramShouldContainExpectedStdDevDimension() throws Exception { metricRegistry.histogram(CloudWatchReporterTest.ARBITRARY_HISTOGRAM_NAME).update(1); metricRegistry.histogram(CloudWatchReporterTest.ARBITRARY_HISTOGRAM_NAME).update(2); reporterBuilder.withStdDev().build().report(); final List<Dimension> dimensions = allDimensionsFromCapturedRequest(); assertThat(dimensions).contains(Dimension.builder().name(DIMENSION_NAME_TYPE).value(DIMENSION_SNAPSHOT_STD_DEV).build()); } @Test public void reportedTimerShouldContainExpectedArithmeticMeanDimension() throws Exception { metricRegistry.timer(ARBITRARY_TIMER_NAME).update(3, TimeUnit.MILLISECONDS); reporterBuilder.withArithmeticMean().build().report(); final List<Dimension> dimensions = allDimensionsFromCapturedRequest(); assertThat(dimensions).contains(Dimension.builder().name(DIMENSION_NAME_TYPE).value("snapshot-mean [in-milliseconds]").build()); } @Test public void reportedTimerShouldContainExpectedStdDevDimension() throws Exception { metricRegistry.timer(ARBITRARY_TIMER_NAME).update(1, TimeUnit.MILLISECONDS); metricRegistry.timer(ARBITRARY_TIMER_NAME).update(3, TimeUnit.MILLISECONDS); reporterBuilder.withStdDev().build().report(); final List<Dimension> dimensions = allDimensionsFromCapturedRequest(); assertThat(dimensions).contains(Dimension.builder().name(DIMENSION_NAME_TYPE).value("snapshot-std-dev [in-milliseconds]").build()); } @Test public void shouldReportExpectedSingleGlobalDimension() throws Exception { metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); reporterBuilder.withGlobalDimensions("Region=us-west-2").build().report(); final List<Dimension> dimensions = firstMetricDatumDimensionsFromCapturedRequest(); assertThat(dimensions).contains(Dimension.builder().name("Region").value("us-west-2").build()); } @Test public void shouldReportExpectedGlobalAndCustomDimensions() throws Exception { metricRegistry.counter(DimensionedName.withName(ARBITRARY_COUNTER_NAME) .withDimension("key1", "value1") .withDimension("key2", "value2") .build().encode()).inc(); reporterBuilder.withGlobalDimensions("Region=us-west-2").build().report(); final List<Dimension> dimensions = firstMetricDatumDimensionsFromCapturedRequest(); assertThat(dimensions).contains(Dimension.builder().name("Region").value("us-west-2").build()); assertThat(dimensions).contains(Dimension.builder().name("key1").value("value1").build()); assertThat(dimensions).contains(Dimension.builder().name("key2").value("value2").build()); } @Test public void shouldReportExpectedMultipleGlobalDimensions() throws Exception { metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); reporterBuilder.withGlobalDimensions("Region=us-west-2", "Instance=stage").build().report(); final List<Dimension> dimensions = firstMetricDatumDimensionsFromCapturedRequest(); assertThat(dimensions).contains(Dimension.builder().name("Region").value("us-west-2").build()); assertThat(dimensions).contains(Dimension.builder().name("Instance").value("stage").build()); } @Test public void shouldNotReportDuplicateGlobalDimensions() throws Exception { metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); reporterBuilder.withGlobalDimensions("Region=us-west-2", "Region=us-west-2").build().report(); final List<Dimension> dimensions = firstMetricDatumDimensionsFromCapturedRequest(); assertThat(dimensions).containsNoDuplicates(); } @Test public void shouldReportExpectedCounterValue() throws Exception { metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); reporterBuilder.build().report(); final MetricDatum metricDatum = firstMetricDatumFromCapturedRequest(); assertThat(metricDatum.value()).isWithin(1.0); assertThat(metricDatum.unit()).isEqualTo(StandardUnit.COUNT); } @Test public void shouldNotReportUnchangedCounterValue() throws Exception { metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); final CloudWatchReporter cloudWatchReporter = reporterBuilder.build(); cloudWatchReporter.report(); MetricDatum metricDatum = firstMetricDatumFromCapturedRequest(); assertThat(metricDatum.value().intValue()).isEqualTo(1); metricDataRequestCaptor.getAllValues().clear(); cloudWatchReporter.report(); verify(mockAmazonCloudWatchAsyncClient, times(1)).putMetricData(any(PutMetricDataRequest.class)); } @Test public void shouldReportCounterValueDelta() throws Exception { metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); final CloudWatchReporter cloudWatchReporter = reporterBuilder.build(); cloudWatchReporter.report(); MetricDatum metricDatum = firstMetricDatumFromCapturedRequest(); assertThat(metricDatum.value().intValue()).isEqualTo(2); metricDataRequestCaptor.getAllValues().clear(); metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); cloudWatchReporter.report(); metricDatum = firstMetricDatumFromCapturedRequest(); assertThat(metricDatum.value().intValue()).isEqualTo(6); verify(mockAmazonCloudWatchAsyncClient, times(2)).putMetricData(any(PutMetricDataRequest.class)); } @Test public void shouldReportArithmeticMeanAfterConversionByDefaultDurationWhenReportingTimer() throws Exception { metricRegistry.timer(ARBITRARY_TIMER_NAME).update(1_000_000, TimeUnit.NANOSECONDS); reporterBuilder.withArithmeticMean().build().report(); final MetricDatum metricData = metricDatumByDimensionFromCapturedRequest("snapshot-mean [in-milliseconds]"); assertThat(metricData.value().intValue()).isEqualTo(1); assertThat(metricData.unit()).isEqualTo(StandardUnit.MILLISECONDS); } @Test public void shouldReportStdDevAfterConversionByDefaultDurationWhenReportingTimer() throws Exception { metricRegistry.timer(ARBITRARY_TIMER_NAME).update(1_000_000, TimeUnit.NANOSECONDS); metricRegistry.timer(ARBITRARY_TIMER_NAME).update(2_000_000, TimeUnit.NANOSECONDS); metricRegistry.timer(ARBITRARY_TIMER_NAME).update(3_000_000, TimeUnit.NANOSECONDS); metricRegistry.timer(ARBITRARY_TIMER_NAME).update(30_000_000, TimeUnit.NANOSECONDS); reporterBuilder.withStdDev().build().report(); final MetricDatum metricData = metricDatumByDimensionFromCapturedRequest("snapshot-std-dev [in-milliseconds]"); assertThat(metricData.value().intValue()).isEqualTo(12); assertThat(metricData.unit()).isEqualTo(StandardUnit.MILLISECONDS); } @Test public void shouldReportSnapshotValuesAfterConversionByCustomDurationWhenReportingTimer() throws Exception { metricRegistry.timer(ARBITRARY_TIMER_NAME).update(1, TimeUnit.SECONDS); metricRegistry.timer(ARBITRARY_TIMER_NAME).update(2, TimeUnit.SECONDS); metricRegistry.timer(ARBITRARY_TIMER_NAME).update(3, TimeUnit.SECONDS); metricRegistry.timer(ARBITRARY_TIMER_NAME).update(30, TimeUnit.SECONDS); reporterBuilder.withStatisticSet().convertDurationsTo(TimeUnit.MICROSECONDS).build().report(); final MetricDatum metricData = metricDatumByDimensionFromCapturedRequest(DIMENSION_SNAPSHOT_SUMMARY); assertThat(metricData.statisticValues().sum().intValue()).isEqualTo(36_000_000); assertThat(metricData.statisticValues().maximum().intValue()).isEqualTo(30_000_000); assertThat(metricData.statisticValues().minimum().intValue()).isEqualTo(1_000_000); assertThat(metricData.statisticValues().sampleCount().intValue()).isEqualTo(4); assertThat(metricData.unit()).isEqualTo(StandardUnit.MICROSECONDS); } @Test public void shouldReportArithmeticMeanWithoutConversionWhenReportingHistogram() throws Exception { metricRegistry.histogram(CloudWatchReporterTest.ARBITRARY_HISTOGRAM_NAME).update(1); reporterBuilder.withArithmeticMean().build().report(); final MetricDatum metricData = metricDatumByDimensionFromCapturedRequest(DIMENSION_SNAPSHOT_MEAN); assertThat(metricData.value().intValue()).isEqualTo(1); assertThat(metricData.unit()).isEqualTo(StandardUnit.NONE); } @Test public void shouldReportStdDevWithoutConversionWhenReportingHistogram() throws Exception { metricRegistry.histogram(CloudWatchReporterTest.ARBITRARY_HISTOGRAM_NAME).update(1); metricRegistry.histogram(CloudWatchReporterTest.ARBITRARY_HISTOGRAM_NAME).update(2); metricRegistry.histogram(CloudWatchReporterTest.ARBITRARY_HISTOGRAM_NAME).update(3); metricRegistry.histogram(CloudWatchReporterTest.ARBITRARY_HISTOGRAM_NAME).update(30); reporterBuilder.withStdDev().build().report(); final MetricDatum metricData = metricDatumByDimensionFromCapturedRequest(DIMENSION_SNAPSHOT_STD_DEV); assertThat(metricData.value().intValue()).isEqualTo(12); assertThat(metricData.unit()).isEqualTo(StandardUnit.NONE); } @Test public void shouldReportSnapshotValuesWithoutConversionWhenReportingHistogram() throws Exception { metricRegistry.histogram(CloudWatchReporterTest.ARBITRARY_HISTOGRAM_NAME).update(1); metricRegistry.histogram(CloudWatchReporterTest.ARBITRARY_HISTOGRAM_NAME).update(2); metricRegistry.histogram(CloudWatchReporterTest.ARBITRARY_HISTOGRAM_NAME).update(3); metricRegistry.histogram(CloudWatchReporterTest.ARBITRARY_HISTOGRAM_NAME).update(30); reporterBuilder.withStatisticSet().build().report(); final MetricDatum metricData = metricDatumByDimensionFromCapturedRequest(DIMENSION_SNAPSHOT_SUMMARY); assertThat(metricData.statisticValues().sum().intValue()).isEqualTo(36); assertThat(metricData.statisticValues().maximum().intValue()).isEqualTo(30); assertThat(metricData.statisticValues().minimum().intValue()).isEqualTo(1); assertThat(metricData.statisticValues().sampleCount().intValue()).isEqualTo(4); assertThat(metricData.unit()).isEqualTo(StandardUnit.NONE); } @Test public void shouldReportHistogramSubsequentSnapshotValues_SumMaxMinValues() throws Exception { CloudWatchReporter reporter = reporterBuilder.withStatisticSet().build(); final Histogram slidingWindowHistogram = new Histogram(new SlidingWindowReservoir(4)); metricRegistry.register("SlidingWindowHistogram", slidingWindowHistogram); slidingWindowHistogram.update(1); slidingWindowHistogram.update(2); slidingWindowHistogram.update(30); reporter.report(); final MetricDatum metricData = metricDatumByDimensionFromCapturedRequest(DIMENSION_SNAPSHOT_SUMMARY); assertThat(metricData.statisticValues().maximum().intValue()).isEqualTo(30); assertThat(metricData.statisticValues().minimum().intValue()).isEqualTo(1); assertThat(metricData.statisticValues().sampleCount().intValue()).isEqualTo(3); assertThat(metricData.statisticValues().sum().intValue()).isEqualTo(33); assertThat(metricData.unit()).isEqualTo(StandardUnit.NONE); slidingWindowHistogram.update(4); slidingWindowHistogram.update(100); slidingWindowHistogram.update(5); slidingWindowHistogram.update(6); reporter.report(); final MetricDatum secondMetricData = metricDatumByDimensionFromCapturedRequest(DIMENSION_SNAPSHOT_SUMMARY); assertThat(secondMetricData.statisticValues().maximum().intValue()).isEqualTo(100); assertThat(secondMetricData.statisticValues().minimum().intValue()).isEqualTo(4); assertThat(secondMetricData.statisticValues().sampleCount().intValue()).isEqualTo(4); assertThat(secondMetricData.statisticValues().sum().intValue()).isEqualTo(115); assertThat(secondMetricData.unit()).isEqualTo(StandardUnit.NONE); } @Test public void shouldNotReportCounterValueDeltaWhenReportingRawCountValue() throws Exception { metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); final CloudWatchReporter cloudWatchReporter = reporterBuilder.withReportRawCountValue().build(); cloudWatchReporter.report(); MetricDatum metricDatum = firstMetricDatumFromCapturedRequest(); assertThat(metricDatum.value().intValue()).isEqualTo(2); metricDataRequestCaptor.getAllValues().clear(); metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); metricRegistry.counter(ARBITRARY_COUNTER_NAME).inc(); cloudWatchReporter.report(); metricDatum = firstMetricDatumFromCapturedRequest(); assertThat(metricDatum.value().intValue()).isEqualTo(8); verify(mockAmazonCloudWatchAsyncClient, times(2)).putMetricData(any(PutMetricDataRequest.class)); } private MetricDatum metricDatumByDimensionFromCapturedRequest(final String dimensionValue) { final PutMetricDataRequest putMetricDataRequest = metricDataRequestCaptor.getValue(); final List<MetricDatum> metricData = putMetricDataRequest.metricData(); final Optional<MetricDatum> metricDatumOptional = metricData .stream() .filter(metricDatum -> metricDatum.dimensions() .contains(Dimension.builder().name(DIMENSION_NAME_TYPE).value(dimensionValue).build())) .findFirst(); if (metricDatumOptional.isPresent()) { return metricDatumOptional.get(); } throw new IllegalStateException("Could not find MetricDatum for Dimension value: " + dimensionValue); } private MetricDatum firstMetricDatumFromCapturedRequest() { final PutMetricDataRequest putMetricDataRequest = metricDataRequestCaptor.getValue(); return putMetricDataRequest.metricData().get(0); } private List<MetricDatum> allMetricDataFromCapturedRequests() { final PutMetricDataRequest putMetricDataRequest = metricDataRequestCaptor.getValue(); return putMetricDataRequest.metricData(); } private List<Dimension> firstMetricDatumDimensionsFromCapturedRequest() { final PutMetricDataRequest putMetricDataRequest = metricDataRequestCaptor.getValue(); final MetricDatum metricDatum = putMetricDataRequest.metricData().get(0); return metricDatum.dimensions(); } private List<Dimension> allDimensionsFromCapturedRequest() { final PutMetricDataRequest putMetricDataRequest = metricDataRequestCaptor.getValue(); final List<MetricDatum> metricData = putMetricDataRequest.metricData(); final List<Dimension> all = new LinkedList<>(); for (final MetricDatum metricDatum : metricData) { all.addAll(metricDatum.dimensions()); } return all; } private void buildReportWithSleep(final CloudWatchReporter.Builder cloudWatchReporterBuilder) throws InterruptedException { final CloudWatchReporter cloudWatchReporter = cloudWatchReporterBuilder.build(); Thread.sleep(10); cloudWatchReporter.report(); } /** * This is a very ugly way to fool the {@link EWMA} by reducing the default tick interval * in {@link Meter} from {@code 5} seconds to {@code 1} millisecond in order to ensure that * exponentially-weighted moving average rates are populated. This helps to verify that all * the expected {@link Dimension}s are present in {@link MetricDatum}. * * @throws NoSuchFieldException * @throws IllegalAccessException * @see Meter#tickIfNecessary() * @see MetricDatum#getDimensions() */ private static void reduceMeterDefaultTickInterval() throws NoSuchFieldException, IllegalAccessException { setFinalStaticField(Meter.class, "TICK_INTERVAL", TimeUnit.MILLISECONDS.toNanos(1)); } private static void setFinalStaticField(final Class clazz, final String fieldName, long value) throws NoSuchFieldException, IllegalAccessException { final Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); final Field modifiers = field.getClass().getDeclaredField("modifiers"); modifiers.setAccessible(true); modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(null, value); } }