Skip to content

Commit 94cfb4f

Browse files
bm1549claude
andcommitted
Optimize scope lifecycle by skipping work when listeners empty and profiling disabled
tag: no release note tag: ai generated Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a31f629 commit 94cfb4f

File tree

3 files changed

+133
-34
lines changed

3 files changed

+133
-34
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package datadog.trace.core;
2+
3+
import static java.util.concurrent.TimeUnit.MICROSECONDS;
4+
5+
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
6+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
7+
import org.openjdk.jmh.annotations.Benchmark;
8+
import org.openjdk.jmh.annotations.BenchmarkMode;
9+
import org.openjdk.jmh.annotations.Fork;
10+
import org.openjdk.jmh.annotations.Level;
11+
import org.openjdk.jmh.annotations.Measurement;
12+
import org.openjdk.jmh.annotations.Mode;
13+
import org.openjdk.jmh.annotations.OutputTimeUnit;
14+
import org.openjdk.jmh.annotations.Scope;
15+
import org.openjdk.jmh.annotations.Setup;
16+
import org.openjdk.jmh.annotations.State;
17+
import org.openjdk.jmh.annotations.TearDown;
18+
import org.openjdk.jmh.annotations.Threads;
19+
import org.openjdk.jmh.annotations.Warmup;
20+
21+
@State(Scope.Benchmark)
22+
@Warmup(iterations = 3)
23+
@Measurement(iterations = 5)
24+
@BenchmarkMode(Mode.Throughput)
25+
@Threads(8)
26+
@OutputTimeUnit(MICROSECONDS)
27+
@Fork(value = 1)
28+
public class ScopeLifecycleBenchmark {
29+
static final CoreTracer TRACER = CoreTracer.builder().build();
30+
31+
@State(Scope.Thread)
32+
public static class ThreadState {
33+
AgentSpan span;
34+
AgentSpan childSpan;
35+
36+
@Setup(Level.Iteration)
37+
public void setup() {
38+
span = TRACER.startSpan("benchmark", "parent");
39+
childSpan = TRACER.startSpan("benchmark", "child");
40+
}
41+
42+
@TearDown(Level.Iteration)
43+
public void tearDown() {
44+
childSpan.finish();
45+
span.finish();
46+
}
47+
}
48+
49+
@Benchmark
50+
public void activateAndClose(ThreadState state) {
51+
AgentScope scope = TRACER.activateSpan(state.span);
52+
scope.close();
53+
}
54+
55+
@Benchmark
56+
public void activateSameSpan(ThreadState state) {
57+
AgentScope outer = TRACER.activateSpan(state.span);
58+
AgentScope inner = TRACER.activateSpan(state.span);
59+
inner.close();
60+
outer.close();
61+
}
62+
63+
@Benchmark
64+
public void nestedActivateAndClose(ThreadState state) {
65+
AgentScope parentScope = TRACER.activateSpan(state.span);
66+
AgentScope childScope = TRACER.activateSpan(state.childSpan);
67+
childScope.close();
68+
parentScope.close();
69+
}
70+
71+
@Benchmark
72+
public AgentSpan activeSpanLookup() {
73+
return TRACER.activeSpan();
74+
}
75+
}

