getSpans(Transaction t) {
span.addLabel("framework", "some-framework");
spans.add(span);
- spans.add(new Span(mock(ElasticApmTracer.class))
+ spans.add(new Span(MockTracer.create())
.start(TraceContext.fromParent(), t, -1, false)
.withName("GET /api/types")
.withType("request"));
- spans.add(new Span(mock(ElasticApmTracer.class))
+ spans.add(new Span(MockTracer.create())
.start(TraceContext.fromParent(), t, -1, false)
.withName("GET /api/types")
.withType("request"));
- spans.add(new Span(mock(ElasticApmTracer.class))
+ spans.add(new Span(MockTracer.create())
.start(TraceContext.fromParent(), t, -1, false)
.withName("GET /api/types")
.withType("request"));
- span = new Span(mock(ElasticApmTracer.class))
+ span = new Span(MockTracer.create())
.start(TraceContext.fromParent(), t, -1, false)
.appendToName("GET ")
.appendToName("test.elastic.co")
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/SpyConfiguration.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/SpyConfiguration.java
index 44a205e010..53c2ddc916 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/SpyConfiguration.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/configuration/SpyConfiguration.java
@@ -28,6 +28,7 @@
import org.mockito.Mockito;
import org.stagemonitor.configuration.ConfigurationOptionProvider;
import org.stagemonitor.configuration.ConfigurationRegistry;
+import org.stagemonitor.configuration.source.ConfigurationSource;
import org.stagemonitor.configuration.source.SimpleSource;
import java.util.ServiceLoader;
@@ -38,6 +39,18 @@ public class SpyConfiguration {
public static final String CONFIG_SOURCE_NAME = "test config source";
+ /**
+ * Creates a configuration registry where all {@link ConfigurationOptionProvider}s are wrapped with
+ * {@link Mockito#spy(Object)}
+ *
+ * That way, the default configuration values are returned but can be overridden by {@link Mockito#when(Object)}
+ *
+ * @return a syp configuration registry
+ */
+ public static ConfigurationRegistry createSpyConfig() {
+ return createSpyConfig(new SimpleSource(CONFIG_SOURCE_NAME));
+ }
+
/**
* Creates a configuration registry where all {@link ConfigurationOptionProvider}s are wrapped with
* {@link org.mockito.Mockito#spy(Object)}
@@ -45,14 +58,15 @@ public class SpyConfiguration {
* That way, the default configuration values are returned but can be overridden by {@link org.mockito.Mockito#when(Object)}
*
* @return a syp configuration registry
+ * @param configurationSource
*/
- public static ConfigurationRegistry createSpyConfig() {
+ public static ConfigurationRegistry createSpyConfig(ConfigurationSource configurationSource) {
ConfigurationRegistry.Builder builder = ConfigurationRegistry.builder();
for (ConfigurationOptionProvider options : ServiceLoader.load(ConfigurationOptionProvider.class)) {
builder.addOptionProvider(spy(options));
}
return builder
- .addConfigSource(new SimpleSource(CONFIG_SOURCE_NAME))
+ .addConfigSource(configurationSource)
.addConfigSource(new PropertyFileConfigurationSource("elasticapm.properties"))
.build();
}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java
index 2ce95b9a8a..a4295b46e6 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ElasticApmTracerTest.java
@@ -352,9 +352,9 @@ void testTimestamps() {
transaction.end(30);
assertThat(transaction.getTimestamp()).isEqualTo(0);
- assertThat(transaction.getDuration()).isEqualTo(0.03);
+ assertThat(transaction.getDuration()).isEqualTo(30);
assertThat(span.getTimestamp()).isEqualTo(10);
- assertThat(span.getDuration()).isEqualTo(0.01);
+ assertThat(span.getDuration()).isEqualTo(10);
}
@Test
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ScopeManagementTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ScopeManagementTest.java
index 18259c17a4..ce226c59b4 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ScopeManagementTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/ScopeManagementTest.java
@@ -98,7 +98,7 @@ void testActivateTwice() {
}
@Test
- void testMissingDeactivation() {
+ void testRedundantActivation() {
runTestWithAssertionsDisabled(() -> {
final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, null).activate();
transaction.createSpan().activate();
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/SpanTypeBreakdownTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/SpanTypeBreakdownTest.java
new file mode 100644
index 0000000000..3593ca9cb8
--- /dev/null
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/SpanTypeBreakdownTest.java
@@ -0,0 +1,387 @@
+/*-
+ * #%L
+ * Elastic APM Java agent
+ * %%
+ * Copyright (C) 2018 - 2019 Elastic and contributors
+ * %%
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you 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
+ *
+ * http://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.
+ * #L%
+ */
+package co.elastic.apm.agent.impl;
+
+import co.elastic.apm.agent.MockReporter;
+import co.elastic.apm.agent.MockTracer;
+import co.elastic.apm.agent.configuration.SpyConfiguration;
+import co.elastic.apm.agent.impl.sampling.ConstantSampler;
+import co.elastic.apm.agent.impl.transaction.Span;
+import co.elastic.apm.agent.impl.transaction.TraceContext;
+import co.elastic.apm.agent.impl.transaction.TraceContextHolder;
+import co.elastic.apm.agent.impl.transaction.Transaction;
+import co.elastic.apm.agent.metrics.Labels;
+import co.elastic.apm.agent.metrics.MetricSet;
+import co.elastic.apm.agent.metrics.Timer;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.stagemonitor.configuration.source.SimpleSource;
+
+import javax.annotation.Nullable;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class SpanTypeBreakdownTest {
+
+ private MockReporter reporter;
+ private ElasticApmTracer tracer;
+
+ @BeforeEach
+ void setUp() {
+ reporter = new MockReporter();
+ tracer = MockTracer.createRealTracer(reporter);
+ }
+
+ /*
+ * ██████████████████████████████
+ * 10 20 30
+ */
+ @Test
+ void testBreakdown_noSpans() {
+ tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader())
+ .withName("test")
+ .withType("request")
+ .end(30);
+ tracer.getMetricRegistry().report(metricSets -> {
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getCount()).isEqualTo(1);
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getTotalTimeUs()).isEqualTo(30);
+ assertThat(getTimer(metricSets, "transaction.duration", null, null).getTotalTimeUs()).isEqualTo(30);
+ assertThatTransactionBreakdownCounterCreated(metricSets);
+ });
+ }
+
+ /*
+ * ██████████░░░░░░░░░░██████████
+ * └─────────██████████
+ * 10 20 30g
+ */
+ @Test
+ void testBreakdown_singleDbSpan() {
+ final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader())
+ .withName("test")
+ .withType("request");
+ transaction.createSpan(10).withType("db").withSubtype("mysql").end(20);
+ transaction.end(30);
+
+ tracer.getMetricRegistry().report(metricSets -> {
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getCount()).isEqualTo(1);
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getTotalTimeUs()).isEqualTo(20);
+ assertThat(getTimer(metricSets, "transaction.duration", null, null).getTotalTimeUs()).isEqualTo(30);
+ assertThatTransactionBreakdownCounterCreated(metricSets);
+ assertThat(getTimer(metricSets, "span.self_time", "db", "mysql").getCount()).isEqualTo(1);
+ assertThat(getTimer(metricSets, "span.self_time", "db", "mysql").getTotalTimeUs()).isEqualTo(10);
+ });
+ }
+
+ /*
+ * ██████████░░░░░░░░░░██████████
+ * └─────────██████████
+ * 10 20 30
+ */
+ @Test
+ void testBreakdown_singleDbSpan_breakdownMetricsDisabled() {
+ tracer = MockTracer.createRealTracer(reporter, SpyConfiguration.createSpyConfig(SimpleSource.forTest("disable_metrics", "span.self_time")));
+ final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader())
+ .withName("test")
+ .withType("request");
+ transaction.createSpan(10).withType("db").withSubtype("mysql").end(20);
+ transaction.end(30);
+
+ assertThat(transaction.getTimerBySpanTypeAndSubtype()).isEmpty();
+ tracer.getMetricRegistry().report(metricSets -> {
+ assertThat(getTimer(metricSets, "span.self_time", "app", null)).isNull();
+ assertThat(getTimer(metricSets, "span.self_time", "db", "mysql")).isNull();
+ assertThat(getTimer(metricSets, "transaction.duration", null, null).getTotalTimeUs()).isEqualTo(30);
+ });
+ }
+
+ /*
+ * ██████████░░░░░░░░░░██████████
+ * └─────────██████████
+ * 10 20 30
+ */
+ @Test
+ void testBreakdown_singleAppSpan() {
+ final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader())
+ .withName("test")
+ .withType("request");
+ transaction.createSpan(10).withType("app").end(20);
+ transaction.end(30);
+
+ tracer.getMetricRegistry().report(metricSets -> {
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getCount()).isEqualTo(2);
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getTotalTimeUs()).isEqualTo(30);
+ assertThat(getTimer(metricSets, "transaction.duration", null, null).getTotalTimeUs()).isEqualTo(30);
+ assertThatTransactionBreakdownCounterCreated(metricSets);
+ });
+ }
+
+ /*
+ * ██████████░░░░░░░░░░██████████
+ * ├─────────██████████
+ * └─────────██████████
+ * 10 20 30
+ */
+ @Test
+ void testBreakdown_concurrentDbSpans_fullyOverlapping() {
+ final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader())
+ .withName("test")
+ .withType("request");
+ final Span span1 = transaction.createSpan(10).withType("db").withSubtype("mysql");
+ final Span span2 = transaction.createSpan(10).withType("db").withSubtype("mysql");
+ span1.end(20);
+ span2.end(20);
+ transaction.end(30);
+
+ tracer.getMetricRegistry().report(metricSets -> {
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getCount()).isEqualTo(1);
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getTotalTimeUs()).isEqualTo(20);
+ assertThat(getTimer(metricSets, "transaction.duration", null, null).getTotalTimeUs()).isEqualTo(30);
+ assertThatTransactionBreakdownCounterCreated(metricSets);
+ assertThat(getTimer(metricSets, "span.self_time", "db", "mysql").getCount()).isEqualTo(2);
+ assertThat(getTimer(metricSets, "span.self_time", "db", "mysql").getTotalTimeUs()).isEqualTo(20);
+ });
+ }
+
+ /*
+ * ██████████░░░░░░░░░░░░░░░█████
+ * ├─────────██████████
+ * └──────────────██████████
+ * 10 20 30
+ */
+ @Test
+ void testBreakdown_concurrentDbSpans_partiallyOverlapping() {
+ final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader())
+ .withName("test")
+ .withType("request");
+ final Span span1 = transaction.createSpan(10).withType("db").withSubtype("mysql");
+ final Span span2 = transaction.createSpan(15).withType("db").withSubtype("mysql");
+ span1.end(20);
+ span2.end(25);
+ transaction.end(30);
+
+ tracer.getMetricRegistry().report(metricSets -> {
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getCount()).isEqualTo(1);
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getTotalTimeUs()).isEqualTo(15);
+ assertThat(getTimer(metricSets, "transaction.duration", null, null).getTotalTimeUs()).isEqualTo(30);
+ assertThatTransactionBreakdownCounterCreated(metricSets);
+ assertThat(getTimer(metricSets, "span.self_time", "db", "mysql").getCount()).isEqualTo(2);
+ assertThat(getTimer(metricSets, "span.self_time", "db", "mysql").getTotalTimeUs()).isEqualTo(20);
+ });
+ }
+
+ /*
+ * █████░░░░░░░░░░░░░░░░░░░░█████
+ * ├────██████████
+ * └──────────────██████████
+ * 10 20 30
+ */
+ @Test
+ void testBreakdown_serialDbSpans_notOverlapping_withoutGap() {
+ final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader())
+ .withName("test")
+ .withType("request");
+ transaction.createSpan(5).withType("db").withSubtype("mysql").end(15);
+ transaction.createSpan(15).withType("db").withSubtype("mysql").end(25);
+ transaction.end(30);
+
+ tracer.getMetricRegistry().report(metricSets -> {
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getCount()).isEqualTo(1);
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getTotalTimeUs()).isEqualTo(10);
+ assertThat(getTimer(metricSets, "transaction.duration", null, null).getTotalTimeUs()).isEqualTo(30);
+ assertThatTransactionBreakdownCounterCreated(metricSets);
+ assertThat(getTimer(metricSets, "span.self_time", "db", "mysql").getCount()).isEqualTo(2);
+ assertThat(getTimer(metricSets, "span.self_time", "db", "mysql").getTotalTimeUs()).isEqualTo(20);
+ });
+ }
+
+ /*
+ * ██████████░░░░░█████░░░░░█████
+ * ├─────────█████
+ * └───────────────────█████
+ * 10 20 30
+ */
+ @Test
+ void testBreakdown_serialDbSpans_notOverlapping_withGap() {
+ final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader())
+ .withName("test")
+ .withType("request");
+ transaction.createSpan(10).withType("db").withSubtype("mysql").end(15);
+ transaction.createSpan(20).withType("db").withSubtype("mysql").end(25);
+ transaction.end(30);
+
+ tracer.getMetricRegistry().report(metricSets -> {
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getCount()).isEqualTo(1);
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getTotalTimeUs()).isEqualTo(20);
+ assertThat(getTimer(metricSets, "transaction.duration", null, null).getTotalTimeUs()).isEqualTo(30);
+ assertThatTransactionBreakdownCounterCreated(metricSets);
+ assertThat(getTimer(metricSets, "span.self_time", "db", "mysql").getCount()).isEqualTo(2);
+ assertThat(getTimer(metricSets, "span.self_time", "db", "mysql").getTotalTimeUs()).isEqualTo(10);
+ });
+ }
+
+ /*
+ * ██████████░░░░░░░░░░██████████
+ * └─────────█████░░░░░ <- all child timers are force-stopped when a span finishes
+ * └────██████████ <- does not influence the transaction's self-time as it's not a direct child
+ * 10 20 30
+ */
+ @Test
+ void testBreakdown_asyncGrandchildExceedsChild() {
+ final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader())
+ .withName("test")
+ .withType("request");
+ final Span app = transaction.createSpan(10).withType("app");
+ final Span db = app.createSpan(15).withType("db").withSubtype("mysql");
+ app.end(20);
+ db.end(25);
+ transaction.end(30);
+
+ tracer.getMetricRegistry().report(metricSets -> {
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getCount()).isEqualTo(2);
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getTotalTimeUs()).isEqualTo(25);
+ assertThat(getTimer(metricSets, "transaction.duration", null, null).getTotalTimeUs()).isEqualTo(30);
+ assertThatTransactionBreakdownCounterCreated(metricSets);
+ assertThat(getTimer(metricSets, "span.self_time", "db", "mysql").getCount()).isEqualTo(1);
+ assertThat(getTimer(metricSets, "span.self_time", "db", "mysql").getTotalTimeUs()).isEqualTo(10);
+ });
+ }
+
+ /*
+ * breakdowns are reported when the transaction ends
+ * any spans which outlive the transaction are not included in the breakdown
+ * v
+ * ██████████░░░░░░░░░░
+ * └─────────██████████░░░░░░░░░░
+ * └─────────██████████
+ * 10 20 30
+ */
+ @Test
+ void testBreakdown_asyncGrandchildExceedsChildAndTransaction() {
+ final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader())
+ .withName("test")
+ .withType("request");
+ final Span app = transaction.createSpan(10).withType("app");
+ transaction.end(20);
+ reporter.decrementReferences();
+ final Span db = app.createSpan(20).withType("db").withSubtype("mysql");
+ app.end(30);
+ db.end(30);
+
+ assertThat(transaction.getSelfDuration()).isEqualTo(10);
+ assertThat(app.getSelfDuration()).isEqualTo(10);
+ assertThat(db.getSelfDuration()).isEqualTo(10);
+
+ reporter.decrementReferences();
+ assertThat(transaction.isReferenced()).isFalse();
+ assertThat(app.isReferenced()).isFalse();
+ assertThat(db.isReferenced()).isFalse();
+
+ tracer.getMetricRegistry().report(metricSets -> {
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getCount()).isEqualTo(1);
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getTotalTimeUs()).isEqualTo(10);
+ assertThat(getTimer(metricSets, "transaction.duration", null, null).getTotalTimeUs()).isEqualTo(20);
+ assertThatTransactionBreakdownCounterCreated(metricSets);
+ assertThat(getTimer(metricSets, "span.self_time", "db", "mysql")).isNull();
+ });
+ }
+
+ /*
+ * breakdowns are reported when the transaction ends
+ * any spans which outlive the transaction are not included in the breakdown
+ * v
+ * ██████████░░░░░░░░░░
+ * └─────────████████████████████
+ * 10 20 30
+ */
+ @Test
+ void testBreakdown_singleDbSpan_exceedingParent() {
+ final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader())
+ .withName("test")
+ .withType("request");
+ final Span span = transaction.createSpan(10).withType("db").withSubtype("mysql");
+ transaction.end(20);
+ span.end(30);
+
+ // recycled transactions should not leak child timings
+ reporter.assertRecycledAfterDecrementingReferences();
+ assertThat(reporter.getFirstTransaction().getTimerBySpanTypeAndSubtype().get("db")).isNull();
+
+ tracer.getMetricRegistry().report(metricSets -> {
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getCount()).isEqualTo(1);
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getTotalTimeUs()).isEqualTo(10);
+ assertThat(getTimer(metricSets, "transaction.duration", null, null).getTotalTimeUs()).isEqualTo(20);
+ assertThatTransactionBreakdownCounterCreated(metricSets);
+ assertThat(getTimer(metricSets, "span.self_time", "db", "mysql")).isNull();
+ });
+ }
+
+ /*
+ * breakdowns are reported when the transaction ends
+ * any spans which outlive the transaction are not included in the breakdown
+ * v
+ * ██████████
+ * └───────────────────██████████
+ * 10 20 30
+ */
+ @Test
+ void testBreakdown_spanStartedAfterParentEnded() {
+ final Transaction transaction = tracer.startTransaction(TraceContext.asRoot(), null, ConstantSampler.of(true), 0, getClass().getClassLoader())
+ .withName("test")
+ .withType("request");
+ final Runnable runnable = transaction.withActive(() -> {
+ final TraceContextHolder> active = tracer.getActive();
+ assertThat(active).isSameAs(transaction);
+ assertThat(transaction.getTraceContext().getId().isEmpty()).isFalse();
+ active.createSpan(20).withType("db").withSubtype("mysql").end(30);
+ });
+ transaction.end(10);
+ runnable.run();
+
+ reporter.assertRecycledAfterDecrementingReferences();
+
+ tracer.getMetricRegistry().report(metricSets -> {
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getCount()).isEqualTo(1);
+ assertThat(getTimer(metricSets, "span.self_time", "app", null).getTotalTimeUs()).isEqualTo(10);
+ assertThat(getTimer(metricSets, "transaction.duration", null, null).getTotalTimeUs()).isEqualTo(10);
+ assertThatTransactionBreakdownCounterCreated(metricSets);
+ assertThat(getTimer(metricSets, "span.self_time", "db", "mysql")).isNull();
+ });
+ }
+
+ private void assertThatTransactionBreakdownCounterCreated(Map extends Labels, MetricSet> metricSets) {
+ assertThat(metricSets.get(Labels.Mutable.of().transactionName("test").transactionType("request")).getCounters().get("transaction.breakdown.count").get()).isEqualTo(1);
+ }
+
+ @Nullable
+ private Timer getTimer(Map extends Labels, MetricSet> metricSets, String timerName, @Nullable String spanType, @Nullable String spanSubType) {
+ final MetricSet metricSet = metricSets.get(Labels.Mutable.of().transactionName("test").transactionType("request").spanType(spanType).spanSubType(spanSubType));
+ if (metricSet == null) {
+ return null;
+ }
+ return metricSet.timer(timerName);
+ }
+}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/payload/PayloadUtils.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/payload/PayloadUtils.java
index e04aa3af82..c6d5f16b5b 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/payload/PayloadUtils.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/payload/PayloadUtils.java
@@ -24,6 +24,7 @@
*/
package co.elastic.apm.agent.impl.payload;
+import co.elastic.apm.agent.MockTracer;
import co.elastic.apm.agent.TransactionUtils;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.error.ErrorPayload;
@@ -45,7 +46,7 @@ public class PayloadUtils {
}
public static TransactionPayload createTransactionPayloadWithAllValues() {
- final Transaction transaction = new Transaction(mock(ElasticApmTracer.class));
+ final Transaction transaction = new Transaction(MockTracer.create());
TransactionUtils.fillTransaction(transaction);
final TransactionPayload payload = createTransactionPayload();
payload.getTransactions().add(transaction);
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/payload/TransactionPayloadJsonSchemaTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/payload/TransactionPayloadJsonSchemaTest.java
index e11555307b..7f9c4fd534 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/payload/TransactionPayloadJsonSchemaTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/payload/TransactionPayloadJsonSchemaTest.java
@@ -24,6 +24,7 @@
*/
package co.elastic.apm.agent.impl.payload;
+import co.elastic.apm.agent.MockTracer;
import co.elastic.apm.agent.TransactionUtils;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.sampling.ConstantSampler;
@@ -67,7 +68,7 @@ private TransactionPayload createPayloadWithRequiredValues() {
final TransactionPayload payload = createPayload();
final Transaction transaction = createTransactionWithRequiredValues();
payload.getTransactions().add(transaction);
- Span span = new Span(mock(ElasticApmTracer.class));
+ Span span = new Span(MockTracer.create());
span.start(TraceContext.fromParent(), transaction, -1, false)
.withType("type")
.withSubtype("subtype")
@@ -78,7 +79,7 @@ private TransactionPayload createPayloadWithRequiredValues() {
}
private Transaction createTransactionWithRequiredValues() {
- Transaction t = new Transaction(mock(ElasticApmTracer.class));
+ Transaction t = new Transaction(MockTracer.create());
t.start(TraceContext.asRoot(), null, (long) 0, ConstantSampler.of(true));
t.withType("type");
t.getContext().getRequest().withMethod("GET");
@@ -87,7 +88,7 @@ private Transaction createTransactionWithRequiredValues() {
}
private TransactionPayload createPayloadWithAllValues() {
- final Transaction transaction = new Transaction(mock(ElasticApmTracer.class));
+ final Transaction transaction = new Transaction(MockTracer.create());
TransactionUtils.fillTransaction(transaction);
final TransactionPayload payload = createPayload();
payload.getTransactions().add(transaction);
@@ -109,7 +110,7 @@ private TransactionPayload createPayload(SystemInfo system) {
@Test
void testJsonSchemaDslJsonEmptyValues() throws IOException {
final TransactionPayload payload = createPayload();
- payload.getTransactions().add(new Transaction(mock(ElasticApmTracer.class)));
+ payload.getTransactions().add(new Transaction(MockTracer.create()));
final String content = new DslJsonSerializer(mock(StacktraceConfiguration.class)).toJsonString(payload);
System.out.println(content);
objectMapper.readTree(content);
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/SpanTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/SpanTest.java
index 4c43355a93..319614f35f 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/SpanTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/SpanTest.java
@@ -24,6 +24,7 @@
*/
package co.elastic.apm.agent.impl.transaction;
+import co.elastic.apm.agent.MockTracer;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import org.junit.jupiter.api.Test;
@@ -34,7 +35,7 @@ class SpanTest {
@Test
void resetState() {
- Span span = new Span(mock(ElasticApmTracer.class))
+ Span span = new Span(MockTracer.create())
.withName("SELECT FROM product_types")
.withType("db")
.withSubtype("postgresql")
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TransactionTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TransactionTest.java
index fab85955a5..c13af0a542 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TransactionTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/impl/transaction/TransactionTest.java
@@ -24,6 +24,7 @@
*/
package co.elastic.apm.agent.impl.transaction;
+import co.elastic.apm.agent.MockTracer;
import co.elastic.apm.agent.TransactionUtils;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.stacktrace.StacktraceConfiguration;
@@ -45,9 +46,9 @@ void setUp() {
@Test
void resetState() {
- final Transaction transaction = new Transaction(mock(ElasticApmTracer.class));
+ final Transaction transaction = new Transaction(MockTracer.create());
TransactionUtils.fillTransaction(transaction);
transaction.resetState();
- assertThat(jsonSerializer.toJsonString(transaction)).isEqualTo(jsonSerializer.toJsonString(new Transaction(mock(ElasticApmTracer.class))));
+ assertThat(jsonSerializer.toJsonString(transaction)).isEqualTo(jsonSerializer.toJsonString(new Transaction(MockTracer.create())));
}
}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/LabelsTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/LabelsTest.java
new file mode 100644
index 0000000000..9005402f5d
--- /dev/null
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/LabelsTest.java
@@ -0,0 +1,95 @@
+/*-
+ * #%L
+ * Elastic APM Java agent
+ * %%
+ * Copyright (C) 2018 - 2019 Elastic and contributors
+ * %%
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you 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
+ *
+ * http://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.
+ * #L%
+ */
+package co.elastic.apm.agent.metrics;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class LabelsTest {
+
+ @Test
+ void testCharSequenceHash() {
+ assertThat(Labels.Mutable.hash("foo")).isEqualTo(Labels.Mutable.hash(new StringBuilder("foo")));
+ }
+
+ @Test
+ void testEqualsHashCode() {
+ assertEqualsHashCode(
+ Labels.Mutable.of("foo", "bar"),
+ Labels.Mutable.of("foo", "bar"));
+ assertEqualsHashCode(
+ Labels.Mutable.of().transactionName("foo"),
+ Labels.Mutable.of().transactionName("foo"));
+ assertEqualsHashCode(
+ Labels.Mutable.of().transactionName("foo"),
+ Labels.Mutable.of().transactionName(new StringBuilder("foo")));
+ assertEqualsHashCode(
+ Labels.Mutable.of("foo", "bar"),
+ Labels.Mutable.of("foo", new StringBuilder("bar")));
+ assertEqualsHashCode(
+ Labels.Mutable.of("foo", new StringBuilder("bar")).add("baz", "qux"),
+ Labels.Mutable.of("foo", "bar").add("baz", new StringBuilder("qux")));
+ assertEqualsHashCode(
+ Labels.Mutable.of("foo", "bar"),
+ Labels.Mutable.of("foo", new StringBuilder("bar")).immutableCopy());
+ }
+
+ @Test
+ void testNotEquals() {
+ assertNotEqual(
+ Labels.Mutable.of("foo", "bar"),
+ Labels.Mutable.of("bar", "foo"));
+ assertNotEqual(
+ Labels.Mutable.of("foo", "bar").add("baz", "qux"),
+ Labels.Mutable.of("baz", "qux").add("foo", "bar"));
+ assertNotEqual(
+ Labels.Mutable.of("foo", "bar").add("baz", "qux"),
+ Labels.Mutable.of("baz", "qux").add("foo", "bar"));
+ }
+
+ @Test
+ void testRecycle() {
+ final Labels.Mutable resetLabels = Labels.Mutable.of("foo", "bar").transactionName(new StringBuilder("baz"));
+ final Labels immutableLabels = resetLabels.immutableCopy();
+ resetLabels.resetState();
+ assertEqualsHashCode(
+ immutableLabels,
+ Labels.Mutable.of("foo", "bar").transactionName("baz"));
+ assertNotEqual(resetLabels, immutableLabels);
+ assertEqualsHashCode(resetLabels, Labels.EMPTY);
+ }
+
+ private void assertNotEqual(Labels l1, Labels l2) {
+ assertThat(l1.hashCode()).isNotEqualTo(l2.hashCode());
+ assertThat(l1).isNotEqualTo(l2);
+ }
+
+ private void assertEqualsHashCode(Labels l1, Labels l2) {
+ assertThat(l1).hasSameHashCodeAs(l2);
+ assertThat(l1).isEqualTo(l2);
+ }
+}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/MetricRegistryTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/MetricRegistryTest.java
index 60afdb26ed..951af52b50 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/MetricRegistryTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/MetricRegistryTest.java
@@ -11,9 +11,9 @@
* 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
- *
+ *
* http://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
@@ -30,8 +30,8 @@
import org.junit.jupiter.api.Test;
import java.util.List;
+import java.util.stream.IntStream;
-import static java.util.Collections.emptyMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -53,9 +53,27 @@ void testDisabledMetrics() {
final DoubleSupplier problematicMetric = () -> {
throw new RuntimeException("Huston, we have a problem");
};
- metricRegistry.addUnlessNegative("jvm.gc.count", emptyMap(), problematicMetric);
- metricRegistry.addUnlessNan("jvm.gc.count", emptyMap(), problematicMetric);
- metricRegistry.add("jvm.gc.count", emptyMap(), problematicMetric);
- assertThat(metricRegistry.getMetricSets()).isEmpty();
+ metricRegistry.addUnlessNegative("jvm.gc.count", Labels.EMPTY, problematicMetric);
+ metricRegistry.addUnlessNan("jvm.gc.count", Labels.EMPTY, problematicMetric);
+ metricRegistry.add("jvm.gc.count", Labels.EMPTY, problematicMetric);
+ metricRegistry.report(metricSets -> assertThat(metricSets).isEmpty());
+ }
+
+ @Test
+ void testReportGaugeTwice() {
+ metricRegistry.add("foo", Labels.EMPTY, () -> 42);
+ metricRegistry.report(metricSets -> assertThat(metricSets.get(Labels.EMPTY).getGauge("foo").get()).isEqualTo(42));
+ // the active and inactive metricSets are now switched, verify that the previous inactive metricSets also contain the same gauges
+ metricRegistry.report(metricSets -> assertThat(metricSets.get(Labels.EMPTY).getGauge("foo").get()).isEqualTo(42));
+ }
+
+ @Test
+ void testLimitTimers() {
+ IntStream.range(1, 505).forEach(i -> metricRegistry.updateTimer("timer" + i, Labels.Mutable.of("foo", Integer.toString(i)), 1));
+ IntStream.range(1, 505).forEach(i -> metricRegistry.updateTimer("timer" + i, Labels.Mutable.of("bar", Integer.toString(i)), 1));
+
+ metricRegistry.report(metricSets -> assertThat(metricSets).hasSize(1000));
+ // the active and inactive metricSets are now switched, also check the size of the previously inactive metricSets
+ metricRegistry.report(metricSets -> assertThat(metricSets).hasSize(1000));
}
}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetricsTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetricsTest.java
index c9ae080829..af2c4f551e 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetricsTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/JvmMemoryMetricsTest.java
@@ -24,12 +24,11 @@
*/
package co.elastic.apm.agent.metrics.builtin;
+import co.elastic.apm.agent.metrics.Labels;
import co.elastic.apm.agent.metrics.MetricRegistry;
import co.elastic.apm.agent.report.ReporterConfiguration;
import org.junit.jupiter.api.Test;
-import java.util.Collections;
-
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -42,12 +41,12 @@ void testMetrics() {
final MetricRegistry registry = new MetricRegistry(mock(ReporterConfiguration.class));
jvmMemoryMetrics.bindTo(registry);
System.out.println(registry.toString());
- assertThat(registry.get("jvm.memory.heap.used", Collections.emptyMap())).isNotZero();
- assertThat(registry.get("jvm.memory.heap.committed", Collections.emptyMap())).isNotZero();
- assertThat(registry.get("jvm.memory.heap.max", Collections.emptyMap())).isNotZero();
- assertThat(registry.get("jvm.memory.non_heap.used", Collections.emptyMap())).isNotZero();
- assertThat(registry.get("jvm.memory.non_heap.committed", Collections.emptyMap())).isNotZero();
- assertThat(registry.get("jvm.memory.non_heap.max", Collections.emptyMap())).isNotZero();
+ assertThat(registry.getGauge("jvm.memory.heap.used", Labels.EMPTY)).isNotZero();
+ assertThat(registry.getGauge("jvm.memory.heap.committed", Labels.EMPTY)).isNotZero();
+ assertThat(registry.getGauge("jvm.memory.heap.max", Labels.EMPTY)).isNotZero();
+ assertThat(registry.getGauge("jvm.memory.non_heap.used", Labels.EMPTY)).isNotZero();
+ assertThat(registry.getGauge("jvm.memory.non_heap.committed", Labels.EMPTY)).isNotZero();
+ assertThat(registry.getGauge("jvm.memory.non_heap.max", Labels.EMPTY)).isNotZero();
final long[] longs = new long[1000000];
System.out.println(registry.toString());
}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/SystemMetricsTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/SystemMetricsTest.java
index 94cded7c36..59d305a7cb 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/SystemMetricsTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/SystemMetricsTest.java
@@ -24,6 +24,7 @@
*/
package co.elastic.apm.agent.metrics.builtin;
+import co.elastic.apm.agent.metrics.Labels;
import co.elastic.apm.agent.metrics.MetricRegistry;
import co.elastic.apm.agent.report.ReporterConfiguration;
import org.junit.jupiter.api.Test;
@@ -31,7 +32,6 @@
import org.junit.jupiter.params.provider.CsvSource;
import java.io.File;
-import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -47,10 +47,11 @@ void testSystemMetrics() throws InterruptedException {
// makes sure system.cpu.total.norm.pct does not return NaN
consumeCpu();
Thread.sleep(1000);
- assertThat(metricRegistry.get("system.process.cpu.total.norm.pct", Collections.emptyMap())).isBetween(0.0, 1.0);
- assertThat(metricRegistry.get("system.memory.total", Collections.emptyMap())).isGreaterThan(0.0);
- assertThat(metricRegistry.get("system.memory.actual.free", Collections.emptyMap())).isGreaterThan(0.0);
- assertThat(metricRegistry.get("system.process.memory.size", Collections.emptyMap())).isGreaterThan(0.0);
+ assertThat(metricRegistry.getGauge("system.cpu.total.norm.pct", Labels.EMPTY)).isBetween(0.0, 1.0);
+ assertThat(metricRegistry.getGauge("system.process.cpu.total.norm.pct", Labels.EMPTY)).isBetween(0.0, 1.0);
+ assertThat(metricRegistry.getGauge("system.memory.total", Labels.EMPTY)).isGreaterThan(0.0);
+ assertThat(metricRegistry.getGauge("system.memory.actual.free", Labels.EMPTY)).isGreaterThan(0.0);
+ assertThat(metricRegistry.getGauge("system.process.memory.size", Labels.EMPTY)).isGreaterThan(0.0);
}
@ParameterizedTest
@@ -61,7 +62,7 @@ void testSystemMetrics() throws InterruptedException {
void testFreeMemoryMeminfo(String file, long value) throws Exception {
SystemMetrics systemMetrics = new SystemMetrics(new File(getClass().getResource(file).toURI()));
systemMetrics.bindTo(metricRegistry);
- assertThat(metricRegistry.get("system.memory.actual.free", Collections.emptyMap())).isEqualTo(value);
+ assertThat(metricRegistry.getGauge("system.memory.actual.free", Labels.EMPTY)).isEqualTo(value);
}
private void consumeCpu() {
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/ThreadMetricsTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/ThreadMetricsTest.java
index 5e49834103..7a6fe52f18 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/ThreadMetricsTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/metrics/builtin/ThreadMetricsTest.java
@@ -24,12 +24,11 @@
*/
package co.elastic.apm.agent.metrics.builtin;
+import co.elastic.apm.agent.metrics.Labels;
import co.elastic.apm.agent.metrics.MetricRegistry;
import co.elastic.apm.agent.report.ReporterConfiguration;
import org.junit.jupiter.api.Test;
-import java.util.Collections;
-
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -42,7 +41,7 @@ class ThreadMetricsTest {
@Test
void testThreadCount() {
threadMetrics.bindTo(registry);
- double numThreads = registry.get("jvm.thread.count", Collections.emptyMap());
+ double numThreads = registry.getGauge("jvm.thread.count", Labels.EMPTY);
assertThat(numThreads).isNotZero();
for (int i = 0; i < NUM_ADDED_THREADS; i++) {
Thread thread = new Thread(() -> {
@@ -56,6 +55,6 @@ void testThreadCount() {
thread.setDaemon(true);
thread.start();
}
- assertThat(registry.get("jvm.thread.count", Collections.emptyMap())).isEqualTo(numThreads + NUM_ADDED_THREADS);
+ assertThat(registry.getGauge("jvm.thread.count", Labels.EMPTY)).isEqualTo(numThreads + NUM_ADDED_THREADS);
}
}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/ApmServerReporterTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/ApmServerReporterTest.java
index e41c321572..d9ceec5224 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/ApmServerReporterTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/ApmServerReporterTest.java
@@ -61,7 +61,7 @@ void setUp() {
@Test
void testTransactionProcessor() throws Exception {
- reporter.report(new Transaction(mock(ElasticApmTracer.class)));
+ reporter.report(new Transaction(MockTracer.create()));
reporter.flush().get();
assertThat(reporter.getDropped()).isEqualTo(0);
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/IntakeV2ReportingEventHandlerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/IntakeV2ReportingEventHandlerTest.java
index 3bb2bd6cea..50ca65c1d2 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/IntakeV2ReportingEventHandlerTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/IntakeV2ReportingEventHandlerTest.java
@@ -213,14 +213,14 @@ void testRandomJitter() {
private void reportTransaction(IntakeV2ReportingEventHandler reportingEventHandler) {
final ReportingEvent reportingEvent = new ReportingEvent();
- reportingEvent.setTransaction(new Transaction(mock(ElasticApmTracer.class)));
+ reportingEvent.setTransaction(new Transaction(MockTracer.create()));
reportingEventHandler.onEvent(reportingEvent, -1, true);
}
private void reportSpan() {
final ReportingEvent reportingEvent = new ReportingEvent();
- reportingEvent.setSpan(new Span(mock(ElasticApmTracer.class)));
+ reportingEvent.setSpan(new Span(MockTracer.create()));
reportingEventHandler.onEvent(reportingEvent, -1, true);
}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/ReporterFactoryTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/ReporterFactoryTest.java
index 0e694d910d..2eb50f4559 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/ReporterFactoryTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/ReporterFactoryTest.java
@@ -24,6 +24,7 @@
*/
package co.elastic.apm.agent.report;
+import co.elastic.apm.agent.MockTracer;
import co.elastic.apm.agent.configuration.SpyConfiguration;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.MetaData;
@@ -112,7 +113,7 @@ void testNotValidatingSslCertificate() throws Exception {
when(reporterConfiguration.isVerifyServerCert()).thenReturn(false);
final Reporter reporter = reporterFactory.createReporter(configuration, new ApmServerClient(reporterConfiguration), MetaData.create(configuration, null, null));
- reporter.report(new Transaction(mock(ElasticApmTracer.class)));
+ reporter.report(new Transaction(MockTracer.create()));
reporter.flush().get();
assertThat(requestHandled).isTrue();
@@ -124,7 +125,7 @@ void testValidatingSslCertificate() throws Exception {
when(reporterConfiguration.isVerifyServerCert()).thenReturn(true);
final Reporter reporter = reporterFactory.createReporter(configuration, new ApmServerClient(reporterConfiguration), MetaData.create(configuration, null, null));
- reporter.report(new Transaction(mock(ElasticApmTracer.class)));
+ reporter.report(new Transaction(MockTracer.create()));
reporter.flush().get();
assertThat(requestHandled).isFalse();
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java
index ded5eadd33..5d440e4cfd 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/DslJsonSerializerTest.java
@@ -158,7 +158,7 @@ void testLimitStringValueLength() throws IOException {
@Test
void testNullHeaders() throws IOException {
- Transaction transaction = new Transaction(mock(ElasticApmTracer.class));
+ Transaction transaction = new Transaction(MockTracer.create());
transaction.getContext().getRequest().addHeader("foo", (String) null);
transaction.getContext().getRequest().addHeader("baz", (Enumeration) null);
transaction.getContext().getRequest().getHeaders().add("bar", null);
@@ -173,7 +173,7 @@ void testNullHeaders() throws IOException {
@Test
void testSpanTypeSerialization() throws IOException {
- Span span = new Span(mock(ElasticApmTracer.class));
+ Span span = new Span(MockTracer.create());
span.getTraceContext().asRootSpan(ConstantSampler.of(true));
span.withType("template.jsf.render.view");
JsonNode spanJson = objectMapper.readTree(serializer.toJsonString(span));
@@ -183,19 +183,19 @@ void testSpanTypeSerialization() throws IOException {
spanJson = objectMapper.readTree(serializer.toJsonString(span));
assertThat(spanJson.get("type").textValue()).isEqualTo("template.jsf_lifecycle.render_view");
- span = new Span(mock(ElasticApmTracer.class));
+ span = new Span(MockTracer.create());
span.getTraceContext().asRootSpan(ConstantSampler.of(true));
span.withType("template").withAction("jsf.render");
spanJson = objectMapper.readTree(serializer.toJsonString(span));
assertThat(spanJson.get("type").textValue()).isEqualTo("template..jsf_render");
- span = new Span(mock(ElasticApmTracer.class));
+ span = new Span(MockTracer.create());
span.getTraceContext().asRootSpan(ConstantSampler.of(true));
span.withType("template").withSubtype("jsf.render");
spanJson = objectMapper.readTree(serializer.toJsonString(span));
assertThat(spanJson.get("type").textValue()).isEqualTo("template.jsf_render");
- span = new Span(mock(ElasticApmTracer.class));
+ span = new Span(MockTracer.create());
span.getTraceContext().asRootSpan(ConstantSampler.of(true));
span.withSubtype("jsf").withAction("render");
spanJson = objectMapper.readTree(serializer.toJsonString(span));
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricSetSerializationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricSetSerializationTest.java
index 38c843d021..8b52d94897 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricSetSerializationTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/report/serialize/MetricSetSerializationTest.java
@@ -11,9 +11,9 @@
* 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
- *
+ *
* http://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
@@ -24,48 +24,109 @@
*/
package co.elastic.apm.agent.report.serialize;
-import co.elastic.apm.agent.metrics.MetricSet;
+import co.elastic.apm.agent.metrics.Labels;
+import co.elastic.apm.agent.metrics.MetricRegistry;
+import co.elastic.apm.agent.report.ReporterConfiguration;
import com.dslplatform.json.DslJson;
import com.dslplatform.json.JsonWriter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
+import javax.annotation.Nonnull;
import java.io.IOException;
-import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
class MetricSetSerializationTest {
private JsonWriter jw = new DslJson<>().newWriter();
private ObjectMapper objectMapper = new ObjectMapper();
+ private MetricRegistry registry = new MetricRegistry(mock(ReporterConfiguration.class));
@Test
- void testSerialization() throws IOException {
- final MetricSet metricSet = new MetricSet(Collections.singletonMap("foo.bar", "baz"));
- metricSet.add("foo.bar", () -> 42);
- metricSet.add("bar.baz", () -> 42);
- MetricRegistrySerializer.serializeMetricSet(metricSet, System.currentTimeMillis() * 1000, new StringBuilder(), jw);
- final String metricSetAsString = jw.toString();
- System.out.println(metricSetAsString);
- final JsonNode jsonNode = objectMapper.readTree(metricSetAsString);
+ void testSerializeGauges() throws IOException {
+ final Labels.Mutable labels = Labels.Mutable.of("foo.bar", "baz");
+ registry.add("foo.bar", labels, () -> 42);
+ registry.add("bar.baz", labels, () -> 42);
+
+ final JsonNode jsonNode = reportAsJson(labels);
+
assertThat(jsonNode.get("metricset").get("samples").get("foo.bar").get("value").doubleValue()).isEqualTo(42);
}
+ @Test
+ void testSerializeTimers() throws IOException {
+ final Labels.Mutable labels = Labels.Mutable.of("foo.bar", "baz");
+
+ registry.updateTimer("foo.bar", labels, 42);
+ registry.updateTimer("bar.baz", labels, 42, 2);
+ final JsonNode jsonNode = reportAsJson(labels);
+ final JsonNode samples = jsonNode.get("metricset").get("samples");
+ assertThat(samples.get("foo.bar.sum.us").get("value").doubleValue()).isEqualTo(42);
+ assertThat(samples.get("foo.bar.count").get("value").doubleValue()).isEqualTo(1);
+ assertThat(samples.get("bar.baz.sum.us").get("value").doubleValue()).isEqualTo(42);
+ assertThat(samples.get("bar.baz.count").get("value").doubleValue()).isEqualTo(2);
+ }
+
+ @Test
+ void testSerializeTimersWithTopLevelLabels() throws IOException {
+ final Labels.Mutable labels = Labels.Mutable.of("foo", "bar")
+ .transactionName("foo")
+ .transactionType("bar")
+ .spanType("baz")
+ .spanSubType("qux");
+ registry.updateTimer("foo.bar", labels, 42);
+
+ final JsonNode jsonNode = reportAsJson(labels);
+
+ final JsonNode metricset = jsonNode.get("metricset");
+ assertThat(metricset.get("tags").get("foo").textValue()).isEqualTo("bar");
+ assertThat(metricset.get("tags").get("transaction_name")).isNull();
+ assertThat(metricset.get("tags").get("transaction.name")).isNull();
+ assertThat(metricset.get("transaction").get("name").textValue()).isEqualTo("foo");
+ assertThat(metricset.get("transaction").get("type").textValue()).isEqualTo("bar");
+ assertThat(metricset.get("span").get("type").textValue()).isEqualTo("baz");
+ assertThat(metricset.get("span").get("subtype").textValue()).isEqualTo("qux");
+ assertThat(metricset.get("samples").get("foo.bar.sum.us").get("value").doubleValue()).isEqualTo(42);
+ }
+
+ @Test
+ void testSerializeTimersReset() throws IOException {
+ final Labels.Mutable labels = Labels.Mutable.of("foo.bar", "baz");
+ registry.updateTimer("foo.bar", labels, 42);
+ registry.updateTimer("bar.baz", labels, 42, 2);
+
+ reportAsJson(labels);
+
+ registry.updateTimer("foo.bar", labels, 42);
+ final JsonNode samples = reportAsJson(labels).get("metricset").get("samples");
+
+ assertThat(samples.get("foo.bar.sum.us").get("value").doubleValue()).isEqualTo(42);
+ assertThat(samples.get("foo.bar.count").get("value").doubleValue()).isEqualTo(1);
+ }
+
+ @Test
+ void testSerializeEmptyMetricSet() throws IOException {
+ final Labels.Mutable labels = Labels.Mutable.of("foo.bar", "baz");
+ registry.updateTimer("foo.bar", labels, 42);
+
+ assertThat(reportAsJson(labels).get("metricset").get("samples")).isNotEmpty();
+
+ assertThat(reportAsJson(labels).get("metricset").get("samples")).isEmpty();
+ }
+
@Test
void testNonFiniteSerialization() throws IOException {
- final MetricSet metricSet = new MetricSet(Collections.emptyMap());
- metricSet.add("valid", () -> 4.0);
- metricSet.add("infinite", () -> Double.POSITIVE_INFINITY);
- metricSet.add("NaN", () -> Double.NaN);
- metricSet.add("negative.infinite", () -> Double.NEGATIVE_INFINITY);
- metricSet.add("also.valid", () -> 5.0);
- MetricRegistrySerializer.serializeMetricSet(metricSet, System.currentTimeMillis() * 1000, new StringBuilder(), jw);
- final String metricSetAsString = jw.toString();
- System.out.println(metricSetAsString);
- final JsonNode jsonNode = objectMapper.readTree(metricSetAsString);
- JsonNode samples = jsonNode.get("metricset").get("samples");
+ registry.add("valid", Labels.EMPTY, () -> 4.0);
+ registry.add("infinite", Labels.EMPTY, () -> Double.POSITIVE_INFINITY);
+ registry.add("NaN", Labels.EMPTY, () -> Double.NaN);
+ registry.add("negative.infinite", Labels.EMPTY, () -> Double.NEGATIVE_INFINITY);
+ registry.add("also.valid", Labels.EMPTY, () -> 5.0);
+
+ JsonNode samples = reportAsJson(Labels.EMPTY).get("metricset").get("samples");
+
assertThat(samples.size()).isEqualTo(2);
assertThat(samples.get("valid").get("value").doubleValue()).isEqualTo(4.0);
assertThat(samples.get("also.valid").get("value").doubleValue()).isEqualTo(5.0);
@@ -73,21 +134,54 @@ void testNonFiniteSerialization() throws IOException {
@Test
void testNonFiniteCornerCasesSerialization() throws IOException {
- final MetricSet metricSet = new MetricSet(Collections.emptyMap());
- MetricRegistrySerializer.serializeMetricSet(metricSet, System.currentTimeMillis() * 1000, new StringBuilder(), jw);
- String metricSetAsString = jw.toString();
- System.out.println(metricSetAsString);
- JsonNode jsonNode = objectMapper.readTree(metricSetAsString);
- JsonNode samples = jsonNode.get("metricset").get("samples");
- assertThat(samples.size()).isEqualTo(0);
-
- metricSet.add("infinite", () -> Double.POSITIVE_INFINITY);
- metricSet.add("NaN", () -> Double.NaN);
- metricSet.add("negative.infinite", () -> Double.NEGATIVE_INFINITY);
- MetricRegistrySerializer.serializeMetricSet(metricSet, System.currentTimeMillis() * 1000, new StringBuilder(), jw);
- metricSetAsString = jw.toString();
- jsonNode = objectMapper.readTree(metricSetAsString);
- samples = jsonNode.get("metricset").get("samples");
- assertThat(samples.size()).isEqualTo(0);
+ registry.add("infinite", Labels.EMPTY, () -> Double.POSITIVE_INFINITY);
+ registry.add("NaN", Labels.EMPTY, () -> Double.NaN);
+ registry.add("negative.infinite", Labels.EMPTY, () -> Double.NEGATIVE_INFINITY);
+
+ final JsonNode jsonNode = reportAsJson(Labels.EMPTY);
+
+ assertThat(jsonNode.get("metricset").get("samples")).isEmpty();
+ }
+
+ @Test
+ void serializeEmptyMetricSet() throws IOException {
+ registry.updateTimer("foo", Labels.EMPTY, 0, 0);
+
+ final JsonNode jsonNode = reportAsJson(Labels.EMPTY);
+
+ assertThat(jsonNode.get("metricset").get("samples")).isEmpty();
+ }
+
+ @Test
+ void testCounterReset() throws IOException {
+ registry.incrementCounter("foo", Labels.EMPTY);
+
+ JsonNode samples = reportAsJson(Labels.EMPTY).get("metricset").get("samples");
+ assertThat(samples.size()).isEqualTo(1);
+ assertThat(samples.get("foo").get("value").intValue()).isOne();
+
+ assertThat(reportAsJson(Labels.EMPTY).get("metricset").get("samples")).hasSize(0);
+ }
+
+ @Test
+ void testTimerReset() throws IOException {
+ registry.updateTimer("foo", Labels.EMPTY, 1);
+
+ JsonNode samples = reportAsJson(Labels.EMPTY).get("metricset").get("samples");
+ assertThat(samples.size()).isEqualTo(2);
+ assertThat(samples.get("foo.sum.us").get("value").intValue()).isOne();
+ assertThat(samples.get("foo.count").get("value").intValue()).isOne();
+
+ assertThat(reportAsJson(Labels.EMPTY).get("metricset").get("samples")).hasSize(0);
+ }
+
+ @Nonnull
+ private JsonNode reportAsJson(Labels labels) throws IOException {
+ registry.report(metricSets -> MetricRegistrySerializer.serializeMetricSet(metricSets.get(labels), System.currentTimeMillis() * 1000, new StringBuilder(), jw));
+ final String jsonString = jw.toString();
+ System.out.println(jsonString);
+ final JsonNode json = objectMapper.readTree(jsonString);
+ jw.reset();
+ return json;
}
}
diff --git a/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java b/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java
index d1fbc824f6..6da3c822c0 100644
--- a/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java
+++ b/apm-agent-plugins/apm-api-plugin/src/test/java/co/elastic/apm/api/ElasticApmApiInstrumentationTest.java
@@ -301,8 +301,8 @@ void testManualTimestamps() {
transaction.startSpan().setStartTimestamp(1000).end(2000);
transaction.end(3000);
- assertThat(reporter.getFirstTransaction().getDuration()).isEqualTo(3);
- assertThat(reporter.getFirstSpan().getDuration()).isEqualTo(1);
+ assertThat(reporter.getFirstTransaction().getDuration()).isEqualTo(3000);
+ assertThat(reporter.getFirstSpan().getDuration()).isEqualTo(1000);
}
@Test
diff --git a/apm-agent-plugins/apm-java-concurrent-plugin/src/main/java/co/elastic/apm/agent/concurrent/ExecutorInstrumentation.java b/apm-agent-plugins/apm-java-concurrent-plugin/src/main/java/co/elastic/apm/agent/concurrent/ExecutorInstrumentation.java
index bf738bfd20..c92f834147 100644
--- a/apm-agent-plugins/apm-java-concurrent-plugin/src/main/java/co/elastic/apm/agent/concurrent/ExecutorInstrumentation.java
+++ b/apm-agent-plugins/apm-java-concurrent-plugin/src/main/java/co/elastic/apm/agent/concurrent/ExecutorInstrumentation.java
@@ -11,9 +11,9 @@
* 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
- *
+ *
* http://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
diff --git a/apm-agent-plugins/apm-web-plugin/src/test/java/co/elastic/apm/agent/web/SanitizingWebProcessorTest.java b/apm-agent-plugins/apm-web-plugin/src/test/java/co/elastic/apm/agent/web/SanitizingWebProcessorTest.java
index 47e2babbe5..20a4ea57ed 100644
--- a/apm-agent-plugins/apm-web-plugin/src/test/java/co/elastic/apm/agent/web/SanitizingWebProcessorTest.java
+++ b/apm-agent-plugins/apm-web-plugin/src/test/java/co/elastic/apm/agent/web/SanitizingWebProcessorTest.java
@@ -24,6 +24,7 @@
*/
package co.elastic.apm.agent.web;
+import co.elastic.apm.agent.MockTracer;
import co.elastic.apm.agent.configuration.SpyConfiguration;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.context.TransactionContext;
@@ -46,7 +47,7 @@ void setUp() {
@Test
void processTransactions() {
- Transaction transaction = new Transaction(mock(ElasticApmTracer.class));
+ Transaction transaction = new Transaction(MockTracer.create());
fillContext(transaction.getContext());
processor.processBeforeReport(transaction);
diff --git a/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java b/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java
index 28fc82b439..32ae30f8ef 100644
--- a/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java
+++ b/apm-opentracing/src/test/java/co/elastic/apm/opentracing/OpenTracingBridgeTest.java
@@ -68,7 +68,7 @@ void testCreateNonActiveTransaction() {
span.finish(TimeUnit.MILLISECONDS.toMicros(1));
assertThat(reporter.getTransactions()).hasSize(1);
- assertThat(reporter.getFirstTransaction().getDuration()).isEqualTo(1);
+ assertThat(reporter.getFirstTransaction().getDuration()).isEqualTo(1000);
assertThat(reporter.getFirstTransaction().getName().toString()).isEqualTo("test");
}
@@ -80,11 +80,11 @@ void sanityCheckRealTimestamps() {
final long epochMicros = System.currentTimeMillis() * 1000;
assertThat(reporter.getTransactions()).hasSize(1);
- assertThat(reporter.getFirstTransaction().getDuration()).isLessThan(MINUTES.toMillis(1));
+ assertThat(reporter.getFirstTransaction().getDuration()).isLessThan(MINUTES.toMicros(1));
assertThat(reporter.getFirstTransaction().getTimestamp()).isCloseTo(epochMicros, offset(MINUTES.toMicros(1)));
assertThat(reporter.getSpans()).hasSize(1);
- assertThat(reporter.getFirstSpan().getDuration()).isLessThan(MINUTES.toMillis(1));
+ assertThat(reporter.getFirstSpan().getDuration()).isLessThan(MINUTES.toMicros(1));
assertThat(reporter.getFirstSpan().getTimestamp()).isCloseTo(epochMicros, offset(MINUTES.toMicros(1)));
}
@@ -189,7 +189,7 @@ void testCreateActiveTransaction() {
assertThat(reporter.getTransactions()).hasSize(0);
// manually finish span
- scope.span().finish(TimeUnit.MILLISECONDS.toMicros(1));
+ scope.span().finish(1);
assertThat(reporter.getTransactions()).hasSize(1);
assertThat(reporter.getFirstTransaction().getDuration()).isEqualTo(1);
assertThat(reporter.getFirstTransaction().getName().toString()).isEqualTo("test");
diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc
index 1754042c6a..ef5a31ffe1 100644
--- a/docs/configuration.asciidoc
+++ b/docs/configuration.asciidoc
@@ -1071,6 +1071,8 @@ Disables the collection of certain metrics.
If the name of a metric matches any of the wildcard expressions, it will not be collected.
Example: `foo.*,bar.*`
+To disable all breakdown metric collection code paths, add `span.self_time` to the list.
+
This option supports the wildcard `*`, which matches zero or more characters.
Examples: `/foo/*/bar/*/baz*`, `*foo*`.
Matching is case insensitive by default.
@@ -1727,6 +1729,8 @@ The default unit for this option is `ms`
# If the name of a metric matches any of the wildcard expressions, it will not be collected.
# Example: `foo.*,bar.*`
#
+# To disable all breakdown metric collection code paths, add `span.self_time` to the list.
+#
# This option supports the wildcard `*`, which matches zero or more characters.
# Examples: `/foo/*/bar/*/baz*`, `*foo*`.
# Matching is case insensitive by default.
diff --git a/docs/metrics.asciidoc b/docs/metrics.asciidoc
index a3122c8c19..666d50a687 100644
--- a/docs/metrics.asciidoc
+++ b/docs/metrics.asciidoc
@@ -205,3 +205,65 @@ An approximation of the total amount of memory,
in bytes, allocated in heap memory.
--
+[float]
+[[metrics-application]]
+=== Application Metrics
+
+*`transaction.duration`*::
++
+--
+type: simple timer
+
+This timer tracks the duration of transactions and allows for the creation of graphs displaying a weighted average.
+
+Fields:
+
+* `sum`: The sum of all transaction durations in ms since the last report (the delta)
+* `count`: The count of all transactions since the last report (the delta)
+
+You can filter and group by these dimensions:
+
+* `transaction.name`: The name of the transaction
+* `transaction.type`: The type of the transaction, for example `request`
+
+--
+
+
+*`transaction.breakdown.count`*::
++
+--
+type: long
+
+format: count (delta)
+
+The number of transactions for which breakdown metrics (`span.self_time`) have been created.
+As the Java agent tracks the breakdown for both sampled and non-sampled transactions,
+this metric is equivalent to `transaction.duration.count`
+
+You can filter and group by these dimensions:
+
+* `transaction.name`: The name of the transaction
+* `transaction.type`: The type of the transaction, for example `request`
+
+--
+
+*`span.self_time`*::
++
+--
+type: simple timer
+
+This timer tracks the span self-times and is the basis of the transaction breakdown visualization.
+
+Fields:
+
+* `sum`: The sum of all span self-times in ms since the last report (the delta)
+* `count`: The count of all span self-times since the last report (the delta)
+
+You can filter and group by these dimensions:
+
+* `transaction.name`: The name of the transaction
+* `transaction.type`: The type of the transaction, for example `request`
+* `span.type`: The type of the span, for example `app`, `template` or `db`
+* `span.subtype`: The sub-type of the span, for example `mysql` (optional)
+
+--
diff --git a/elastic-apm-agent/pom.xml b/elastic-apm-agent/pom.xml
index da4ebc01dd..5f757fdbef 100644
--- a/elastic-apm-agent/pom.xml
+++ b/elastic-apm-agent/pom.xml
@@ -55,6 +55,17 @@
**/NOTICE
+
+ org.hdrhistogram:HdrHistogram
+
+
+ org/HdrHistogram/WriterReaderPhaser.class
+
+
@@ -98,6 +109,10 @@
com.blogspot.mydailyjava.weaklockfree
co.elastic.apm.agent.shaded.weaklockfree
+
+ org.HdrHistogram
+ co.elastic.apm.agent.shaded.HdrHistogram
+