From 9829cc8b3a0276f2f29881c963aab196d1befd54 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Mon, 31 Aug 2026 18:19:29 +0200 Subject: [PATCH] ref(checkin): Measure check-in durations with Stopwatch (JAVA-576) All four check-in paths kept a `long startTime = System.nanoTime()` and subtracted it in a finally block: CheckInUtils and the SentryCheckInAdvice in sentry-spring, sentry-spring-jakarta and sentry-spring-7. The serialized duration is unchanged, to the bit. JavaUptimeClock.tickNanos() is System.nanoTime(), and check-ins have no Android path where the two could diverge, so this is the same arithmetic behind a name. That is the point of naming the guarantee rather than the mechanism: it makes a conversion that touches a customer-facing value provably inert, and therefore landable before the major. Each site carries a TODO [MAJOR] for the change that is not inert: on elapsed-real-time, a cron job that spans device sleep would report the duration a user would measure rather than the CPU time it had. The clock is resolved as JavaUptimeClock.getInstance() rather than through options, so an uninitialised SDK reaches a working clock without a scopes lookup. Co-Authored-By: Claude Opus 5 (1M context) --- .../io/sentry/spring7/checkin/SentryCheckInAdvice.java | 8 ++++++-- .../spring/jakarta/checkin/SentryCheckInAdvice.java | 8 ++++++-- .../io/sentry/spring/checkin/SentryCheckInAdvice.java | 8 ++++++-- sentry/src/main/java/io/sentry/util/CheckInUtils.java | 8 ++++++-- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/sentry-spring-7/src/main/java/io/sentry/spring7/checkin/SentryCheckInAdvice.java b/sentry-spring-7/src/main/java/io/sentry/spring7/checkin/SentryCheckInAdvice.java index d2c164b9a6e..4c230f6391f 100644 --- a/sentry-spring-7/src/main/java/io/sentry/spring7/checkin/SentryCheckInAdvice.java +++ b/sentry-spring-7/src/main/java/io/sentry/spring7/checkin/SentryCheckInAdvice.java @@ -9,6 +9,8 @@ import io.sentry.ScopesAdapter; import io.sentry.SentryLevel; import io.sentry.protocol.SentryId; +import io.sentry.time.JavaUptimeClock; +import io.sentry.time.Stopwatch; import io.sentry.util.Objects; import io.sentry.util.TracingUtils; import java.lang.reflect.Method; @@ -91,7 +93,9 @@ public Object invoke(final @NotNull MethodInvocation invocation) throws Throwabl TracingUtils.startNewTrace(scopes); @Nullable SentryId checkInId = null; - final long startTime = System.nanoTime(); + // TODO [MAJOR]: switch to ElapsedRealtimeClock, so that a job spanning device sleep + // reports the duration a user would measure rather than the CPU time it had. + final @NotNull Stopwatch stopwatch = Stopwatch.started(JavaUptimeClock.getInstance()); boolean didError = false; try { @@ -105,7 +109,7 @@ public Object invoke(final @NotNull MethodInvocation invocation) throws Throwabl } finally { final @NotNull CheckInStatus status = didError ? CheckInStatus.ERROR : CheckInStatus.OK; CheckIn checkIn = new CheckIn(checkInId, monitorSlug, status); - checkIn.setDuration(DateUtils.nanosToSeconds(System.nanoTime() - startTime)); + checkIn.setDuration(DateUtils.nanosToSeconds(stopwatch.elapsedNanos())); scopes.captureCheckIn(checkIn); } } diff --git a/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/checkin/SentryCheckInAdvice.java b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/checkin/SentryCheckInAdvice.java index fa64ac0e3e4..c4a4db4dabb 100644 --- a/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/checkin/SentryCheckInAdvice.java +++ b/sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/checkin/SentryCheckInAdvice.java @@ -9,6 +9,8 @@ import io.sentry.ScopesAdapter; import io.sentry.SentryLevel; import io.sentry.protocol.SentryId; +import io.sentry.time.JavaUptimeClock; +import io.sentry.time.Stopwatch; import io.sentry.util.Objects; import io.sentry.util.TracingUtils; import java.lang.reflect.Method; @@ -91,7 +93,9 @@ public Object invoke(final @NotNull MethodInvocation invocation) throws Throwabl TracingUtils.startNewTrace(scopes); @Nullable SentryId checkInId = null; - final long startTime = System.nanoTime(); + // TODO [MAJOR]: switch to ElapsedRealtimeClock, so that a job spanning device sleep + // reports the duration a user would measure rather than the CPU time it had. + final @NotNull Stopwatch stopwatch = Stopwatch.started(JavaUptimeClock.getInstance()); boolean didError = false; try { @@ -105,7 +109,7 @@ public Object invoke(final @NotNull MethodInvocation invocation) throws Throwabl } finally { final @NotNull CheckInStatus status = didError ? CheckInStatus.ERROR : CheckInStatus.OK; CheckIn checkIn = new CheckIn(checkInId, monitorSlug, status); - checkIn.setDuration(DateUtils.nanosToSeconds(System.nanoTime() - startTime)); + checkIn.setDuration(DateUtils.nanosToSeconds(stopwatch.elapsedNanos())); scopes.captureCheckIn(checkIn); } } diff --git a/sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java b/sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java index a96e9e29808..dae0b1a12da 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java +++ b/sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java @@ -9,6 +9,8 @@ import io.sentry.ScopesAdapter; import io.sentry.SentryLevel; import io.sentry.protocol.SentryId; +import io.sentry.time.JavaUptimeClock; +import io.sentry.time.Stopwatch; import io.sentry.util.Objects; import io.sentry.util.TracingUtils; import java.lang.reflect.Method; @@ -94,7 +96,9 @@ public Object invoke(final @NotNull MethodInvocation invocation) throws Throwabl TracingUtils.startNewTrace(scopes); @Nullable SentryId checkInId = null; - final long startTime = System.nanoTime(); + // TODO [MAJOR]: switch to ElapsedRealtimeClock, so that a job spanning device sleep + // reports the duration a user would measure rather than the CPU time it had. + final @NotNull Stopwatch stopwatch = Stopwatch.started(JavaUptimeClock.getInstance()); boolean didError = false; try { @@ -108,7 +112,7 @@ public Object invoke(final @NotNull MethodInvocation invocation) throws Throwabl } finally { final @NotNull CheckInStatus status = didError ? CheckInStatus.ERROR : CheckInStatus.OK; CheckIn checkIn = new CheckIn(checkInId, monitorSlug, status); - checkIn.setDuration(DateUtils.nanosToSeconds(System.nanoTime() - startTime)); + checkIn.setDuration(DateUtils.nanosToSeconds(stopwatch.elapsedNanos())); scopes.captureCheckIn(checkIn); } } diff --git a/sentry/src/main/java/io/sentry/util/CheckInUtils.java b/sentry/src/main/java/io/sentry/util/CheckInUtils.java index 3deea093142..c0fa55ec343 100644 --- a/sentry/src/main/java/io/sentry/util/CheckInUtils.java +++ b/sentry/src/main/java/io/sentry/util/CheckInUtils.java @@ -9,6 +9,8 @@ import io.sentry.MonitorConfig; import io.sentry.Sentry; import io.sentry.protocol.SentryId; +import io.sentry.time.JavaUptimeClock; +import io.sentry.time.Stopwatch; import java.util.List; import java.util.concurrent.Callable; import org.jetbrains.annotations.ApiStatus; @@ -37,7 +39,9 @@ public static U withCheckIn( try (final @NotNull ISentryLifecycleToken ignored = Sentry.forkedScopes("CheckInUtils").makeCurrent()) { final @NotNull IScopes scopes = Sentry.getCurrentScopes(); - final long startTime = System.nanoTime(); + // TODO [MAJOR]: switch to ElapsedRealtimeClock, so that a job spanning device sleep + // reports the duration a user would measure rather than the CPU time it had. + final @NotNull Stopwatch stopwatch = Stopwatch.started(JavaUptimeClock.getInstance()); boolean didError = false; TracingUtils.startNewTrace(scopes); @@ -61,7 +65,7 @@ public static U withCheckIn( if (environment != null) { checkIn.setEnvironment(environment); } - checkIn.setDuration(DateUtils.nanosToSeconds(System.nanoTime() - startTime)); + checkIn.setDuration(DateUtils.nanosToSeconds(stopwatch.elapsedNanos())); scopes.captureCheckIn(checkIn); } }