diff --git a/AGENTS.md b/AGENTS.md index f447fc31512..eafac499bfd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -202,6 +202,29 @@ Two more rules that catch most of the rest: Prefer `AutoClosableReentrantLock` over `synchronized`, matching the rest of the codebase. +### Measuring Time + +**New code that reads a clock uses `io.sentry.time`**, not `System.currentTimeMillis()`, +`System.nanoTime()`, `SystemClock`, `DateUtils.getCurrentDateTime()`, a `SentryDateProvider`, or an +`ICurrentDateProvider`. Those are legacy, not a precedent to follow. + +Pick the type by what the number is for: + +| You need | Use | +|---|---| +| An instant that leaves the process (event, breadcrumb, session) | `EpochClock` → `Timestamp`. Never subtract two of them | +| How long something took | `Stopwatch` | +| Whether a window has elapsed (TTL, backoff, timeout) | `Deadline` | +| Several instants compared with each other (spans of a transaction) | `AnchoredClock` | +| A raw tick, when none of the above fits | `MonotonicTicker` | + +Get the clock from `options.getEpochClock()` or `options.getMonotonicTicker()`, not a +`getInstance()`: on Android the ticker keeps counting through device suspend, and `System.nanoTime()` +does not. Take it as a constructor parameter; tests use `TestMonotonicTicker` instead of sleeping. + +If none of these fits, propose a new type, and the clock bug it makes impossible, before writing +call sites against it. Don't inline raw tick arithmetic as a stopgap. + ### Testing Requirements - Write comprehensive unit tests for new features - Android modules require both unit tests and instrumented tests where applicable diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/time/AndroidMonotonicTicker.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/time/AndroidMonotonicTicker.java index 22973b0d937..06deaa5a48a 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/time/AndroidMonotonicTicker.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/time/AndroidMonotonicTicker.java @@ -16,6 +16,7 @@ public final class AndroidMonotonicTicker implements MonotonicTicker { private static final AndroidMonotonicTicker instance = new AndroidMonotonicTicker(); + /** Prefer {@link io.sentry.SentryOptions#getMonotonicTicker()}; on Android it returns this. */ public static @NotNull MonotonicTicker getInstance() { return instance; } diff --git a/sentry/src/main/java/io/sentry/time/JavaMonotonicTicker.java b/sentry/src/main/java/io/sentry/time/JavaMonotonicTicker.java index b5b000f2805..fab92fd1b9a 100644 --- a/sentry/src/main/java/io/sentry/time/JavaMonotonicTicker.java +++ b/sentry/src/main/java/io/sentry/time/JavaMonotonicTicker.java @@ -9,6 +9,11 @@ public final class JavaMonotonicTicker implements MonotonicTicker { private static final JavaMonotonicTicker instance = new JavaMonotonicTicker(); + /** + * Prefer {@link io.sentry.SentryOptions#getMonotonicTicker()}, which picks the platform's ticker. + * On Android this one stops counting during device suspend, so intervals spanning a deep sleep + * come out short. + */ public static @NotNull MonotonicTicker getInstance() { return instance; } diff --git a/sentry/src/main/java/io/sentry/time/SystemEpochClock.java b/sentry/src/main/java/io/sentry/time/SystemEpochClock.java index 2cb037c0e09..b11eb18fb72 100644 --- a/sentry/src/main/java/io/sentry/time/SystemEpochClock.java +++ b/sentry/src/main/java/io/sentry/time/SystemEpochClock.java @@ -24,6 +24,10 @@ public final class SystemEpochClock implements EpochClock { private static final SystemEpochClock instance = new SystemEpochClock(); + /** + * Prefer {@link io.sentry.SentryOptions#getEpochClock()}, which is where a platform would + * substitute its own wall clock, the way Android already substitutes the ticker. + */ public static @NotNull EpochClock getInstance() { return instance; }