package com.nike.riposte.metrics.codahale; import com.nike.internal.util.Pair; import com.nike.riposte.metrics.codahale.contrib.SignalFxReporterFactory; import com.nike.riposte.metrics.codahale.impl.SignalFxEndpointMetricsHandler.RollingWindowHistogramBuilder; import com.nike.riposte.metrics.codahale.impl.SignalFxEndpointMetricsHandler.RollingWindowTimerBuilder; import com.codahale.metrics.Counter; import com.codahale.metrics.Gauge; import com.codahale.metrics.Histogram; import com.codahale.metrics.Meter; import com.codahale.metrics.Metric; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; import com.signalfx.codahale.metrics.MetricBuilder; import com.signalfx.codahale.reporter.MetricMetadata; import com.signalfx.codahale.reporter.MetricMetadata.BuilderTagger; import com.signalfx.codahale.reporter.MetricMetadata.Tagger; import com.signalfx.codahale.reporter.SignalFxReporter; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import com.nike.riposte.testutils.Whitebox; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.catchThrowable; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; /** * Tests the functionality of {@link SignalFxAwareCodahaleMetricsCollector}. * * @author Nic Munroe */ @RunWith(DataProviderRunner.class) public class SignalFxAwareCodahaleMetricsCollectorTest { private MetricRegistry metricRegistryMock; private SignalFxReporterFactory sfxReporterFactoryMock; private SignalFxReporter sfxReporterMock; private MetricMetadata metricMetadataMock; private MetricBuilder<Timer> timerBuilderMock; private MetricBuilder<Histogram> histogramBuilderMock; private MetricBuilder<Metric> genericMetricBuilderMock; private Timer timerMock; private Meter meterMock; private Histogram histogramMock; private Counter counterMock; private Gauge<String> gaugeMock; private Metric genericMetricMock; private BuilderTagger<Timer> timerTaggerMock; private BuilderTagger<Meter> meterTaggerMock; private BuilderTagger<Histogram> histogramTaggerMock; private BuilderTagger<Counter> counterTaggerMock; private Tagger<Gauge<String>> gaugeTaggerMock; private BuilderTagger<Metric> genericMetricTaggerMock; private long reportingInterval = 42; private TimeUnit reportingTimeUnit = TimeUnit.HOURS; private SignalFxAwareCodahaleMetricsCollector sfxImpl; @Before public void beforeMethod() { metricRegistryMock = mock(MetricRegistry.class); sfxReporterFactoryMock = mock(SignalFxReporterFactory.class); sfxReporterMock = mock(SignalFxReporter.class); metricMetadataMock = mock(MetricMetadata.class); doReturn(sfxReporterMock).when(sfxReporterFactoryMock).getReporter(any(MetricRegistry.class)); doReturn(metricMetadataMock).when(sfxReporterMock).getMetricMetadata(); doReturn(reportingInterval).when(sfxReporterFactoryMock).getInterval(); doReturn(reportingTimeUnit).when(sfxReporterFactoryMock).getTimeUnit(); timerBuilderMock = mock(MetricBuilder.class); histogramBuilderMock = mock(MetricBuilder.class); genericMetricBuilderMock = mock(MetricBuilder.class); timerMock = mock(Timer.class); meterMock = mock(Meter.class); histogramMock = mock(Histogram.class); counterMock = mock(Counter.class); gaugeMock = mock(Gauge.class); genericMetricMock = mock(Metric.class); timerTaggerMock = mock(BuilderTagger.class); meterTaggerMock = mock(BuilderTagger.class); histogramTaggerMock = mock(BuilderTagger.class); counterTaggerMock = mock(BuilderTagger.class); gaugeTaggerMock = mock(Tagger.class); genericMetricTaggerMock = mock(BuilderTagger.class); setupBuilderTaggerMock(timerTaggerMock, timerBuilderMock, timerMock); setupBuilderTaggerMock(meterTaggerMock, MetricBuilder.METERS, meterMock); setupBuilderTaggerMock(histogramTaggerMock, histogramBuilderMock, histogramMock); setupBuilderTaggerMock(counterTaggerMock, MetricBuilder.COUNTERS, counterMock); setupTaggerMock(gaugeTaggerMock); setupBuilderTaggerMock(genericMetricTaggerMock, genericMetricBuilderMock, genericMetricMock); sfxImpl = new SignalFxAwareCodahaleMetricsCollector( metricRegistryMock, metricMetadataMock, timerBuilderMock, histogramBuilderMock ); } private <M extends Metric> void setupBuilderTaggerMock( BuilderTagger<M> builderTaggerMock, MetricBuilder<M> metricBuilder, M metric ) { doReturn(builderTaggerMock).when(metricMetadataMock).forBuilder(metricBuilder); doReturn(builderTaggerMock).when(builderTaggerMock).withMetricName(anyString()); doReturn(builderTaggerMock).when(builderTaggerMock).withDimension(anyString(), anyString()); doReturn(metric).when(builderTaggerMock).createOrGet(metricRegistryMock); } private <M extends Metric> void setupTaggerMock( Tagger<M> taggerMock ) { List<M> metricHolder = new ArrayList<>(); doAnswer(invocation -> { metricHolder.add((M) invocation.getArguments()[0]); return taggerMock; }).when(metricMetadataMock).forMetric(any(Metric.class)); doReturn(taggerMock).when(taggerMock).withMetricName(anyString()); doReturn(taggerMock).when(taggerMock).withDimension(anyString(), anyString()); doAnswer(invocation -> metricHolder.get(0)).when(taggerMock).register(metricRegistryMock); } private void verifyRollingWindowTimerBuilder(MetricBuilder<Timer> timerBuilder, long expectedReportingInterval, TimeUnit expectedTimeUnit) { assertThat(timerBuilder).isInstanceOf(RollingWindowTimerBuilder.class); assertThat(Whitebox.getInternalState(timerBuilder, "amount")).isEqualTo(expectedReportingInterval); assertThat(Whitebox.getInternalState(timerBuilder, "timeUnit")).isEqualTo(expectedTimeUnit); } private void verifyRollingWindowHistogramBuilder(MetricBuilder<Histogram> histogramBuilder, long expectedReportingInterval, TimeUnit expectedTimeUnit) { assertThat(histogramBuilder).isInstanceOf(RollingWindowHistogramBuilder.class); assertThat(Whitebox.getInternalState(histogramBuilder, "amount")).isEqualTo(expectedReportingInterval); assertThat(Whitebox.getInternalState(histogramBuilder, "timeUnit")).isEqualTo(expectedTimeUnit); } @Test public void single_arg_constructor_sets_fields_as_expected() { // when SignalFxAwareCodahaleMetricsCollector cmc = new SignalFxAwareCodahaleMetricsCollector(sfxReporterFactoryMock); // then assertThat(cmc.metricRegistry) .isNotNull() .isNotSameAs(metricRegistryMock); verify(sfxReporterFactoryMock).getReporter(cmc.metricRegistry); assertThat(cmc.metricMetadata).isSameAs(metricMetadataMock); verifyRollingWindowTimerBuilder(cmc.timerBuilder, reportingInterval, reportingTimeUnit); verifyRollingWindowHistogramBuilder(cmc.histogramBuilder, reportingInterval, reportingTimeUnit); } @Test public void double_arg_constructor_sets_fields_as_expected() { // when SignalFxAwareCodahaleMetricsCollector cmc = new SignalFxAwareCodahaleMetricsCollector( metricRegistryMock, sfxReporterFactoryMock ); // then assertThat(cmc.metricRegistry).isSameAs(metricRegistryMock); verify(sfxReporterFactoryMock).getReporter(metricRegistryMock); assertThat(cmc.metricMetadata).isSameAs(metricMetadataMock); verifyRollingWindowTimerBuilder(cmc.timerBuilder, reportingInterval, reportingTimeUnit); verifyRollingWindowHistogramBuilder(cmc.histogramBuilder, reportingInterval, reportingTimeUnit); } @Test public void kitchen_sink_constructor_sets_fields_as_expected() { // when SignalFxAwareCodahaleMetricsCollector cmc = new SignalFxAwareCodahaleMetricsCollector( metricRegistryMock, metricMetadataMock, timerBuilderMock, histogramBuilderMock ); // then assertThat(cmc.metricRegistry).isSameAs(metricRegistryMock); assertThat(cmc.metricMetadata).isSameAs(metricMetadataMock); assertThat(cmc.timerBuilder).isSameAs(timerBuilderMock); assertThat(cmc.histogramBuilder).isSameAs(histogramBuilderMock); } private enum NullArgScenario { NULL_METRIC_REGISTRY( true, false, false, false, "registry cannot be null." ), NULL_METRIC_METADATA( false, true, false, false, "metricMetadata cannot be null." ), NULL_TIMER_BUILDER( false, false, true, false, "timerBuilder cannot be null." ), NULL_HISTORGRAM_BUILDER( false, false, false, true, "histogramBuilder cannot be null." ); public final boolean metricRegistryIsNull; public final boolean metricMetadataIsNull; public final boolean timerBuilderIsNull; public final boolean histogramBuilderIsNull; public final String expectedExceptionMessage; NullArgScenario( boolean metricRegistryIsNull, boolean metricMetadataIsNull, boolean timerBuilderIsNull, boolean histogramBuilderIsNull, String expectedExceptionMessage ) { this.metricRegistryIsNull = metricRegistryIsNull; this.metricMetadataIsNull = metricMetadataIsNull; this.timerBuilderIsNull = timerBuilderIsNull; this.histogramBuilderIsNull = histogramBuilderIsNull; this.expectedExceptionMessage = expectedExceptionMessage; } } @DataProvider(value = { "NULL_METRIC_REGISTRY", "NULL_METRIC_METADATA", "NULL_TIMER_BUILDER", "NULL_HISTORGRAM_BUILDER", }) @Test public void kitchen_sink_constructor_throws_IllegalArgumentException_when_passed_null_arguments( NullArgScenario scenario ) { // given MetricRegistry registry = scenario.metricRegistryIsNull ? null : metricRegistryMock; MetricMetadata metadata = scenario.metricMetadataIsNull ? null : metricMetadataMock; MetricBuilder<Timer> timerBuilder = scenario.timerBuilderIsNull ? null : timerBuilderMock; MetricBuilder<Histogram> histogramBuilder = scenario.histogramBuilderIsNull ? null : histogramBuilderMock; // when Throwable ex = catchThrowable( () -> new SignalFxAwareCodahaleMetricsCollector(registry, metadata, timerBuilder, histogramBuilder) ); // then assertThat(ex) .isInstanceOf(IllegalArgumentException.class) .hasMessage(scenario.expectedExceptionMessage); } private <M extends Metric> void verifyMetricCreation( MetricBuilder<M> metricBuilder, BuilderTagger<M> builderTaggerMock, String metricName, M expectedMetricResult, M actualMetricResult ) { verifyMetricCreation( metricBuilder, builderTaggerMock, metricName, null, expectedMetricResult, actualMetricResult ); } private <M extends Metric> void verifyMetricCreation( MetricBuilder<M> metricBuilder, BuilderTagger<M> builderTaggerMock, String metricName, List<Pair<String, String>> dimensions, M expectedMetricResult, M actualMetricResult ) { int numDimensions = (dimensions == null) ? 0 : dimensions.size(); verify(metricMetadataMock).forBuilder(metricBuilder); verify(builderTaggerMock).withMetricName(metricName); if (numDimensions == 0) { verify(builderTaggerMock, never()).withDimension(anyString(), anyString()); } else { for (Pair<String, String> dimension : dimensions) { verify(builderTaggerMock).withDimension(dimension.getKey(), dimension.getValue()); } } verify(builderTaggerMock).createOrGet(metricRegistryMock); verifyNoMoreInteractions(metricMetadataMock, builderTaggerMock); assertThat(actualMetricResult).isSameAs(expectedMetricResult); } private <M extends Metric, V> void verifyMetricRegistration( Tagger<Gauge<V>> taggerMock, String gaugeName, Gauge<V> expectedGauge, Gauge<V> actualGauge ) { verifyMetricRegistration(taggerMock, gaugeName, null, expectedGauge, actualGauge); } private <M extends Metric, V> void verifyMetricRegistration( Tagger<Gauge<V>> taggerMock, String gaugeName, List<Pair<String, String>> dimensions, Gauge<V> expectedGauge, Gauge<V> actualGauge ) { int numDimensions = (dimensions == null) ? 0 : dimensions.size(); ArgumentCaptor<Gauge> gaugeArgumentCaptor = ArgumentCaptor.forClass(Gauge.class); verify(metricMetadataMock).forMetric(gaugeArgumentCaptor.capture()); verify(taggerMock).withMetricName(gaugeName); if (numDimensions == 0) { verify(taggerMock, never()).withDimension(anyString(), anyString()); } else { for (Pair<String, String> dimension : dimensions) { verify(taggerMock).withDimension(dimension.getKey(), dimension.getValue()); } } verify(taggerMock).register(metricRegistryMock); verifyNoMoreInteractions(metricMetadataMock, taggerMock); Gauge gaugeRegistered = gaugeArgumentCaptor.getValue(); assertThat(gaugeRegistered).isNotNull(); assertThat(gaugeRegistered).isSameAs(actualGauge); assertThat(actualGauge).isSameAs(expectedGauge); } private Pair<String, String>[] generateVarargDimensions(Integer numDims) { if (numDims == null) return null; return generateIterableDimensions(numDims).toArray(new Pair[]{}); } private List<Pair<String, String>> generateIterableDimensions(Integer numDims) { if (numDims == null) return null; List<Pair<String, String>> result = new ArrayList<>(); for (int i = 0; i < numDims; i++) { result.add(Pair.of(UUID.randomUUID().toString(), UUID.randomUUID().toString())); } return result; } @Test public void getNamedTimer_creates_timer_using_sfx_mechanisms() { // given String timerName = UUID.randomUUID().toString(); // when Timer result = sfxImpl.getNamedTimer(timerName); // then verifyMetricCreation(timerBuilderMock, timerTaggerMock, timerName, timerMock, result); } @DataProvider(value = { "null", "0", "1", "2" }, splitBy = "\\|") @Test public void getNamedTimer_with_varargs_dimensions_creates_dimensioned_timer_using_sfx_mechanisms( Integer numDimensions ) { // given String timerName = UUID.randomUUID().toString(); Pair<String, String>[] varargDims = generateVarargDimensions(numDimensions); List<Pair<String, String>> dimsAsList = (varargDims == null) ? null : Arrays.asList(varargDims); // when Timer result = sfxImpl.getNamedTimer(timerName, varargDims); // then verifyMetricCreation(timerBuilderMock, timerTaggerMock, timerName, dimsAsList, timerMock, result); } @DataProvider(value = { "null", "0", "1", "2" }, splitBy = "\\|") @Test public void getNamedTimer_with_iterable_dimensions_creates_dimensioned_timer_using_sfx_mechanisms( Integer numDimensions ) { // given String timerName = UUID.randomUUID().toString(); List<Pair<String, String>> iterableDims = generateIterableDimensions(numDimensions); // when Timer result = sfxImpl.getNamedTimer(timerName, iterableDims); // then verifyMetricCreation(timerBuilderMock, timerTaggerMock, timerName, iterableDims, timerMock, result); } @Test public void getNamedMeter_creates_timer_using_sfx_mechanisms() { // given String meterName = UUID.randomUUID().toString(); // when Meter result = sfxImpl.getNamedMeter(meterName); // then verifyMetricCreation(MetricBuilder.METERS, meterTaggerMock, meterName, meterMock, result); } @DataProvider(value = { "null", "0", "1", "2" }, splitBy = "\\|") @Test public void getNamedMeter_with_varargs_dimensions_creates_dimensioned_meter_using_sfx_mechanisms( Integer numDimensions ) { // given String meterName = UUID.randomUUID().toString(); Pair<String, String>[] varargDims = generateVarargDimensions(numDimensions); List<Pair<String, String>> dimsAsList = (varargDims == null) ? null : Arrays.asList(varargDims); // when Meter result = sfxImpl.getNamedMeter(meterName, varargDims); // then verifyMetricCreation(MetricBuilder.METERS, meterTaggerMock, meterName, dimsAsList, meterMock, result); } @DataProvider(value = { "null", "0", "1", "2" }, splitBy = "\\|") @Test public void getNamedMeter_with_iterable_dimensions_creates_dimensioned_meter_using_sfx_mechanisms( Integer numDimensions ) { // given String meterName = UUID.randomUUID().toString(); List<Pair<String, String>> iterableDims = generateIterableDimensions(numDimensions); // when Meter result = sfxImpl.getNamedMeter(meterName, iterableDims); // then verifyMetricCreation(MetricBuilder.METERS, meterTaggerMock, meterName, iterableDims, meterMock, result); } @Test public void getNamedHistogram_creates_histogram_using_sfx_mechanisms() { // given String histogramName = UUID.randomUUID().toString(); // when Histogram result = sfxImpl.getNamedHistogram(histogramName); // then verifyMetricCreation(histogramBuilderMock, histogramTaggerMock, histogramName, histogramMock, result); } @DataProvider(value = { "null", "0", "1", "2" }, splitBy = "\\|") @Test public void getNamedHistogram_with_varargs_dimensions_creates_dimensioned_histogram_using_sfx_mechanisms( Integer numDimensions ) { // given String histogramName = UUID.randomUUID().toString(); Pair<String, String>[] varargDims = generateVarargDimensions(numDimensions); List<Pair<String, String>> dimsAsList = (varargDims == null) ? null : Arrays.asList(varargDims); // when Histogram result = sfxImpl.getNamedHistogram(histogramName, varargDims); // then verifyMetricCreation(histogramBuilderMock, histogramTaggerMock, histogramName, dimsAsList, histogramMock, result); } @DataProvider(value = { "null", "0", "1", "2" }, splitBy = "\\|") @Test public void getNamedHistogram_with_iterable_dimensions_creates_dimensioned_histogram_using_sfx_mechanisms( Integer numDimensions ) { // given String histogramName = UUID.randomUUID().toString(); List<Pair<String, String>> iterableDims = generateIterableDimensions(numDimensions); // when Histogram result = sfxImpl.getNamedHistogram(histogramName, iterableDims); // then verifyMetricCreation(histogramBuilderMock, histogramTaggerMock, histogramName, iterableDims, histogramMock, result); } @Test public void getNamedCounter_creates_timer_using_sfx_mechanisms() { // given String counterName = UUID.randomUUID().toString(); // when Counter result = sfxImpl.getNamedCounter(counterName); // then verifyMetricCreation(MetricBuilder.COUNTERS, counterTaggerMock, counterName, counterMock, result); } @DataProvider(value = { "null", "0", "1", "2" }, splitBy = "\\|") @Test public void getNamedCounter_with_varargs_dimensions_creates_dimensioned_counter_using_sfx_mechanisms( Integer numDimensions ) { // given String counterName = UUID.randomUUID().toString(); Pair<String, String>[] varargDims = generateVarargDimensions(numDimensions); List<Pair<String, String>> dimsAsList = (varargDims == null) ? null : Arrays.asList(varargDims); // when Counter result = sfxImpl.getNamedCounter(counterName, varargDims); // then verifyMetricCreation(MetricBuilder.COUNTERS, counterTaggerMock, counterName, dimsAsList, counterMock, result); } @DataProvider(value = { "null", "0", "1", "2" }, splitBy = "\\|") @Test public void getNamedCounter_with_iterable_dimensions_creates_dimensioned_counter_using_sfx_mechanisms( Integer numDimensions ) { // given String counterName = UUID.randomUUID().toString(); List<Pair<String, String>> iterableDims = generateIterableDimensions(numDimensions); // when Counter result = sfxImpl.getNamedCounter(counterName, iterableDims); // then verifyMetricCreation(MetricBuilder.COUNTERS, counterTaggerMock, counterName, iterableDims, counterMock, result); } @Test public void getNamedMetric_creates_metric_using_sfx_mechanisms() { // given String metricName = UUID.randomUUID().toString(); // when Metric result = sfxImpl.getNamedMetric(metricName, genericMetricBuilderMock); // then verifyMetricCreation(genericMetricBuilderMock, genericMetricTaggerMock, metricName, genericMetricMock, result); } @DataProvider(value = { "null", "0", "1", "2" }, splitBy = "\\|") @Test public void getNamedMetric_with_varargs_dimensions_creates_dimensioned_metric_using_sfx_mechanisms( Integer numDimensions ) { // given String metricName = UUID.randomUUID().toString(); Pair<String, String>[] varargDims = generateVarargDimensions(numDimensions); List<Pair<String, String>> dimsAsList = (varargDims == null) ? null : Arrays.asList(varargDims); // when Metric result = sfxImpl.getNamedMetric(metricName, genericMetricBuilderMock, varargDims); // then verifyMetricCreation(genericMetricBuilderMock, genericMetricTaggerMock, metricName, dimsAsList, genericMetricMock, result); } @DataProvider(value = { "null", "0", "1", "2" }, splitBy = "\\|") @Test public void getNamedMetric_with_iterable_dimensions_creates_dimensioned_metric_using_sfx_mechanisms( Integer numDimensions ) { // given String metricName = UUID.randomUUID().toString(); List<Pair<String, String>> iterableDims = generateIterableDimensions(numDimensions); // when Metric result = sfxImpl.getNamedMetric(metricName, genericMetricBuilderMock, iterableDims); // then verifyMetricCreation(genericMetricBuilderMock, genericMetricTaggerMock, metricName, iterableDims, genericMetricMock, result); } @Test public void registerNamedMetric_registers_metric_using_sfx_mechanisms() { // given String gaugeName = UUID.randomUUID().toString(); // when Gauge<String> result = sfxImpl.registerNamedMetric(gaugeName, gaugeMock); // then verifyMetricRegistration(gaugeTaggerMock, gaugeName, gaugeMock, result); } @DataProvider(value = { "null", "0", "1", "2" }, splitBy = "\\|") @Test public void registerNamedMetric_with_varargs_dimensions_creates_dimensioned_gauge_using_sfx_mechanisms( Integer numDimensions ) { // given String gaugeName = UUID.randomUUID().toString(); Pair<String, String>[] varargDims = generateVarargDimensions(numDimensions); List<Pair<String, String>> dimsAsList = (varargDims == null) ? null : Arrays.asList(varargDims); // when Gauge<String> result = sfxImpl.registerNamedMetric(gaugeName, gaugeMock, varargDims); // then verifyMetricRegistration(gaugeTaggerMock, gaugeName, dimsAsList, gaugeMock, result); } @DataProvider(value = { "null", "0", "1", "2" }, splitBy = "\\|") @Test public void registerNamedMetric_with_iterable_dimensions_creates_dimensioned_gauge_using_sfx_mechanisms( Integer numDimensions ) { // given String gaugeName = UUID.randomUUID().toString(); List<Pair<String, String>> iterableDims = generateIterableDimensions(numDimensions); // when Gauge<String> result = sfxImpl.registerNamedMetric(gaugeName, gaugeMock, iterableDims); // then verifyMetricRegistration(gaugeTaggerMock, gaugeName, iterableDims, gaugeMock, result); } }