Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Fixes

- Keep UI profiling running when `startProfiler` is called before a requested stop takes effect at the end of the current chunk ([#6291](https://github.com/getsentry/sentry-java/pull/6291))
- Before, the start call was ignored and the profiler still stopped at the end of the chunk, even in `TRACE` mode while sampled spans were active. On API 34 and lower, every later profile also stopped after one chunk.
- Keep sending events from Android 7.1.0 and older devices after Sentry changes its TLS certificate authority ([#6227](https://github.com/getsentry/sentry-java/pull/6227))
- In February 2027, Sentry moves its TLS certificates from DigiCert to Let's Encrypt and Google Trust Services ([announcement](https://sentry.io/changelog/were-changing-our-tls-certificate-authority-in-february-2027)).
- Android 7.1.0 (API 25) and older don't trust the Let's Encrypt root certificate (ISRG Root X1), so without this fix, the SDK on those devices can no longer send events to Sentry after the switch.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ public void startProfiler(
logger.log(SentryLevel.DEBUG, "Profiler was not started due to sampling decision.");
return;
}
// A stop only takes effect when the current chunk ends, so a start before then cancels it.
shouldStop = false;
switch (profileLifecycle) {
case TRACE:
// rootSpanCounter should never be negative, unless the user changed profile lifecycle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,23 +141,22 @@ public void startProfiler(
logger.log(SentryLevel.DEBUG, "Profiler was not started due to sampling decision.");
return;
}
// A stop only takes effect when the current chunk ends, so a start before then cancels it.
shouldStop = false;
switch (profileLifecycle) {
case TRACE:
activeTraceCount = Math.max(0, activeTraceCount); // safety check.
activeTraceCount++;
break;
case MANUAL:
if (isRunning()) {
logger.log(
SentryLevel.WARNING,
"Unexpected call to startProfiler(MANUAL) while profiler already running. Skipping.");
logger.log(SentryLevel.DEBUG, "Profiler is already running.");
return;
}
break;
}
if (!isRunning()) {
logger.log(SentryLevel.DEBUG, "Started Profiler.");
shouldStop = false;
startInternal();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,18 @@ class AndroidContinuousProfilerTest {
}
.testDoesNotStartWhenOffline(mocks)

@Test
fun `manual start cancels a pending stop`() =
fixture.getSut().testManualStartCancelsPendingStop(mocks)

@Test
fun `trace start cancels a pending stop`() =
fixture.getSut().testTraceStartCancelsPendingStop(mocks)

@Test
fun `manual profiler can be started again after a full start-stop cycle`() =
fixture.getSut().testCanBeStartedAgainAfterStopCycle(mocks)

// -- Legacy-specific tests (AndroidContinuousProfiler only) --

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,23 @@ fun IContinuousProfiler.testCanBeStartedAgainAfterStopCycle(mocks: ProfilerMocks
mocks.executor.runAll()
assertTrue(isRunning, "shouldStop must be reset on start")
}

fun IContinuousProfiler.testManualStartCancelsPendingStop(mocks: ProfilerMocks) {
startProfiler(ProfileLifecycle.MANUAL, mocks.tracesSampler)
val profilerIdBeforeStop = profilerId
stopProfiler(ProfileLifecycle.MANUAL)
startProfiler(ProfileLifecycle.MANUAL, mocks.tracesSampler)
mocks.executor.runAll()
assertTrue(isRunning, "a start before the chunk ends must cancel the pending stop")
assertEquals(profilerIdBeforeStop, profilerId)
}

fun IContinuousProfiler.testTraceStartCancelsPendingStop(mocks: ProfilerMocks) {
startProfiler(ProfileLifecycle.TRACE, mocks.tracesSampler)
val profilerIdBeforeStop = profilerId
stopProfiler(ProfileLifecycle.TRACE)
startProfiler(ProfileLifecycle.TRACE, mocks.tracesSampler)
mocks.executor.runAll()
assertTrue(isRunning, "a start before the chunk ends must cancel the pending stop")
assertEquals(profilerIdBeforeStop, profilerId)
}
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,14 @@ class PerfettoContinuousProfilerTest {
fun `manual profiler can be started again after a full start-stop cycle`() =
fixture.getSut().testCanBeStartedAgainAfterStopCycle(mocks)

@Test
fun `manual start cancels a pending stop`() =
fixture.getSut().testManualStartCancelsPendingStop(mocks)

@Test
fun `trace start cancels a pending stop`() =
fixture.getSut().testTraceStartCancelsPendingStop(mocks)

// -- Perfetto-specific tests --

@Test
Expand Down Expand Up @@ -503,10 +511,6 @@ class PerfettoContinuousProfilerTest {
assertTrue(profiler.isRunning)
profiler.startProfiler(ProfileLifecycle.MANUAL, fixture.mockTracesSampler)
assertTrue(profiler.isRunning)
verify(fixture.mockLogger)
.log(
eq(SentryLevel.WARNING),
eq("Unexpected call to startProfiler(MANUAL) while profiler already running. Skipping."),
)
verify(fixture.mockLogger).log(eq(SentryLevel.DEBUG), eq("Profiler is already running."))
}
}
Loading