Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
953c909
Recycle via reference counting
felixbarny Apr 16, 2019
847aecd
Revert unrelated changes
felixbarny Apr 16, 2019
8bc8dd1
Introduce Labels
felixbarny Apr 16, 2019
d0e5d51
Add breakdown
felixbarny Apr 16, 2019
dffbba0
Make tracking breakdown metrics garbage free
felixbarny Apr 23, 2019
576f2da
Merge remote-tracking branch 'origin/master' into timing-breakdown
felixbarny May 3, 2019
790f2e2
Update to new metric names
felixbarny May 3, 2019
2c8d6d2
Merge remote-tracking branch 'origin/master' into timing-breakdown
felixbarny Jun 3, 2019
33bfcae
Thread safe publication of immutable labels
felixbarny Jun 3, 2019
b5395a1
Add span.subtype to breakdown metrics
felixbarny Jun 3, 2019
a609d71
Disable all breakdown code paths when disable_metrics=span.self_time
felixbarny Jun 4, 2019
dc701f7
Document metrics
felixbarny Jun 4, 2019
280f663
Clarify docs
felixbarny Jun 5, 2019
a336827
Another docs clarification
felixbarny Jun 5, 2019
833bbe1
Fix tests
felixbarny Jun 5, 2019
5639197
Guard agains concurrent reads/writes
felixbarny Jun 5, 2019
a0d167d
Limit number of metricsets
felixbarny Jun 5, 2019
9aca16f
Add some Javadocs
felixbarny Jun 5, 2019
253ad87
Eliminate allocations
felixbarny Jun 5, 2019
dbf5884
Addressed some comments
felixbarny Jun 21, 2019
6549546
Merge remote-tracking branch 'origin/master' into timing-breakdown
felixbarny Jun 21, 2019
d8394c3
Report timers in microseconds and use .us suffix
felixbarny Jun 21, 2019
308f519
Merge remote-tracking branch 'origin/master' into timing-breakdown
felixbarny Jul 12, 2019
580bf1d
Merge remote-tracking branch 'origin/master' into timing-breakdown
felixbarny Jul 17, 2019
bf6c221
Fix race condition between trackMetrics and incrementTimer
felixbarny Jul 18, 2019
f84852a
Renaming and add comments
felixbarny Jul 19, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ public void endSpan(Span span) {
if (span.isSampled()) {
long spanFramesMinDurationMs = stacktraceConfiguration.getSpanFramesMinDurationMs();
if (spanFramesMinDurationMs != 0 && span.isSampled()) {
if (span.getDuration() >= spanFramesMinDurationMs) {
if (span.getDurationMs() >= spanFramesMinDurationMs) {
span.withStacktrace(new Throwable());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

public abstract class AbstractSpan<T extends AbstractSpan> extends TraceContextHolder<T> {
private static final Logger logger = LoggerFactory.getLogger(AbstractSpan.class);
Expand All @@ -40,18 +41,70 @@ public abstract class AbstractSpan<T extends AbstractSpan> extends TraceContextH
protected final StringBuilder name = new StringBuilder();
private long timestamp;

/**
* How long the transaction took to complete, in ms with 3 decimal points
* (Required)
*/
protected double duration;
// in microseconds
protected long duration;
protected ReentrantTimer childDurations = new ReentrantTimer();
protected AtomicInteger references = new AtomicInteger();
protected volatile boolean finished = true;

public int getReferenceCount() {
return references.get();
}

public static class ReentrantTimer implements Recyclable {

private AtomicInteger nestingLevel = new AtomicInteger();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this name confusing. I thought it is about nested children, but it is more about counting concurrent-updaters of only first level (ie direct children) spans.

private AtomicLong start = new AtomicLong();
private AtomicLong duration = new AtomicLong();

/**
* Starts the timer if it has not been started already.
*
* @param startTimestamp
*/
public void start(long startTimestamp) {
if (nestingLevel.incrementAndGet() == 1) {
start.set(startTimestamp);
}
}

/**
* Stops the timer and increments the duration if no other direct children are still running
* @param endTimestamp
*/
public void stop(long endTimestamp) {
if (nestingLevel.decrementAndGet() == 0) {
incrementDuration(endTimestamp);
}
}

/**
* Stops the timer and increments the duration even if there are direct children which are still running
*
* @param endTimestamp
*/
public void forceStop(long endTimestamp) {
if (nestingLevel.getAndSet(0) != 0) {
incrementDuration(endTimestamp);
}
}

private void incrementDuration(long epochMicros) {
duration.addAndGet(epochMicros - start.get());
}

@Override
public void resetState() {
nestingLevel.set(0);
start.set(0);
duration.set(0);
}

public long getDuration() {
return duration.get();
}
}

public AbstractSpan(ElasticApmTracer tracer) {
super(tracer);
traceContext = TraceContext.with64BitId(this.tracer);
Expand All @@ -62,13 +115,20 @@ public boolean isReferenced() {
}

/**
* How long the transaction took to complete, in ms with 3 decimal points
* (Required)
* How long the transaction took to complete, in µs
*/
public double getDuration() {
public long getDuration() {
return duration;
}

public long getSelfDuration() {
return duration - childDurations.getDuration();
}

public double getDurationMs() {
return duration / AbstractSpan.MS_IN_MICROS;
}

/**
* Generic designation of a transaction in the scope of a single service (eg: 'GET /users/:id')
*/
Expand Down Expand Up @@ -122,6 +182,7 @@ public void resetState() {
timestamp = 0;
duration = 0;
traceContext.resetState();
childDurations.resetState();
references.set(0);
}

Expand Down Expand Up @@ -164,10 +225,11 @@ public void end() {

public final void end(long epochMicros) {
if (!finished) {
this.duration = (epochMicros - timestamp) / AbstractSpan.MS_IN_MICROS;
this.duration = (epochMicros - timestamp);
if (name.length() == 0) {
name.append("unnamed");
}
childDurations.forceStop(epochMicros);
doEnd(epochMicros);
// has to be set last so doEnd callbacks don't think it has already been finished
this.finished = true;
Expand Down Expand Up @@ -231,9 +293,11 @@ public void setStartTimestamp(long epochMicros) {

private void onChildStart(Span span, long epochMicros) {
incrementReferences();
childDurations.start(epochMicros);
}

void onChildEnd(Span span, long epochMicros) {
childDurations.stop(epochMicros);
decrementReferences();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ public void doEnd(long epochMicros) {
if (type == null) {
type = "custom";
}
if (transaction != null) {
transaction.incrementTimer(getType(), getSelfDuration());
}
if (parent != null) {
parent.onChildEnd(this, epochMicros);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.context.TransactionContext;
import co.elastic.apm.agent.impl.sampling.Sampler;
import co.elastic.apm.agent.metrics.Labels;
import co.elastic.apm.agent.metrics.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

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

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

@Override
public void doEnd(long epochMicros) {
incrementTimer("transaction", getSelfDuration());
if (!isSampled()) {
context.resetState();
}
if (type == null) {
type = "custom";
}
context.onTransactionEnd();
trackMetrics();
this.tracer.endTransaction(this);
}

public SpanCount getSpanCount() {
return spanCount;
}

public ConcurrentMap<String, Timer> getSpanTimings() {
return spanTimings;
}

@Override
public void resetState() {
super.resetState();
Expand Down Expand Up @@ -226,4 +238,43 @@ public void decrementReferences() {
tracer.recycle(this);
}
}

void incrementTimer(@Nullable String type, long duration) {
if (type != null && !finished) {
Timer timer = spanTimings.get(type);
if (timer == null) {
timer = new Timer();
Timer racyTimer = spanTimings.putIfAbsent(type, timer);
if (racyTimer != null) {
timer = racyTimer;
}
}
timer.update(duration);
if (finished) {
// in case end()->trackMetrics() has been called concurrently
// don't leak timers
timer.resetState();
}
}
}

private void trackMetrics() {
final String type = getType();
if (type == null) {
return;
}
final StringBuilder transactionName = getName();
final Labels labels = new Labels();
for (Map.Entry<String, Timer> entry : getSpanTimings().entrySet()) {
final Timer timer = entry.getValue();
if (timer.getCount() > 0) {
labels.resetState();
labels.transactionName(transactionName)
.transactionType(type)
.spanType(entry.getKey());
tracer.getMetricRegistry().timer("self_time", labels).update(timer.getTotalTimeNs(), timer.getCount());
timer.resetState();
}
}
}
}
Loading