Skip to content

Commit c11fec4

Browse files
runningcodeclaude
andcommitted
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 clock now comes from the options, so a check-in measures on whatever the SDK measures on: System.nanoTime() on the JVM, unchanged to the bit, and SystemClock.elapsedRealtimeNanos() on Android. A cron job that spans a suspend therefore reports the duration a user would measure rather than the time the CPU happened to be awake, which is what a check-in duration is meant to mean. An uninitialised SDK still reaches a clock: NoOpScopes.getOptions() hands back empty options, whose clock is the JVM one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 8bb8665 commit c11fec4

6 files changed

Lines changed: 18 additions & 8 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
### Behavioral Changes
1010

1111
- Measure HTTP rate-limit backoff on a monotonic clock instead of the wall clock, so that a device time change no longer lifts or extends an active rate limit ([#6030](https://github.com/getsentry/sentry-java/pull/6030))
12+
- Measure check-in durations on the monotonic clock, so a cron job that spans device sleep reports the time a user would measure instead of the time the CPU was awake ([#6032](https://github.com/getsentry/sentry-java/pull/6032))
1213

1314
### Internal
1415

‎sentry-spring-7/src/main/java/io/sentry/spring7/checkin/SentryCheckInAdvice.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.sentry.ScopesAdapter;
1010
import io.sentry.SentryLevel;
1111
import io.sentry.protocol.SentryId;
12+
import io.sentry.time.Stopwatch;
1213
import io.sentry.util.Objects;
1314
import io.sentry.util.TracingUtils;
1415
import java.lang.reflect.Method;
@@ -91,7 +92,8 @@ public Object invoke(final @NotNull MethodInvocation invocation) throws Throwabl
9192
TracingUtils.startNewTrace(scopes);
9293

9394
@Nullable SentryId checkInId = null;
94-
final long startTime = System.nanoTime();
95+
final @NotNull Stopwatch stopwatch =
96+
Stopwatch.started(scopes.getOptions().getMonotonicClock());
9597
boolean didError = false;
9698

9799
try {
@@ -105,7 +107,7 @@ public Object invoke(final @NotNull MethodInvocation invocation) throws Throwabl
105107
} finally {
106108
final @NotNull CheckInStatus status = didError ? CheckInStatus.ERROR : CheckInStatus.OK;
107109
CheckIn checkIn = new CheckIn(checkInId, monitorSlug, status);
108-
checkIn.setDuration(DateUtils.nanosToSeconds(System.nanoTime() - startTime));
110+
checkIn.setDuration(DateUtils.nanosToSeconds(stopwatch.elapsedNanos()));
109111
scopes.captureCheckIn(checkIn);
110112
}
111113
}

‎sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/checkin/SentryCheckInAdvice.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.sentry.ScopesAdapter;
1010
import io.sentry.SentryLevel;
1111
import io.sentry.protocol.SentryId;
12+
import io.sentry.time.Stopwatch;
1213
import io.sentry.util.Objects;
1314
import io.sentry.util.TracingUtils;
1415
import java.lang.reflect.Method;
@@ -91,7 +92,8 @@ public Object invoke(final @NotNull MethodInvocation invocation) throws Throwabl
9192
TracingUtils.startNewTrace(scopes);
9293

9394
@Nullable SentryId checkInId = null;
94-
final long startTime = System.nanoTime();
95+
final @NotNull Stopwatch stopwatch =
96+
Stopwatch.started(scopes.getOptions().getMonotonicClock());
9597
boolean didError = false;
9698

9799
try {
@@ -105,7 +107,7 @@ public Object invoke(final @NotNull MethodInvocation invocation) throws Throwabl
105107
} finally {
106108
final @NotNull CheckInStatus status = didError ? CheckInStatus.ERROR : CheckInStatus.OK;
107109
CheckIn checkIn = new CheckIn(checkInId, monitorSlug, status);
108-
checkIn.setDuration(DateUtils.nanosToSeconds(System.nanoTime() - startTime));
110+
checkIn.setDuration(DateUtils.nanosToSeconds(stopwatch.elapsedNanos()));
109111
scopes.captureCheckIn(checkIn);
110112
}
111113
}

‎sentry-spring/src/main/java/io/sentry/spring/checkin/SentryCheckInAdvice.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.sentry.ScopesAdapter;
1010
import io.sentry.SentryLevel;
1111
import io.sentry.protocol.SentryId;
12+
import io.sentry.time.Stopwatch;
1213
import io.sentry.util.Objects;
1314
import io.sentry.util.TracingUtils;
1415
import java.lang.reflect.Method;
@@ -94,7 +95,8 @@ public Object invoke(final @NotNull MethodInvocation invocation) throws Throwabl
9495
TracingUtils.startNewTrace(scopes);
9596

9697
@Nullable SentryId checkInId = null;
97-
final long startTime = System.nanoTime();
98+
final @NotNull Stopwatch stopwatch =
99+
Stopwatch.started(scopes.getOptions().getMonotonicClock());
98100
boolean didError = false;
99101

100102
try {
@@ -108,7 +110,7 @@ public Object invoke(final @NotNull MethodInvocation invocation) throws Throwabl
108110
} finally {
109111
final @NotNull CheckInStatus status = didError ? CheckInStatus.ERROR : CheckInStatus.OK;
110112
CheckIn checkIn = new CheckIn(checkInId, monitorSlug, status);
111-
checkIn.setDuration(DateUtils.nanosToSeconds(System.nanoTime() - startTime));
113+
checkIn.setDuration(DateUtils.nanosToSeconds(stopwatch.elapsedNanos()));
112114
scopes.captureCheckIn(checkIn);
113115
}
114116
}

‎sentry/src/main/java/io/sentry/util/CheckInUtils.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.sentry.MonitorConfig;
1010
import io.sentry.Sentry;
1111
import io.sentry.protocol.SentryId;
12+
import io.sentry.time.Stopwatch;
1213
import java.util.List;
1314
import java.util.concurrent.Callable;
1415
import org.jetbrains.annotations.ApiStatus;
@@ -37,7 +38,8 @@ public static <U> U withCheckIn(
3738
try (final @NotNull ISentryLifecycleToken ignored =
3839
Sentry.forkedScopes("CheckInUtils").makeCurrent()) {
3940
final @NotNull IScopes scopes = Sentry.getCurrentScopes();
40-
final long startTime = System.nanoTime();
41+
final @NotNull Stopwatch stopwatch =
42+
Stopwatch.started(scopes.getOptions().getMonotonicClock());
4143
boolean didError = false;
4244

4345
TracingUtils.startNewTrace(scopes);
@@ -61,7 +63,7 @@ public static <U> U withCheckIn(
6163
if (environment != null) {
6264
checkIn.setEnvironment(environment);
6365
}
64-
checkIn.setDuration(DateUtils.nanosToSeconds(System.nanoTime() - startTime));
66+
checkIn.setDuration(DateUtils.nanosToSeconds(stopwatch.elapsedNanos()));
6567
scopes.captureCheckIn(checkIn);
6668
}
6769
}

‎sentry/src/test/java/io/sentry/util/CheckInUtilsTest.kt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ class CheckInUtilsTest {
143143
sentry.`when`<Any> { Sentry.forkedScopes(any()) }.then { scopes.forkedScopes("test") }
144144
whenever(scopes.forkedScopes(any())).thenReturn(scopes)
145145
whenever(scopes.makeCurrent()).thenReturn(lifecycleToken)
146+
whenever(scopes.options).thenReturn(SentryOptions())
146147

147148
try {
148149
CheckInUtils.withCheckIn("monitor-1") { throw RuntimeException("thrown on purpose") }

0 commit comments

Comments
 (0)