io.micrometer.newrelic.NewRelicInsightsAgentClientProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of micrometer-registry-new-relic Show documentation
Show all versions of micrometer-registry-new-relic Show documentation
Application monitoring instrumentation facade
/*
* Copyright 2020 VMware, 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 com.newrelic.api.agent.Agent;
import com.newrelic.api.agent.NewRelic;
import io.micrometer.core.instrument.*;
import io.micrometer.core.instrument.config.NamingConvention;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Publishes metrics to New Relic Insights via Java Agent API.
*
* @author Neil Powell
* @since 1.4.0
*/
public class NewRelicInsightsAgentClientProvider implements NewRelicClientProvider {
private final Logger logger = LoggerFactory.getLogger(NewRelicInsightsAgentClientProvider.class);
private final NewRelicConfig config;
private final Agent newRelicAgent;
// VisibleForTesting
NamingConvention namingConvention;
public NewRelicInsightsAgentClientProvider(NewRelicConfig config) {
this(config, NewRelic.getAgent(), new NewRelicNamingConvention());
}
/**
* Create a {@code NewRelicInsightsAgentClientProvider} instance.
* @param config config
* @param newRelicAgent New Relic agent
* @since 1.4.2
*/
public NewRelicInsightsAgentClientProvider(NewRelicConfig config, Agent newRelicAgent) {
this(config, newRelicAgent, new NewRelicNamingConvention());
}
// VisibleForTesting
NewRelicInsightsAgentClientProvider(NewRelicConfig config, Agent newRelicAgent, NamingConvention namingConvention) {
config.requireValid();
this.config = config;
this.newRelicAgent = newRelicAgent;
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()) {
// @formatter:off
sendEvents(meter.getId(), meter.match(
this::writeGauge,
this::writeCounter,
this::writeTimer,
this::writeSummary,
this::writeLongTaskTimer,
this::writeTimeGauge,
this::writeFunctionCounter,
this::writeFunctionTimer,
this::writeMeter));
// @formatter:on
}
}
@Override
public Map writeLongTaskTimer(LongTaskTimer timer) {
Map attributes = new HashMap<>();
addAttribute(ACTIVE_TASKS, timer.activeTasks(), attributes);
addAttribute(DURATION, timer.duration(timer.baseTimeUnit()), attributes);
addAttribute(TIME_UNIT, timer.baseTimeUnit().name().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());
}
private Map writeCounterValues(Meter.Id id, double count) {
if (!Double.isFinite(count)) {
return Collections.emptyMap();
}
Map attributes = new HashMap<>();
addAttribute(THROUGHPUT, count, attributes);
// process meter's name, type and tags
addMeterAsAttributes(id, attributes);
return attributes;
}
@Override
public Map writeGauge(Gauge gauge) {
double value = gauge.value();
if (!Double.isFinite(value)) {
return Collections.emptyMap();
}
Map attributes = new HashMap<>();
addAttribute(VALUE, value, attributes);
// process meter's name, type and tags
addMeterAsAttributes(gauge.getId(), attributes);
return attributes;
}
@Override
public Map writeTimeGauge(TimeGauge gauge) {
double value = gauge.value();
if (!Double.isFinite(value)) {
return Collections.emptyMap();
}
Map attributes = new HashMap<>();
addAttribute(VALUE, value, attributes);
addAttribute(TIME_UNIT, gauge.baseTimeUnit().name().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 = timer.baseTimeUnit();
addAttribute(COUNT, timer.count(), attributes);
addAttribute(AVG, timer.mean(timeUnit), attributes);
addAttribute(TOTAL_TIME, timer.totalTime(timeUnit), attributes);
addAttribute(MAX, timer.max(timeUnit), attributes);
addAttribute(TIME_UNIT, timeUnit.name().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 = timer.baseTimeUnit();
addAttribute(COUNT, timer.count(), attributes);
addAttribute(AVG, timer.mean(timeUnit), attributes);
addAttribute(TOTAL_TIME, timer.totalTime(timeUnit), attributes);
addAttribute(TIME_UNIT, timeUnit.name().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;
}
private 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());
}
}
private 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());
}
}
private 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()) {
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);
}
}
}
@Override
public void setNamingConvention(NamingConvention namingConvention) {
this.namingConvention = namingConvention;
}
}