opentelemetry-android icon indicating copy to clipboard operation
opentelemetry-android copied to clipboard

`Clock.getDefault()` isn’t configurable; add platform-aware selection & override hooks

Open Sabaev opened this issue 1 month ago • 5 comments

Component(s): sdk, common, bootstrap
Environment: Cross-platform (JVM & Android)

Summary

Clock.getDefault() effectively hardwires System.nanoTime(). On Android this ignores deep sleep, leading to incorrect elapsed-time semantics for features like timeouts and background durations. There’s no easy global way to swap the default clock per platform.

Expected behavior

The SDK should choose a platform-appropriate default clock and allow an explicit override:

  • JVM/server: System.nanoTime() is fine for monotonic elapsed durations.
  • Android: prefer SystemClock.elapsedRealtimeNanos() (includes deep sleep).

Actual behavior

Users cannot globally configure the default clock. Each component must be hand-wired (if possible), which is fragile and easy to miss.

Proposed change

Introduce a lightweight, platform-aware ClockProvider SPI with discovery (optimized for Android, similar in spirit to kotlinx.coroutines’ FastServiceLoader):

Sabaev avatar Nov 06 '25 13:11 Sabaev

Hi @Sabaev in the PR https://github.com/open-telemetry/opentelemetry-android/pull/1381 I have created a AndroidClock which for android replaces the Clock.getDefault() usage with AndroidClock.INSTANCE usage. This should fix the handling in session timeout and app startup calculation

Though the usages from java side will not be fixed and in that case your proposed solution of service loader can be used

atulgpt avatar Nov 11 '25 19:11 atulgpt

SystemClock.elapsedRealtimeNanos() is not suitable as it returns the time since boot, rather than the wall-clock time. System.nanoTime() is suitable for elapsed durations but doesn't provide a wall-clock time.

IMO the best approach to calculate wall-clock time is to obtain a baseline reading of System.currentTimeMillis() with SystemClock.elapsedRealtimeNanos() subtracted, and then for subsequent measurements add SystemClock.elapsedRealtimeNanos(). We follow this approach in the Embrace Android SDK: https://github.com/embrace-io/embrace-android-sdk/blob/main/embrace-android-utils/src/main/kotlin/io/embrace/android/embracesdk/internal/clock/NormalizedIntervalClock.kt

There are also alternative time source APIs in newer versions of Android such as GnssClock and currentNetworkTimeClock that might be worth investigating

fractalwrench avatar Nov 13 '25 09:11 fractalwrench

Also related to #1363.

breedx-splk avatar Nov 13 '25 17:11 breedx-splk

Here is the approach I’m currently using:

Shared Clock abstraction.

In our internal project we expose our own Clock interface, accessible via a static companion object. This allows us to retrieve a clock instance from anywhere — Kotlin modules, Java modules, Android code, or even inside ContentProviders — without worrying about initialization order.

Bootstrap layer.

For initialization we use a custom ServiceProvider, optimized for R8 to avoid reflection overhead.

Android implementation.

Clock.now

On Android API ≥ Tiramisu, we use SystemClock.currentNetworkTimeClock().

On API < Tiramisu, we follow the same “anchored time” technique used by Embrace (combining wall-clock time with a monotonic clock).

Clock.nanoTime

for monotonic clock we always use SystemClock.elapsedRealtimeNanos()

Fallback logic.

If currentNetworkTimeClock() throws a DateTimeException, we fall back to the anchored-time implementation

Sabaev avatar Nov 21 '25 11:11 Sabaev

For monotonic time on Android, SystemClock.elapsedRealtimeNanos() is generally a better choice than System.nanoTime(). The reason: System.nanoTime() does not include time spent in deep sleep, so duration calculations (timeouts, background intervals, etc.) become inaccurate on real devices.

The Android documentation explicitly states:

The value returned by this method does not account for elapsed time during deep sleep. For timekeeping facilities available on Android, see android.os.SystemClock.

Android doc for System.nanoTime(): 🔗 https://developer.android.com/reference/java/lang/System#nanoTime()

Sabaev avatar Nov 21 '25 11:11 Sabaev