Skip to content

Commit 080ff41

Browse files
Report the JVM/JMX metrics for each service name (elastic#2233)
1 parent e8e4876 commit 080ff41

File tree

8 files changed

+145
-10
lines changed

8 files changed

+145
-10
lines changed

CHANGELOG.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ by the agent may be different. This was done in order to improve the integration
4444
* Breakdown metrics are now tracked per service (when using APM Server 8.0) - {pull}2208[#2208]
4545
* Add support for Spring AMQP batch API - {pull}1716[#1716]
4646
* Add the (current) transaction name to the error (when using APM Server 8.0) - {pull}2235[#2235]
47+
* The JVM/JMX metrics are reported for each service name individually (when using APM Server 8.0) - {pull}2233[#2233]
4748
4849
[float]
4950
===== Performance improvements

apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@
5454
import javax.annotation.Nullable;
5555
import java.io.Closeable;
5656
import java.util.ArrayDeque;
57+
import java.util.ArrayList;
5758
import java.util.Deque;
59+
import java.util.Iterator;
5860
import java.util.List;
61+
import java.util.Map;
5962
import java.util.concurrent.CopyOnWriteArrayList;
6063
import java.util.concurrent.Future;
6164
import java.util.concurrent.ScheduledThreadPoolExecutor;
@@ -731,6 +734,14 @@ public MetricRegistry getMetricRegistry() {
731734
return metricRegistry;
732735
}
733736

737+
public List<String> getServiceNameOverrides() {
738+
List<String> serviceNames = new ArrayList<>(serviceNameByClassLoader.approximateSize());
739+
for (Map.Entry<ClassLoader, String> entry : serviceNameByClassLoader) {
740+
serviceNames.add(entry.getValue());
741+
}
742+
return serviceNames;
743+
}
744+
734745
@Override
735746
public void overrideServiceNameForClassLoader(@Nullable ClassLoader classLoader, @Nullable String serviceName) {
736747
// overriding the service name for the bootstrap class loader is not an actual use-case

apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/DslJsonSerializer.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
import java.util.concurrent.TimeUnit;
7878
import java.util.concurrent.TimeoutException;
7979

80+
import static co.elastic.apm.agent.util.ObjectUtils.defaultIfNull;
8081
import static com.dslplatform.json.JsonWriter.ARRAY_END;
8182
import static com.dslplatform.json.JsonWriter.ARRAY_START;
8283
import static com.dslplatform.json.JsonWriter.COMMA;
@@ -1055,9 +1056,9 @@ private static void serializeStringKeyScalarValueMap(Iterator<? extends Map.Entr
10551056
jw.writeByte(OBJECT_END);
10561057
}
10571058

1058-
static void serializeLabels(Labels labels, final StringBuilder replaceBuilder, final JsonWriter jw) {
1059+
static void serializeLabels(Labels labels, final String serviceName, final StringBuilder replaceBuilder, final JsonWriter jw) {
1060+
serializeServiceName(defaultIfNull(labels.getServiceName(), serviceName), replaceBuilder, jw);
10591061
if (!labels.isEmpty()) {
1060-
serializeServiceName(labels.getServiceName(), replaceBuilder, jw);
10611062
if (labels.getTransactionName() != null || labels.getTransactionType() != null) {
10621063
writeFieldName("transaction", jw);
10631064
jw.writeByte(OBJECT_START);

apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistryReporter.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import co.elastic.apm.agent.report.ReporterConfiguration;
2828
import com.dslplatform.json.JsonWriter;
2929

30+
import java.util.List;
3031
import java.util.Map;
3132
import java.util.concurrent.TimeUnit;
3233

@@ -67,8 +68,9 @@ public void stop() throws Exception {
6768
@Override
6869
public void report(Map<? extends Labels, MetricSet> metricSets) {
6970
if (tracer.isRunning()) {
71+
List<String> serviceNames = tracer.getServiceNameOverrides();
7072
for (MetricSet metricSet : metricSets.values()) {
71-
JsonWriter jw = serializer.serialize(metricSet);
73+
JsonWriter jw = serializer.serialize(metricSet, serviceNames);
7274
if (jw != null) {
7375
reporter.report(jw);
7476
}

apm-agent-core/src/main/java/co/elastic/apm/agent/report/serialize/MetricRegistrySerializer.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import javax.annotation.Nullable;
2929
import java.util.Iterator;
30+
import java.util.List;
3031
import java.util.Map;
3132
import java.util.concurrent.atomic.AtomicLong;
3233

@@ -47,18 +48,32 @@ public class MetricRegistrySerializer {
4748
* @return the serialized metric-set or {@code null} if no samples were serialized
4849
*/
4950
@Nullable
50-
public JsonWriter serialize(MetricSet metricSet) {
51+
public JsonWriter serialize(MetricSet metricSet, List<String> serviceNames) {
5152
JsonWriter jw = dslJson.newWriter(maxSerializedSize);
52-
final long timestamp = System.currentTimeMillis() * 1000;
53-
boolean hasSamples = serialize(metricSet, timestamp, replaceBuilder, jw);
53+
boolean hasSamples = false;
54+
if (serviceNames.isEmpty() || metricSet.getLabels().getServiceName() != null) {
55+
hasSamples = serialize(metricSet, null, jw);
56+
} else {
57+
hasSamples = serialize(metricSet, serviceNames.get(0), jw);
58+
if (hasSamples) {
59+
for (int i = 1; i < serviceNames.size(); ++i) {
60+
serialize(metricSet, serviceNames.get(i), jw);
61+
}
62+
}
63+
}
5464
if (hasSamples) {
5565
maxSerializedSize = Math.max(Math.min(jw.size(), BUFFER_SIZE_LIMIT), maxSerializedSize);
5666
return jw;
5767
}
5868
return null;
5969
}
6070

61-
private static boolean serialize(MetricSet metricSet, long epochMicros, StringBuilder replaceBuilder, JsonWriter jw) {
71+
private boolean serialize(MetricSet metricSet, String serviceName, JsonWriter jw) {
72+
final long timestamp = System.currentTimeMillis() * 1000;
73+
return serialize(metricSet, timestamp, serviceName, replaceBuilder, jw);
74+
}
75+
76+
private static boolean serialize(MetricSet metricSet, long epochMicros, String serviceName, StringBuilder replaceBuilder, JsonWriter jw) {
6277
boolean hasSamples;
6378
jw.writeByte(JsonWriter.OBJECT_START);
6479
{
@@ -68,7 +83,7 @@ private static boolean serialize(MetricSet metricSet, long epochMicros, StringBu
6883
DslJsonSerializer.writeFieldName("timestamp", jw);
6984
NumberConverter.serialize(epochMicros, jw);
7085
jw.writeByte(JsonWriter.COMMA);
71-
DslJsonSerializer.serializeLabels(metricSet.getLabels(), replaceBuilder, jw);
86+
DslJsonSerializer.serializeLabels(metricSet.getLabels(), serviceName, replaceBuilder, jw);
7287
DslJsonSerializer.writeFieldName("samples", jw);
7388
jw.writeByte(JsonWriter.OBJECT_START);
7489
hasSamples = serializeGauges(metricSet.getGauges(), jw);
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.apm.agent.report.serialize;
20+
21+
import co.elastic.apm.agent.MockReporter;
22+
import co.elastic.apm.agent.configuration.SpyConfiguration;
23+
import co.elastic.apm.agent.configuration.source.PropertyFileConfigurationSource;
24+
import co.elastic.apm.agent.impl.ElasticApmTracer;
25+
import co.elastic.apm.agent.impl.ElasticApmTracerBuilder;
26+
import com.fasterxml.jackson.databind.JsonNode;
27+
import com.fasterxml.jackson.databind.ObjectMapper;
28+
import org.junit.jupiter.api.Test;
29+
import org.stagemonitor.configuration.source.SimpleSource;
30+
31+
import static org.assertj.core.api.Assertions.assertThat;
32+
33+
class MetricRegistryReporterTest {
34+
35+
@Test
36+
void testReportedMetricsUseDefaultServiceNameIfServiceNameIsExplicitlySet() throws Exception {
37+
ElasticApmTracer tracer = null;
38+
try {
39+
MockReporter reporter = new MockReporter();
40+
tracer = new ElasticApmTracerBuilder()
41+
.configurationRegistry(SpyConfiguration.createSpyConfig(SimpleSource.forTest("service_name", "foo")))
42+
.reporter(reporter)
43+
.buildAndStart();
44+
tracer.overrideServiceNameForClassLoader(MetricRegistryReporterTest.class.getClassLoader(), "MetricRegistryReporterTest");
45+
46+
new MetricRegistryReporter(tracer).run();
47+
48+
assertThat(reporter.getBytes()).isNotEmpty();
49+
50+
ObjectMapper objectMapper = new ObjectMapper();
51+
for (byte[] json : reporter.getBytes()) {
52+
JsonNode jsonNode = objectMapper.readTree(json);
53+
assertThat(jsonNode.get("metricset").get("service")).isNull();
54+
}
55+
} finally {
56+
tracer.stop();
57+
}
58+
}
59+
}

apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricSetSerializationTest.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@
2727
import org.junit.jupiter.api.Test;
2828

2929
import javax.annotation.Nullable;
30+
import java.util.List;
3031
import java.util.concurrent.CompletableFuture;
3132

33+
import static java.util.Collections.emptyList;
34+
import static java.util.Collections.singletonList;
3235
import static org.assertj.core.api.Assertions.assertThat;
3336
import static org.mockito.Mockito.mock;
3437

@@ -185,11 +188,53 @@ void testServiceName() throws Exception {
185188
assertThat(serviceName.asText()).isEqualTo("bar");
186189
}
187190

191+
@Test
192+
void testServiceNameOverrideWithOneService() throws Exception {
193+
registry.updateTimer("foo", Labels.Mutable.of(), 1);
194+
195+
JsonNode jsonNode = reportAsJson(singletonList("bar"));
196+
assertThat(jsonNode).isNotNull();
197+
JsonNode serviceName = jsonNode.get("metricset").get("service").get("name");
198+
assertThat(serviceName.asText()).isEqualTo("bar");
199+
}
200+
201+
@Test
202+
void testServiceNameOverrideWithMultipleService() throws Exception {
203+
registry.updateTimer("foo", Labels.Mutable.of(), 1);
204+
205+
final CompletableFuture<JsonWriter> jwFuture = new CompletableFuture<>();
206+
registry.flipPhaseAndReport(
207+
metricSets -> jwFuture.complete(metricRegistrySerializer.serialize(metricSets.values().iterator().next(), List.of("bar1", "bar2")))
208+
);
209+
210+
String[] jsonStrings = jwFuture.getNow(null).toString().split("\n");
211+
assertThat(jsonStrings.length).isEqualTo(2);
212+
213+
JsonNode jsonNode1 = objectMapper.readTree(jsonStrings[0]);
214+
String serviceName1 = jsonNode1.get("metricset").get("service").get("name").asText();
215+
assertThat(serviceName1).isEqualTo("bar1");
216+
JsonNode samples1 = jsonNode1.get("metricset").get("samples");
217+
assertThat(samples1.get("foo.sum.us").get("value").intValue()).isOne();
218+
assertThat(samples1.get("foo.count").get("value").intValue()).isOne();
219+
220+
JsonNode jsonNode2 = objectMapper.readTree(jsonStrings[1]);
221+
String serviceName2 = jsonNode2.get("metricset").get("service").get("name").asText();
222+
assertThat(serviceName2).isEqualTo("bar2");
223+
JsonNode samples2 = jsonNode2.get("metricset").get("samples");
224+
assertThat(samples2.get("foo.sum.us").get("value").intValue()).isOne();
225+
assertThat(samples2.get("foo.count").get("value").intValue()).isOne();
226+
}
227+
188228
@Nullable
189229
private JsonNode reportAsJson() throws Exception {
230+
return reportAsJson(emptyList());
231+
}
232+
233+
@Nullable
234+
private JsonNode reportAsJson(List<String> serviceNames) throws Exception {
190235
final CompletableFuture<JsonWriter> jwFuture = new CompletableFuture<>();
191236
registry.flipPhaseAndReport(
192-
metricSets -> jwFuture.complete(metricRegistrySerializer.serialize(metricSets.values().iterator().next()))
237+
metricSets -> jwFuture.complete(metricRegistrySerializer.serialize(metricSets.values().iterator().next(), serviceNames))
193238
);
194239
JsonNode json = null;
195240
JsonWriter jw = jwFuture.getNow(null);

apm-agent-plugins/apm-jmx-plugin/src/test/java/co/elastic/apm/agent/jmx/JmxMetricTrackerTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.lang.management.ManagementFactory;
3434
import java.util.Arrays;
3535

36+
import static java.util.Collections.emptyList;
3637
import static org.assertj.core.api.Assertions.assertThat;
3738

3839
class JmxMetricTrackerTest {
@@ -162,7 +163,7 @@ private void printMetricSets() {
162163
metricRegistry.flipPhaseAndReport(
163164
metricSets -> {
164165
metricSets.values().forEach(
165-
metricSet -> System.out.println(new MetricRegistrySerializer().serialize(metricSet).toString())
166+
metricSet -> System.out.println(new MetricRegistrySerializer().serialize(metricSet, emptyList()).toString())
166167
);
167168
}
168169
);

0 commit comments

Comments
 (0)