Skip to content

Commit b4f1c3b

Browse files
43jayclaude
andcommitted
fix(profiling): PerfettoContinuousProfiler Reset shouldStop=false on startProfiler
shouldStop was always true, so we'd ever only collect max one profile session (startProfiler(MANUAL)->stopProfiler(MANUAL) plus one more chunk before profiler stopped itself. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4549c86 commit b4f1c3b

File tree

2 files changed

+26
-0
lines changed

2 files changed

+26
-0
lines changed

sentry-android-core/src/main/java/io/sentry/android/core/PerfettoContinuousProfiler.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public void startProfiler(
9393
final @NotNull ProfileLifecycle profileLifecycle,
9494
final @NotNull TracesSampler tracesSampler) {
9595
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
96+
shouldStop = false;
9697
if (shouldSample) {
9798
isSampled = tracesSampler.sampleSessionProfile(SentryRandom.current().nextDouble());
9899
shouldSample = false;

sentry-android-core/src/test/java/io/sentry/android/core/PerfettoContinuousProfilerTest.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,29 @@ class PerfettoContinuousProfilerTest {
108108
verify(fixture.mockLogger)
109109
.log(eq(SentryLevel.WARNING), eq("SDK is rate limited. Stopping profiler."))
110110
}
111+
112+
@Test
113+
fun `manual profiler can be started again after a full start-stop cycle`() {
114+
// DeferredExecutorService captures scheduled runnables instead of waiting.
115+
// executor.runAll() fires them immediately, simulating the 60s chunk timer elapsing.
116+
val profiler = fixture.getSut()
117+
118+
// Session 1: start profiling, then stop it
119+
profiler.startProfiler(ProfileLifecycle.MANUAL, fixture.mockTracesSampler)
120+
assertTrue(profiler.isRunning)
121+
profiler.stopProfiler(ProfileLifecycle.MANUAL)
122+
// Simulate the 60s chunk timer firing — stopInternal(restartProfiler=true) runs,
123+
// sees shouldStop=true, and does NOT restart. Profiler stops.
124+
fixture.executor.runAll()
125+
assertFalse(profiler.isRunning)
126+
127+
// Session 2: start profiling again
128+
profiler.startProfiler(ProfileLifecycle.MANUAL, fixture.mockTracesSampler)
129+
assertTrue(profiler.isRunning)
130+
// Simulate the 60s chunk timer firing — stopInternal(restartProfiler=true) runs.
131+
// shouldStop must have been reset to false by startProfiler, so the profiler
132+
// should restart for the next chunk.
133+
fixture.executor.runAll()
134+
assertTrue(profiler.isRunning, "Profiler should continue running after chunk restart — shouldStop must be reset on start")
135+
}
111136
}

0 commit comments

Comments
 (0)