diff --git a/implementations/micrometer-registry-new-relic/.gitignore b/implementations/micrometer-registry-new-relic/.gitignore
new file mode 100644
index 0000000000..9bb88d3761
--- /dev/null
+++ b/implementations/micrometer-registry-new-relic/.gitignore
@@ -0,0 +1 @@
+/.DS_Store
diff --git a/implementations/micrometer-registry-new-relic/build.gradle b/implementations/micrometer-registry-new-relic/build.gradle
index 7ad798c7c0..2977c51d18 100644
--- a/implementations/micrometer-registry-new-relic/build.gradle
+++ b/implementations/micrometer-registry-new-relic/build.gradle
@@ -2,6 +2,7 @@ dependencies {
api project(':micrometer-core')
implementation 'org.slf4j:slf4j-api'
+ implementation 'com.newrelic.agent.java:newrelic-api:5.+'
testImplementation project(':micrometer-test')
-}
+}
\ No newline at end of file
diff --git a/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicAgentClientProviderImpl.java b/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicAgentClientProviderImpl.java
new file mode 100644
index 0000000000..a7f55454b6
--- /dev/null
+++ b/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicAgentClientProviderImpl.java
@@ -0,0 +1,251 @@
+/**
+ * Copyright 2017 Pivotal Software, Inc.
+ *
+ * Licensed 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
+ *
+ * https://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 io.micrometer.newrelic;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.newrelic.api.agent.Agent;
+import com.newrelic.api.agent.NewRelic;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.DistributionSummary;
+import io.micrometer.core.instrument.FunctionCounter;
+import io.micrometer.core.instrument.FunctionTimer;
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.LongTaskTimer;
+import io.micrometer.core.instrument.Measurement;
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.Tag;
+import io.micrometer.core.instrument.TimeGauge;
+import io.micrometer.core.instrument.Timer;
+import io.micrometer.core.instrument.config.MissingRequiredConfigurationException;
+import io.micrometer.core.instrument.config.NamingConvention;
+import io.micrometer.core.instrument.util.StringUtils;
+
+/**
+ * Publishes metrics to New Relic Insights via Java Agent API.
+ *
+ * @author Neil Powell
+ */
+public class NewRelicAgentClientProviderImpl implements NewRelicClientProvider {
+
+ private final Logger logger = LoggerFactory.getLogger(NewRelicAgentClientProviderImpl.class);
+
+ private final Agent newRelicAgent;
+ private final NewRelicConfig config;
+ private final NamingConvention namingConvention;
+
+ public NewRelicAgentClientProviderImpl(NewRelicConfig config) {
+ this(config, NewRelic.getAgent(), new NewRelicNamingConvention());
+ }
+
+ public NewRelicAgentClientProviderImpl(NewRelicConfig config, Agent newRelicAgent, NamingConvention namingConvention) {
+
+ if (config.meterNameEventTypeEnabled() == false
+ && StringUtils.isEmpty(config.eventType())) {
+ throw new MissingRequiredConfigurationException("eventType must be set to report metrics to New Relic");
+ }
+
+ this.newRelicAgent = newRelicAgent;
+ this.config = config;
+ this.namingConvention = namingConvention;
+ }
+
+ @Override
+ public void publish(NewRelicMeterRegistry meterRegistry) {
+ // New Relic's Java Agent Insights API is backed by a reservoir/buffer
+ // and handles the actual publishing of events to New Relic.
+ // 1:1 mapping between Micrometer meters and New Relic events
+ for (Meter meter : meterRegistry.getMeters()) {
+ sendEvents(
+ meter.getId(),
+ meter.match(
+ this::writeGauge,
+ this::writeCounter,
+ this::writeTimer,
+ this::writeSummary,
+ this::writeLongTaskTimer,
+ this::writeTimeGauge,
+ this::writeFunctionCounter,
+ this::writeFunctionTimer,
+ this::writeMeter)
+ );
+ }
+ }
+
+ @Override
+ public Map writeLongTaskTimer(LongTaskTimer timer) {
+ Map attributes = new HashMap();
+ TimeUnit timeUnit = TimeUnit.valueOf(timer.getId().getBaseUnit());
+ addAttribute(ACTIVE_TASKS, timer.activeTasks(), attributes);
+ addAttribute(DURATION, timer.duration(timeUnit), attributes);
+ addAttribute(TIME_UNIT, timeUnit.toString().toLowerCase(), attributes);
+ //process meter's name, type and tags
+ addMeterAsAttributes(timer.getId(), attributes);
+ return attributes;
+ }
+
+ @Override
+ public Map writeFunctionCounter(FunctionCounter counter) {
+ return writeCounterValues(counter.getId(), counter.count());
+ }
+
+ @Override
+ public Map writeCounter(Counter counter) {
+ return writeCounterValues(counter.getId(), counter.count());
+ }
+
+ Map writeCounterValues(Meter.Id id, double count) {
+ Map attributes = new HashMap();
+ if (Double.isFinite(count)) {
+ addAttribute(THROUGHPUT, count, attributes);
+ //process meter's name, type and tags
+ addMeterAsAttributes(id, attributes);
+ }
+ return attributes;
+ }
+
+ @Override
+ public Map writeGauge(Gauge gauge) {
+ Map attributes = new HashMap();
+ double value = gauge.value();
+ if (Double.isFinite(value)) {
+ addAttribute(VALUE, value, attributes);
+ //process meter's name, type and tags
+ addMeterAsAttributes(gauge.getId(), attributes);
+ }
+ return attributes;
+ }
+
+ @Override
+ public Map writeTimeGauge(TimeGauge gauge) {
+ Map attributes = new HashMap();
+ double value = gauge.value();
+ if (Double.isFinite(value)) {
+ addAttribute(VALUE, value, attributes);
+ addAttribute(TIME_UNIT, gauge.baseTimeUnit().toString().toLowerCase(), attributes);
+ //process meter's name, type and tags
+ addMeterAsAttributes(gauge.getId(), attributes);
+ }
+ return attributes;
+ }
+
+ @Override
+ public Map writeSummary(DistributionSummary summary) {
+ Map attributes = new HashMap();
+ addAttribute(COUNT, summary.count(), attributes);
+ addAttribute(AVG, summary.mean(), attributes);
+ addAttribute(TOTAL, summary.totalAmount(), attributes);
+ addAttribute(MAX, summary.max(), attributes);
+ //process meter's name, type and tags
+ addMeterAsAttributes(summary.getId(), attributes);
+ return attributes;
+ }
+
+ @Override
+ public Map writeTimer(Timer timer) {
+ Map attributes = new HashMap();
+ TimeUnit timeUnit = TimeUnit.valueOf(timer.getId().getBaseUnit());
+ addAttribute(COUNT, (new Double(timer.count())).longValue(), attributes);
+ addAttribute(AVG, timer.mean(timeUnit), attributes);
+ addAttribute(TOTAL_TIME, timer.totalTime(timeUnit), attributes);
+ addAttribute(MAX, timer.max(timeUnit), attributes);
+ addAttribute(TIME_UNIT, timeUnit.toString().toLowerCase(), attributes);
+ //process meter's name, type and tags
+ addMeterAsAttributes(timer.getId(), attributes);
+ return attributes;
+ }
+
+ @Override
+ public Map writeFunctionTimer(FunctionTimer timer) {
+ Map attributes = new HashMap();
+ TimeUnit timeUnit = TimeUnit.valueOf(timer.getId().getBaseUnit());
+ addAttribute(COUNT, (new Double(timer.count())).longValue(), attributes);
+ addAttribute(AVG, timer.mean(timeUnit), attributes);
+ addAttribute(TOTAL_TIME, timer.totalTime(timeUnit), attributes);
+ addAttribute(TIME_UNIT, timeUnit.toString().toLowerCase(), attributes);
+ //process meter's name, type and tags
+ addMeterAsAttributes(timer.getId(), attributes);
+ return attributes;
+ }
+
+ @Override
+ public Map writeMeter(Meter meter) {
+ Map attributes = new HashMap();
+ for (Measurement measurement : meter.measure()) {
+ double value = measurement.getValue();
+ if (!Double.isFinite(value)) {
+ continue;
+ }
+ addAttribute(measurement.getStatistic().getTagValueRepresentation(), value, attributes);
+ }
+ if (attributes.isEmpty()) {
+ return attributes;
+ }
+ //process meter's name, type and tags
+ addMeterAsAttributes(meter.getId(), attributes);
+ return attributes;
+ }
+
+ void addMeterAsAttributes(Meter.Id id, Map attributes) {
+ if (!config.meterNameEventTypeEnabled()) {
+ // Include contextual attributes when publishing all metrics under a single categorical eventType,
+ // NOT when publishing an eventType per Meter/metric name
+ String name = id.getConventionName(namingConvention);
+ attributes.put(METRIC_NAME, name);
+ attributes.put(METRIC_TYPE, id.getType().toString());
+ }
+ //process meter tags
+ for (Tag tag : id.getConventionTags(namingConvention)) {
+ attributes.put(tag.getKey(), tag.getValue());
+ }
+ }
+
+ void addAttribute(String key, Number value, Map attributes) {
+ //process other tags
+
+ //Replicate DoubleFormat.wholeOrDecimal(value.doubleValue()) formatting behavior
+ if (Math.floor(value.doubleValue()) == value.doubleValue()) {
+ //whole number - don't include decimal
+ attributes.put(namingConvention.tagKey(key), value.intValue());
+ } else {
+ //include decimal
+ attributes.put(namingConvention.tagKey(key), value.doubleValue());
+ }
+ }
+
+ void addAttribute(String key, String value, Map attributes) {
+ //process other tags
+ attributes.put(namingConvention.tagKey(key), namingConvention.tagValue(value));
+ }
+
+ void sendEvents(Meter.Id id, Map attributes) {
+ //Delegate to New Relic Java Agent
+ if (attributes != null && attributes.isEmpty() == false) {
+ String eventType = getEventType(id, config, namingConvention);
+ try {
+ newRelicAgent.getInsights().recordCustomEvent(eventType, attributes);
+ } catch (Throwable e) {
+ logger.warn("failed to send metrics to new relic", e);
+ }
+ }
+ }
+}
diff --git a/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicClientProvider.java b/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicClientProvider.java
new file mode 100644
index 0000000000..02635ebd93
--- /dev/null
+++ b/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicClientProvider.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright 2017 Pivotal Software, Inc.
+ *
+ * Licensed 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
+ *
+ * https://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 io.micrometer.newrelic;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.DistributionSummary;
+import io.micrometer.core.instrument.FunctionCounter;
+import io.micrometer.core.instrument.FunctionTimer;
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.LongTaskTimer;
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.TimeGauge;
+import io.micrometer.core.instrument.Timer;
+import io.micrometer.core.instrument.config.NamingConvention;
+
+/**
+ * @author Neil Powell
+ */
+public interface NewRelicClientProvider {
+ //long task timer
+ String DURATION = "duration";
+ String ACTIVE_TASKS = "activeTasks";
+ //distribution summary & timer
+ String MAX = "max";
+ String TOTAL = "total";
+ String AVG = "avg";
+ String COUNT = "count";
+ //timer
+ String TOTAL_TIME = "totalTime";
+ String TIME = "time";
+ //gauge
+ String VALUE = "value";
+ //counter
+ String THROUGHPUT = "throughput"; //TODO Why not "count"? ..confusing if just counting something
+ //timer
+ String TIME_UNIT = "timeUnit";
+ //all
+ String METRIC_TYPE = "metricType";
+ String METRIC_NAME = "metricName";
+
+ default String getEventType(Meter.Id id, NewRelicConfig config, NamingConvention namingConvention) {
+ String eventType = null;
+ if (config.meterNameEventTypeEnabled()) {
+ //meter/metric name event type
+ eventType = id.getConventionName(namingConvention);
+ } else {
+ //static eventType "category"
+ eventType = config.eventType();
+ }
+ return eventType;
+ }
+
+ void publish(NewRelicMeterRegistry meterRegistry);
+
+ Object writeFunctionTimer(FunctionTimer timer);
+
+ Object writeTimer(Timer timer);
+
+ Object writeSummary(DistributionSummary summary);
+
+ Object writeLongTaskTimer(LongTaskTimer timer);
+
+ Object writeTimeGauge(TimeGauge gauge);
+
+ Object writeGauge(Gauge gauge);
+
+ Object writeCounter(Counter counter);
+
+ Object writeFunctionCounter(FunctionCounter counter);
+
+ Object writeMeter(Meter meter);
+}
diff --git a/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicConfig.java b/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicConfig.java
index f7989f3fa3..7911c65245 100644
--- a/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicConfig.java
+++ b/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicConfig.java
@@ -15,13 +15,13 @@
*/
package io.micrometer.newrelic;
-import io.micrometer.core.instrument.config.MissingRequiredConfigurationException;
import io.micrometer.core.instrument.step.StepRegistryConfig;
/**
* Configuration for {@link NewRelicMeterRegistry}.
*
* @author Jon Schneider
+ * @author Neil Powell
* @since 1.0.0
*/
public interface NewRelicConfig extends StepRegistryConfig {
@@ -55,15 +55,11 @@ default String eventType() {
default String apiKey() {
String v = get(prefix() + ".apiKey");
- if (v == null)
- throw new MissingRequiredConfigurationException("apiKey must be set to report metrics to New Relic");
return v;
}
default String accountId() {
String v = get(prefix() + ".accountId");
- if (v == null)
- throw new MissingRequiredConfigurationException("accountId must be set to report metrics to New Relic");
return v;
}
@@ -76,4 +72,5 @@ default String uri() {
String v = get(prefix() + ".uri");
return (v == null) ? "https://insights-collector.newrelic.com" : v;
}
+
}
diff --git a/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicHttpClientProviderImpl.java b/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicHttpClientProviderImpl.java
new file mode 100644
index 0000000000..25dcff1f24
--- /dev/null
+++ b/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicHttpClientProviderImpl.java
@@ -0,0 +1,287 @@
+/**
+ * Copyright 2017 Pivotal Software, Inc.
+ *
+ * Licensed 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
+ *
+ * https://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 io.micrometer.newrelic;
+
+import static io.micrometer.core.instrument.util.StringEscapeUtils.escapeJson;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.DistributionSummary;
+import io.micrometer.core.instrument.FunctionCounter;
+import io.micrometer.core.instrument.FunctionTimer;
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.LongTaskTimer;
+import io.micrometer.core.instrument.Measurement;
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.Tag;
+import io.micrometer.core.instrument.Tags;
+import io.micrometer.core.instrument.TimeGauge;
+import io.micrometer.core.instrument.Timer;
+import io.micrometer.core.instrument.config.MissingRequiredConfigurationException;
+import io.micrometer.core.instrument.config.NamingConvention;
+import io.micrometer.core.instrument.util.*;
+import io.micrometer.core.ipc.http.HttpSender;
+import io.micrometer.core.ipc.http.HttpUrlConnectionSender;
+
+/**
+ * Publishes metrics to New Relic Insights REST API.
+ *
+ * @author Jon Schneider
+ * @author Johnny Lim
+ * @author Neil Powell
+ */
+public class NewRelicHttpClientProviderImpl implements NewRelicClientProvider {
+
+ private final Logger logger = LoggerFactory.getLogger(NewRelicHttpClientProviderImpl.class);
+
+ private final NewRelicConfig config;
+ private final HttpSender httpClient;
+ private final String insightsEndpoint;
+ private final NamingConvention namingConvention;
+
+ @SuppressWarnings("deprecation")
+ public NewRelicHttpClientProviderImpl(NewRelicConfig config) {
+ this(config, new HttpUrlConnectionSender(config.connectTimeout(), config.readTimeout()), new NewRelicNamingConvention());
+ }
+
+ public NewRelicHttpClientProviderImpl(NewRelicConfig config, HttpSender httpClient, NamingConvention namingConvention) {
+
+ if (!config.meterNameEventTypeEnabled() && StringUtils.isEmpty(config.eventType())) {
+ throw new MissingRequiredConfigurationException("eventType must be set to report metrics to New Relic");
+ }
+ if (StringUtils.isEmpty(config.accountId())) {
+ throw new MissingRequiredConfigurationException("accountId must be set to report metrics to New Relic");
+ }
+ if (StringUtils.isEmpty(config.apiKey())) {
+ throw new MissingRequiredConfigurationException("apiKey must be set to report metrics to New Relic");
+ }
+ if (StringUtils.isEmpty(config.uri())) {
+ throw new MissingRequiredConfigurationException("uri must be set to report metrics to New Relic");
+ }
+
+ this.config = config;
+ this.httpClient = httpClient;
+ this.namingConvention = namingConvention;
+ this.insightsEndpoint = config.uri() + "/v1/accounts/" + config.accountId() + "/events";
+ }
+
+ @Override
+ public void publish(NewRelicMeterRegistry meterRegistry) {
+ // New Relic's Insights API limits us to 1000 events per call
+ // 1:1 mapping between Micrometer meters and New Relic events
+ for (List batch : MeterPartition.partition(meterRegistry, Math.min(config.batchSize(), 1000))) {
+ sendEvents(batch.stream().flatMap(meter -> meter.match(
+ this::writeGauge,
+ this::writeCounter,
+ this::writeTimer,
+ this::writeSummary,
+ this::writeLongTaskTimer,
+ this::writeTimeGauge,
+ this::writeFunctionCounter,
+ this::writeFunctionTimer,
+ this::writeMeter)));
+ }
+ }
+
+ @Override
+ public Stream writeLongTaskTimer(LongTaskTimer timer) {
+ TimeUnit timeUnit = TimeUnit.valueOf(timer.getId().getBaseUnit());
+ return Stream.of(
+ event(timer.getId(),
+ new Attribute(ACTIVE_TASKS, timer.activeTasks()),
+ new Attribute(DURATION, timer.duration(timeUnit)),
+ new Attribute(TIME_UNIT, timeUnit.toString().toLowerCase())
+ )
+ );
+ }
+
+ @Override
+ public Stream writeFunctionCounter(FunctionCounter counter) {
+ double count = counter.count();
+ if (Double.isFinite(count)) {
+ return Stream.of(event(counter.getId(), new Attribute(THROUGHPUT, count)));
+ }
+ return Stream.empty();
+ }
+
+ @Override
+ public Stream writeCounter(Counter counter) {
+ return Stream.of(event(counter.getId(), new Attribute(THROUGHPUT, counter.count())));
+ }
+
+ @Override
+ public Stream writeGauge(Gauge gauge) {
+ Double value = gauge.value();
+ if (Double.isFinite(value)) {
+ return Stream.of(event(gauge.getId(), new Attribute(VALUE, value)));
+ }
+ return Stream.empty();
+ }
+
+ @Override
+ public Stream writeTimeGauge(TimeGauge gauge) {
+ Double value = gauge.value();
+ if (Double.isFinite(value)) {
+ return Stream.of(
+ event(gauge.getId(),
+ new Attribute(VALUE, value),
+ new Attribute(TIME_UNIT, gauge.baseTimeUnit().toString().toLowerCase())
+ )
+ );
+ }
+ return Stream.empty();
+ }
+
+ @Override
+ public Stream writeSummary(DistributionSummary summary) {
+ return Stream.of(
+ event(summary.getId(),
+ new Attribute(COUNT, summary.count()),
+ new Attribute(AVG, summary.mean()),
+ new Attribute(TOTAL, summary.totalAmount()),
+ new Attribute(MAX, summary.max())
+ )
+ );
+ }
+
+ @Override
+ public Stream writeTimer(Timer timer) {
+ TimeUnit timeUnit = TimeUnit.valueOf(timer.getId().getBaseUnit());
+ return Stream.of(
+ event(timer.getId(),
+ new Attribute(COUNT, timer.count()),
+ new Attribute(AVG, timer.mean(timeUnit)),
+ new Attribute(TOTAL_TIME, timer.totalTime(timeUnit)),
+ new Attribute(MAX, timer.max(timeUnit)),
+ new Attribute(TIME_UNIT, timeUnit.toString().toLowerCase())
+ )
+ );
+ }
+
+ @Override
+ public Stream writeFunctionTimer(FunctionTimer timer) {
+ TimeUnit timeUnit = TimeUnit.valueOf(timer.getId().getBaseUnit());
+ return Stream.of(
+ event(timer.getId(),
+ new Attribute(COUNT, timer.count()),
+ new Attribute(AVG, timer.mean(timeUnit)),
+ new Attribute(TOTAL_TIME, timer.totalTime(timeUnit)),
+ new Attribute(TIME_UNIT, timeUnit.toString().toLowerCase())
+ )
+ );
+ }
+
+ @Override
+ public Stream writeMeter(Meter meter) {
+ // Snapshot values should be used throughout this method as there are chances for values to be changed in-between.
+ Map attributes = new HashMap<>();
+ for (Measurement measurement : meter.measure()) {
+ double value = measurement.getValue();
+ if (!Double.isFinite(value)) {
+ continue;
+ }
+ String name = measurement.getStatistic().getTagValueRepresentation();
+ attributes.put(name, new Attribute(name, value));
+ }
+ if (attributes.isEmpty()) {
+ return Stream.empty();
+ }
+ return Stream.of(event(meter.getId(), attributes.values().toArray(new Attribute[0])));
+ }
+
+ private String event(Meter.Id id, Attribute... attributes) {
+ if (!config.meterNameEventTypeEnabled()) {
+ // Include contextual attributes when publishing all metrics under a single categorical eventType,
+ // NOT when publishing an eventType per Meter/metric name
+ int size = attributes.length;
+ Attribute[] newAttrs = Arrays.copyOf(attributes, size + 2);
+
+ String name = id.getConventionName(namingConvention);
+ newAttrs[size] = new Attribute(METRIC_NAME, name);
+ newAttrs[size + 1] = new Attribute(METRIC_TYPE, id.getType().toString());
+
+ return event(id, Tags.empty(), newAttrs);
+ }
+ return event(id, Tags.empty(), attributes);
+ }
+
+ private String event(Meter.Id id, Iterable extraTags, Attribute... attributes) {
+ StringBuilder tagsJson = new StringBuilder();
+
+ for (Tag tag : id.getConventionTags(namingConvention)) {
+ tagsJson.append(",\"").append(escapeJson(tag.getKey())).append("\":\"").append(escapeJson(tag.getValue())).append("\"");
+ }
+
+ for (Tag tag : extraTags) {
+ tagsJson.append(",\"").append(escapeJson(namingConvention.tagKey(tag.getKey())))
+ .append("\":\"").append(escapeJson(namingConvention.tagValue(tag.getValue()))).append("\"");
+ }
+
+ String eventType = getEventType(id, config, namingConvention);
+
+ return Arrays.stream(attributes)
+ .map(attr ->
+ (attr.getValue() instanceof Number)
+ ? ",\"" + attr.getName() + "\":" + DoubleFormat.wholeOrDecimal(((Number)attr.getValue()).doubleValue())
+ : ",\"" + attr.getName() + "\":\"" + namingConvention.tagValue(attr.getValue().toString()) + "\""
+ )
+ .collect(Collectors.joining("", "{\"eventType\":\"" + escapeJson(eventType) + "\"", tagsJson + "}"));
+ }
+
+ void sendEvents(Stream events) {
+ try {
+ AtomicInteger totalEvents = new AtomicInteger();
+
+ httpClient.post(insightsEndpoint)
+ .withHeader("X-Insert-Key", config.apiKey())
+ .withJsonContent(events.peek(ev -> totalEvents.incrementAndGet()).collect(Collectors.joining(",", "[", "]")))
+ .send()
+ .onSuccess(response -> logger.debug("successfully sent {} metrics to New Relic.", totalEvents))
+ .onError(response -> logger.error("failed to send metrics to new relic: http {} {}", response.code(), response.body()));
+ } catch (Throwable e) {
+ logger.warn("failed to send metrics to new relic", e);
+ }
+ }
+
+ private class Attribute {
+ private final String name;
+ private final Object value;
+
+ private Attribute(String name, Object value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+ }
+}
diff --git a/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicMeterRegistry.java b/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicMeterRegistry.java
index 337ef67801..f74848c0a6 100644
--- a/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicMeterRegistry.java
+++ b/implementations/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicMeterRegistry.java
@@ -15,82 +15,65 @@
*/
package io.micrometer.newrelic;
-import io.micrometer.core.instrument.*;
-import io.micrometer.core.instrument.Timer;
-import io.micrometer.core.instrument.config.MissingRequiredConfigurationException;
-import io.micrometer.core.instrument.config.NamingConvention;
-import io.micrometer.core.instrument.step.StepMeterRegistry;
-import io.micrometer.core.instrument.util.*;
-import io.micrometer.core.ipc.http.HttpSender;
-import io.micrometer.core.ipc.http.HttpUrlConnectionSender;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.*;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static io.micrometer.core.instrument.util.StringEscapeUtils.escapeJson;
+import io.micrometer.core.instrument.Clock;
+import io.micrometer.core.instrument.config.MissingRequiredConfigurationException;
+import io.micrometer.core.instrument.config.NamingConvention;
+import io.micrometer.core.instrument.step.StepMeterRegistry;
+import io.micrometer.core.instrument.util.NamedThreadFactory;
+import io.micrometer.core.instrument.util.TimeUtils;
/**
- * Publishes metrics to New Relic Insights.
+ * Publishes metrics to New Relic Insights based on client provider selected (Http or Java Agent).
+ * Defaults to the HTTP/REST client provider.
*
* @author Jon Schneider
* @author Johnny Lim
- * @since 1.0.0
+ * @author Neil Powell
*/
public class NewRelicMeterRegistry extends StepMeterRegistry {
+
private static final ThreadFactory DEFAULT_THREAD_FACTORY = new NamedThreadFactory("new-relic-metrics-publisher");
private final NewRelicConfig config;
- private final HttpSender httpClient;
+ private NewRelicClientProvider clientProvider;
private final Logger logger = LoggerFactory.getLogger(NewRelicMeterRegistry.class);
/**
* @param config Configuration options for the registry that are describable as properties.
* @param clock The clock to use for timings.
*/
- @SuppressWarnings("deprecation")
public NewRelicMeterRegistry(NewRelicConfig config, Clock clock) {
- this(config, clock, DEFAULT_THREAD_FACTORY,
- new HttpUrlConnectionSender(config.connectTimeout(), config.readTimeout()));
+ //default to the HTTP/REST client
+ this(config, new NewRelicHttpClientProviderImpl(config), clock);
}
-
+
/**
- * @param config Configuration options for the registry that are describable as properties.
- * @param clock The clock to use for timings.
- * @param threadFactory The thread factory to use to create the publishing thread.
- * @deprecated Use {@link #builder(NewRelicConfig)} instead.
+ * @param config Configuration options for the registry that are describable as properties.
+ * @param clientProvider Provider of the HTTP or Agent-based client that publishes metrics to New Relic
+ * @param clock The clock to use for timings.
*/
- @Deprecated
- public NewRelicMeterRegistry(NewRelicConfig config, Clock clock, ThreadFactory threadFactory) {
- this(config, clock, threadFactory, new HttpUrlConnectionSender(config.connectTimeout(), config.readTimeout()));
+ public NewRelicMeterRegistry(NewRelicConfig config, NewRelicClientProvider clientProvider, Clock clock) {
+ this(config, clientProvider, new NewRelicNamingConvention(), clock, DEFAULT_THREAD_FACTORY);
}
// VisibleForTesting
- NewRelicMeterRegistry(NewRelicConfig config, Clock clock, ThreadFactory threadFactory, HttpSender httpClient) {
+ NewRelicMeterRegistry(NewRelicConfig config, NewRelicClientProvider clientProvider,
+ NamingConvention namingConvention, Clock clock, ThreadFactory threadFactory) {
super(config, clock);
- if (!config.meterNameEventTypeEnabled() && StringUtils.isEmpty(config.eventType())) {
- throw new MissingRequiredConfigurationException("eventType must be set to report metrics to New Relic");
- }
- if (StringUtils.isEmpty(config.accountId())) {
- throw new MissingRequiredConfigurationException("accountId must be set to report metrics to New Relic");
- }
- if (StringUtils.isEmpty(config.apiKey())) {
- throw new MissingRequiredConfigurationException("apiKey must be set to report metrics to New Relic");
+ if (clientProvider == null) {
+ throw new MissingRequiredConfigurationException("clientProvider required to report metrics to New Relic");
}
- if (StringUtils.isEmpty(config.uri())) {
- throw new MissingRequiredConfigurationException("uri must be set to report metrics to New Relic");
- }
-
+
this.config = config;
- this.httpClient = httpClient;
+ this.clientProvider = clientProvider;
- config().namingConvention(new NewRelicNamingConvention());
+ config().namingConvention(namingConvention);
start(threadFactory);
}
@@ -105,181 +88,10 @@ public void start(ThreadFactory threadFactory) {
}
super.start(threadFactory);
}
-
+
@Override
protected void publish() {
- String insightsEndpoint = config.uri() + "/v1/accounts/" + config.accountId() + "/events";
-
- // New Relic's Insights API limits us to 1000 events per call
- // 1:1 mapping between Micrometer meters and New Relic events
- for (List batch : MeterPartition.partition(this, Math.min(config.batchSize(), 1000))) {
- sendEvents(insightsEndpoint, batch.stream().flatMap(meter -> meter.match(
- this::writeGauge,
- this::writeCounter,
- this::writeTimer,
- this::writeSummary,
- this::writeLongTaskTimer,
- this::writeTimeGauge,
- this::writeFunctionCounter,
- this::writeFunctionTimer,
- this::writeMeter)));
- }
- }
-
- private Stream writeLongTaskTimer(LongTaskTimer ltt) {
- return Stream.of(
- event(ltt.getId(),
- new Attribute("activeTasks", ltt.activeTasks()),
- new Attribute("duration", ltt.duration(getBaseTimeUnit())),
- new Attribute("timeUnit", getBaseTimeUnit().name().toLowerCase()))
- );
- }
-
- // VisibleForTesting
- Stream writeFunctionCounter(FunctionCounter counter) {
- double count = counter.count();
- if (Double.isFinite(count)) {
- return Stream.of(event(counter.getId(), new Attribute("throughput", count)));
- }
- return Stream.empty();
- }
-
- private Stream writeCounter(Counter counter) {
- return Stream.of(event(counter.getId(), new Attribute("throughput", counter.count())));
- }
-
- // VisibleForTesting
- Stream writeGauge(Gauge gauge) {
- Double value = gauge.value();
- if (Double.isFinite(value)) {
- return Stream.of(event(gauge.getId(), new Attribute("value", value)));
- }
- return Stream.empty();
- }
-
- // VisibleForTesting
- Stream writeTimeGauge(TimeGauge gauge) {
- Double value = gauge.value(getBaseTimeUnit());
- if (Double.isFinite(value)) {
- return Stream.of(
- event(gauge.getId(),
- new Attribute("value", value),
- new Attribute("timeUnit", getBaseTimeUnit().name().toLowerCase())));
- }
- return Stream.empty();
- }
-
- private Stream writeSummary(DistributionSummary summary) {
- return Stream.of(
- event(summary.getId(),
- new Attribute("count", summary.count()),
- new Attribute("avg", summary.mean()),
- new Attribute("total", summary.totalAmount()),
- new Attribute("max", summary.max())
- )
- );
- }
-
- private Stream writeTimer(Timer timer) {
- return Stream.of(event(timer.getId(),
- new Attribute("count", timer.count()),
- new Attribute("avg", timer.mean(getBaseTimeUnit())),
- new Attribute("totalTime", timer.totalTime(getBaseTimeUnit())),
- new Attribute("max", timer.max(getBaseTimeUnit())),
- new Attribute("timeUnit", getBaseTimeUnit().name().toLowerCase())
- ));
- }
-
- private Stream writeFunctionTimer(FunctionTimer timer) {
- return Stream.of(
- event(timer.getId(),
- new Attribute("count", timer.count()),
- new Attribute("avg", timer.mean(getBaseTimeUnit())),
- new Attribute("totalTime", timer.totalTime(getBaseTimeUnit())),
- new Attribute("timeUnit", getBaseTimeUnit().name().toLowerCase())
- )
- );
- }
-
- // VisibleForTesting
- Stream writeMeter(Meter meter) {
- // Snapshot values should be used throughout this method as there are chances for values to be changed in-between.
- Map attributes = new HashMap<>();
- for (Measurement measurement : meter.measure()) {
- double value = measurement.getValue();
- if (!Double.isFinite(value)) {
- continue;
- }
- String name = measurement.getStatistic().getTagValueRepresentation();
- attributes.put(name, new Attribute(name, value));
- }
- if (attributes.isEmpty()) {
- return Stream.empty();
- }
- return Stream.of(event(meter.getId(), attributes.values().toArray(new Attribute[0])));
- }
-
- private String event(Meter.Id id, Attribute... attributes) {
- if (!config.meterNameEventTypeEnabled()) {
- // Include contextual attributes when publishing all metrics under a single categorical eventType,
- // NOT when publishing an eventType per Meter/metric name
- int size = attributes.length;
- Attribute[] newAttrs = Arrays.copyOf(attributes, size + 2);
-
- String name = id.getConventionName(config().namingConvention());
- newAttrs[size] = new Attribute("metricName", name);
- newAttrs[size + 1] = new Attribute("metricType", id.getType().toString());
-
- return event(id, Tags.empty(), newAttrs);
- }
- return event(id, Tags.empty(), attributes);
- }
-
- private String event(Meter.Id id, Iterable extraTags, Attribute... attributes) {
- StringBuilder tagsJson = new StringBuilder();
-
- for (Tag tag : getConventionTags(id)) {
- tagsJson.append(",\"").append(escapeJson(tag.getKey())).append("\":\"").append(escapeJson(tag.getValue())).append("\"");
- }
-
- NamingConvention convention = config().namingConvention();
- for (Tag tag : extraTags) {
- tagsJson.append(",\"").append(escapeJson(convention.tagKey(tag.getKey())))
- .append("\":\"").append(escapeJson(convention.tagValue(tag.getValue()))).append("\"");
- }
-
- String eventType = getEventType(id);
-
- return Arrays.stream(attributes)
- .map(attr ->
- (attr.getValue() instanceof Number)
- ? ",\"" + attr.getName() + "\":" + DoubleFormat.wholeOrDecimal(((Number)attr.getValue()).doubleValue())
- : ",\"" + attr.getName() + "\":\"" + convention.tagValue(attr.getValue().toString()) + "\""
- )
- .collect(Collectors.joining("", "{\"eventType\":\"" + escapeJson(eventType) + "\"", tagsJson + "}"));
- }
-
- private String getEventType(Meter.Id id) {
- if (config.meterNameEventTypeEnabled()) {
- return id.getConventionName(config().namingConvention());
- } else {
- return config.eventType();
- }
- }
-
- private void sendEvents(String insightsEndpoint, Stream events) {
- try {
- AtomicInteger totalEvents = new AtomicInteger();
-
- httpClient.post(insightsEndpoint)
- .withHeader("X-Insert-Key", config.apiKey())
- .withJsonContent(events.peek(ev -> totalEvents.incrementAndGet()).collect(Collectors.joining(",", "[", "]")))
- .send()
- .onSuccess(response -> logger.debug("successfully sent {} metrics to New Relic.", totalEvents))
- .onError(response -> logger.error("failed to send metrics to new relic: http {} {}", response.code(), response.body()));
- } catch (Throwable e) {
- logger.warn("failed to send metrics to new relic", e);
- }
+ clientProvider.publish(this);
}
@Override
@@ -290,14 +102,31 @@ protected TimeUnit getBaseTimeUnit() {
public static class Builder {
private final NewRelicConfig config;
+ private NewRelicClientProvider clientProvider;
+ private NamingConvention convention = new NewRelicNamingConvention();
private Clock clock = Clock.SYSTEM;
private ThreadFactory threadFactory = DEFAULT_THREAD_FACTORY;
- private HttpSender httpClient;
- @SuppressWarnings("deprecation")
Builder(NewRelicConfig config) {
this.config = config;
- this.httpClient = new HttpUrlConnectionSender(config.connectTimeout(), config.readTimeout());
+ }
+
+ public Builder agentClientProvider() {
+ return clientProvider(new NewRelicAgentClientProviderImpl(config));
+ }
+
+ public Builder httpClientProvider() {
+ return clientProvider(new NewRelicHttpClientProviderImpl(config));
+ }
+
+ Builder clientProvider(NewRelicClientProvider clientProvider) {
+ this.clientProvider = clientProvider;
+ return this;
+ }
+
+ public Builder namingConvention(NamingConvention convention) {
+ this.convention = convention;
+ return this;
}
public Builder clock(Clock clock) {
@@ -310,31 +139,12 @@ public Builder threadFactory(ThreadFactory threadFactory) {
return this;
}
- public Builder httpClient(HttpSender httpClient) {
- this.httpClient = httpClient;
- return this;
- }
-
public NewRelicMeterRegistry build() {
- return new NewRelicMeterRegistry(config, clock, threadFactory, httpClient);
- }
- }
-
- private class Attribute {
- private final String name;
- private final Object value;
-
- private Attribute(String name, Object value) {
- this.name = name;
- this.value = value;
- }
-
- public String getName() {
- return name;
- }
-
- public Object getValue() {
- return value;
+ if (clientProvider == null) {
+ //default to the HTTP/REST client
+ clientProvider = new NewRelicHttpClientProviderImpl(config);
+ }
+ return new NewRelicMeterRegistry(config, clientProvider, convention, clock, threadFactory);
}
}
}
diff --git a/implementations/micrometer-registry-new-relic/src/test/java/io/micrometer/newrelic/NewRelicMeterRegistryTest.java b/implementations/micrometer-registry-new-relic/src/test/java/io/micrometer/newrelic/NewRelicMeterRegistryTest.java
index 48fafbd88a..bd73ffcbd4 100644
--- a/implementations/micrometer-registry-new-relic/src/test/java/io/micrometer/newrelic/NewRelicMeterRegistryTest.java
+++ b/implementations/micrometer-registry-new-relic/src/test/java/io/micrometer/newrelic/NewRelicMeterRegistryTest.java
@@ -19,33 +19,56 @@
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
+import com.newrelic.api.agent.Agent;
+import com.newrelic.api.agent.Config;
+import com.newrelic.api.agent.Insights;
+import com.newrelic.api.agent.Logger;
+import com.newrelic.api.agent.MetricAggregator;
+import com.newrelic.api.agent.TraceMetadata;
+import com.newrelic.api.agent.TracedMethod;
+import com.newrelic.api.agent.Transaction;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.FunctionCounter;
+import io.micrometer.core.instrument.FunctionTimer;
import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.LongTaskTimer;
import io.micrometer.core.instrument.Measurement;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MockClock;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.TimeGauge;
+import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.config.MissingRequiredConfigurationException;
-import io.micrometer.core.instrument.util.NamedThreadFactory;
import io.micrometer.core.ipc.http.HttpSender;
+import io.micrometer.newrelic.NewRelicMeterRegistryTest.MockNewRelicAgent.MockNewRelicInsights;
/**
* Tests for {@link NewRelicMeterRegistry}.
*
* @author Johnny Lim
+ * @author Neil Powell
*/
class NewRelicMeterRegistryTest {
- private final NewRelicConfig config = new NewRelicConfig() {
-
+ private final NewRelicConfig agentConfig = new NewRelicConfig() {
+ @Override
+ public String get(String key) {
+ return null;
+ }
+ };
+
+ private final NewRelicConfig httpConfig = new NewRelicConfig() {
@Override
public String get(String key) {
return null;
@@ -55,22 +78,21 @@ public String get(String key) {
public String accountId() {
return "accountId";
}
-
+
@Override
public String apiKey() {
return "apiKey";
}
-
};
-
+
private final NewRelicConfig meterNameEventTypeEnabledConfig = new NewRelicConfig() {
-
+
@Override
public boolean meterNameEventTypeEnabled() {
// Previous behavior for backward compatibility
return true;
}
-
+
@Override
public String get(String key) {
return null;
@@ -80,173 +102,338 @@ public String get(String key) {
public String accountId() {
return "accountId";
}
-
+
@Override
public String apiKey() {
return "apiKey";
}
-
};
private final MockClock clock = new MockClock();
- private final NewRelicMeterRegistry meterNameEventTypeEnabledRegistry = new NewRelicMeterRegistry(meterNameEventTypeEnabledConfig, clock);
- private final NewRelicMeterRegistry registry = new NewRelicMeterRegistry(config, clock);
+ private final NewRelicMeterRegistry registry = new NewRelicMeterRegistry(httpConfig, new MockClientProvider(), clock);
+
+ NewRelicAgentClientProviderImpl getAgentClientProvider(NewRelicConfig config) {
+ return new NewRelicAgentClientProviderImpl(config);
+ }
+ NewRelicHttpClientProviderImpl getHttpClientProvider(NewRelicConfig config) {
+ return new NewRelicHttpClientProviderImpl(config);
+ }
@Test
void writeGauge() {
- writeGauge(this.meterNameEventTypeEnabledRegistry, "{\"eventType\":\"myGauge\",\"value\":1}");
- writeGauge(this.registry,
+ //test Http clientProvider
+ writeGauge(meterNameEventTypeEnabledConfig, "{\"eventType\":\"myGauge\",\"value\":1}");
+ writeGauge(httpConfig,
"{\"eventType\":\"MicrometerSample\",\"value\":1,\"metricName\":\"myGauge\",\"metricType\":\"GAUGE\"}");
+
+ //test Agent clientProvider
+ Map expectedEntries = new HashMap<>();
+ expectedEntries.put("value", 1);
+ writeGauge(meterNameEventTypeEnabledConfig, expectedEntries);
+ expectedEntries.put("metricName", "myGauge2");
+ expectedEntries.put("metricType", "GAUGE");
+ writeGauge(agentConfig, expectedEntries);
}
- private void writeGauge(NewRelicMeterRegistry meterRegistry, String expectedJson) {
- meterRegistry.gauge("my.gauge", 1d);
- Gauge gauge = meterRegistry.find("my.gauge").gauge();
- assertThat(meterRegistry.writeGauge(gauge)).containsExactly(expectedJson);
+ private void writeGauge(NewRelicConfig config, String expectedJson) {
+ registry.gauge("my.gauge", 1d);
+ Gauge gauge = registry.find("my.gauge").gauge();
+ assertThat(getHttpClientProvider(config).writeGauge(gauge)).containsExactly(expectedJson);
+ }
+
+ private void writeGauge(NewRelicConfig config, Map expectedEntries) {
+ registry.gauge("my.gauge2", 1d);
+ Gauge gauge = registry.find("my.gauge2").gauge();
+ Map result = getAgentClientProvider(config).writeGauge(gauge);
+ assertThat(result).hasSize(expectedEntries.size());
+ assertThat(result).containsExactlyEntriesOf(expectedEntries);
}
+
@Test
void writeGaugeShouldDropNanValue() {
- writeGaugeShouldDropNanValue(this.meterNameEventTypeEnabledRegistry);
- writeGaugeShouldDropNanValue(this.registry);
+ //test Http clientProvider
+ writeGaugeShouldDropNanValue(getHttpClientProvider(meterNameEventTypeEnabledConfig));
+ writeGaugeShouldDropNanValue(getHttpClientProvider(httpConfig));
+
+ //test Agent clientProvider
+ writeGaugeShouldDropNanValue(getAgentClientProvider(meterNameEventTypeEnabledConfig));
+ writeGaugeShouldDropNanValue(getAgentClientProvider(agentConfig));
}
-
- private void writeGaugeShouldDropNanValue(NewRelicMeterRegistry meterRegistry) {
- meterRegistry.gauge("my.gauge", Double.NaN);
- Gauge gauge = meterRegistry.find("my.gauge").gauge();
- assertThat(meterRegistry.writeGauge(gauge)).isEmpty();
+
+ private void writeGaugeShouldDropNanValue(NewRelicHttpClientProviderImpl clientProvider) {
+ registry.gauge("my.gauge", Double.NaN);
+ Gauge gauge = registry.find("my.gauge").gauge();
+ assertThat(clientProvider.writeGauge(gauge)).isEmpty();
}
+
+ private void writeGaugeShouldDropNanValue(NewRelicAgentClientProviderImpl clientProvider) {
+ registry.gauge("my.gauge2", Double.NaN);
+ Gauge gauge = registry.find("my.gauge2").gauge();
+ assertThat(clientProvider.writeGauge(gauge)).isEmpty();
+ }
@Test
void writeGaugeShouldDropInfiniteValues() {
- writeGaugeShouldDropInfiniteValues(this.meterNameEventTypeEnabledRegistry);
- writeGaugeShouldDropInfiniteValues(this.registry);
+ //test Http clientProvider
+ writeGaugeShouldDropInfiniteValues(getHttpClientProvider(meterNameEventTypeEnabledConfig));
+ writeGaugeShouldDropInfiniteValues(getHttpClientProvider(httpConfig));
+
+ //test Agent clientProvider
+ writeGaugeShouldDropInfiniteValues(getAgentClientProvider(meterNameEventTypeEnabledConfig));
+ writeGaugeShouldDropInfiniteValues(getAgentClientProvider(agentConfig));
}
- private void writeGaugeShouldDropInfiniteValues(NewRelicMeterRegistry meterRegistry) {
- meterRegistry.gauge("my.gauge", Double.POSITIVE_INFINITY);
- Gauge gauge = meterRegistry.find("my.gauge").gauge();
- assertThat(meterRegistry.writeGauge(gauge)).isEmpty();
+ private void writeGaugeShouldDropInfiniteValues(NewRelicHttpClientProviderImpl clientProvider) {
+ registry.gauge("my.gauge", Double.POSITIVE_INFINITY);
+ Gauge gauge = registry.find("my.gauge").gauge();
+ assertThat(clientProvider.writeGauge(gauge)).isEmpty();
- meterRegistry.gauge("my.gauge", Double.NEGATIVE_INFINITY);
- gauge = meterRegistry.find("my.gauge").gauge();
- assertThat(meterRegistry.writeGauge(gauge)).isEmpty();
+ registry.gauge("my.gauge", Double.NEGATIVE_INFINITY);
+ gauge = registry.find("my.gauge").gauge();
+ assertThat(clientProvider.writeGauge(gauge)).isEmpty();
}
-
+
+ private void writeGaugeShouldDropInfiniteValues(NewRelicAgentClientProviderImpl clientProvider) {
+ registry.gauge("my.gauge2", Double.POSITIVE_INFINITY);
+ Gauge gauge = registry.find("my.gauge2").gauge();
+ assertThat(clientProvider.writeGauge(gauge)).isEmpty();
+
+ registry.gauge("my.gauge2", Double.NEGATIVE_INFINITY);
+ gauge = registry.find("my.gauge2").gauge();
+ assertThat(clientProvider.writeGauge(gauge)).isEmpty();
+ }
+
@Test
void writeGaugeWithTimeGauge() {
- writeGaugeWithTimeGauge(this.meterNameEventTypeEnabledRegistry,
+ //test Http clientProvider
+ writeGaugeWithTimeGauge(getHttpClientProvider(meterNameEventTypeEnabledConfig),
"{\"eventType\":\"myTimeGauge\",\"value\":1,\"timeUnit\":\"seconds\"}");
- writeGaugeWithTimeGauge(this.registry,
+ writeGaugeWithTimeGauge(getHttpClientProvider(httpConfig),
"{\"eventType\":\"MicrometerSample\",\"value\":1,\"timeUnit\":\"seconds\",\"metricName\":\"myTimeGauge\",\"metricType\":\"GAUGE\"}");
+
+ //test Agent clientProvider
+ Map expectedEntries = new HashMap<>();
+ expectedEntries.put("value", 1);
+ expectedEntries.put("timeUnit", "seconds");
+ writeGaugeWithTimeGauge(getAgentClientProvider(meterNameEventTypeEnabledConfig), expectedEntries);
+ expectedEntries.put("metricName", "myTimeGauge2");
+ expectedEntries.put("metricType", "GAUGE");
+ writeGaugeWithTimeGauge(getAgentClientProvider(agentConfig), expectedEntries);
}
-
- private void writeGaugeWithTimeGauge(NewRelicMeterRegistry meterRegistry, String expectedJson) {
+
+ private void writeGaugeWithTimeGauge(NewRelicHttpClientProviderImpl clientProvider, String expectedJson) {
AtomicReference obj = new AtomicReference<>(1d);
- meterRegistry.more().timeGauge("my.timeGauge", Tags.empty(), obj, TimeUnit.SECONDS, AtomicReference::get);
- TimeGauge timeGauge = meterRegistry.find("my.timeGauge").timeGauge();
- assertThat(meterRegistry.writeTimeGauge(timeGauge)).containsExactly(expectedJson);
+ registry.more().timeGauge("my.timeGauge", Tags.empty(), obj, TimeUnit.SECONDS, AtomicReference::get);
+ TimeGauge timeGauge = registry.find("my.timeGauge").timeGauge();
+ assertThat(clientProvider.writeTimeGauge(timeGauge)).containsExactly(expectedJson);
}
-
+
+ private void writeGaugeWithTimeGauge(NewRelicAgentClientProviderImpl clientProvider, Map expectedEntries) {
+ AtomicReference obj = new AtomicReference<>(1d);
+ registry.more().timeGauge("my.timeGauge2", Tags.empty(), obj, TimeUnit.SECONDS, AtomicReference::get);
+ TimeGauge timeGauge = registry.find("my.timeGauge2").timeGauge();
+ Map result = clientProvider.writeTimeGauge(timeGauge);
+ assertThat(result).hasSize(expectedEntries.size());
+ assertThat(result).containsExactlyEntriesOf(expectedEntries);
+ }
+
@Test
void writeGaugeWithTimeGaugeShouldDropNanValue() {
- writeGaugeWithTimeGaugeShouldDropNanValue(this.meterNameEventTypeEnabledRegistry);
- writeGaugeWithTimeGaugeShouldDropNanValue(this.registry);
+ //test Http clientProvider
+ writeGaugeWithTimeGaugeShouldDropNanValue(getHttpClientProvider(meterNameEventTypeEnabledConfig));
+ writeGaugeWithTimeGaugeShouldDropNanValue(getHttpClientProvider(httpConfig));
+
+ //test Agent clientProvider
+ writeGaugeWithTimeGaugeShouldDropNanValue(getAgentClientProvider(meterNameEventTypeEnabledConfig));
+ writeGaugeWithTimeGaugeShouldDropNanValue(getAgentClientProvider(agentConfig));
}
-
- private void writeGaugeWithTimeGaugeShouldDropNanValue(NewRelicMeterRegistry meterRegistry) {
+
+ private void writeGaugeWithTimeGaugeShouldDropNanValue(NewRelicHttpClientProviderImpl clientProvider) {
+ AtomicReference obj = new AtomicReference<>(Double.NaN);
+ registry.more().timeGauge("my.timeGauge", Tags.empty(), obj, TimeUnit.SECONDS, AtomicReference::get);
+ TimeGauge timeGauge = registry.find("my.timeGauge").timeGauge();
+ assertThat(clientProvider.writeTimeGauge(timeGauge)).isEmpty();
+ }
+
+ private void writeGaugeWithTimeGaugeShouldDropNanValue(NewRelicAgentClientProviderImpl clientProvider) {
AtomicReference obj = new AtomicReference<>(Double.NaN);
- meterRegistry.more().timeGauge("my.timeGauge", Tags.empty(), obj, TimeUnit.SECONDS, AtomicReference::get);
- TimeGauge timeGauge = meterRegistry.find("my.timeGauge").timeGauge();
- assertThat(meterRegistry.writeTimeGauge(timeGauge)).isEmpty();
+ registry.more().timeGauge("my.timeGauge2", Tags.empty(), obj, TimeUnit.SECONDS, AtomicReference::get);
+ TimeGauge timeGauge = registry.find("my.timeGauge2").timeGauge();
+ assertThat(clientProvider.writeTimeGauge(timeGauge)).isEmpty();
}
@Test
void writeGaugeWithTimeGaugeShouldDropInfiniteValues() {
- writeGaugeWithTimeGaugeShouldDropInfiniteValues(this.meterNameEventTypeEnabledRegistry);
- writeGaugeWithTimeGaugeShouldDropInfiniteValues(this.registry);
+ //test Http clientProvider
+ writeGaugeWithTimeGaugeShouldDropInfiniteValues(getHttpClientProvider(meterNameEventTypeEnabledConfig));
+ writeGaugeWithTimeGaugeShouldDropInfiniteValues(getHttpClientProvider(httpConfig));
+
+ //test Agent clientProvider
+ writeGaugeWithTimeGaugeShouldDropInfiniteValues(getAgentClientProvider(meterNameEventTypeEnabledConfig));
+ writeGaugeWithTimeGaugeShouldDropInfiniteValues(getAgentClientProvider(agentConfig));
}
+
+ private void writeGaugeWithTimeGaugeShouldDropInfiniteValues(NewRelicHttpClientProviderImpl clientProvider) {
+ AtomicReference obj = new AtomicReference<>(Double.POSITIVE_INFINITY);
+ registry.more().timeGauge("my.timeGauge", Tags.empty(), obj, TimeUnit.SECONDS, AtomicReference::get);
+ TimeGauge timeGauge = registry.find("my.timeGauge").timeGauge();
+ assertThat(clientProvider.writeTimeGauge(timeGauge)).isEmpty();
- private void writeGaugeWithTimeGaugeShouldDropInfiniteValues(NewRelicMeterRegistry meterRegistry) {
+ obj = new AtomicReference<>(Double.NEGATIVE_INFINITY);
+ registry.more().timeGauge("my.timeGauge", Tags.empty(), obj, TimeUnit.SECONDS, AtomicReference::get);
+ timeGauge = registry.find("my.timeGauge").timeGauge();
+ assertThat(clientProvider.writeTimeGauge(timeGauge)).isEmpty();
+ }
+
+ private void writeGaugeWithTimeGaugeShouldDropInfiniteValues(NewRelicAgentClientProviderImpl clientProvider) {
AtomicReference obj = new AtomicReference<>(Double.POSITIVE_INFINITY);
- meterRegistry.more().timeGauge("my.timeGauge", Tags.empty(), obj, TimeUnit.SECONDS, AtomicReference::get);
- TimeGauge timeGauge = meterRegistry.find("my.timeGauge").timeGauge();
- assertThat(meterRegistry.writeTimeGauge(timeGauge)).isEmpty();
+ registry.more().timeGauge("my.timeGauge", Tags.empty(), obj, TimeUnit.SECONDS, AtomicReference::get);
+ TimeGauge timeGauge = registry.find("my.timeGauge").timeGauge();
+ assertThat(clientProvider.writeTimeGauge(timeGauge)).isEmpty();
obj = new AtomicReference<>(Double.NEGATIVE_INFINITY);
- meterRegistry.more().timeGauge("my.timeGauge", Tags.empty(), obj, TimeUnit.SECONDS, AtomicReference::get);
- timeGauge = meterRegistry.find("my.timeGauge").timeGauge();
- assertThat(meterRegistry.writeTimeGauge(timeGauge)).isEmpty();
+ registry.more().timeGauge("my.timeGauge2", Tags.empty(), obj, TimeUnit.SECONDS, AtomicReference::get);
+ timeGauge = registry.find("my.timeGauge2").timeGauge();
+ assertThat(clientProvider.writeTimeGauge(timeGauge)).isEmpty();
}
@Test
void writeCounterWithFunctionCounter() {
- writeCounterWithFunctionCounter(this.meterNameEventTypeEnabledRegistry,
+ FunctionCounter counter = FunctionCounter.builder("myCounter", 1d, Number::doubleValue).register(registry);
+ clock.add(httpConfig.step());
+ //test Http clientProvider
+ writeCounterWithFunctionCounter(counter, getHttpClientProvider(meterNameEventTypeEnabledConfig),
"{\"eventType\":\"myCounter\",\"throughput\":1}");
- writeCounterWithFunctionCounter(this.registry,
+ writeCounterWithFunctionCounter(counter, getHttpClientProvider(httpConfig),
"{\"eventType\":\"MicrometerSample\",\"throughput\":1,\"metricName\":\"myCounter\",\"metricType\":\"COUNTER\"}");
+
+ //test Agent clientProvider
+ Map expectedEntries = new HashMap<>();
+ expectedEntries.put("throughput", 1);
+ writeCounterWithFunctionCounter(counter, getAgentClientProvider(meterNameEventTypeEnabledConfig), expectedEntries);
+ expectedEntries.put("metricName", "myCounter");
+ expectedEntries.put("metricType", "COUNTER");
+ writeCounterWithFunctionCounter(counter, getAgentClientProvider(agentConfig), expectedEntries);
}
- private void writeCounterWithFunctionCounter(NewRelicMeterRegistry meterRegistry, String expectedJson) {
- FunctionCounter counter = FunctionCounter.builder("myCounter", 1d, Number::doubleValue).register(meterRegistry);
- clock.add(config.step());
- assertThat(meterRegistry.writeFunctionCounter(counter)).containsExactly(expectedJson);
+ private void writeCounterWithFunctionCounter(FunctionCounter counter, NewRelicHttpClientProviderImpl clientProvider, String expectedJson) {
+ assertThat(clientProvider.writeFunctionCounter(counter)).containsExactly(expectedJson);
}
-
+
+ private void writeCounterWithFunctionCounter(FunctionCounter counter, NewRelicAgentClientProviderImpl clientProvider, Map expectedEntries) {
+ Map result = clientProvider.writeFunctionCounter(counter);
+ assertThat(result).hasSize(expectedEntries.size());
+ assertThat(result).containsExactlyEntriesOf(expectedEntries);
+ }
+
@Test
void writeCounterWithFunctionCounterShouldDropInfiniteValues() {
- writeCounterWithFunctionCounterShouldDropInfiniteValues(this.meterNameEventTypeEnabledRegistry);
- writeCounterWithFunctionCounterShouldDropInfiniteValues(this.registry);
+ //test Http clientProvider
+ writeCounterWithFunctionCounterShouldDropInfiniteValues(getHttpClientProvider(meterNameEventTypeEnabledConfig));
+ writeCounterWithFunctionCounterShouldDropInfiniteValues(getHttpClientProvider(httpConfig));
+
+ //test Agent clientProvider
+ writeCounterWithFunctionCounterShouldDropInfiniteValues(getAgentClientProvider(meterNameEventTypeEnabledConfig));
+ writeCounterWithFunctionCounterShouldDropInfiniteValues(getAgentClientProvider(agentConfig));
}
- private void writeCounterWithFunctionCounterShouldDropInfiniteValues(NewRelicMeterRegistry meterRegistry) {
+ private void writeCounterWithFunctionCounterShouldDropInfiniteValues(NewRelicHttpClientProviderImpl clientProvider) {
+ FunctionCounter counter = FunctionCounter.builder("myCounter", Double.POSITIVE_INFINITY, Number::doubleValue)
+ .register(registry);
+ clock.add(httpConfig.step());
+ assertThat(clientProvider.writeFunctionCounter(counter)).isEmpty();
+
+ counter = FunctionCounter.builder("myCounter", Double.NEGATIVE_INFINITY, Number::doubleValue)
+ .register(registry);
+ clock.add(httpConfig.step());
+ assertThat(clientProvider.writeFunctionCounter(counter)).isEmpty();
+ }
+
+ private void writeCounterWithFunctionCounterShouldDropInfiniteValues(NewRelicAgentClientProviderImpl clientProvider) {
FunctionCounter counter = FunctionCounter.builder("myCounter", Double.POSITIVE_INFINITY, Number::doubleValue)
- .register(meterRegistry);
- clock.add(config.step());
- assertThat(meterRegistry.writeFunctionCounter(counter)).isEmpty();
+ .register(registry);
+ clock.add(httpConfig.step());
+ assertThat(clientProvider.writeFunctionCounter(counter)).isEmpty();
counter = FunctionCounter.builder("myCounter", Double.NEGATIVE_INFINITY, Number::doubleValue)
- .register(meterRegistry);
- clock.add(config.step());
- assertThat(meterRegistry.writeFunctionCounter(counter)).isEmpty();
+ .register(registry);
+ clock.add(httpConfig.step());
+ assertThat(clientProvider.writeFunctionCounter(counter)).isEmpty();
}
@Test
void writeMeterWhenCustomMeterHasOnlyNonFiniteValuesShouldNotBeWritten() {
- writeMeterWhenCustomMeterHasOnlyNonFiniteValuesShouldNotBeWritten(this.meterNameEventTypeEnabledRegistry);
- writeMeterWhenCustomMeterHasOnlyNonFiniteValuesShouldNotBeWritten(this.registry);
- }
-
- private void writeMeterWhenCustomMeterHasOnlyNonFiniteValuesShouldNotBeWritten(
- NewRelicMeterRegistry meterRegistry) {
Measurement measurement1 = new Measurement(() -> Double.POSITIVE_INFINITY, Statistic.VALUE);
Measurement measurement2 = new Measurement(() -> Double.NEGATIVE_INFINITY, Statistic.VALUE);
Measurement measurement3 = new Measurement(() -> Double.NaN, Statistic.VALUE);
List measurements = Arrays.asList(measurement1, measurement2, measurement3);
- Meter meter = Meter.builder("my.meter", Meter.Type.GAUGE, measurements).register(meterRegistry);
- assertThat(meterRegistry.writeMeter(meter)).isEmpty();
+
+ //test Http clientProvider
+ writeMeterWhenCustomMeterHasOnlyNonFiniteValuesShouldNotBeWritten(
+ measurements, getHttpClientProvider(meterNameEventTypeEnabledConfig));
+ writeMeterWhenCustomMeterHasOnlyNonFiniteValuesShouldNotBeWritten(
+ measurements, getHttpClientProvider(httpConfig));
+
+ //test Agent clientProvider
+ writeMeterWhenCustomMeterHasOnlyNonFiniteValuesShouldNotBeWritten(
+ measurements, getAgentClientProvider(meterNameEventTypeEnabledConfig));
+ writeMeterWhenCustomMeterHasOnlyNonFiniteValuesShouldNotBeWritten(
+ measurements, getAgentClientProvider(agentConfig));
}
- @Test
- void writeMeterWhenCustomMeterHasMixedFiniteAndNonFiniteValuesShouldSkipOnlyNonFiniteValues() {
- writeMeterWhenCustomMeterHasMixedFiniteAndNonFiniteValuesShouldSkipOnlyNonFiniteValues(
- this.meterNameEventTypeEnabledRegistry, "{\"eventType\":\"myMeter\",\"value\":1}");
- writeMeterWhenCustomMeterHasMixedFiniteAndNonFiniteValuesShouldSkipOnlyNonFiniteValues(
- this.registry,
- "{\"eventType\":\"MicrometerSample\",\"value\":1,\"metricName\":\"myMeter\",\"metricType\":\"GAUGE\"}");
+ private void writeMeterWhenCustomMeterHasOnlyNonFiniteValuesShouldNotBeWritten(
+ List measurements, NewRelicHttpClientProviderImpl clientProvider) {
+ Meter meter = Meter.builder("my.meter", Meter.Type.GAUGE, measurements).register(registry);
+ assertThat(clientProvider.writeMeter(meter)).isEmpty();
+ }
+
+ private void writeMeterWhenCustomMeterHasOnlyNonFiniteValuesShouldNotBeWritten(
+ List measurements, NewRelicAgentClientProviderImpl clientProvider) {
+ Meter meter = Meter.builder("my.meter2", Meter.Type.GAUGE, measurements).register(registry);
+ assertThat(clientProvider.writeMeter(meter)).isEmpty();
}
- private void writeMeterWhenCustomMeterHasMixedFiniteAndNonFiniteValuesShouldSkipOnlyNonFiniteValues(
- NewRelicMeterRegistry meterRegistry, String expectedJson) {
+ @Test
+ void writeMeterWhenCustomMeterHasMixedFiniteAndNonFiniteValuesShouldSkipOnlyNonFiniteValues() {
Measurement measurement1 = new Measurement(() -> Double.POSITIVE_INFINITY, Statistic.VALUE);
Measurement measurement2 = new Measurement(() -> Double.NEGATIVE_INFINITY, Statistic.VALUE);
Measurement measurement3 = new Measurement(() -> Double.NaN, Statistic.VALUE);
Measurement measurement4 = new Measurement(() -> 1d, Statistic.VALUE);
List measurements = Arrays.asList(measurement1, measurement2, measurement3, measurement4);
- Meter meter = Meter.builder("my.meter", Meter.Type.GAUGE, measurements).register(meterRegistry);
- assertThat(meterRegistry.writeMeter(meter)).containsExactly(expectedJson);
+ //test Http clientProvider
+ writeMeterWhenCustomMeterHasMixedFiniteAndNonFiniteValuesShouldSkipOnlyNonFiniteValues(
+ measurements, getHttpClientProvider(meterNameEventTypeEnabledConfig),
+ "{\"eventType\":\"myMeter\",\"value\":1}");
+ writeMeterWhenCustomMeterHasMixedFiniteAndNonFiniteValuesShouldSkipOnlyNonFiniteValues(
+ measurements, getHttpClientProvider(httpConfig),
+ "{\"eventType\":\"MicrometerSample\",\"value\":1,\"metricName\":\"myMeter\",\"metricType\":\"GAUGE\"}");
+
+ //test Agent clientProvider
+ Map expectedEntries = new HashMap<>();
+ expectedEntries.put("value", 1);
+ writeMeterWhenCustomMeterHasMixedFiniteAndNonFiniteValuesShouldSkipOnlyNonFiniteValues(
+ measurements, getAgentClientProvider(meterNameEventTypeEnabledConfig), expectedEntries);
+ expectedEntries.put("metricName", "myMeter2");
+ expectedEntries.put("metricType", "GAUGE");
+ writeMeterWhenCustomMeterHasMixedFiniteAndNonFiniteValuesShouldSkipOnlyNonFiniteValues(
+ measurements, getAgentClientProvider(agentConfig), expectedEntries);
}
+ private void writeMeterWhenCustomMeterHasMixedFiniteAndNonFiniteValuesShouldSkipOnlyNonFiniteValues(
+ List measurements, NewRelicHttpClientProviderImpl clientProvider, String expectedJson) {
+ Meter meter = Meter.builder("my.meter", Meter.Type.GAUGE, measurements).register(registry);
+ assertThat(clientProvider.writeMeter(meter)).containsExactly(expectedJson);
+ }
+
+ private void writeMeterWhenCustomMeterHasMixedFiniteAndNonFiniteValuesShouldSkipOnlyNonFiniteValues(
+ List measurements, NewRelicAgentClientProviderImpl clientProvider, Map expectedEntries) {
+ Meter meter = Meter.builder("my.meter2", Meter.Type.GAUGE, measurements).register(registry);
+ Map result = clientProvider.writeMeter(meter);
+ assertThat(result).hasSize(expectedEntries.size());
+ assertThat(result).containsExactlyEntriesOf(expectedEntries);
+ }
+
@Test
void writeMeterWhenCustomMeterHasDuplicatesKeysShouldWriteOnlyLastValue() {
Measurement measurement1 = new Measurement(() -> 3d, Statistic.VALUE);
@@ -254,26 +441,152 @@ void writeMeterWhenCustomMeterHasDuplicatesKeysShouldWriteOnlyLastValue() {
Measurement measurement3 = new Measurement(() -> 2d, Statistic.VALUE);
List measurements = Arrays.asList(measurement1, measurement2, measurement3);
Meter meter = Meter.builder("my.meter", Meter.Type.GAUGE, measurements).register(this.registry);
- assertThat(registry.writeMeter(meter)).containsExactly("{\"eventType\":\"MicrometerSample\",\"value\":2,\"metricName\":\"myMeter\",\"metricType\":\"GAUGE\"}");
+ //test Http clientProvider
+ assertThat(getHttpClientProvider(httpConfig).writeMeter(meter)).containsExactly(
+ "{\"eventType\":\"MicrometerSample\",\"value\":2,\"metricName\":\"myMeter\",\"metricType\":\"GAUGE\"}");
+
+ //test Agent clientProvider
+ Map expectedEntries = new HashMap<>();
+ expectedEntries.put("value", 2);
+ expectedEntries.put("metricName", "myMeter");
+ expectedEntries.put("metricType", "GAUGE");
+ Map result = getAgentClientProvider(agentConfig).writeMeter(meter);
+ assertThat(result).hasSize(expectedEntries.size());
+ assertThat(result).containsExactlyEntriesOf(expectedEntries);
}
+
+
+
+ @Test
+ void sendEventsWithHttpProvider() {
+ //test meterNameEventTypeEnabledConfig = false (default)
+ MockHttpSender mockHttpClient = new MockHttpSender();
+ NewRelicHttpClientProviderImpl httpProvider = new NewRelicHttpClientProviderImpl(
+ httpConfig, mockHttpClient, registry.config().namingConvention());
+
+ NewRelicMeterRegistry registry = new NewRelicMeterRegistry(httpConfig, httpProvider, clock);
+
+ registry.gauge("my.gauge", 1d);
+ Gauge gauge = registry.find("my.gauge").gauge();
+
+ httpProvider.sendEvents(httpProvider.writeGauge(gauge));
+ assertThat(new String(mockHttpClient.getRequest().getEntity()))
+ .contains("{\"eventType\":\"MicrometerSample\",\"value\":1,\"metricName\":\"myGauge\",\"metricType\":\"GAUGE\"}");
+
+ //test meterNameEventTypeEnabledConfig = true
+ mockHttpClient = new MockHttpSender();
+ httpProvider = new NewRelicHttpClientProviderImpl(
+ meterNameEventTypeEnabledConfig, mockHttpClient, registry.config().namingConvention());
+
+ registry.gauge("my.gauge2", 1d);
+ gauge = registry.find("my.gauge2").gauge();
+
+ httpProvider.sendEvents(httpProvider.writeGauge(gauge));
+
+ assertThat(new String(mockHttpClient.getRequest().getEntity()))
+ .contains("{\"eventType\":\"myGauge2\",\"value\":1}");
+ }
+
@Test
- void publish() {
- MockHttpSender mockHttpSender = new MockHttpSender();
- NewRelicMeterRegistry registry = new NewRelicMeterRegistry(config, clock, new NamedThreadFactory("new-relic-test"), mockHttpSender);
+ void sendEventsWithAgentProvider() {
+ //test meterNameEventTypeEnabledConfig = false (default)
+ MockNewRelicAgent mockNewRelicAgent = new MockNewRelicAgent();
+ NewRelicAgentClientProviderImpl agentProvider = new NewRelicAgentClientProviderImpl(
+ agentConfig, mockNewRelicAgent, registry.config().namingConvention());
+
+ NewRelicMeterRegistry registry = new NewRelicMeterRegistry(agentConfig, agentProvider, clock);
registry.gauge("my.gauge", 1d);
Gauge gauge = registry.find("my.gauge").gauge();
+
+ agentProvider.sendEvents(gauge.getId(), agentProvider.writeGauge(gauge));
+
+ assertThat(((MockNewRelicInsights)mockNewRelicAgent.getInsights()).getInsightData().getEventType()).isEqualTo("MicrometerSample");
+ Map result = ((MockNewRelicInsights)mockNewRelicAgent.getInsights()).getInsightData().getAttributes();
+ assertThat(result).hasSize(3);
+
+ //test meterNameEventTypeEnabledConfig = true
+ mockNewRelicAgent = new MockNewRelicAgent();
+ agentProvider = new NewRelicAgentClientProviderImpl(
+ meterNameEventTypeEnabledConfig, mockNewRelicAgent, registry.config().namingConvention());
+
+ registry.gauge("my.gauge2", 1d);
+ gauge = registry.find("my.gauge2").gauge();
+
+ agentProvider.sendEvents(gauge.getId(), agentProvider.writeGauge(gauge));
+
+ assertThat(((MockNewRelicInsights)mockNewRelicAgent.getInsights()).getInsightData().getEventType()).isEqualTo("myGauge2");
+ result = ((MockNewRelicInsights)mockNewRelicAgent.getInsights()).getInsightData().getAttributes();
+ assertThat(result).hasSize(1);
+ }
+
+ @Test
+ void publishWithHttpClientProvider() {
+ //test meterNameEventTypeEnabledConfig = false (default)
+ MockHttpSender mockHttpClient = new MockHttpSender();
+ NewRelicHttpClientProviderImpl httpProvider = new NewRelicHttpClientProviderImpl(
+ httpConfig, mockHttpClient, registry.config().namingConvention());
+
+ NewRelicMeterRegistry registry = new NewRelicMeterRegistry(httpConfig, httpProvider, clock);
+
+ registry.gauge("my.gauge", Tags.of("theTag", "theValue"), 1d);
+ Gauge gauge = registry.find("my.gauge").gauge();
assertThat(gauge).isNotNull();
- registry.publish();
+ registry.gauge("other.gauge", 2d);
+ Gauge other = registry.find("other.gauge").gauge();
+ assertThat(other).isNotNull();
- assertThat(new String(mockHttpSender.getRequest().getEntity()))
- .contains("{\"eventType\":\"MicrometerSample\",\"value\":1,\"metricName\":\"myGauge\",\"metricType\":\"GAUGE\"}");
+ registry.publish();
+
+ //should send a batch of multiple in one json payload
+ assertThat(new String(mockHttpClient.getRequest().getEntity()))
+ .contains("[{\"eventType\":\"MicrometerSample\",\"value\":2,\"metricName\":\"otherGauge\",\"metricType\":\"GAUGE\"}," +
+ "{\"eventType\":\"MicrometerSample\",\"value\":1,\"metricName\":\"myGauge\",\"metricType\":\"GAUGE\",\"theTag\":\"theValue\"}]");
}
@Test
- void configMissingEventType() {
+ void publishWithAgentClientProvider() {
+ //test meterNameEventTypeEnabledConfig = false (default)
+ MockNewRelicAgent mockNewRelicAgent = new MockNewRelicAgent();
+ NewRelicAgentClientProviderImpl agentProvider = new NewRelicAgentClientProviderImpl(
+ agentConfig, mockNewRelicAgent, registry.config().namingConvention());
+
+ NewRelicMeterRegistry registry = new NewRelicMeterRegistry(agentConfig, agentProvider, clock);
+
+ registry.gauge("my.gauge", Tags.of("theTag", "theValue"), 1d);
+ Gauge gauge = registry.find("my.gauge").gauge();
+ assertThat(gauge).isNotNull();
+
+ registry.gauge("other.gauge", 2d);
+ Gauge other = registry.find("other.gauge").gauge();
+ assertThat(other).isNotNull();
+
+ registry.publish();
+
+ //should delegate to the Agent one at a time
+ assertThat(((MockNewRelicInsights)mockNewRelicAgent.getInsights()).getInsightData().getEventType()).isEqualTo("MicrometerSample");
+ Map result = ((MockNewRelicInsights)mockNewRelicAgent.getInsights()).getInsightData().getAttributes();
+ assertThat(result).hasSize(4);
+ }
+
+ @Test
+ void failsConfigMissingClientProvider() {
+ NewRelicConfig config = new NewRelicConfig() {
+ @Override
+ public String get(String key) {
+ return null;
+ }
+ };
+
+ assertThatThrownBy(() -> new NewRelicMeterRegistry(config, null, clock))
+ .isExactlyInstanceOf(MissingRequiredConfigurationException.class)
+ .hasMessageContaining("clientProvider");
+ }
+
+ @Test
+ void failsConfigHttpMissingEventType() {
NewRelicConfig config = new NewRelicConfig() {
@Override
public String eventType() {
@@ -285,13 +598,41 @@ public String get(String key) {
}
};
- assertThatThrownBy(() -> new NewRelicMeterRegistry(config, clock))
- .isExactlyInstanceOf(MissingRequiredConfigurationException.class)
- .hasMessageContaining("eventType");
+ assertThatThrownBy(() -> getHttpClientProvider(config))
+ .isExactlyInstanceOf(MissingRequiredConfigurationException.class)
+ .hasMessageContaining("eventType");
+ }
+
+ @Test
+ void succeedsConfigHttpMissingEventType() {
+ NewRelicConfig config = new NewRelicConfig() {
+ @Override
+ public boolean meterNameEventTypeEnabled() {
+ return true;
+ }
+ @Override
+ public String eventType() {
+ return "";
+ }
+ @Override
+ public String accountId() {
+ return "accountId";
+ }
+ @Override
+ public String apiKey() {
+ return "apiKey";
+ }
+ @Override
+ public String get(String key) {
+ return null;
+ }
+ };
+
+ assertThat( getHttpClientProvider(config) ).isNotNull();
}
@Test
- void configMissingAccountId() {
+ void failsConfigHttpMissingAccountId() {
NewRelicConfig config = new NewRelicConfig() {
@Override
public String eventType() {
@@ -306,14 +647,14 @@ public String get(String key) {
return null;
}
};
-
- assertThatThrownBy(() -> new NewRelicMeterRegistry(config, clock))
- .isExactlyInstanceOf(MissingRequiredConfigurationException.class)
- .hasMessageContaining("accountId");
+
+ assertThatThrownBy(() -> getHttpClientProvider(config))
+ .isExactlyInstanceOf(MissingRequiredConfigurationException.class)
+ .hasMessageContaining("accountId");
}
@Test
- void configMissingApiKey() {
+ void failsConfigHttpMissingApiKey() {
NewRelicConfig config = new NewRelicConfig() {
@Override
public String eventType() {
@@ -332,14 +673,14 @@ public String get(String key) {
return null;
}
};
-
- assertThatThrownBy(() -> new NewRelicMeterRegistry(config, clock))
- .isExactlyInstanceOf(MissingRequiredConfigurationException.class)
- .hasMessageContaining("apiKey");
+
+ assertThatThrownBy(() -> getHttpClientProvider(config))
+ .isExactlyInstanceOf(MissingRequiredConfigurationException.class)
+ .hasMessageContaining("apiKey");
}
@Test
- void configMissingUri() {
+ void failsConfigHttpMissingUri() {
NewRelicConfig config = new NewRelicConfig() {
@Override
public String eventType() {
@@ -362,18 +703,56 @@ public String get(String key) {
return null;
}
};
+
+ assertThatThrownBy(() -> getHttpClientProvider(config))
+ .isExactlyInstanceOf(MissingRequiredConfigurationException.class)
+ .hasMessageContaining("uri");
+ }
+
+ @Test
+ void failsConfigAgentMissingEventType() {
+ NewRelicConfig config = new NewRelicConfig() {
+ @Override
+ public String eventType() {
+ return "";
+ }
+ @Override
+ public String get(String key) {
+ return null;
+ }
+ };
+
+ assertThatThrownBy(() -> getAgentClientProvider(config))
+ .isExactlyInstanceOf(MissingRequiredConfigurationException.class)
+ .hasMessageContaining("eventType");
+ }
+
+ @Test
+ void succeedsConfigAgentMissingEventType() {
+ NewRelicConfig config = new NewRelicConfig() {
+ @Override
+ public boolean meterNameEventTypeEnabled() {
+ return true;
+ }
+ @Override
+ public String eventType() {
+ return "";
+ }
+ @Override
+ public String get(String key) {
+ return null;
+ }
+ };
- assertThatThrownBy(() -> new NewRelicMeterRegistry(config, clock))
- .isExactlyInstanceOf(MissingRequiredConfigurationException.class)
- .hasMessageContaining("uri");
+ assertThat( getAgentClientProvider(config) ).isNotNull();
}
- static class MockHttpSender implements HttpSender {
+ class MockHttpSender implements HttpSender {
private Request request;
@Override
- public Response send(Request request) {
+ public Response send(Request request) throws Throwable {
this.request = request;
return new Response(200, "body");
}
@@ -383,4 +762,158 @@ public Request getRequest() {
}
}
+ class MockClientProvider implements NewRelicClientProvider {
+
+ @Override
+ public void publish(NewRelicMeterRegistry meterRegistry) {
+ //No-op
+ }
+
+ @Override
+ public Object writeFunctionTimer(FunctionTimer timer) {
+ //No-op
+ return null;
+ }
+
+ @Override
+ public Object writeTimer(Timer timer) {
+ //No-op
+ return null;
+ }
+
+ @Override
+ public Object writeSummary(DistributionSummary summary) {
+ //No-op
+ return null;
+ }
+
+ @Override
+ public Object writeLongTaskTimer(LongTaskTimer timer) {
+ //No-op
+ return null;
+ }
+
+ @Override
+ public Object writeTimeGauge(TimeGauge gauge) {
+ //No-op
+ return null;
+ }
+
+ @Override
+ public Object writeGauge(Gauge gauge) {
+ //No-op
+ return null;
+ }
+
+ @Override
+ public Object writeCounter(Counter counter) {
+ //No-op
+ return null;
+ }
+
+ @Override
+ public Object writeFunctionCounter(FunctionCounter counter) {
+ //No-op
+ return null;
+ }
+
+ @Override
+ public Object writeMeter(Meter meter) {
+ //No-op
+ return null;
+ }
+
+ }
+
+ class MockNewRelicAgent implements Agent {
+
+ private final Insights insights;
+
+ public MockNewRelicAgent() {
+ this.insights = new MockNewRelicInsights();
+ }
+
+ @Override
+ public Config getConfig() {
+ //No-op
+ return null;
+ }
+
+ @Override
+ public Insights getInsights() {
+ return insights;
+ }
+
+ public class MockNewRelicInsights implements Insights {
+
+ private InsightData insightData;
+
+ public InsightData getInsightData() {
+ return insightData;
+ }
+
+ @Override
+ public void recordCustomEvent(String eventType, Map attributes) {
+ this.insightData = new InsightData(eventType, attributes);
+ }
+
+ public void setInsightData(InsightData insightData) {
+ this.insightData = insightData;
+ }
+
+ class InsightData {
+ private String eventType;
+ private Map attributes;
+
+ public InsightData(String eventType, Map attributes) {
+ this.eventType = eventType;
+ this.attributes = attributes;
+ }
+
+ public String getEventType() {
+ return eventType;
+ }
+ public Map getAttributes() {
+ return attributes;
+ }
+ }
+
+ }
+
+ @Override
+ public Logger getLogger() {
+ //No-op
+ return null;
+ }
+
+ @Override
+ public MetricAggregator getMetricAggregator() {
+ //No-op
+ return null;
+ }
+
+ @Override
+ public TracedMethod getTracedMethod() {
+ //No-op
+ return null;
+ }
+
+ @Override
+ public Transaction getTransaction() {
+ //No-op
+ return null;
+ }
+
+ @Override
+ public Map getLinkingMetadata() {
+ //No-op
+ return null;
+ }
+
+ @Override
+ public TraceMetadata getTraceMetadata() {
+ //No-op
+ return null;
+ }
+ }
}