Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for categorical "MicrometerSample" eventType to publish New Relic metrics. #1588

Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ default String prefix() {
return "newrelic";
}

default boolean meterNameEventTypeEnabled() {
String v = get(prefix() + ".meterNameEventTypeEnabled");
return (v == null) ? false : new Boolean(v);
}

default String eventType() {
String v = get(prefix() + ".eventType");
if (v == null)
v = "MicrometerSample";
return v;
}

default String apiKey() {
String v = get(prefix() + ".apiKey");
if (v == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.micrometer.core.instrument.util.TimeUtils;
import io.micrometer.core.ipc.http.HttpSender;
import io.micrometer.core.ipc.http.HttpUrlConnectionSender;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -73,15 +74,23 @@ public NewRelicMeterRegistry(NewRelicConfig config, Clock clock, ThreadFactory t
this(config, clock, threadFactory, new HttpUrlConnectionSender(config.connectTimeout(), config.readTimeout()));
}

private NewRelicMeterRegistry(NewRelicConfig config, Clock clock, ThreadFactory threadFactory, HttpSender httpClient) {
// VisibleForTesting
NewRelicMeterRegistry(NewRelicConfig config, Clock clock, ThreadFactory threadFactory, HttpSender httpClient) {
super(config, clock);

if (config.accountId() == null) {
if (config.meterNameEventTypeEnabled() == false
&& (config.eventType() == null || config.eventType().isEmpty())) {
throw new MissingRequiredConfigurationException("eventType must be set to report metrics to New Relic");
}
if (config.accountId() == null || config.accountId().isEmpty()) {
throw new MissingRequiredConfigurationException("accountId must be set to report metrics to New Relic");
}
if (config.apiKey() == null) {
if (config.apiKey() == null || config.apiKey().isEmpty()) {
throw new MissingRequiredConfigurationException("apiKey must be set to report metrics to New Relic");
}
if (config.uri() == null || config.uri().isEmpty()) {
throw new MissingRequiredConfigurationException("uri must be set to report metrics to New Relic");
}

this.config = config;
this.httpClient = httpClient;
Expand Down Expand Up @@ -126,7 +135,8 @@ private Stream<String> writeLongTaskTimer(LongTaskTimer ltt) {
return Stream.of(
event(ltt.getId(),
new Attribute("activeTasks", ltt.activeTasks()),
new Attribute("duration", ltt.duration(getBaseTimeUnit())))
new Attribute("duration", ltt.duration(getBaseTimeUnit())),
new Attribute("timeUnit", getBaseTimeUnit().name().toLowerCase()))
neiljpowell marked this conversation as resolved.
Show resolved Hide resolved
neiljpowell marked this conversation as resolved.
Show resolved Hide resolved
);
}

Expand All @@ -140,6 +150,7 @@ Stream<String> writeFunctionCounter(FunctionCounter counter) {
}

private Stream<String> writeCounter(Counter counter) {
//TODO: Double.isFinite() check here like writeFunctionCounter ???
return Stream.of(event(counter.getId(), new Attribute("throughput", counter.count())));
}

Expand All @@ -156,7 +167,10 @@ Stream<String> writeGauge(Gauge gauge) {
Stream<String> writeTimeGauge(TimeGauge gauge) {
Double value = gauge.value(getBaseTimeUnit());
if (Double.isFinite(value)) {
return Stream.of(event(gauge.getId(), new Attribute("value", value)));
return Stream.of(
event(gauge.getId(),
new Attribute("value", value),
new Attribute("timeUnit", getBaseTimeUnit().name().toLowerCase())));
neiljpowell marked this conversation as resolved.
Show resolved Hide resolved
}
return Stream.empty();
}
Expand All @@ -177,7 +191,8 @@ private Stream<String> writeTimer(Timer timer) {
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("max", timer.max(getBaseTimeUnit())),
new Attribute("timeUnit", getBaseTimeUnit().name().toLowerCase())
));
}

Expand All @@ -186,7 +201,8 @@ private Stream<String> writeFunctionTimer(FunctionTimer timer) {
event(timer.getId(),
new Attribute("count", timer.count()),
new Attribute("avg", timer.mean(getBaseTimeUnit())),
new Attribute("totalTime", timer.totalTime(getBaseTimeUnit()))
new Attribute("totalTime", timer.totalTime(getBaseTimeUnit())),
new Attribute("timeUnit", getBaseTimeUnit().name().toLowerCase())
)
);
}
Expand All @@ -209,6 +225,18 @@ Stream<String> writeMeter(Meter meter) {
}

private String event(Meter.Id id, Attribute... attributes) {
if (config.meterNameEventTypeEnabled() == false) {
//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);
}

Expand All @@ -225,11 +253,29 @@ private String event(Meter.Id id, Iterable<Tag> extraTags, Attribute... attribut
.append("\":\"").append(escapeJson(convention.tagValue(tag.getValue()))).append("\"");
}

String eventType = getEventType(id, config, convention);

return Arrays.stream(attributes)
.map(attr -> ",\"" + attr.getName() + "\":" + DoubleFormat.wholeOrDecimal(attr.getValue().doubleValue()))
.collect(Collectors.joining("", "{\"eventType\":\"" + escapeJson(getConventionName(id)) + "\"", tagsJson + "}"));
.map(attr ->
(attr.getValue() instanceof Number)
? ",\"" + attr.getName() + "\":" + DoubleFormat.wholeOrDecimal(((Number)attr.getValue()).doubleValue())
: ",\"" + attr.getName() + "\":\"" + convention.tagValue(((String)attr.getValue()).toString()) + "\""
shakuzen marked this conversation as resolved.
Show resolved Hide resolved
)
.collect(Collectors.joining("", "{\"eventType\":\"" + escapeJson(eventType) + "\"", tagsJson + "}"));
}

String getEventType(Meter.Id id, NewRelicConfig config, NamingConvention convention) {
String eventType = null;
if (config.meterNameEventTypeEnabled()) {
//meter/metric name event type
eventType = id.getConventionName(convention);
} else {
//static eventType "category"
eventType = config.eventType();
}
return eventType;
}

private void sendEvents(String insightsEndpoint, Stream<String> events) {
try {
AtomicInteger totalEvents = new AtomicInteger();
Expand Down Expand Up @@ -285,9 +331,9 @@ public NewRelicMeterRegistry build() {

private class Attribute {
private final String name;
private final Number value;
private final Object value;

private Attribute(String name, Number value) {
private Attribute(String name, Object value) {
this.name = name;
this.value = value;
}
Expand All @@ -296,7 +342,7 @@ public String getName() {
return name;
}

public Number getValue() {
public Object getValue() {
return value;
}
}
Expand Down
Loading