diff --git a/README.md b/README.md index 84a6307..260a317 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,53 @@ NewRelicReporter reporter = NewRelicReporter.build(metricRegistry, metricBatchSe .commonAttributes(commonAttributes) .build(); +reporter.start(15, TimeUnit.SECONDS); +``` +## Customizing Reported Metrics +If you would like to customize the way metric names or attributes are reported to New Relic, you will want to supply +customizers to the `NewRelicReporterBuilder`. + +Consider metrics with tags encoded in their names, formatted like so: `"metricName[tag:value,othertag:othervalue]"`, +you might set up a reporter to report the metric name as `metricName` and add merge of the key/value pairs in `name` +with the `commonAttributes`. + +``` +MetricRegistry metricRegistry = new MetricRegistry(); // If you're already using dropwizard-metrics you may already have one of these. +... +String apiKey = ""; +MetricBatchSender metricBatchSender = MetricBatchSenderFactory + .fromHttpImplementation(OkHttpPoster::new) + .createBatchSender(apiKey); + +Attributes commonAttributes = new Attributes() + .put("host", InetAddress.getLocalHost().getHostName()) + .put("appName", "Your Application Name Here") + .put("other", "any other common attributes you wish"); + +MetricAttributeCustomizer mergeAttributesFromTaggedMetricName = + (name, metric, attributes) -> { + Attributes tagsAsAttributes = new Attributes(); + + // get a stream of each tag:value pair within the square brackets of name and add + // each pair to the tagsAsAttributes Attributes object + Stream.of(name.substring(name.indexOf('['), name.indexOf(']')).split(",")) + .forEach( + str -> { + String[] keyValuePair = str.split(":"); + + tagsAsAttributes.put(keyValuePair[0], keyValuePair[1]); + }); + + return tagsAsAttributes.putAll(attributes); + }; + +NewRelicReporter reporter = NewRelicReporter.build(metricRegistry, metricBatchSender) + .commonAttributes(commonAttributes) + // customizer to strip encoded tags from metric name + .metricNameCustomizer(name -> name.substring(0, name.indexOf('['))) + .metricAttributeCustomizer(mergeAttributesFromTaggedMetricName) + .build(); + reporter.start(15, TimeUnit.SECONDS); ``` diff --git a/src/main/java/com/codahale/metrics/newrelic/NewRelicReporterBuilder.java b/src/main/java/com/codahale/metrics/newrelic/NewRelicReporterBuilder.java index 20e94f1..ae9b119 100644 --- a/src/main/java/com/codahale/metrics/newrelic/NewRelicReporterBuilder.java +++ b/src/main/java/com/codahale/metrics/newrelic/NewRelicReporterBuilder.java @@ -16,6 +16,8 @@ import com.codahale.metrics.newrelic.transformer.HistogramTransformer; import com.codahale.metrics.newrelic.transformer.MeterTransformer; import com.codahale.metrics.newrelic.transformer.TimerTransformer; +import com.codahale.metrics.newrelic.transformer.customizer.MetricAttributesCustomizer; +import com.codahale.metrics.newrelic.transformer.customizer.MetricNameCustomizer; import com.codahale.metrics.newrelic.util.TimeTracker; import com.newrelic.telemetry.Attributes; import com.newrelic.telemetry.TelemetryClient; @@ -35,6 +37,8 @@ public class NewRelicReporterBuilder { private TimeUnit durationUnit = TimeUnit.MILLISECONDS; private Attributes commonAttributes = new Attributes(); private Set disabledMetricAttributes = Collections.emptySet(); + private MetricNameCustomizer nameCustomizer = MetricNameCustomizer.DEFAULT; + private MetricAttributesCustomizer attributeCustomizer = MetricAttributesCustomizer.DEFAULT; public static NewRelicReporterBuilder forRegistry( MetricRegistry registry, MetricBatchSender metricBatchSender) { @@ -77,6 +81,17 @@ public NewRelicReporterBuilder disabledMetricAttributes( return this; } + public NewRelicReporterBuilder metricNameCustomizer(MetricNameCustomizer nameCustomizer) { + this.nameCustomizer = nameCustomizer; + return this; + } + + public NewRelicReporterBuilder metricAttributeCustomizer( + MetricAttributesCustomizer attributeCustomizer) { + this.attributeCustomizer = attributeCustomizer; + return this; + } + public NewRelicReporter build() { long rateFactor = rateUnit.toSeconds(1); double durationFactor = durationUnit.toNanos(1); @@ -85,12 +100,21 @@ public NewRelicReporter build() { TimeTracker timeTracker = new TimeTracker(Clock.defaultClock()); MeterTransformer meterTransformer = - MeterTransformer.build(timeTracker, rateFactor, metricAttributePredicate); + MeterTransformer.build( + timeTracker, rateFactor, metricAttributePredicate, nameCustomizer, attributeCustomizer); TimerTransformer timerTransformer = - TimerTransformer.build(timeTracker, rateFactor, durationFactor, metricAttributePredicate); - GaugeTransformer gaugeTransformer = new GaugeTransformer(); - CounterTransformer counterTransformer = new CounterTransformer(); - HistogramTransformer histogramTransformer = HistogramTransformer.build(timeTracker); + TimerTransformer.build( + timeTracker, + rateFactor, + durationFactor, + metricAttributePredicate, + nameCustomizer, + attributeCustomizer); + GaugeTransformer gaugeTransformer = new GaugeTransformer(nameCustomizer, attributeCustomizer); + CounterTransformer counterTransformer = + new CounterTransformer(nameCustomizer, attributeCustomizer); + HistogramTransformer histogramTransformer = + HistogramTransformer.build(timeTracker, nameCustomizer, attributeCustomizer); return new NewRelicReporter( timeTracker, diff --git a/src/main/java/com/codahale/metrics/newrelic/transformer/CounterTransformer.java b/src/main/java/com/codahale/metrics/newrelic/transformer/CounterTransformer.java index 7671b40..903a661 100644 --- a/src/main/java/com/codahale/metrics/newrelic/transformer/CounterTransformer.java +++ b/src/main/java/com/codahale/metrics/newrelic/transformer/CounterTransformer.java @@ -11,26 +11,49 @@ import com.codahale.metrics.Clock; import com.codahale.metrics.Counter; +import com.codahale.metrics.newrelic.transformer.customizer.MetricAttributesCustomizer; +import com.codahale.metrics.newrelic.transformer.customizer.MetricNameCustomizer; import com.newrelic.telemetry.Attributes; import com.newrelic.telemetry.metrics.Gauge; import com.newrelic.telemetry.metrics.Metric; import java.util.Collection; +import java.util.Objects; public class CounterTransformer implements DropWizardMetricTransformer { private final Clock clock; + private final MetricNameCustomizer nameCustomizer; + private final MetricAttributesCustomizer attributeCustomizer; public CounterTransformer() { this(Clock.defaultClock()); } + public CounterTransformer( + MetricNameCustomizer nameCustomizer, MetricAttributesCustomizer attributeCustomizer) { + this(Clock.defaultClock(), nameCustomizer, attributeCustomizer); + } + // exists for testing CounterTransformer(Clock clock) { + this(clock, MetricNameCustomizer.DEFAULT, MetricAttributesCustomizer.DEFAULT); + } + + CounterTransformer( + Clock clock, + MetricNameCustomizer nameCustomizer, + MetricAttributesCustomizer attributeCustomizer) { this.clock = clock; + this.nameCustomizer = Objects.requireNonNull(nameCustomizer); + this.attributeCustomizer = Objects.requireNonNull(attributeCustomizer); } @Override public Collection transform(String name, Counter counter) { - return singleton(new Gauge(name, counter.getCount(), clock.getTime(), new Attributes())); + String customizedName = nameCustomizer.customizeMetricName(name); + Attributes customizedAttributes = + attributeCustomizer.customizeMetricAttributes(name, counter, new Attributes()); + return singleton( + new Gauge(customizedName, counter.getCount(), clock.getTime(), customizedAttributes)); } } diff --git a/src/main/java/com/codahale/metrics/newrelic/transformer/GaugeTransformer.java b/src/main/java/com/codahale/metrics/newrelic/transformer/GaugeTransformer.java index 83ce621..c9e961c 100644 --- a/src/main/java/com/codahale/metrics/newrelic/transformer/GaugeTransformer.java +++ b/src/main/java/com/codahale/metrics/newrelic/transformer/GaugeTransformer.java @@ -12,9 +12,12 @@ import com.codahale.metrics.Clock; import com.codahale.metrics.Gauge; +import com.codahale.metrics.newrelic.transformer.customizer.MetricAttributesCustomizer; +import com.codahale.metrics.newrelic.transformer.customizer.MetricNameCustomizer; import com.newrelic.telemetry.Attributes; import com.newrelic.telemetry.metrics.Metric; import java.util.Collection; +import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,33 +25,56 @@ public class GaugeTransformer implements DropWizardMetricTransformer { private static final Logger LOG = LoggerFactory.getLogger(GaugeTransformer.class); private final Clock clock; + private final MetricNameCustomizer nameCustomizer; + private final MetricAttributesCustomizer attributeCustomizer; public GaugeTransformer() { - this(Clock.defaultClock()); + this(MetricNameCustomizer.DEFAULT, MetricAttributesCustomizer.DEFAULT); + } + + public GaugeTransformer( + MetricNameCustomizer nameCustomizer, MetricAttributesCustomizer attributeCustomizer) { + this(Clock.defaultClock(), nameCustomizer, attributeCustomizer); } // exists for testing public GaugeTransformer(Clock clock) { + this(clock, MetricNameCustomizer.DEFAULT, MetricAttributesCustomizer.DEFAULT); + } + + public GaugeTransformer( + Clock clock, + MetricNameCustomizer nameCustomizer, + MetricAttributesCustomizer attributeCustomizer) { this.clock = clock; + this.nameCustomizer = Objects.requireNonNull(nameCustomizer); + this.attributeCustomizer = Objects.requireNonNull(attributeCustomizer); } @Override public Collection transform(String name, Gauge gauge) { + String customizedName = nameCustomizer.customizeMetricName(name); + Attributes customizedAttributes = + attributeCustomizer.customizeMetricAttributes(name, gauge, new Attributes()); long timestamp = clock.getTime(); Object gaugeValue = gauge.getValue(); if (gaugeValue == null) { - LOG.debug("Ignoring gauge with null value. Gauge name: {}", name); + LOG.debug( + "Ignoring gauge with null value. Gauge name: {}, Gauge attributes: {}", + customizedName, + customizedAttributes); return emptySet(); } if (gaugeValue instanceof Number) { Metric metric = new com.newrelic.telemetry.metrics.Gauge( - name, ((Number) gaugeValue).doubleValue(), timestamp, new Attributes()); + customizedName, ((Number) gaugeValue).doubleValue(), timestamp, customizedAttributes); return singleton(metric); } LOG.debug( - "Ignoring gauge [name: {}] with value of type {} (non-numeric gauges are unsupported)", - name, + "Ignoring gauge [name: {}, Attributes: {}] with value of type {} (non-numeric gauges are unsupported)", + customizedName, + customizedAttributes, gaugeValue.getClass().getName()); return emptySet(); } diff --git a/src/main/java/com/codahale/metrics/newrelic/transformer/HistogramTransformer.java b/src/main/java/com/codahale/metrics/newrelic/transformer/HistogramTransformer.java index 929f848..a60f499 100644 --- a/src/main/java/com/codahale/metrics/newrelic/transformer/HistogramTransformer.java +++ b/src/main/java/com/codahale/metrics/newrelic/transformer/HistogramTransformer.java @@ -11,12 +11,15 @@ import static java.util.stream.Stream.concat; import com.codahale.metrics.Histogram; +import com.codahale.metrics.newrelic.transformer.customizer.MetricAttributesCustomizer; +import com.codahale.metrics.newrelic.transformer.customizer.MetricNameCustomizer; import com.codahale.metrics.newrelic.transformer.interfaces.CountingTransformer; import com.codahale.metrics.newrelic.transformer.interfaces.SamplingTransformer; import com.codahale.metrics.newrelic.util.TimeTracker; import com.newrelic.telemetry.Attributes; import com.newrelic.telemetry.metrics.Metric; import java.util.Collection; +import java.util.Objects; import java.util.function.Supplier; public class HistogramTransformer implements DropWizardMetricTransformer { @@ -25,30 +28,65 @@ public class HistogramTransformer implements DropWizardMetricTransformer transform(String name, Histogram histogram) { - Collection counts = countingTransformer.transform(name, histogram, ATTRIBUTES_SUPPLIER); + String customizedName = nameCustomizer.customizeMetricName(name); + Supplier customizedAttributeSupplier = + () -> + attributeCustomizer.customizeMetricAttributes( + name, histogram, ATTRIBUTES_SUPPLIER.get()); + + Collection counts = + countingTransformer.transform(customizedName, histogram, customizedAttributeSupplier); return concat( counts.stream(), - samplingTransformer.transform(name, histogram, ATTRIBUTES_SUPPLIER).stream()) + samplingTransformer + .transform(customizedName, histogram, customizedAttributeSupplier) + .stream()) .collect(toSet()); } @Override public void onHistogramRemoved(String name) { - countingTransformer.remove(name); + countingTransformer.remove(nameCustomizer.customizeMetricName(name)); } } diff --git a/src/main/java/com/codahale/metrics/newrelic/transformer/MeterTransformer.java b/src/main/java/com/codahale/metrics/newrelic/transformer/MeterTransformer.java index b3c51fe..14dc39a 100644 --- a/src/main/java/com/codahale/metrics/newrelic/transformer/MeterTransformer.java +++ b/src/main/java/com/codahale/metrics/newrelic/transformer/MeterTransformer.java @@ -12,6 +12,8 @@ import com.codahale.metrics.Meter; import com.codahale.metrics.MetricAttribute; +import com.codahale.metrics.newrelic.transformer.customizer.MetricAttributesCustomizer; +import com.codahale.metrics.newrelic.transformer.customizer.MetricNameCustomizer; import com.codahale.metrics.newrelic.transformer.interfaces.CountingTransformer; import com.codahale.metrics.newrelic.transformer.interfaces.MeteredTransformer; import com.codahale.metrics.newrelic.util.TimeTracker; @@ -25,33 +27,73 @@ public class MeterTransformer implements DropWizardMetricTransformer { static final Supplier BASE_ATTRIBUTES = Attributes::new; - private final MeteredTransformer meteredTransformer; - private final CountingTransformer countingTransformer; + private final MetricNameCustomizer nameCustomizer; + private final MetricAttributesCustomizer attributeCustomizer; public static MeterTransformer build( TimeTracker timeTracker, long rateFactor, - Predicate metricAttributePredicate) { + Predicate metricAttributePredicate, + MetricNameCustomizer nameCustomizer, + MetricAttributesCustomizer attributeCustomizer) { return new MeterTransformer( new MeteredTransformer(rateFactor, metricAttributePredicate), - new CountingTransformer(timeTracker)); + new CountingTransformer(timeTracker), + nameCustomizer, + attributeCustomizer); } - MeterTransformer(MeteredTransformer meteredTransformer, CountingTransformer countingTransformer) { + public static MeterTransformer build( + TimeTracker timeTracker, + long rateFactor, + Predicate metricAttributePredicate) { + return build( + timeTracker, + rateFactor, + metricAttributePredicate, + MetricNameCustomizer.DEFAULT, + MetricAttributesCustomizer.DEFAULT); + } + + private final MeteredTransformer meteredTransformer; + private final CountingTransformer countingTransformer; + + MeterTransformer( + MeteredTransformer meteredTransformer, + CountingTransformer countingTransformer, + MetricNameCustomizer nameCustomizer, + MetricAttributesCustomizer attributeCustomizer) { this.meteredTransformer = meteredTransformer; this.countingTransformer = countingTransformer; + this.nameCustomizer = nameCustomizer; + this.attributeCustomizer = attributeCustomizer; + } + + MeterTransformer(MeteredTransformer meteredTransformer, CountingTransformer countingTransformer) { + this( + meteredTransformer, + countingTransformer, + MetricNameCustomizer.DEFAULT, + MetricAttributesCustomizer.DEFAULT); } @Override public Collection transform(String name, Meter meter) { + String customizedName = nameCustomizer.customizeMetricName(name); + Supplier customizedAttributeSupplier = + () -> attributeCustomizer.customizeMetricAttributes(name, meter, BASE_ATTRIBUTES.get()); return concat( - countingTransformer.transform(name, meter, BASE_ATTRIBUTES).stream(), - meteredTransformer.transform(name, meter, BASE_ATTRIBUTES).stream()) + countingTransformer + .transform(customizedName, meter, customizedAttributeSupplier) + .stream(), + meteredTransformer + .transform(customizedName, meter, customizedAttributeSupplier) + .stream()) .collect(toSet()); } @Override public void onMeterRemoved(String name) { - countingTransformer.remove(name); + countingTransformer.remove(nameCustomizer.customizeMetricName(name)); } } diff --git a/src/main/java/com/codahale/metrics/newrelic/transformer/TimerTransformer.java b/src/main/java/com/codahale/metrics/newrelic/transformer/TimerTransformer.java index 085fbf5..6951c8e 100644 --- a/src/main/java/com/codahale/metrics/newrelic/transformer/TimerTransformer.java +++ b/src/main/java/com/codahale/metrics/newrelic/transformer/TimerTransformer.java @@ -11,6 +11,8 @@ import com.codahale.metrics.MetricAttribute; import com.codahale.metrics.Timer; +import com.codahale.metrics.newrelic.transformer.customizer.MetricAttributesCustomizer; +import com.codahale.metrics.newrelic.transformer.customizer.MetricNameCustomizer; import com.codahale.metrics.newrelic.transformer.interfaces.CountingTransformer; import com.codahale.metrics.newrelic.transformer.interfaces.MeteredTransformer; import com.codahale.metrics.newrelic.transformer.interfaces.SamplingTransformer; @@ -29,39 +31,64 @@ public class TimerTransformer implements DropWizardMetricTransformer { private final SamplingTransformer samplingTransformer; private final MeteredTransformer meteredTransformer; private final CountingTransformer countingTransformer; + private final MetricNameCustomizer nameCustomizer; + private final MetricAttributesCustomizer attributeCustomizer; public static TimerTransformer build( TimeTracker timeTracker, long rateFactor, double scaleFactor, - Predicate metricAttributePredicate) { + Predicate metricAttributePredicate, + MetricNameCustomizer nameCustomizer, + MetricAttributesCustomizer attributeCustomizer) { return new TimerTransformer( new SamplingTransformer(timeTracker, scaleFactor), new MeteredTransformer(rateFactor, metricAttributePredicate), - new CountingTransformer(timeTracker)); + new CountingTransformer(timeTracker), + nameCustomizer, + attributeCustomizer); } TimerTransformer( SamplingTransformer samplingTransformer, MeteredTransformer meteredTransformer, CountingTransformer countingTransformer) { + this( + samplingTransformer, + meteredTransformer, + countingTransformer, + MetricNameCustomizer.DEFAULT, + MetricAttributesCustomizer.DEFAULT); + } + + TimerTransformer( + SamplingTransformer samplingTransformer, + MeteredTransformer meteredTransformer, + CountingTransformer countingTransformer, + MetricNameCustomizer nameCustomizer, + MetricAttributesCustomizer attributeCustomizer) { this.samplingTransformer = samplingTransformer; this.meteredTransformer = meteredTransformer; this.countingTransformer = countingTransformer; + this.nameCustomizer = nameCustomizer; + this.attributeCustomizer = attributeCustomizer; } @Override public Collection transform(String name, Timer timer) { + String customizedName = nameCustomizer.customizeMetricName(name); + Supplier customizedAttributeSupplier = + () -> attributeCustomizer.customizeMetricAttributes(name, timer, ATTRIBUTES_SUPPLIER.get()); return Stream.of( - samplingTransformer.transform(name, timer, ATTRIBUTES_SUPPLIER), - meteredTransformer.transform(name, timer, ATTRIBUTES_SUPPLIER), - countingTransformer.transform(name, timer, ATTRIBUTES_SUPPLIER)) + samplingTransformer.transform(customizedName, timer, customizedAttributeSupplier), + meteredTransformer.transform(customizedName, timer, customizedAttributeSupplier), + countingTransformer.transform(customizedName, timer, customizedAttributeSupplier)) .flatMap(Collection::stream) .collect(toSet()); } @Override public void onTimerRemoved(String name) { - countingTransformer.remove(name); + countingTransformer.remove(nameCustomizer.customizeMetricName(name)); } } diff --git a/src/main/java/com/codahale/metrics/newrelic/transformer/customizer/MetricAttributesCustomizer.java b/src/main/java/com/codahale/metrics/newrelic/transformer/customizer/MetricAttributesCustomizer.java new file mode 100644 index 0000000..6610571 --- /dev/null +++ b/src/main/java/com/codahale/metrics/newrelic/transformer/customizer/MetricAttributesCustomizer.java @@ -0,0 +1,30 @@ +package com.codahale.metrics.newrelic.transformer.customizer; + +import com.codahale.metrics.Metric; +import com.codahale.metrics.MetricRegistry; +import com.newrelic.telemetry.Attributes; + +/** + * {@code MetricAttributesCustomizer} is a {@link FunctionalInterface} intended to give a hook into + * metric transformers to provide custom per-metric attributes that are based on data available at + * runtime. + * + *

An example use may be an end user providing an implementation that extracts encoded data from + * a Metric name and returns that encoded data as the contents of an {@code Attributes} object. + */ +@FunctionalInterface +public interface MetricAttributesCustomizer { + MetricAttributesCustomizer DEFAULT = (name, metric, attributes) -> attributes; + + /** + * Customizes metric attributes using any data available at runtime within the scope of the method + * call. The default implementation returns the {@code baseAttributes} parameter unchanged. + * + * @param name the name that a metric is registered under in the effective {@link MetricRegistry} + * @param metric incoming metric. This may be used to fetch data for attributes. + * @param attributes incoming attributes. These may be returned, dropped, or merged with computed + * attributes. + * @return an Attributes object to report to NewRelic with the transformed {@link Metric} + */ + Attributes customizeMetricAttributes(String name, Metric metric, Attributes attributes); +} diff --git a/src/main/java/com/codahale/metrics/newrelic/transformer/customizer/MetricNameCustomizer.java b/src/main/java/com/codahale/metrics/newrelic/transformer/customizer/MetricNameCustomizer.java new file mode 100644 index 0000000..b6964a7 --- /dev/null +++ b/src/main/java/com/codahale/metrics/newrelic/transformer/customizer/MetricNameCustomizer.java @@ -0,0 +1,41 @@ +package com.codahale.metrics.newrelic.transformer.customizer; + +import com.codahale.metrics.MetricRegistry; + +/** + * {@code MetricNameCustomizer} is a {@link FunctionalInterface} intended to give a hook into metric + * transformers to provide metric names different than those registered with a {@link + * MetricRegistry}. + * + *

Example usage, removing encoded data from metric names before sending them to NewRelic: + * + *

{@code
+ * new MetricNameCustomizer(){
+ *     String customizeMetricName(String name){
+ *         char delimiter='[';
+ *         return name.substring(0, name.indexOf(delimiter));
+ *     }
+ * }
+ * }
+ * + * or as a lambda: + * + *
{@code
+ * MetricNameCustomizer stripEncodedDataCustomizer =
+ *       name -> name.substring(0, name.indexOf('['));
+ * }
+ */ +@FunctionalInterface +public interface MetricNameCustomizer { + + MetricNameCustomizer DEFAULT = n -> n; + + /** + * Customizes metric name using any data available at runtime within the scope of the method call. + * The default implementation returns the {@code name} parameter unchanged. + * + * @param name the name that a metric is registered under in the effective {@link MetricRegistry} + * @return a customized name. + */ + String customizeMetricName(String name); +} diff --git a/src/test/java/com/codahale/metrics/newrelic/transformer/HistogramTransformerTest.java b/src/test/java/com/codahale/metrics/newrelic/transformer/HistogramTransformerTest.java index 80d94c7..5139fdb 100644 --- a/src/test/java/com/codahale/metrics/newrelic/transformer/HistogramTransformerTest.java +++ b/src/test/java/com/codahale/metrics/newrelic/transformer/HistogramTransformerTest.java @@ -9,12 +9,17 @@ import static java.util.Collections.singleton; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.codahale.metrics.ExponentiallyDecayingReservoir; import com.codahale.metrics.Histogram; +import com.codahale.metrics.newrelic.transformer.customizer.MetricAttributesCustomizer; +import com.codahale.metrics.newrelic.transformer.customizer.MetricCustomizerTestUtils; import com.codahale.metrics.newrelic.transformer.interfaces.CountingTransformer; import com.codahale.metrics.newrelic.transformer.interfaces.SamplingTransformer; import com.newrelic.telemetry.Attributes; @@ -22,31 +27,36 @@ import com.newrelic.telemetry.metrics.Gauge; import com.newrelic.telemetry.metrics.Metric; import java.util.Collection; -import java.util.function.Supplier; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.internal.util.collections.Sets; class HistogramTransformerTest { + CountingTransformer counter = mock(CountingTransformer.class); + SamplingTransformer sampler = mock(SamplingTransformer.class); + + @BeforeEach + public void setup() { + reset(counter); + reset(sampler); + } + @Test - void testHistogramTransform() { + void testTransform() { Histogram histogram = new Histogram(new ExponentiallyDecayingReservoir()); long startTime = System.currentTimeMillis(); long endTime = startTime + 11000; - Supplier baseAttrSupplier = HistogramTransformer.ATTRIBUTES_SUPPLIER; Count expectedCount = new Count("countery", 12.6, startTime, endTime, new Attributes()); Metric expectedSamplingResult = new Gauge("samplery", 77.1, endTime, new Attributes()); Collection expectedMetrics = Sets.newSet(expectedCount, expectedSamplingResult); - CountingTransformer counter = mock(CountingTransformer.class); - SamplingTransformer sampler = mock(SamplingTransformer.class); - - when(counter.transform("history", histogram, baseAttrSupplier)) + when(counter.transform(eq("history"), eq(histogram), notNull())) .thenReturn(singleton(expectedCount)); - when(sampler.transform("history", histogram, baseAttrSupplier)) + when(sampler.transform(eq("history"), eq(histogram), notNull())) .thenReturn(singleton(expectedSamplingResult)); HistogramTransformer testClass = new HistogramTransformer(counter, sampler); @@ -55,6 +65,37 @@ void testHistogramTransform() { assertEquals(expectedMetrics, result); } + @Test + void testTransformWithAttributeAndNameCustomization() { + Histogram histogram = new Histogram(new ExponentiallyDecayingReservoir()); + String baseName = "history"; + String name = baseName + "[tag:value,otherTag:otherValue]"; + Attributes otherAttributes = new Attributes().put("tag", "value").put("otherTag", "otherValue"); + + long startTime = System.currentTimeMillis(); + long endTime = startTime + 11000; + + Count expectedCount = new Count("countery", 12.6, startTime, endTime, otherAttributes); + Metric expectedSamplingResult = new Gauge("samplery", 77.1, endTime, otherAttributes); + + Collection expectedMetrics = Sets.newSet(expectedCount, expectedSamplingResult); + + when(counter.transform(eq("history"), eq(histogram), notNull())) + .thenReturn(singleton(expectedCount)); + when(sampler.transform(eq("history"), eq(histogram), notNull())) + .thenReturn(singleton(expectedSamplingResult)); + + HistogramTransformer testClass = + new HistogramTransformer( + counter, + sampler, + MetricCustomizerTestUtils.NAME_TAG_STRIPPER, + MetricCustomizerTestUtils.ATTRIBUTES_FROM_TAGGED_NAME); + Collection result = testClass.transform(name, histogram); + + assertEquals(expectedMetrics, result); + } + @Test void testRemove() { CountingTransformer counting = mock(CountingTransformer.class); @@ -62,4 +103,17 @@ void testRemove() { testClass.onHistogramRemoved("jim"); verify(counting).remove("jim"); } + + @Test + void testRemoveWithNameCustomization() { + CountingTransformer counting = mock(CountingTransformer.class); + HistogramTransformer testClass = + new HistogramTransformer( + counting, + null, + MetricCustomizerTestUtils.NAME_TAG_STRIPPER, + MetricAttributesCustomizer.DEFAULT); + testClass.onHistogramRemoved("jim[tag:value]"); + verify(counting).remove("jim"); + } } diff --git a/src/test/java/com/codahale/metrics/newrelic/transformer/MeterTransformerTest.java b/src/test/java/com/codahale/metrics/newrelic/transformer/MeterTransformerTest.java index 2369446..2fedf07 100644 --- a/src/test/java/com/codahale/metrics/newrelic/transformer/MeterTransformerTest.java +++ b/src/test/java/com/codahale/metrics/newrelic/transformer/MeterTransformerTest.java @@ -7,14 +7,17 @@ package com.codahale.metrics.newrelic.transformer; -import static com.codahale.metrics.newrelic.transformer.MeterTransformer.BASE_ATTRIBUTES; import static java.util.Collections.singleton; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.codahale.metrics.Meter; +import com.codahale.metrics.newrelic.transformer.customizer.MetricCustomizerTestUtils; import com.codahale.metrics.newrelic.transformer.interfaces.CountingTransformer; import com.codahale.metrics.newrelic.transformer.interfaces.MeteredTransformer; import com.newrelic.telemetry.Attributes; @@ -22,41 +25,63 @@ import com.newrelic.telemetry.metrics.Gauge; import com.newrelic.telemetry.metrics.Metric; import java.util.Collection; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.internal.util.collections.Sets; class MeterTransformerTest { + private static final String baseName = "$$"; + private final MeteredTransformer converter = mock(MeteredTransformer.class); + private final CountingTransformer countingTransformer = mock(CountingTransformer.class); + + @BeforeEach + public void setup() { + reset(converter); + reset(countingTransformer); + } + @Test void testTransform() { - String name = "$$"; Meter meter = new Meter(); - Metric expectedMetric = - new Gauge( - name, - 85299999.21, - System.currentTimeMillis(), - new Attributes().put("sourceType", "meter")); - Count expectedCount = - new Count( - name, - 14.5, - System.currentTimeMillis(), - System.currentTimeMillis() + 12, - new Attributes().put("something", "else")); + Metric expectedMetric = createExpectedGaugeMetric(new Attributes()); + Count expectedCount = createExpectedCountMetric(new Attributes()); Collection expectedMeters = Sets.newSet(expectedMetric, expectedCount); - MeteredTransformer converter = mock(MeteredTransformer.class); - CountingTransformer countingTransformer = mock(CountingTransformer.class); - - when(converter.transform(name, meter, BASE_ATTRIBUTES)).thenReturn(expectedMeters); - when(countingTransformer.transform(name, meter, BASE_ATTRIBUTES)) + when(converter.transform(eq(baseName), eq(meter), notNull())).thenReturn(expectedMeters); + when(countingTransformer.transform(eq(baseName), eq(meter), notNull())) .thenReturn(singleton(expectedCount)); MeterTransformer testClass = new MeterTransformer(converter, countingTransformer); - Collection result = testClass.transform(name, meter); + Collection result = testClass.transform(baseName, meter); + + assertEquals(expectedMeters, result); + } + + @Test + void testTransformWithAttributeAndNameCustomization() { + Attributes otherAttributes = new Attributes().put("tag", "value").put("otherTag", "otherValue"); + Meter meter = new Meter(); + Metric expectedMetric = createExpectedGaugeMetric(otherAttributes); + Count expectedCount = createExpectedCountMetric(otherAttributes); + + Collection expectedMeters = Sets.newSet(expectedMetric, expectedCount); + + when(converter.transform(eq(baseName), eq(meter), notNull())).thenReturn(expectedMeters); + when(countingTransformer.transform(eq(baseName), eq(meter), notNull())) + .thenReturn(singleton(expectedCount)); + + MeterTransformer testClass = + new MeterTransformer( + converter, + countingTransformer, + MetricCustomizerTestUtils.NAME_TAG_STRIPPER, + MetricCustomizerTestUtils.ATTRIBUTES_FROM_TAGGED_NAME); + + Collection result = + testClass.transform(baseName + "[tag:value,otherTag:otherValue]", meter); assertEquals(expectedMeters, result); } @@ -68,4 +93,30 @@ void testRemove() { testClass.onMeterRemoved("something"); verify(counting).remove("something"); } + + @Test + void testRemoveWithNameCustomization() { + CountingTransformer counting = mock(CountingTransformer.class); + MeterTransformer testClass = + new MeterTransformer(null, counting, MetricCustomizerTestUtils.NAME_TAG_STRIPPER, null); + testClass.onMeterRemoved("something[tag:value]"); + verify(counting).remove("something"); + } + + private Metric createExpectedGaugeMetric(Attributes optionalAttributes) { + return new Gauge( + baseName, + 85299999.21, + System.currentTimeMillis(), + new Attributes().put("sourceType", "meter").putAll(optionalAttributes)); + } + + private Count createExpectedCountMetric(Attributes optionalAttributes) { + return new Count( + baseName, + 14.5, + System.currentTimeMillis(), + System.currentTimeMillis() + 12, + optionalAttributes.put("something", "else")); + } } diff --git a/src/test/java/com/codahale/metrics/newrelic/transformer/TimerTransformerTest.java b/src/test/java/com/codahale/metrics/newrelic/transformer/TimerTransformerTest.java index dc5dbd1..be7da91 100644 --- a/src/test/java/com/codahale/metrics/newrelic/transformer/TimerTransformerTest.java +++ b/src/test/java/com/codahale/metrics/newrelic/transformer/TimerTransformerTest.java @@ -7,14 +7,16 @@ package com.codahale.metrics.newrelic.transformer; -import static com.codahale.metrics.newrelic.transformer.TimerTransformer.ATTRIBUTES_SUPPLIER; import static java.util.Collections.singleton; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.codahale.metrics.Timer; +import com.codahale.metrics.newrelic.transformer.customizer.MetricCustomizerTestUtils; import com.codahale.metrics.newrelic.transformer.interfaces.CountingTransformer; import com.codahale.metrics.newrelic.transformer.interfaces.MeteredTransformer; import com.codahale.metrics.newrelic.transformer.interfaces.SamplingTransformer; @@ -23,41 +25,73 @@ import com.newrelic.telemetry.metrics.Gauge; import com.newrelic.telemetry.metrics.Metric; import java.util.Collection; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.mockito.internal.util.collections.Sets; class TimerTransformerTest { + public static final String baseName = "fast"; + @Test - void testConvert() { + void testTransform() { long now = System.currentTimeMillis(); - Gauge result1 = new Gauge("fast", 55d, now, new Attributes()); - Gauge result2 = new Gauge("fast", 999d, now, new Attributes()); - Count expectedCount = - new Count( - "fast", - 99999.6666, - System.currentTimeMillis(), - System.currentTimeMillis() + 100, - new Attributes()); + Gauge result1 = new Gauge(baseName, 55d, now, new Attributes()); + Gauge result2 = new Gauge(baseName, 999d, now, new Attributes()); + Count expectedCount = createExpectedCountMetric(new Attributes()); Timer timer = mock(Timer.class); SamplingTransformer samplingTransformer = mock(SamplingTransformer.class); MeteredTransformer meteredTransformer = mock(MeteredTransformer.class); CountingTransformer countingTransformer = mock(CountingTransformer.class); - when(samplingTransformer.transform("fast", timer, ATTRIBUTES_SUPPLIER)) + when(samplingTransformer.transform(eq(baseName), eq(timer), notNull())) .thenReturn(singleton(result1)); - when(meteredTransformer.transform("fast", timer, ATTRIBUTES_SUPPLIER)) + when(meteredTransformer.transform(eq(baseName), eq(timer), notNull())) .thenReturn(singleton(result2)); - when(countingTransformer.transform("fast", timer, ATTRIBUTES_SUPPLIER)) + when(countingTransformer.transform(eq(baseName), eq(timer), notNull())) .thenReturn(singleton(expectedCount)); TimerTransformer timerTransformer = new TimerTransformer(samplingTransformer, meteredTransformer, countingTransformer); - Collection results = timerTransformer.transform("fast", timer); - Collection expected = Sets.newSet(result1, result2, expectedCount); + Collection results = timerTransformer.transform(baseName, timer); + Collection expected = Sets.newSet(result1, result2, expectedCount); + assertEquals(expected, results); + } + + @Test + void testTransformWithAttributeAndNameCustomization() { + Attributes otherTags = new Attributes().put("tag", "value").put("otherTag", "otherValue"); + long now = System.currentTimeMillis(); + Gauge result1 = new Gauge(baseName, 55d, now, otherTags); + Gauge result2 = new Gauge(baseName, 999d, now, otherTags); + Count expectedCount = createExpectedCountMetric(otherTags); + + Timer timer = mock(Timer.class); + SamplingTransformer samplingTransformer = mock(SamplingTransformer.class); + MeteredTransformer meteredTransformer = mock(MeteredTransformer.class); + CountingTransformer countingTransformer = mock(CountingTransformer.class); + + when(samplingTransformer.transform(eq(baseName), eq(timer), notNull())) + .thenReturn(singleton(result1)); + when(meteredTransformer.transform(eq(baseName), eq(timer), notNull())) + .thenReturn(singleton(result2)); + when(countingTransformer.transform(eq(baseName), eq(timer), notNull())) + .thenReturn(singleton(expectedCount)); + + TimerTransformer timerTransformer = + new TimerTransformer( + samplingTransformer, + meteredTransformer, + countingTransformer, + MetricCustomizerTestUtils.NAME_TAG_STRIPPER, + MetricCustomizerTestUtils.ATTRIBUTES_FROM_TAGGED_NAME); + + Collection results = + timerTransformer.transform(baseName + "[tag:value,otherTag:otherValue]", timer); + Collection expected = Sets.newSet(result1, result2, expectedCount); + assertEquals(expected, results); } @@ -68,4 +102,24 @@ void testRemove() throws Exception { testClass.onTimerRemoved("money"); verify(counting).remove("money"); } + + @Test + void testRemoveWithNameCustomization() { + CountingTransformer counting = mock(CountingTransformer.class); + TimerTransformer testClass = + new TimerTransformer( + null, null, counting, MetricCustomizerTestUtils.NAME_TAG_STRIPPER, null); + testClass.onTimerRemoved("money[tag:value]"); + verify(counting).remove("money"); + } + + @NotNull + private Count createExpectedCountMetric(Attributes attributes) { + return new Count( + baseName, + 99999.6666, + System.currentTimeMillis(), + System.currentTimeMillis() + 100, + attributes); + } } diff --git a/src/test/java/com/codahale/metrics/newrelic/transformer/customizer/MetricCustomizerTestUtils.java b/src/test/java/com/codahale/metrics/newrelic/transformer/customizer/MetricCustomizerTestUtils.java new file mode 100644 index 0000000..94c27ba --- /dev/null +++ b/src/test/java/com/codahale/metrics/newrelic/transformer/customizer/MetricCustomizerTestUtils.java @@ -0,0 +1,23 @@ +package com.codahale.metrics.newrelic.transformer.customizer; + +import com.newrelic.telemetry.Attributes; +import java.util.stream.Stream; + +public class MetricCustomizerTestUtils { + public static final MetricAttributesCustomizer ATTRIBUTES_FROM_TAGGED_NAME = + (name, metric, baseAttributes) -> { + Attributes modifiedAttributes = new Attributes(); + // insert every "key:value" pair within square brackets of name into modifiedAttributes + Stream.of(name.substring(name.indexOf('['), name.indexOf(']')).split(",")) + .forEach( + str -> { + String[] tuple = str.split(":"); + modifiedAttributes.put(tuple[0], tuple[1]); + }); + + return modifiedAttributes.putAll(baseAttributes); + }; + + public static final MetricNameCustomizer NAME_TAG_STRIPPER = + name -> name.substring(0, name.indexOf('[')); +}