forked from micrometer-metrics/micrometer
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New relic integration (fixes micrometer-metrics#122)
- Loading branch information
1 parent
0d125ae
commit 8d1402f
Showing
36 changed files
with
685 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
apply plugin: 'org.junit.platform.gradle.plugin' | ||
|
||
dependencies { | ||
compile project(':micrometer-core') | ||
compile 'com.fasterxml.jackson.core:jackson-databind:latest.release' | ||
|
||
testCompile project(':micrometer-test') | ||
} |
49 changes: 49 additions & 0 deletions
49
...ns/micrometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/** | ||
* Copyright 2017 Pivotal Software, Inc. | ||
* <p> | ||
* 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 | ||
* <p> | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* 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.step.StepRegistryConfig; | ||
|
||
public interface NewRelicConfig extends StepRegistryConfig { | ||
@Override | ||
default String prefix() { | ||
return "newrelic"; | ||
} | ||
|
||
default String apiKey() { | ||
String v = get(prefix() + ".apiKey"); | ||
if (v == null) | ||
throw new IllegalStateException(prefix() + ".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 IllegalStateException(prefix() + ".accountId must be set to report metrics to New Relic"); | ||
return v; | ||
} | ||
|
||
/** | ||
* Returns the URI for the New Relic insights API. The default is | ||
* {@code https://insights-collector.newrelic.com}. If you need to pass through | ||
* a proxy, you can change this value. | ||
*/ | ||
default String uri() { | ||
String v = get(prefix() + ".uri"); | ||
return (v == null) ? "https://insights-collector.newrelic.com" : v; | ||
} | ||
} |
185 changes: 185 additions & 0 deletions
185
...ometer-registry-new-relic/src/main/java/io/micrometer/newrelic/NewRelicMeterRegistry.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
/** | ||
* Copyright 2017 Pivotal Software, Inc. | ||
* <p> | ||
* 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 | ||
* <p> | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* 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.fasterxml.jackson.databind.ObjectMapper; | ||
import io.micrometer.core.annotation.Incubating; | ||
import io.micrometer.core.instrument.*; | ||
import io.micrometer.core.instrument.config.NamingConvention; | ||
import io.micrometer.core.instrument.step.StepMeterRegistry; | ||
import io.micrometer.core.instrument.util.DoubleFormat; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.InputStream; | ||
import java.io.InputStreamReader; | ||
import java.io.OutputStream; | ||
import java.net.HttpURLConnection; | ||
import java.net.MalformedURLException; | ||
import java.net.URI; | ||
import java.net.URL; | ||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import static java.util.stream.Collectors.joining; | ||
|
||
/** | ||
* @author Jon Schneider | ||
*/ | ||
@Incubating(since = "1.0.0-rc.5") | ||
public class NewRelicMeterRegistry extends StepMeterRegistry { | ||
private final NewRelicConfig config; | ||
private final ObjectMapper mapper = new ObjectMapper(); | ||
private final Logger logger = LoggerFactory.getLogger(NewRelicMeterRegistry.class); | ||
|
||
public NewRelicMeterRegistry(NewRelicConfig config, Clock clock) { | ||
super(config, clock); | ||
this.config = config; | ||
config().namingConvention(NamingConvention.camelCase); | ||
start(); | ||
} | ||
|
||
@Override | ||
protected void publish() { | ||
try { | ||
URL insightsEndpoint = URI.create(config.uri() + "/v1/accounts/" + config.accountId() + "/events").toURL(); | ||
|
||
// New Relic's Insights API limits us to 1000 events per call | ||
final int batchSize = Math.min(config.batchSize(), 1000); | ||
|
||
List<Event> events = new ArrayList<>(); | ||
|
||
for (Meter meter : getMeters()) { | ||
Meter.Id id = meter.getId(); | ||
|
||
if (meter instanceof Timer) { | ||
HistogramSnapshot t = ((Timer) meter).takeSnapshot(false); | ||
|
||
events.add(event(id, "count", t.count())); | ||
events.add(event(id, "sum", t.total(getBaseTimeUnit()))); | ||
events.add(event(id, "avg", t.mean(getBaseTimeUnit()))); | ||
events.add(event(id, "max", t.max(getBaseTimeUnit()))); | ||
|
||
for (ValueAtPercentile valueAtPercentile : t.percentileValues()) { | ||
events.add(event(id, "percentile", valueAtPercentile.value(getBaseTimeUnit()), "phi", | ||
DoubleFormat.toString(valueAtPercentile.percentile()))); | ||
} | ||
} else if (meter instanceof FunctionTimer) { | ||
FunctionTimer t = (FunctionTimer) meter; | ||
events.add(event(id, "count", t.count())); | ||
events.add(event(id, "sum", t.count())); | ||
events.add(event(id, "mean", t.mean(getBaseTimeUnit()))); | ||
} else if (meter instanceof DistributionSummary) { | ||
HistogramSnapshot t = ((DistributionSummary) meter).takeSnapshot(false); | ||
|
||
events.add(event(id, "count", t.count())); | ||
events.add(event(id, "sum", t.total())); | ||
events.add(event(id, "avg", t.mean())); | ||
events.add(event(id, "max", t.max())); | ||
|
||
for (ValueAtPercentile valueAtPercentile : t.percentileValues()) { | ||
events.add(event(id, "percentile", valueAtPercentile.value(), "phi", | ||
DoubleFormat.toString(valueAtPercentile.percentile()))); | ||
} | ||
} else { | ||
for (Measurement measurement : meter.measure()) { | ||
events.add(event(id, measurement.getStatistic().toString(), measurement.getValue())); | ||
} | ||
} | ||
|
||
if (events.size() > batchSize) { | ||
sendEvents(insightsEndpoint, events.subList(0, batchSize)); | ||
events = new ArrayList<>(events.subList(batchSize, events.size())); | ||
} else if (events.size() == batchSize) { | ||
sendEvents(insightsEndpoint, events); | ||
events = new ArrayList<>(); | ||
} | ||
} | ||
|
||
// drain the remaining event list | ||
if (!events.isEmpty()) { | ||
sendEvents(insightsEndpoint, events); | ||
} | ||
} catch (MalformedURLException e) { | ||
throw new IllegalArgumentException("Malformed New Relic insights endpoint, see '" + config.prefix() + ".uri'", e); | ||
} | ||
} | ||
|
||
private Event event(Meter.Id id, String statistic, Number value, String... additionalTags) { | ||
Event event = new Event(); | ||
|
||
event.put("eventType", getConventionName(id)); | ||
event.put("statistic", statistic); | ||
event.put("value", value); | ||
|
||
for (int i = 0; i < additionalTags.length; i += 2) { | ||
event.put(additionalTags[i], additionalTags[i + 1]); | ||
} | ||
|
||
id.getTags().forEach(t -> event.put(t.getKey(), t.getValue())); | ||
|
||
return event; | ||
} | ||
|
||
// TODO HTTP/1.1 Persistent connections are supported | ||
private void sendEvents(URL insightsEndpoint, List<Event> events) { | ||
try { | ||
HttpURLConnection con = (HttpURLConnection) insightsEndpoint.openConnection(); | ||
con.setConnectTimeout((int) config.connectTimeout().toMillis()); | ||
con.setReadTimeout((int) config.readTimeout().toMillis()); | ||
con.setRequestMethod("POST"); | ||
con.setRequestProperty("Content-Type", "application/json"); | ||
con.setRequestProperty("X-Insert-Key", config.apiKey()); | ||
|
||
con.setDoOutput(true); | ||
|
||
String body = mapper.writeValueAsString(events); | ||
|
||
try (OutputStream os = con.getOutputStream()) { | ||
os.write(body.getBytes()); | ||
os.flush(); | ||
} | ||
|
||
int status = con.getResponseCode(); | ||
|
||
if (status >= 200 && status < 300) { | ||
logger.info("successfully sent {} events to New Relic", events.size()); | ||
} else if (status >= 400) { | ||
try (InputStream in = con.getErrorStream()) { | ||
logger.error("failed to send metrics: " + new BufferedReader(new InputStreamReader(in)) | ||
.lines().collect(joining("\n"))); | ||
} | ||
} else { | ||
logger.error("failed to send metrics: http " + status); | ||
} | ||
|
||
con.disconnect(); | ||
} catch (Throwable e) { | ||
logger.warn("failed to send metrics", e); | ||
} | ||
} | ||
|
||
@Override | ||
protected TimeUnit getBaseTimeUnit() { | ||
return TimeUnit.SECONDS; | ||
} | ||
|
||
private class Event extends HashMap<String, Object> { | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
...ew-relic/src/test/java/io/micrometer/newrelic/NewRelicMeterRegistryCompatibilityTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/** | ||
* Copyright 2017 Pivotal Software, Inc. | ||
* <p> | ||
* 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 | ||
* <p> | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* <p> | ||
* 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.MeterRegistry; | ||
import io.micrometer.core.instrument.MockClock; | ||
import io.micrometer.core.tck.MeterRegistryCompatibilityKit; | ||
|
||
import java.time.Duration; | ||
|
||
public class NewRelicMeterRegistryCompatibilityTest extends MeterRegistryCompatibilityKit { | ||
@Override | ||
public MeterRegistry registry() { | ||
return new NewRelicMeterRegistry(new NewRelicConfig() { | ||
@Override | ||
public boolean enabled() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public String apiKey() { | ||
return "DOESNOTMATTER"; | ||
} | ||
|
||
@Override | ||
public String accountId() { | ||
return "DOESNOTMATTER"; | ||
} | ||
|
||
@Override | ||
public String get(String k) { | ||
return null; | ||
} | ||
|
||
@Override | ||
public Duration step() { | ||
return Duration.ofMillis(800); | ||
} | ||
}, new MockClock()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.