dd-trace-core/src/main/java/datadog/trace/core/scopemanager/ContinuableScope.java

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -80,19 +80,22 @@ void cleanup(final ScopeStack scopeStack) {
8080
* I would hope this becomes unnecessary.
8181
*/
8282
final void onProperClose() {
83-
for (final ScopeListener listener : scopeManager.scopeListeners) {
84-
try {
85-
listener.afterScopeClosed();
86-
} catch (Exception e) {
87-
ContinuableScopeManager.log.debug("ScopeListener threw exception in close()", e);
83+
if (!scopeManager.scopeListeners.isEmpty()) {
84+
for (final ScopeListener listener : scopeManager.scopeListeners) {
85+
try {
86+
listener.afterScopeClosed();
87+
} catch (Exception e) {
88+
ContinuableScopeManager.log.debug("ScopeListener threw exception in close()", e);
89+
}
8890
}
8991
}
90-
91-
for (final ExtendedScopeListener listener : scopeManager.extendedScopeListeners) {
92-
try {
93-
listener.afterScopeClosed();
94-
} catch (Exception e) {
95-
ContinuableScopeManager.log.debug("ScopeListener threw exception in close()", e);
92+
if (!scopeManager.extendedScopeListeners.isEmpty()) {
93+
for (final ExtendedScopeListener listener : scopeManager.extendedScopeListeners) {
94+
try {
95+
listener.afterScopeClosed();
96+
} catch (Exception e) {
97+
ContinuableScopeManager.log.debug("ScopeListener threw exception in close()", e);
98+
}
9699
}
97100
}
98101
}
@@ -154,6 +157,9 @@ public boolean rollback() {
154157
}
155158

156159
public final void beforeActivated() {
160+
if (scopeState == Stateful.DEFAULT) {
161+
return;
162+
}
157163
AgentSpan span = span();
158164
if (span == null) {
159165
return;
@@ -167,24 +173,30 @@ public final void beforeActivated() {
167173
}
168174

169175
public final void afterActivated() {
176+
if (scopeManager.scopeListeners.isEmpty() && scopeManager.extendedScopeListeners.isEmpty()) {
177+
return;
178+
}
170179
AgentSpan span = span();
171180
if (span == null) {
172181
return;
173182
}
174-
for (final ScopeListener listener : scopeManager.scopeListeners) {
175-
try {
176-
listener.afterScopeActivated();
177-
} catch (Throwable e) {
178-
ContinuableScopeManager.log.debug("ScopeListener threw exception in afterActivated()", e);
183+
if (!scopeManager.scopeListeners.isEmpty()) {
184+
for (final ScopeListener listener : scopeManager.scopeListeners) {
185+
try {
186+
listener.afterScopeActivated();
187+
} catch (Throwable e) {
188+
ContinuableScopeManager.log.debug("ScopeListener threw exception in afterActivated()", e);
189+
}
179190
}
180191
}
181-
182-
for (final ExtendedScopeListener listener : scopeManager.extendedScopeListeners) {
183-
try {
184-
listener.afterScopeActivated(span.getTraceId(), span.getSpanId());
185-
} catch (Throwable e) {
186-
ContinuableScopeManager.log.debug(
187-
"ExtendedScopeListener threw exception in afterActivated()", e);
192+
if (!scopeManager.extendedScopeListeners.isEmpty()) {
193+
for (final ExtendedScopeListener listener : scopeManager.extendedScopeListeners) {
194+
try {
195+
listener.afterScopeActivated(span.getTraceId(), span.getSpanId());
196+
} catch (Throwable e) {
197+
ContinuableScopeManager.log.debug(
198+
"ExtendedScopeListener threw exception in afterActivated()", e);
199+
}
188200
}
189201
}
190202
}

dd-trace-core/src/main/java/datadog/trace/core/scopemanager/ContinuableScopeManager.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ public final class ContinuableScopeManager implements ContextManager {
5858
private final int depthLimit;
5959
final HealthMetrics healthMetrics;
6060
private final ProfilingContextIntegration profilingContextIntegration;
61+
private final boolean profilingEnabled;
62+
private final boolean hasDepthLimit;
6163

6264
/**
6365
* Constructor with NOOP Profiling and HealthMetrics implementations.
@@ -81,12 +83,15 @@ public ContinuableScopeManager(
8183
final ProfilingContextIntegration profilingContextIntegration,
8284
final HealthMetrics healthMetrics) {
8385
this.depthLimit = depthLimit == 0 ? Integer.MAX_VALUE : depthLimit;
86+
this.hasDepthLimit = this.depthLimit < Integer.MAX_VALUE;
8487
this.strictMode = strictMode;
8588
this.scopeListeners = new CopyOnWriteArrayList<>();
8689
this.extendedScopeListeners = new CopyOnWriteArrayList<>();
8790
this.healthMetrics = healthMetrics;
8891
this.tlsScopeStack = new ScopeStackThreadLocal(profilingContextIntegration);
8992
this.profilingContextIntegration = profilingContextIntegration;
93+
this.profilingEnabled =
94+
!(profilingContextIntegration instanceof ProfilingContextIntegration.NoOp);
9095

9196
ContextManager.register(this);
9297
}
@@ -135,11 +140,13 @@ private AgentScope activate(
135140
}
136141

137142
// DQH - This check could go before the check above, since depth limit checking is fast
138-
final int currentDepth = scopeStack.depth();
139-
if (depthLimit <= currentDepth) {
140-
healthMetrics.onScopeStackOverflow();
141-
log.debug("Scope depth limit exceeded ({}). Returning NoopScope.", currentDepth);
142-
return noopScope();
143+
if (hasDepthLimit) {
144+
final int currentDepth = scopeStack.depth();
145+
if (depthLimit <= currentDepth) {
146+
healthMetrics.onScopeStackOverflow();
147+
log.debug("Scope depth limit exceeded ({}). Returning NoopScope.", currentDepth);
148+
return noopScope();
149+
}
143150
}
144151

145152
assert span != null;
@@ -170,11 +177,13 @@ private AgentScope activate(final Context context) {
170177
}
171178

172179
// DQH - This check could go before the check above, since depth limit checking is fast
173-
final int currentDepth = scopeStack.depth();
174-
if (depthLimit <= currentDepth) {
175-
healthMetrics.onScopeStackOverflow();
176-
log.debug("Scope depth limit exceeded ({}). Returning NoopScope.", currentDepth);
177-
return noopScope();
180+
if (hasDepthLimit) {
181+
final int currentDepth = scopeStack.depth();
182+
if (depthLimit <= currentDepth) {
183+
healthMetrics.onScopeStackOverflow();
184+
log.debug("Scope depth limit exceeded ({}). Returning NoopScope.", currentDepth);
185+
return noopScope();
186+
}
178187
}
179188

180189
assert context != null;
@@ -263,7 +272,7 @@ public AgentScope activateNext(final AgentSpan span) {
263272
ScopeStack scopeStack = scopeStack();
264273

265274
final int currentDepth = scopeStack.depth();
266-
if (depthLimit <= currentDepth) {
275+
if (hasDepthLimit && depthLimit <= currentDepth) {
267276
healthMetrics.onScopeStackOverflow();
268277
log.debug("Scope depth limit exceeded ({}). Returning NoopScope.", currentDepth);
269278
return noopScope();
@@ -341,6 +350,9 @@ private void addExtendedScopeListener(final ExtendedScopeListener listener) {
341350
}
342351

343352
private Stateful createScopeState(Context context) {
353+
if (!profilingEnabled) {
354+
return Stateful.DEFAULT;
355+
}
344356
// currently this just manages things the profiler has to do per scope, but could be expanded
345357
// to encapsulate other scope lifecycle activities
346358
// FIXME DDSpanContext is always a ProfilerContext anyway...

0 commit comments

Comments
 (0)