-
-
Notifications
You must be signed in to change notification settings - Fork 478
feat(time): Add a monotonic clock abstraction (JAVA-571) #6028
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
6e87681
feat(time): Add uptime and elapsed-real-time clock abstractions (JAVAโฆ
runningcode 532cc5f
changelog
runningcode 78707e1
ref(time): Drop the uptime clock options seam (JAVA-571)
runningcode 6da48db
ref(time): Trim comments that restate the type name (JAVA-571)
runningcode 9051e40
feat(android): Back the elapsed-real-time clock with SystemClock (JAVโฆ
runningcode 5359ea1
test(time): Drop the accessor and singleton clock tests (JAVA-571)
runningcode 873e406
ref(android): Override the elapsed-real-time clock instead of setting it
runningcode d0760ca
ref(android): Make AndroidElapsedRealtimeClock a singleton
runningcode 8078fa9
test(android): Drop the elapsed-real-time clock options test (JAVA-571)
runningcode c0b1e92
ref(time): Rename Deadline.in to Deadline.after (JAVA-571)
runningcode 414344b
ref(time): Update API dump for the Deadline.after rename (JAVA-571)
runningcode d27758d
ref(time): Collapse the clocks into a single MonotonicClock (JAVA-571)
runningcode d7bc897
ref(time): Rename MonotonicClock to MonotonicTicker (JAVA-571)
runningcode File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
...droid-core/src/main/java/io/sentry/android/core/internal/time/AndroidMonotonicTicker.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package io.sentry.android.core.internal.time; | ||
|
|
||
| import android.os.SystemClock; | ||
| import io.sentry.time.MonotonicTicker; | ||
| import org.jetbrains.annotations.ApiStatus; | ||
| import org.jetbrains.annotations.NotNull; | ||
|
|
||
| /** | ||
| * {@link MonotonicTicker} backed by {@link SystemClock#elapsedRealtimeNanos()}. | ||
| * | ||
| * <p>That is {@code CLOCK_BOOTTIME}, so it keeps counting while the device is suspended โ unlike | ||
| * {@link System#nanoTime()}, which the core module falls back to and which stops in deep sleep. | ||
| */ | ||
| @ApiStatus.Internal | ||
| public final class AndroidMonotonicTicker implements MonotonicTicker { | ||
|
|
||
| private static final AndroidMonotonicTicker instance = new AndroidMonotonicTicker(); | ||
|
|
||
| public static @NotNull MonotonicTicker getInstance() { | ||
| return instance; | ||
| } | ||
|
|
||
| private AndroidMonotonicTicker() {} | ||
|
|
||
| @Override | ||
| public long tickNanos() { | ||
| return SystemClock.elapsedRealtimeNanos(); | ||
| } | ||
| } | ||
18 changes: 18 additions & 0 deletions
18
sentry-test-support/src/main/kotlin/io/sentry/time/TestMonotonicTicker.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package io.sentry.time | ||
|
|
||
| import java.util.concurrent.TimeUnit | ||
|
|
||
| /** | ||
| * A [MonotonicTicker] that only moves when a test tells it to. | ||
| * | ||
| * Advancing by an amount *and a unit* is the point: a stubbed `thenReturn(1001)` against a | ||
| * nanosecond ticker is off by a factor of a million and still compiles, whereas `advance(1001, | ||
| * MILLISECONDS)` cannot be. | ||
| */ | ||
| class TestMonotonicTicker(private var nanos: Long = 0) : MonotonicTicker { | ||
| override fun tickNanos(): Long = nanos | ||
|
|
||
| fun advance(amount: Long, unit: TimeUnit) { | ||
| nanos += unit.toNanos(amount) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| package io.sentry.time; | ||
|
|
||
| import java.util.concurrent.TimeUnit; | ||
| import org.jetbrains.annotations.ApiStatus; | ||
| import org.jetbrains.annotations.NotNull; | ||
|
|
||
| /** | ||
| * A point in the future, measured on a {@link MonotonicTicker}. | ||
| * | ||
| * <p>Exists so that callers never do arithmetic on raw ticks. A tick carries no unit and no epoch, | ||
| * so spelling out {@code now - then < ttl} at every call site is where unit mix-ups, sentinels that | ||
| * happen to mean "boot", and wrap-unsafe {@code <} comparisons come from. Each of those is decided | ||
| * once, here. | ||
| */ | ||
| @ApiStatus.Internal | ||
| public final class Deadline { | ||
|
runningcode marked this conversation as resolved.
|
||
|
|
||
| private final @NotNull MonotonicTicker ticker; | ||
| private final long deadlineNanos; | ||
|
|
||
| private Deadline(final @NotNull MonotonicTicker ticker, final long deadlineNanos) { | ||
| this.ticker = ticker; | ||
| this.deadlineNanos = deadlineNanos; | ||
| } | ||
|
|
||
| /** | ||
| * A deadline {@code amount} of {@code unit} from now. | ||
| * | ||
| * @throws IllegalArgumentException if {@code amount} is negative. A deadline that starts out in | ||
| * the past is a sign error at the call site; {@link #passed} says it deliberately. | ||
| */ | ||
| public static @NotNull Deadline after( | ||
| final @NotNull MonotonicTicker ticker, final long amount, final @NotNull TimeUnit unit) { | ||
| if (amount < 0) { | ||
| throw new IllegalArgumentException("Deadline amount must not be negative, but was " + amount); | ||
| } | ||
| return new Deadline(ticker, ticker.tickNanos() + unit.toNanos(amount)); | ||
| } | ||
|
|
||
| /** | ||
| * A deadline that has already passed. | ||
| * | ||
| * <p>Saves callers from reserving a tick value to mean "not set yet": {@code 0} is a real and | ||
| * very recent instant on a boot-relative ticker, so a field left at {@code 0} reads as freshly | ||
| * set rather than as unset. | ||
| */ | ||
| public static @NotNull Deadline passed(final @NotNull MonotonicTicker ticker) { | ||
| return new Deadline(ticker, ticker.tickNanos()); | ||
| } | ||
|
|
||
| public boolean hasPassed() { | ||
| // Subtraction rather than `<`: a tick origin is arbitrary, may be negative, and may wrap. | ||
| return ticker.tickNanos() - deadlineNanos >= 0; | ||
| } | ||
|
|
||
| /** | ||
| * How much time is left, rounded up, or zero once the deadline has passed. | ||
| * | ||
| * <p>Rounding up matters: callers schedule work for {@code remaining()} and then re-check {@link | ||
| * #hasPassed()}. Truncating would wake them a fraction early, to find the deadline still | ||
| * standing. | ||
| */ | ||
| public long remaining(final @NotNull TimeUnit unit) { | ||
| final long remainingNanos = deadlineNanos - ticker.tickNanos(); | ||
| if (remainingNanos <= 0) { | ||
| return 0; | ||
| } | ||
| final long unitNanos = unit.toNanos(1); | ||
| final long whole = remainingNanos / unitNanos; | ||
| return remainingNanos % unitNanos == 0 ? whole : whole + 1; | ||
| } | ||
|
|
||
| /** | ||
| * Whether this deadline falls after {@code other}. | ||
| * | ||
| * @throws IllegalArgumentException if the two were created from different tickers, whose origins | ||
| * are unrelated and whose ticks are therefore not comparable. | ||
| */ | ||
| public boolean isAfter(final @NotNull Deadline other) { | ||
| if (ticker != other.ticker) { | ||
| throw new IllegalArgumentException( | ||
|
runningcode marked this conversation as resolved.
|
||
| "Cannot compare deadlines from different tickers: " | ||
| + ticker.getClass().getName() | ||
| + " and " | ||
| + other.ticker.getClass().getName()); | ||
| } | ||
| return deadlineNanos - other.deadlineNanos > 0; | ||
| } | ||
| } | ||
22 changes: 22 additions & 0 deletions
22
sentry/src/main/java/io/sentry/time/JavaMonotonicTicker.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package io.sentry.time; | ||
|
|
||
| import org.jetbrains.annotations.ApiStatus; | ||
| import org.jetbrains.annotations.NotNull; | ||
|
|
||
| /** {@link MonotonicTicker} backed by {@link System#nanoTime()}. */ | ||
| @ApiStatus.Internal | ||
| public final class JavaMonotonicTicker implements MonotonicTicker { | ||
|
|
||
| private static final JavaMonotonicTicker instance = new JavaMonotonicTicker(); | ||
|
|
||
| public static @NotNull MonotonicTicker getInstance() { | ||
| return instance; | ||
| } | ||
|
|
||
| private JavaMonotonicTicker() {} | ||
|
|
||
| @Override | ||
| public long tickNanos() { | ||
| return System.nanoTime(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package io.sentry.time; | ||
|
|
||
| import org.jetbrains.annotations.ApiStatus; | ||
|
|
||
| /** | ||
| * A monotonically increasing nanosecond counter, including time the device spent suspended in deep | ||
| * sleep. | ||
| * | ||
| * <p>This type deliberately promises very little: a tick is a number that does not go backwards, | ||
|
runningcode marked this conversation as resolved.
|
||
| * measured from an origin that is arbitrary and may be negative. Only <em>differences</em> between | ||
| * two ticks from the same instance are meaningful, and a tick must never be persisted, serialized, | ||
| * or compared against a value from another ticker. | ||
| * | ||
| * <p>On Android this is {@code CLOCK_BOOTTIME}, via {@code SystemClock.elapsedRealtimeNanos()}, so | ||
| * an interval measured across a suspend reports the real time that passed rather than only the time | ||
| * the CPU was awake. On the JVM there is no comparable suspend state, so {@link System#nanoTime()} | ||
| * is equivalent. | ||
| */ | ||
| @ApiStatus.Internal | ||
| public interface MonotonicTicker { | ||
| long tickNanos(); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package io.sentry.time; | ||
|
|
||
| import java.util.concurrent.TimeUnit; | ||
| import org.jetbrains.annotations.ApiStatus; | ||
| import org.jetbrains.annotations.NotNull; | ||
|
|
||
| /** | ||
| * Measures how long something took, on a {@link MonotonicTicker}. | ||
| * | ||
| * <p>The counterpart to {@link Deadline}: it keeps the start tick and the unit conversion in one | ||
| * place, so call sites stop repeating {@code System.nanoTime() - startTime}. | ||
| */ | ||
| @ApiStatus.Internal | ||
| public final class Stopwatch { | ||
|
|
||
| private final @NotNull MonotonicTicker ticker; | ||
| private final long startNanos; | ||
|
|
||
| private Stopwatch(final @NotNull MonotonicTicker ticker) { | ||
| this.ticker = ticker; | ||
| this.startNanos = ticker.tickNanos(); | ||
| } | ||
|
|
||
| public static @NotNull Stopwatch started(final @NotNull MonotonicTicker ticker) { | ||
| return new Stopwatch(ticker); | ||
| } | ||
|
|
||
|
runningcode marked this conversation as resolved.
|
||
| public long elapsedNanos() { | ||
| return ticker.tickNanos() - startNanos; | ||
| } | ||
|
|
||
| public long elapsed(final @NotNull TimeUnit unit) { | ||
| return unit.convert(elapsedNanos(), TimeUnit.NANOSECONDS); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.