Skip to content

Commit 8d5c32c

Browse files
committed
Add breakdown
1 parent b3a951a commit 8d5c32c

File tree

8 files changed

+536
-1
lines changed

8 files changed

+536
-1
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ public void doEnd(long epochMicros) {
190190
if (type == null) {
191191
type = "custom";
192192
}
193+
if (transaction != null) {
194+
transaction.incrementTimer(getType(), getSelfDuration());
195+
}
193196
if (parent != null) {
194197
parent.onChildEnd(this, epochMicros);
195198
}

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,15 @@
2222
import co.elastic.apm.agent.impl.ElasticApmTracer;
2323
import co.elastic.apm.agent.impl.context.TransactionContext;
2424
import co.elastic.apm.agent.impl.sampling.Sampler;
25+
import co.elastic.apm.agent.metrics.Labels;
26+
import co.elastic.apm.agent.metrics.Timer;
2527
import org.slf4j.Logger;
2628
import org.slf4j.LoggerFactory;
2729

2830
import javax.annotation.Nullable;
31+
import java.util.Map;
32+
import java.util.concurrent.ConcurrentHashMap;
33+
import java.util.concurrent.ConcurrentMap;
2934

3035
/**
3136
* Data captured by an agent representing an event occurring in a monitored service
@@ -43,6 +48,7 @@ public class Transaction extends AbstractSpan<Transaction> {
4348
*/
4449
private final TransactionContext context = new TransactionContext();
4550
private final SpanCount spanCount = new SpanCount();
51+
private final ConcurrentMap<String, Timer> spanTimings = new ConcurrentHashMap<>();
4652

4753
/**
4854
* The result of the transaction. HTTP status code for HTTP-related transactions.
@@ -169,20 +175,26 @@ public void setUser(String id, String email, String username) {
169175

170176
@Override
171177
public void doEnd(long epochMicros) {
178+
incrementTimer("transaction", getSelfDuration());
172179
if (!isSampled()) {
173180
context.resetState();
174181
}
175182
if (type == null) {
176183
type = "custom";
177184
}
178185
context.onTransactionEnd();
186+
trackMetrics();
179187
this.tracer.endTransaction(this);
180188
}
181189

182190
public SpanCount getSpanCount() {
183191
return spanCount;
184192
}
185193

194+
public ConcurrentMap<String, Timer> getSpanTimings() {
195+
return spanTimings;
196+
}
197+
186198
@Override
187199
public void resetState() {
188200
super.resetState();
@@ -226,4 +238,43 @@ public void decrementReferences() {
226238
tracer.recycle(this);
227239
}
228240
}
241+
242+
void incrementTimer(@Nullable String type, long duration) {
243+
if (type != null && !finished) {
244+
Timer timer = spanTimings.get(type);
245+
if (timer == null) {
246+
timer = new Timer();
247+
Timer racyTimer = spanTimings.putIfAbsent(type, timer);
248+
if (racyTimer != null) {
249+
timer = racyTimer;
250+
}
251+
}
252+
timer.update(duration);
253+
if (finished) {
254+
// in case end()->trackMetrics() has been called concurrently
255+
// don't leak timers
256+
timer.resetState();
257+
}
258+
}
259+
}
260+
261+
private void trackMetrics() {
262+
final String type = getType();
263+
if (type == null) {
264+
return;
265+
}
266+
final StringBuilder transactionName = getName();
267+
final Labels labels = new Labels();
268+
for (Map.Entry<String, Timer> entry : getSpanTimings().entrySet()) {
269+
final Timer timer = entry.getValue();
270+
if (timer.getCount() > 0) {
271+
labels.resetState();
272+
labels.transactionName(transactionName)
273+
.transactionType(type)
274+
.spanType(entry.getKey());
275+
tracer.getMetricRegistry().timer("self_time", labels).update(timer.getTotalTimeNs(), timer.getCount());
276+
timer.resetState();
277+
}
278+
}
279+
}
229280
}

apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricRegistry.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ public Map<Labels, MetricSet> getMetricSets() {
121121
return metricSets;
122122
}
123123

124+
public Timer timer(String timerName, Labels labels) {
125+
return getOrCreateMetricSet(labels).timer(timerName);
126+
}
127+
124128
private MetricSet getOrCreateMetricSet(Labels labels) {
125129
MetricSet metricSet = metricSets.get(labels);
126130
if (metricSet == null) {

apm-agent-core/src/main/java/co/elastic/apm/agent/metrics/MetricSet.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
public class MetricSet {
4141
private final Labels labels;
4242
private final ConcurrentMap<String, DoubleSupplier> gauges = new ConcurrentHashMap<>();
43+
private final ConcurrentMap<String, Timer> timers = new ConcurrentHashMap<>();
4344
private volatile boolean hasNonEmptyTimer;
4445

4546
public MetricSet(Labels labels) {
@@ -62,6 +63,20 @@ public Map<String, DoubleSupplier> getGauges() {
6263
return gauges;
6364
}
6465

66+
public Timer timer(String timerName) {
67+
hasNonEmptyTimer = true;
68+
Timer timer = timers.get(timerName);
69+
if (timer == null) {
70+
timers.putIfAbsent(timerName, new Timer());
71+
timer = timers.get(timerName);
72+
}
73+
return timer;
74+
}
75+
76+
public Map<String, Timer> getTimers() {
77+
return timers;
78+
}
79+
6580
public boolean hasContent() {
6681
return !gauges.isEmpty() || hasNonEmptyTimer;
6782
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*-
2+
* #%L
3+
* Elastic APM Java agent
4+
* %%
5+
* Copyright (C) 2018 - 2019 Elastic and contributors
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package co.elastic.apm.agent.metrics;
21+
22+
import co.elastic.apm.agent.objectpool.Recyclable;
23+
24+
import java.util.concurrent.TimeUnit;
25+
import java.util.concurrent.atomic.AtomicLong;
26+
27+
public class Timer implements Recyclable {
28+
private static final double MS_IN_MICROS = TimeUnit.MILLISECONDS.toMicros(1);
29+
30+
private AtomicLong totalTime = new AtomicLong();
31+
private AtomicLong count = new AtomicLong();
32+
33+
public void update(long durationNs) {
34+
update(durationNs, 1);
35+
}
36+
37+
public void update(long durationNs, long count) {
38+
this.totalTime.addAndGet(durationNs);
39+
this.count.addAndGet(count);
40+
}
41+
42+
public long getTotalTimeNs() {
43+
return totalTime.get();
44+
}
45+
46+
public double getTotalTimeMs() {
47+
return totalTime.get() / MS_IN_MICROS;
48+
}
49+
50+
public double getAverageMs() {
51+
return totalTime.get() / MS_IN_MICROS / count.get();
52+
}
53+
54+
public long getCount() {
55+
return count.get();
56+
}
57+
58+
public boolean hasContent() {
59+
return count.get() > 0;
60+
}
61+
62+
@Override
63+
public void resetState() {
64+
totalTime.set(0);
65+
count.set(0);
66+
}
67+
}

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

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import co.elastic.apm.agent.metrics.DoubleSupplier;
2323
import co.elastic.apm.agent.metrics.MetricRegistry;
2424
import co.elastic.apm.agent.metrics.MetricSet;
25+
import co.elastic.apm.agent.metrics.Timer;
2526
import com.dslplatform.json.JsonWriter;
2627
import com.dslplatform.json.NumberConverter;
2728

@@ -55,7 +56,8 @@ static void serializeMetricSet(MetricSet metricSet, long epochMicros, StringBuil
5556
DslJsonSerializer.serializeLabels(metricSet.getLabels(), replaceBuilder, jw);
5657
DslJsonSerializer.writeFieldName("samples", jw);
5758
jw.writeByte(JsonWriter.OBJECT_START);
58-
serializeGauges(metricSet.getGauges(), jw);
59+
final boolean hasSamples = serializeGauges(metricSet.getGauges(), jw);
60+
serializeTimers(metricSet.getTimers(), hasSamples, jw);
5961
jw.writeByte(JsonWriter.OBJECT_END);
6062
}
6163
jw.writeByte(JsonWriter.OBJECT_END);
@@ -92,10 +94,48 @@ private static boolean serializeGauges(Map<String, DoubleSupplier> gauges, JsonW
9294
return false;
9395
}
9496

97+
private static void serializeTimers(Map<String, Timer> timers, boolean hasSamples, JsonWriter jw) {
98+
99+
final int size = timers.size();
100+
if (size > 0) {
101+
final Iterator<Map.Entry<String, Timer>> iterator = timers.entrySet().iterator();
102+
103+
// serialize first valid value
104+
Timer value = null;
105+
while (iterator.hasNext() && value == null) {
106+
Map.Entry<String, Timer> kv = iterator.next();
107+
if (kv.getValue().hasContent()) {
108+
value = kv.getValue();
109+
if (hasSamples) {
110+
jw.writeByte(JsonWriter.COMMA);
111+
}
112+
serializeTimer(kv.getKey(), value, jw);
113+
}
114+
}
115+
116+
// serialize rest
117+
while (iterator.hasNext()) {
118+
Map.Entry<String, Timer> kv = iterator.next();
119+
value = kv.getValue();
120+
if (value.hasContent()) {
121+
jw.writeByte(JsonWriter.COMMA);
122+
serializeTimer(kv.getKey(), value, jw);
123+
}
124+
}
125+
}
126+
}
127+
95128
private static boolean isValid(double value) {
96129
return !Double.isInfinite(value) && !Double.isNaN(value);
97130
}
98131

132+
private static void serializeTimer(String key, Timer timer, JsonWriter jw) {
133+
serializeValue(key, ".count", timer.getCount(), jw);
134+
jw.writeByte(JsonWriter.COMMA);
135+
serializeValue(key, ".sum", timer.getTotalTimeMs(), jw);
136+
timer.resetState();
137+
}
138+
99139
private static void serializeValue(String key, double value, JsonWriter jw) {
100140
serializeValue(key, "", value, jw);
101141
}

0 commit comments

Comments
 (0)