From ca4ce3947c128be13d7469e6c89608dbf8725806 Mon Sep 17 00:00:00 2001 From: Jason Plumb Date: Tue, 10 Dec 2024 18:10:24 -0800 Subject: [PATCH 1/4] factor out interface --- .../android/OpenTelemetryRumBuilder.java | 3 + .../android/SdkPreconfiguredRumBuilder.kt | 3 +- .../android/session/SessionManager.kt | 84 ++----------------- .../android/session/SessionManagerImpl.kt | 79 +++++++++++++++++ ...nagerTest.kt => SessionManagerImplTest.kt} | 10 +-- 5 files changed, 96 insertions(+), 83 deletions(-) create mode 100644 core/src/main/java/io/opentelemetry/android/session/SessionManagerImpl.kt rename core/src/test/java/io/opentelemetry/android/session/{SessionManagerTest.kt => SessionManagerImplTest.kt} (91%) diff --git a/core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java b/core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java index 66b3c89be..d515f069b 100644 --- a/core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java +++ b/core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java @@ -34,6 +34,7 @@ import io.opentelemetry.android.internal.services.ServiceManagerImpl; import io.opentelemetry.android.internal.services.periodicwork.PeriodicWorkService; import io.opentelemetry.android.session.SessionManager; +import io.opentelemetry.android.session.SessionManagerImpl; import io.opentelemetry.android.session.SessionProvider; import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; @@ -373,6 +374,8 @@ private void initializeExporters( } initializationEvents.spanExporterInitialized(spanExporter); + SessionManager sessionManager = + SessionManagerImpl.create(timeoutHandler, config.getSessionTimeout().toNanos()); bufferedDelegatingLogExporter.setDelegate(logsExporter); bufferDelegatingSpanExporter.setDelegate(spanExporter); diff --git a/core/src/main/java/io/opentelemetry/android/SdkPreconfiguredRumBuilder.kt b/core/src/main/java/io/opentelemetry/android/SdkPreconfiguredRumBuilder.kt index 7bf926952..5c18c55d7 100644 --- a/core/src/main/java/io/opentelemetry/android/SdkPreconfiguredRumBuilder.kt +++ b/core/src/main/java/io/opentelemetry/android/SdkPreconfiguredRumBuilder.kt @@ -11,6 +11,7 @@ import io.opentelemetry.android.instrumentation.AndroidInstrumentationLoader import io.opentelemetry.android.instrumentation.InstallationContext import io.opentelemetry.android.internal.services.ServiceManager import io.opentelemetry.android.session.SessionManager +import io.opentelemetry.android.session.SessionManagerImpl import io.opentelemetry.sdk.OpenTelemetrySdk class SdkPreconfiguredRumBuilder @@ -19,7 +20,7 @@ class SdkPreconfiguredRumBuilder private val application: Application, private val sdk: OpenTelemetrySdk, private val timeoutHandler: SessionIdTimeoutHandler = SessionIdTimeoutHandler(), - private val sessionManager: SessionManager = SessionManager(timeoutHandler = timeoutHandler), + private val sessionManager: SessionManager = SessionManagerImpl(timeoutHandler = timeoutHandler), private val discoverInstrumentations: Boolean, private val serviceManager: ServiceManager, ) { diff --git a/core/src/main/java/io/opentelemetry/android/session/SessionManager.kt b/core/src/main/java/io/opentelemetry/android/session/SessionManager.kt index f86f260a0..cbfa04555 100644 --- a/core/src/main/java/io/opentelemetry/android/session/SessionManager.kt +++ b/core/src/main/java/io/opentelemetry/android/session/SessionManager.kt @@ -1,79 +1,9 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - package io.opentelemetry.android.session -import io.opentelemetry.android.SessionIdTimeoutHandler -import io.opentelemetry.sdk.common.Clock -import java.util.Collections.synchronizedList -import java.util.concurrent.TimeUnit - -internal class SessionManager( - private val clock: Clock = Clock.getDefault(), - private val sessionStorage: SessionStorage = SessionStorage.InMemory(), - private val timeoutHandler: SessionIdTimeoutHandler, - private val idGenerator: SessionIdGenerator = SessionIdGenerator.DEFAULT, - private val sessionLifetimeNanos: Long = TimeUnit.HOURS.toNanos(4), -) : SessionProvider, - SessionPublisher { - // TODO: Make thread safe / wrap with AtomicReference? - private var session: Session = Session.NONE - private val observers = synchronizedList(ArrayList()) - - init { - sessionStorage.save(session) - } - - override fun addObserver(observer: SessionObserver) { - observers.add(observer) - } - - override fun getSessionId(): String { - // value will never be null - var newSession = session - - if (sessionHasExpired() || timeoutHandler.hasTimedOut()) { - val newId = idGenerator.generateSessionId() - - // TODO FIXME: This is not threadsafe -- if two threads call getSessionId() - // at the same time while timed out, two new sessions are created - // Could require SessionStorage impls to be atomic/threadsafe or - // do the locking in this class? - - newSession = Session.DefaultSession(newId, clock.now()) - sessionStorage.save(newSession) - } - - timeoutHandler.bump() - - // observers need to be called after bumping the timer because it may - // create a new span - if (newSession != session) { - observers.forEach { - it.onSessionEnded(session) - it.onSessionStarted(newSession, session) - } - session = newSession - } - return session.getId() - } - - private fun sessionHasExpired(): Boolean { - val elapsedTime = clock.now() - session.getStartTimestamp() - return elapsedTime >= sessionLifetimeNanos - } - - companion object { - @JvmStatic - fun create( - timeoutHandler: SessionIdTimeoutHandler, - sessionLifetimeNanos: Long, - ): SessionManager = - SessionManager( - timeoutHandler = timeoutHandler, - sessionLifetimeNanos = sessionLifetimeNanos, - ) - } -} +/** + * The SessionManager is a public-facing tag interface that brings together + * the SessionProvider and SessionPublisher interfaces under a common + * name. + */ +interface SessionManager: SessionProvider, SessionPublisher { +} \ No newline at end of file diff --git a/core/src/main/java/io/opentelemetry/android/session/SessionManagerImpl.kt b/core/src/main/java/io/opentelemetry/android/session/SessionManagerImpl.kt new file mode 100644 index 000000000..face89905 --- /dev/null +++ b/core/src/main/java/io/opentelemetry/android/session/SessionManagerImpl.kt @@ -0,0 +1,79 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.android.session + +import io.opentelemetry.android.SessionIdTimeoutHandler +import io.opentelemetry.sdk.common.Clock +import java.util.Collections.synchronizedList +import java.util.concurrent.TimeUnit + +internal class SessionManagerImpl( + private val clock: Clock = Clock.getDefault(), + private val sessionStorage: SessionStorage = SessionStorage.InMemory(), + private val timeoutHandler: SessionIdTimeoutHandler, + private val idGenerator: SessionIdGenerator = SessionIdGenerator.DEFAULT, + private val sessionLifetimeNanos: Long = TimeUnit.HOURS.toNanos(4), +) : SessionManager { + // TODO: Make thread safe / wrap with AtomicReference? + private var session: Session = Session.NONE + private val observers = synchronizedList(ArrayList()) + + init { + sessionStorage.save(session) + } + + override fun addObserver(observer: SessionObserver) { + observers.add(observer) + } + + override fun getSessionId(): String { + // value will never be null + var newSession = session + + if (sessionHasExpired() || timeoutHandler.hasTimedOut()) { + val newId = idGenerator.generateSessionId() + + // TODO FIXME: This is not threadsafe -- if two threads call getSessionId() + // at the same time while timed out, two new sessions are created + // Could require SessionStorage impls to be atomic/threadsafe or + // do the locking in this class? + + newSession = Session.DefaultSession(newId, clock.now()) + sessionStorage.save(newSession) + } + + timeoutHandler.bump() + + // observers need to be called after bumping the timer because it may + // create a new span + if (newSession != session) { + observers.forEach { + it.onSessionEnded(session) + it.onSessionStarted(newSession, session) + } + session = newSession + } + return session.getId() + } + + private fun sessionHasExpired(): Boolean { + val elapsedTime = clock.now() - session.getStartTimestamp() + return elapsedTime >= sessionLifetimeNanos + } + + companion object { + @JvmStatic + fun create( + timeoutHandler: SessionIdTimeoutHandler, + sessionLifetimeNanos: Long, + ): SessionManagerImpl { + return SessionManagerImpl( + timeoutHandler = timeoutHandler, + sessionLifetimeNanos = sessionLifetimeNanos, + ) + } + } +} diff --git a/core/src/test/java/io/opentelemetry/android/session/SessionManagerTest.kt b/core/src/test/java/io/opentelemetry/android/session/SessionManagerImplTest.kt similarity index 91% rename from core/src/test/java/io/opentelemetry/android/session/SessionManagerTest.kt rename to core/src/test/java/io/opentelemetry/android/session/SessionManagerImplTest.kt index fa28c9c06..8dd860104 100644 --- a/core/src/test/java/io/opentelemetry/android/session/SessionManagerTest.kt +++ b/core/src/test/java/io/opentelemetry/android/session/SessionManagerImplTest.kt @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test import java.util.concurrent.TimeUnit import java.util.regex.Pattern -internal class SessionManagerTest { +internal class SessionManagerImplTest { @MockK lateinit var timeoutHandler: SessionIdTimeoutHandler @@ -35,7 +35,7 @@ internal class SessionManagerTest { @Test fun valueValid() { - val sessionManager = SessionManager(TestClock.create(), timeoutHandler = timeoutHandler) + val sessionManager = SessionManagerImpl(TestClock.create(), timeoutHandler = timeoutHandler) val sessionId = sessionManager.getSessionId() assertThat(sessionId).isNotNull() assertThat(sessionId).hasSize(32) @@ -45,7 +45,7 @@ internal class SessionManagerTest { @Test fun valueSameUntil4Hours() { val clock = TestClock.create() - val sessionManager = SessionManager(clock, timeoutHandler = timeoutHandler) + val sessionManager = SessionManagerImpl(clock, timeoutHandler = timeoutHandler) val value = sessionManager.getSessionId() assertThat(value).isEqualTo(sessionManager.getSessionId()) clock.advance(3, TimeUnit.HOURS) @@ -69,7 +69,7 @@ internal class SessionManagerTest { every { observer.onSessionStarted(any(), any()) } just Runs every { observer.onSessionEnded(any()) } just Runs - val sessionManager = SessionManager(clock, timeoutHandler = timeoutHandler) + val sessionManager = SessionManagerImpl(clock, timeoutHandler = timeoutHandler) sessionManager.addObserver(observer) // The first call expires the Session.NONE initial session and notifies @@ -108,7 +108,7 @@ internal class SessionManagerTest { @Test fun shouldCreateNewSessionIdAfterTimeout() { - val sessionId = SessionManager(timeoutHandler = timeoutHandler) + val sessionId = SessionManagerImpl(timeoutHandler = timeoutHandler) val value = sessionId.getSessionId() verify { timeoutHandler.bump() } From abedcf8effa9261a5322810d22af6b3a72c2a236 Mon Sep 17 00:00:00 2001 From: Jason Plumb Date: Wed, 11 Dec 2024 08:54:23 -0800 Subject: [PATCH 2/4] move session api to its own top module. make implementations internal. --- core/build.gradle.kts | 3 +- .../android/OpenTelemetryRumBuilder.java | 3 +- .../android/SdkPreconfiguredRumBuilder.kt | 3 +- .../android/SessionIdTimeoutHandler.java | 84 -------------- .../session/SessionIdTimeoutHandler.kt | 82 ++++++++++++++ .../session/SessionManagerImpl.kt | 8 +- .../android/OpenTelemetryRumBuilderTest.java | 1 + .../android/SessionIdTimeoutHandlerTest.java | 104 ------------------ .../session/SessionIdTimeoutHandlerTest.kt | 102 +++++++++++++++++ .../session/SessionManagerImplTest.kt | 26 ++++- session/build.gradle.kts | 20 ++++ .../opentelemetry/android/session/Session.kt | 0 .../android/session/SessionIdGenerator.kt | 0 .../android/session/SessionManager.kt | 8 +- .../android/session/SessionObserver.kt | 0 .../android/session/SessionProvider.kt | 0 .../android/session/SessionPublisher.kt | 0 .../android/session/SessionStorage.kt | 0 settings.gradle.kts | 3 +- 19 files changed, 245 insertions(+), 202 deletions(-) delete mode 100644 core/src/main/java/io/opentelemetry/android/SessionIdTimeoutHandler.java create mode 100644 core/src/main/java/io/opentelemetry/android/internal/session/SessionIdTimeoutHandler.kt rename core/src/main/java/io/opentelemetry/android/{ => internal}/session/SessionManagerImpl.kt (88%) delete mode 100644 core/src/test/java/io/opentelemetry/android/SessionIdTimeoutHandlerTest.java create mode 100644 core/src/test/java/io/opentelemetry/android/internal/session/SessionIdTimeoutHandlerTest.kt rename core/src/test/java/io/opentelemetry/android/{ => internal}/session/SessionManagerImplTest.kt (83%) create mode 100644 session/build.gradle.kts rename {core/src/main/java => session/src/main/kotlin}/io/opentelemetry/android/session/Session.kt (100%) rename {core/src/main/java => session/src/main/kotlin}/io/opentelemetry/android/session/SessionIdGenerator.kt (100%) rename {core/src/main/java => session/src/main/kotlin}/io/opentelemetry/android/session/SessionManager.kt (58%) rename {core/src/main/java => session/src/main/kotlin}/io/opentelemetry/android/session/SessionObserver.kt (100%) rename {core/src/main/java => session/src/main/kotlin}/io/opentelemetry/android/session/SessionProvider.kt (100%) rename {core/src/main/java => session/src/main/kotlin}/io/opentelemetry/android/session/SessionPublisher.kt (100%) rename {core/src/main/java => session/src/main/kotlin}/io/opentelemetry/android/session/SessionStorage.kt (100%) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 9dfcdcec7..d8ca947e2 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -63,8 +63,9 @@ android { dependencies { implementation(project(":instrumentation:android-instrumentation")) - implementation(project(":services")) implementation(project(":common")) + implementation(project(":services")) + implementation(project(":session")) implementation(libs.androidx.core) diff --git a/core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java b/core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java index d515f069b..093939ac8 100644 --- a/core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java +++ b/core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java @@ -33,8 +33,9 @@ import io.opentelemetry.android.internal.services.ServiceManager; import io.opentelemetry.android.internal.services.ServiceManagerImpl; import io.opentelemetry.android.internal.services.periodicwork.PeriodicWorkService; +import io.opentelemetry.android.internal.session.SessionIdTimeoutHandler; +import io.opentelemetry.android.internal.session.SessionManagerImpl; import io.opentelemetry.android.session.SessionManager; -import io.opentelemetry.android.session.SessionManagerImpl; import io.opentelemetry.android.session.SessionProvider; import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; diff --git a/core/src/main/java/io/opentelemetry/android/SdkPreconfiguredRumBuilder.kt b/core/src/main/java/io/opentelemetry/android/SdkPreconfiguredRumBuilder.kt index 5c18c55d7..718d71220 100644 --- a/core/src/main/java/io/opentelemetry/android/SdkPreconfiguredRumBuilder.kt +++ b/core/src/main/java/io/opentelemetry/android/SdkPreconfiguredRumBuilder.kt @@ -10,8 +10,9 @@ import io.opentelemetry.android.instrumentation.AndroidInstrumentation import io.opentelemetry.android.instrumentation.AndroidInstrumentationLoader import io.opentelemetry.android.instrumentation.InstallationContext import io.opentelemetry.android.internal.services.ServiceManager +import io.opentelemetry.android.internal.session.SessionIdTimeoutHandler +import io.opentelemetry.android.internal.session.SessionManagerImpl import io.opentelemetry.android.session.SessionManager -import io.opentelemetry.android.session.SessionManagerImpl import io.opentelemetry.sdk.OpenTelemetrySdk class SdkPreconfiguredRumBuilder diff --git a/core/src/main/java/io/opentelemetry/android/SessionIdTimeoutHandler.java b/core/src/main/java/io/opentelemetry/android/SessionIdTimeoutHandler.java deleted file mode 100644 index 215f0fa1e..000000000 --- a/core/src/main/java/io/opentelemetry/android/SessionIdTimeoutHandler.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.android; - -import io.opentelemetry.android.internal.services.applifecycle.ApplicationStateListener; -import io.opentelemetry.sdk.common.Clock; -import java.time.Duration; - -/** - * This class encapsulates the following criteria about the sessionId timeout: - * - *
    - *
  • If the app is in the foreground sessionId should never time out. - *
  • If the app is in the background and no activity (spans) happens for >15 minutes, sessionId - * should time out. - *
  • If the app is in the background and some activity (spans) happens in <15 minute intervals, - * sessionId should not time out. - *
- * - *

Consequently, when the app spent >15 minutes without any activity (spans) in the background, - * after moving to the foreground the first span should trigger the sessionId timeout. - */ -// TODO: Migrate to kotlin and make internal? -public final class SessionIdTimeoutHandler implements ApplicationStateListener { - - static final Duration DEFAULT_SESSION_TIMEOUT = Duration.ofMinutes(15); - private final Duration sessionTimeout; - - private final Clock clock; - private volatile long timeoutStartNanos; - private volatile State state = State.FOREGROUND; - - SessionIdTimeoutHandler() { - this(DEFAULT_SESSION_TIMEOUT); - } - - // for testing - SessionIdTimeoutHandler(Duration sessionTimeout) { - this(Clock.getDefault(), sessionTimeout); - } - - SessionIdTimeoutHandler(Clock clock, Duration sessionTimeout) { - this.clock = clock; - this.sessionTimeout = sessionTimeout; - } - - @Override - public void onApplicationForegrounded() { - state = State.TRANSITIONING_TO_FOREGROUND; - } - - @Override - public void onApplicationBackgrounded() { - state = State.BACKGROUND; - } - - public boolean hasTimedOut() { - // don't apply sessionId timeout to apps in the foreground - if (state == State.FOREGROUND) { - return false; - } - long elapsedTime = clock.nanoTime() - timeoutStartNanos; - return elapsedTime >= sessionTimeout.toNanos(); - } - - public void bump() { - timeoutStartNanos = clock.nanoTime(); - - // move from the temporary transition state to foreground after the first span - if (state == State.TRANSITIONING_TO_FOREGROUND) { - state = State.FOREGROUND; - } - } - - private enum State { - FOREGROUND, - BACKGROUND, - /** A temporary state representing the first event after the app has been brought back. */ - TRANSITIONING_TO_FOREGROUND - } -} diff --git a/core/src/main/java/io/opentelemetry/android/internal/session/SessionIdTimeoutHandler.kt b/core/src/main/java/io/opentelemetry/android/internal/session/SessionIdTimeoutHandler.kt new file mode 100644 index 000000000..a8e70cf90 --- /dev/null +++ b/core/src/main/java/io/opentelemetry/android/internal/session/SessionIdTimeoutHandler.kt @@ -0,0 +1,82 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.android.internal.session + +import io.opentelemetry.android.internal.services.applifecycle.ApplicationStateListener +import io.opentelemetry.sdk.common.Clock +import java.time.Duration + +/** + * This class encapsulates the following criteria about the sessionId timeout: + * + * + * * If the app is in the foreground sessionId should never time out. + * * If the app is in the background and no activity (spans) happens for >15 minutes, sessionId + * should time out. + * * If the app is in the background and some activity (spans) happens in <15 minute intervals, + * sessionId should not time out. + * + * + * Consequently, when the app spent >15 minutes without any activity (spans) in the background, + * after moving to the foreground the first span should trigger the sessionId timeout. + */ +internal class SessionIdTimeoutHandler( + private val clock: Clock, + private val sessionTimeout: Duration, +) : + ApplicationStateListener { + @Volatile + private var timeoutStartNanos: Long = 0 + + @Volatile + private var state = State.FOREGROUND + + // for testing + @JvmOverloads + internal constructor(sessionTimeout: Duration = DEFAULT_SESSION_TIMEOUT) : this( + Clock.getDefault(), + sessionTimeout, + ) + + override fun onApplicationForegrounded() { + state = State.TRANSITIONING_TO_FOREGROUND + } + + override fun onApplicationBackgrounded() { + state = State.BACKGROUND + } + + fun hasTimedOut(): Boolean { + // don't apply sessionId timeout to apps in the foreground + if (state == State.FOREGROUND) { + return false + } + val elapsedTime = clock.nanoTime() - timeoutStartNanos + return elapsedTime >= sessionTimeout.toNanos() + } + + fun bump() { + timeoutStartNanos = clock.nanoTime() + + // move from the temporary transition state to foreground after the first span + if (state == State.TRANSITIONING_TO_FOREGROUND) { + state = State.FOREGROUND + } + } + + private enum class State { + FOREGROUND, + BACKGROUND, + + /** A temporary state representing the first event after the app has been brought back. */ + TRANSITIONING_TO_FOREGROUND, + } + + companion object { + @JvmField + val DEFAULT_SESSION_TIMEOUT: Duration = Duration.ofMinutes(15) + } +} diff --git a/core/src/main/java/io/opentelemetry/android/session/SessionManagerImpl.kt b/core/src/main/java/io/opentelemetry/android/internal/session/SessionManagerImpl.kt similarity index 88% rename from core/src/main/java/io/opentelemetry/android/session/SessionManagerImpl.kt rename to core/src/main/java/io/opentelemetry/android/internal/session/SessionManagerImpl.kt index face89905..2133239d3 100644 --- a/core/src/main/java/io/opentelemetry/android/session/SessionManagerImpl.kt +++ b/core/src/main/java/io/opentelemetry/android/internal/session/SessionManagerImpl.kt @@ -3,9 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.android.session +package io.opentelemetry.android.internal.session -import io.opentelemetry.android.SessionIdTimeoutHandler +import io.opentelemetry.android.session.Session +import io.opentelemetry.android.session.SessionIdGenerator +import io.opentelemetry.android.session.SessionManager +import io.opentelemetry.android.session.SessionObserver +import io.opentelemetry.android.session.SessionStorage import io.opentelemetry.sdk.common.Clock import java.util.Collections.synchronizedList import java.util.concurrent.TimeUnit diff --git a/core/src/test/java/io/opentelemetry/android/OpenTelemetryRumBuilderTest.java b/core/src/test/java/io/opentelemetry/android/OpenTelemetryRumBuilderTest.java index 1fd9663d7..ff83d7e29 100644 --- a/core/src/test/java/io/opentelemetry/android/OpenTelemetryRumBuilderTest.java +++ b/core/src/test/java/io/opentelemetry/android/OpenTelemetryRumBuilderTest.java @@ -42,6 +42,7 @@ import io.opentelemetry.android.internal.services.applifecycle.AppLifecycleService; import io.opentelemetry.android.internal.services.applifecycle.ApplicationStateListener; import io.opentelemetry.android.internal.services.visiblescreen.VisibleScreenService; +import io.opentelemetry.android.internal.session.SessionIdTimeoutHandler; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.KeyValue; import io.opentelemetry.api.common.Value; diff --git a/core/src/test/java/io/opentelemetry/android/SessionIdTimeoutHandlerTest.java b/core/src/test/java/io/opentelemetry/android/SessionIdTimeoutHandlerTest.java deleted file mode 100644 index 197f215c6..000000000 --- a/core/src/test/java/io/opentelemetry/android/SessionIdTimeoutHandlerTest.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.android; - -import static io.opentelemetry.android.SessionIdTimeoutHandler.DEFAULT_SESSION_TIMEOUT; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import io.opentelemetry.sdk.testing.time.TestClock; -import java.time.Duration; -import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.Test; - -class SessionIdTimeoutHandlerTest { - - @Test - void shouldNeverTimeOutInForeground() { - TestClock clock = TestClock.create(); - SessionIdTimeoutHandler timeoutHandler = - new SessionIdTimeoutHandler(clock, DEFAULT_SESSION_TIMEOUT); - - assertFalse(timeoutHandler.hasTimedOut()); - timeoutHandler.bump(); - - // never time out in foreground - clock.advance(Duration.ofHours(4)); - assertFalse(timeoutHandler.hasTimedOut()); - } - - @Test - void shouldApply15MinutesTimeoutToAppsInBackground() { - TestClock clock = TestClock.create(); - SessionIdTimeoutHandler timeoutHandler = - new SessionIdTimeoutHandler(clock, DEFAULT_SESSION_TIMEOUT); - - timeoutHandler.onApplicationBackgrounded(); - timeoutHandler.bump(); - - assertFalse(timeoutHandler.hasTimedOut()); - timeoutHandler.bump(); - - // do not timeout if <15 minutes have passed - clock.advance(14, TimeUnit.MINUTES); - clock.advance(59, TimeUnit.SECONDS); - assertFalse(timeoutHandler.hasTimedOut()); - timeoutHandler.bump(); - - // restart the timeout counter after bump() - clock.advance(1, TimeUnit.MINUTES); - assertFalse(timeoutHandler.hasTimedOut()); - timeoutHandler.bump(); - - // timeout after 15 minutes - clock.advance(15, TimeUnit.MINUTES); - assertTrue(timeoutHandler.hasTimedOut()); - - // bump() resets the counter - timeoutHandler.bump(); - assertFalse(timeoutHandler.hasTimedOut()); - } - - @Test - void shouldApplyTimeoutToFirstSpanAfterAppBeingMovedToForeground() { - TestClock clock = TestClock.create(); - SessionIdTimeoutHandler timeoutHandler = - new SessionIdTimeoutHandler(clock, DEFAULT_SESSION_TIMEOUT); - - timeoutHandler.onApplicationBackgrounded(); - timeoutHandler.bump(); - - // the first span after app is moved to the foreground gets timed out - timeoutHandler.onApplicationForegrounded(); - clock.advance(20, TimeUnit.MINUTES); - assertTrue(timeoutHandler.hasTimedOut()); - timeoutHandler.bump(); - - // after the initial span it's the same as the usual foreground scenario - clock.advance(Duration.ofHours(4)); - assertFalse(timeoutHandler.hasTimedOut()); - } - - @Test - void shouldApplyCustomTimeoutToFirstSpanAfterAppBeingMovedToForeground() { - TestClock clock = TestClock.create(); - SessionIdTimeoutHandler timeoutHandler = - new SessionIdTimeoutHandler(clock, Duration.ofNanos(5)); - - timeoutHandler.onApplicationBackgrounded(); - timeoutHandler.bump(); - - // the first span after app is moved to the foreground gets timed out - timeoutHandler.onApplicationForegrounded(); - clock.advance(6, TimeUnit.MINUTES); - assertTrue(timeoutHandler.hasTimedOut()); - timeoutHandler.bump(); - - // after the initial span it's the same as the usual foreground scenario - clock.advance(Duration.ofHours(4)); - assertFalse(timeoutHandler.hasTimedOut()); - } -} diff --git a/core/src/test/java/io/opentelemetry/android/internal/session/SessionIdTimeoutHandlerTest.kt b/core/src/test/java/io/opentelemetry/android/internal/session/SessionIdTimeoutHandlerTest.kt new file mode 100644 index 000000000..139615a15 --- /dev/null +++ b/core/src/test/java/io/opentelemetry/android/internal/session/SessionIdTimeoutHandlerTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.android.internal.session + +import io.opentelemetry.android.internal.session.SessionIdTimeoutHandler.Companion.DEFAULT_SESSION_TIMEOUT +import io.opentelemetry.sdk.testing.time.TestClock +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import java.time.Duration +import java.util.concurrent.TimeUnit + +class SessionIdTimeoutHandlerTest { + @Test + fun shouldNeverTimeOutInForeground() { + val clock: TestClock = TestClock.create() + val timeoutHandler = + SessionIdTimeoutHandler(clock, DEFAULT_SESSION_TIMEOUT) + + assertFalse(timeoutHandler.hasTimedOut()) + timeoutHandler.bump() + + // never time out in foreground + clock.advance(Duration.ofHours(4)) + assertFalse(timeoutHandler.hasTimedOut()) + } + + @Test + fun shouldApply15MinutesTimeoutToAppsInBackground() { + val clock: TestClock = TestClock.create() + val timeoutHandler = + SessionIdTimeoutHandler(clock, DEFAULT_SESSION_TIMEOUT) + + timeoutHandler.onApplicationBackgrounded() + timeoutHandler.bump() + + assertFalse(timeoutHandler.hasTimedOut()) + timeoutHandler.bump() + + // do not timeout if <15 minutes have passed + clock.advance(14, TimeUnit.MINUTES) + clock.advance(59, TimeUnit.SECONDS) + assertFalse(timeoutHandler.hasTimedOut()) + timeoutHandler.bump() + + // restart the timeout counter after bump() + clock.advance(1, TimeUnit.MINUTES) + assertFalse(timeoutHandler.hasTimedOut()) + timeoutHandler.bump() + + // timeout after 15 minutes + clock.advance(15, TimeUnit.MINUTES) + assertTrue(timeoutHandler.hasTimedOut()) + + // bump() resets the counter + timeoutHandler.bump() + assertFalse(timeoutHandler.hasTimedOut()) + } + + @Test + fun shouldApplyTimeoutToFirstSpanAfterAppBeingMovedToForeground() { + val clock: TestClock = TestClock.create() + val timeoutHandler = + SessionIdTimeoutHandler(clock, DEFAULT_SESSION_TIMEOUT) + + timeoutHandler.onApplicationBackgrounded() + timeoutHandler.bump() + + // the first span after app is moved to the foreground gets timed out + timeoutHandler.onApplicationForegrounded() + clock.advance(20, TimeUnit.MINUTES) + assertTrue(timeoutHandler.hasTimedOut()) + timeoutHandler.bump() + + // after the initial span it's the same as the usual foreground scenario + clock.advance(Duration.ofHours(4)) + assertFalse(timeoutHandler.hasTimedOut()) + } + + @Test + fun shouldApplyCustomTimeoutToFirstSpanAfterAppBeingMovedToForeground() { + val clock: TestClock = TestClock.create() + val timeoutHandler = + SessionIdTimeoutHandler(clock, Duration.ofNanos(5)) + + timeoutHandler.onApplicationBackgrounded() + timeoutHandler.bump() + + // the first span after app is moved to the foreground gets timed out + timeoutHandler.onApplicationForegrounded() + clock.advance(6, TimeUnit.MINUTES) + assertTrue(timeoutHandler.hasTimedOut()) + timeoutHandler.bump() + + // after the initial span it's the same as the usual foreground scenario + clock.advance(Duration.ofHours(4)) + assertFalse(timeoutHandler.hasTimedOut()) + } +} diff --git a/core/src/test/java/io/opentelemetry/android/session/SessionManagerImplTest.kt b/core/src/test/java/io/opentelemetry/android/internal/session/SessionManagerImplTest.kt similarity index 83% rename from core/src/test/java/io/opentelemetry/android/session/SessionManagerImplTest.kt rename to core/src/test/java/io/opentelemetry/android/internal/session/SessionManagerImplTest.kt index 8dd860104..b871336d3 100644 --- a/core/src/test/java/io/opentelemetry/android/session/SessionManagerImplTest.kt +++ b/core/src/test/java/io/opentelemetry/android/internal/session/SessionManagerImplTest.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.android.session +package io.opentelemetry.android.internal.session import io.mockk.MockKAnnotations import io.mockk.Runs @@ -14,7 +14,8 @@ import io.mockk.just import io.mockk.mockk import io.mockk.verify import io.mockk.verifyOrder -import io.opentelemetry.android.SessionIdTimeoutHandler +import io.opentelemetry.android.session.Session +import io.opentelemetry.android.session.SessionObserver import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat import io.opentelemetry.sdk.testing.time.TestClock import org.junit.jupiter.api.BeforeEach @@ -35,7 +36,11 @@ internal class SessionManagerImplTest { @Test fun valueValid() { - val sessionManager = SessionManagerImpl(TestClock.create(), timeoutHandler = timeoutHandler) + val sessionManager = + io.opentelemetry.android.internal.session.SessionManagerImpl( + TestClock.create(), + timeoutHandler = timeoutHandler, + ) val sessionId = sessionManager.getSessionId() assertThat(sessionId).isNotNull() assertThat(sessionId).hasSize(32) @@ -45,7 +50,11 @@ internal class SessionManagerImplTest { @Test fun valueSameUntil4Hours() { val clock = TestClock.create() - val sessionManager = SessionManagerImpl(clock, timeoutHandler = timeoutHandler) + val sessionManager = + io.opentelemetry.android.internal.session.SessionManagerImpl( + clock, + timeoutHandler = timeoutHandler, + ) val value = sessionManager.getSessionId() assertThat(value).isEqualTo(sessionManager.getSessionId()) clock.advance(3, TimeUnit.HOURS) @@ -69,7 +78,11 @@ internal class SessionManagerImplTest { every { observer.onSessionStarted(any(), any()) } just Runs every { observer.onSessionEnded(any()) } just Runs - val sessionManager = SessionManagerImpl(clock, timeoutHandler = timeoutHandler) + val sessionManager = + io.opentelemetry.android.internal.session.SessionManagerImpl( + clock, + timeoutHandler = timeoutHandler, + ) sessionManager.addObserver(observer) // The first call expires the Session.NONE initial session and notifies @@ -108,7 +121,8 @@ internal class SessionManagerImplTest { @Test fun shouldCreateNewSessionIdAfterTimeout() { - val sessionId = SessionManagerImpl(timeoutHandler = timeoutHandler) + val sessionId = + io.opentelemetry.android.internal.session.SessionManagerImpl(timeoutHandler = timeoutHandler) val value = sessionId.getSessionId() verify { timeoutHandler.bump() } diff --git a/session/build.gradle.kts b/session/build.gradle.kts new file mode 100644 index 000000000..b024fcd1b --- /dev/null +++ b/session/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + id("otel.android-library-conventions") +} + +description = "OpenTelemetry android session api" + +android { + namespace = "io.opentelemetry.android.session" + + defaultConfig { + consumerProguardFiles("consumer-rules.pro") + } +} + +dependencies { + api(platform(libs.opentelemetry.platform.alpha)) + api(libs.opentelemetry.api) + implementation(libs.opentelemetry.sdk) + implementation(project(":services")) +} diff --git a/core/src/main/java/io/opentelemetry/android/session/Session.kt b/session/src/main/kotlin/io/opentelemetry/android/session/Session.kt similarity index 100% rename from core/src/main/java/io/opentelemetry/android/session/Session.kt rename to session/src/main/kotlin/io/opentelemetry/android/session/Session.kt diff --git a/core/src/main/java/io/opentelemetry/android/session/SessionIdGenerator.kt b/session/src/main/kotlin/io/opentelemetry/android/session/SessionIdGenerator.kt similarity index 100% rename from core/src/main/java/io/opentelemetry/android/session/SessionIdGenerator.kt rename to session/src/main/kotlin/io/opentelemetry/android/session/SessionIdGenerator.kt diff --git a/core/src/main/java/io/opentelemetry/android/session/SessionManager.kt b/session/src/main/kotlin/io/opentelemetry/android/session/SessionManager.kt similarity index 58% rename from core/src/main/java/io/opentelemetry/android/session/SessionManager.kt rename to session/src/main/kotlin/io/opentelemetry/android/session/SessionManager.kt index cbfa04555..c23c3c07b 100644 --- a/core/src/main/java/io/opentelemetry/android/session/SessionManager.kt +++ b/session/src/main/kotlin/io/opentelemetry/android/session/SessionManager.kt @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.android.session /** @@ -5,5 +10,4 @@ package io.opentelemetry.android.session * the SessionProvider and SessionPublisher interfaces under a common * name. */ -interface SessionManager: SessionProvider, SessionPublisher { -} \ No newline at end of file +interface SessionManager : SessionProvider, SessionPublisher diff --git a/core/src/main/java/io/opentelemetry/android/session/SessionObserver.kt b/session/src/main/kotlin/io/opentelemetry/android/session/SessionObserver.kt similarity index 100% rename from core/src/main/java/io/opentelemetry/android/session/SessionObserver.kt rename to session/src/main/kotlin/io/opentelemetry/android/session/SessionObserver.kt diff --git a/core/src/main/java/io/opentelemetry/android/session/SessionProvider.kt b/session/src/main/kotlin/io/opentelemetry/android/session/SessionProvider.kt similarity index 100% rename from core/src/main/java/io/opentelemetry/android/session/SessionProvider.kt rename to session/src/main/kotlin/io/opentelemetry/android/session/SessionProvider.kt diff --git a/core/src/main/java/io/opentelemetry/android/session/SessionPublisher.kt b/session/src/main/kotlin/io/opentelemetry/android/session/SessionPublisher.kt similarity index 100% rename from core/src/main/java/io/opentelemetry/android/session/SessionPublisher.kt rename to session/src/main/kotlin/io/opentelemetry/android/session/SessionPublisher.kt diff --git a/core/src/main/java/io/opentelemetry/android/session/SessionStorage.kt b/session/src/main/kotlin/io/opentelemetry/android/session/SessionStorage.kt similarity index 100% rename from core/src/main/java/io/opentelemetry/android/session/SessionStorage.kt rename to session/src/main/kotlin/io/opentelemetry/android/session/SessionStorage.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index b095e855c..7ae4d5180 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,5 +19,6 @@ include(":instrumentation:httpurlconnection:library") include(":instrumentation:httpurlconnection:testing") include(":test-common") include(":instrumentation:android-instrumentation") -include(":services") include(":common") +include(":services") +include(":session") From 4c980fa1d14a5e36f160a8f431c9566ce88ccce9 Mon Sep 17 00:00:00 2001 From: Jason Plumb Date: Tue, 7 Jan 2025 15:25:02 -0800 Subject: [PATCH 3/4] spotless --- .../android/internal/session/SessionIdTimeoutHandler.kt | 3 +-- .../android/internal/session/SessionManagerImpl.kt | 5 ++--- .../android/internal/session/SessionManagerImplTest.kt | 3 ++- .../io/opentelemetry/android/session/SessionManager.kt | 4 +++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/io/opentelemetry/android/internal/session/SessionIdTimeoutHandler.kt b/core/src/main/java/io/opentelemetry/android/internal/session/SessionIdTimeoutHandler.kt index a8e70cf90..b751c0945 100644 --- a/core/src/main/java/io/opentelemetry/android/internal/session/SessionIdTimeoutHandler.kt +++ b/core/src/main/java/io/opentelemetry/android/internal/session/SessionIdTimeoutHandler.kt @@ -26,8 +26,7 @@ import java.time.Duration internal class SessionIdTimeoutHandler( private val clock: Clock, private val sessionTimeout: Duration, -) : - ApplicationStateListener { +) : ApplicationStateListener { @Volatile private var timeoutStartNanos: Long = 0 diff --git a/core/src/main/java/io/opentelemetry/android/internal/session/SessionManagerImpl.kt b/core/src/main/java/io/opentelemetry/android/internal/session/SessionManagerImpl.kt index 2133239d3..fe0fb01fd 100644 --- a/core/src/main/java/io/opentelemetry/android/internal/session/SessionManagerImpl.kt +++ b/core/src/main/java/io/opentelemetry/android/internal/session/SessionManagerImpl.kt @@ -73,11 +73,10 @@ internal class SessionManagerImpl( fun create( timeoutHandler: SessionIdTimeoutHandler, sessionLifetimeNanos: Long, - ): SessionManagerImpl { - return SessionManagerImpl( + ): SessionManagerImpl = + SessionManagerImpl( timeoutHandler = timeoutHandler, sessionLifetimeNanos = sessionLifetimeNanos, ) - } } } diff --git a/core/src/test/java/io/opentelemetry/android/internal/session/SessionManagerImplTest.kt b/core/src/test/java/io/opentelemetry/android/internal/session/SessionManagerImplTest.kt index b871336d3..df902b280 100644 --- a/core/src/test/java/io/opentelemetry/android/internal/session/SessionManagerImplTest.kt +++ b/core/src/test/java/io/opentelemetry/android/internal/session/SessionManagerImplTest.kt @@ -122,7 +122,8 @@ internal class SessionManagerImplTest { @Test fun shouldCreateNewSessionIdAfterTimeout() { val sessionId = - io.opentelemetry.android.internal.session.SessionManagerImpl(timeoutHandler = timeoutHandler) + io.opentelemetry.android.internal.session + .SessionManagerImpl(timeoutHandler = timeoutHandler) val value = sessionId.getSessionId() verify { timeoutHandler.bump() } diff --git a/session/src/main/kotlin/io/opentelemetry/android/session/SessionManager.kt b/session/src/main/kotlin/io/opentelemetry/android/session/SessionManager.kt index c23c3c07b..f20159b2f 100644 --- a/session/src/main/kotlin/io/opentelemetry/android/session/SessionManager.kt +++ b/session/src/main/kotlin/io/opentelemetry/android/session/SessionManager.kt @@ -10,4 +10,6 @@ package io.opentelemetry.android.session * the SessionProvider and SessionPublisher interfaces under a common * name. */ -interface SessionManager : SessionProvider, SessionPublisher +interface SessionManager : + SessionProvider, + SessionPublisher From f2026e0f4e5fd3db012c6095b656f0d0a280040d Mon Sep 17 00:00:00 2001 From: Jason Plumb Date: Tue, 7 Jan 2025 15:36:59 -0800 Subject: [PATCH 4/4] use impl, fix compile --- .../java/io/opentelemetry/android/OpenTelemetryRumBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java b/core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java index 093939ac8..4ee053d7d 100644 --- a/core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java +++ b/core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java @@ -304,7 +304,7 @@ public OpenTelemetryRum build() { new BufferDelegatingSpanExporter(); SessionManager sessionManager = - SessionManager.create(timeoutHandler, config.getSessionTimeout().toNanos()); + SessionManagerImpl.create(timeoutHandler, config.getSessionTimeout().toNanos()); OpenTelemetrySdk sdk = OpenTelemetrySdk.builder()