Skip to content

Wait for a valid SessionID before logging the app start event. #5437

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

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2c30305
Setup scaffolding for SessionMaintainer (#5393)
mrober Oct 10, 2023
5e93dd1
Merge branch 'master' into sessions-nine
mrober Oct 11, 2023
f85056c
Introduces a SessionDataService which is bound to by clients in each …
bryanatkinson Oct 11, 2023
8e2255c
New datastore for holding session id and timestamp (#5399)
jrothfeder Oct 11, 2023
c7e23f6
Splits the SessionDataService into a service and client (#5403)
bryanatkinson Oct 12, 2023
e80929f
Avoid changing api surface (#5406)
mrober Oct 12, 2023
2a7e354
Rename SessionCoordinator to SessionFirelogPublisher and add `getInst…
jrothfeder Oct 12, 2023
3cb8371
Make SessionGenerator injectable (#5412)
mrober Oct 13, 2023
ddfa4f5
Notify subscribers on session update. (#5411)
jrothfeder Oct 13, 2023
42ec418
Fix a bunch of warnings on this class so that bryan can continue to h…
jrothfeder Oct 13, 2023
cdc78b7
Hooks up the SessionGenerator to the SessionLifecycleService (#5416)
bryanatkinson Oct 13, 2023
716b65c
Make SessionsSettings injectable (#5415)
mrober Oct 13, 2023
83d407e
Wire datastore in to FirebaseLifecycleService (#5420)
jrothfeder Oct 16, 2023
bcca211
Only support the default Firebase app (#5422)
mrober Oct 17, 2023
771ffb7
Hooks up the SessionLifecycleService to the FirelogPublisher, and has…
bryanatkinson Oct 17, 2023
9276770
Update SessionDatastore.kt's name (#5433)
jrothfeder Oct 17, 2023
cd58103
Updates the service to run on a looper on it's own thread and not use…
bryanatkinson Oct 17, 2023
113b771
Refactors SessionLifecycleService to make the message handler static …
bryanatkinson Oct 17, 2023
24c0e6d
Wait for a valid SessionID before logging the app start event.
visumickey Oct 17, 2023
de3ad2b
Adding changelog.
visumickey Oct 17, 2023
bb8c30b
Cleanup the code to be efficient.
visumickey Oct 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions firebase-perf/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
now deprecated. As early as April 2024, we'll no longer release KTX modules. For details, see the
[FAQ about this initiative](https://firebase.google.com/docs/android/kotlin-migration)

* [changed] Ensure that AppStart trace contains a valid sessionID.

# 20.4.1
* [changed] Updated `firebase-sessions` dependency to v1.0.2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import com.google.firebase.perf.config.ConfigResolver;
import com.google.firebase.perf.logging.AndroidLogger;
import com.google.firebase.perf.session.PerfSession;
import com.google.firebase.perf.session.SessionAwareObject;
import com.google.firebase.perf.session.SessionManager;
import com.google.firebase.perf.transport.TransportManager;
import com.google.firebase.perf.util.Clock;
Expand Down Expand Up @@ -70,7 +71,8 @@
*
* @hide
*/
public class AppStartTrace implements ActivityLifecycleCallbacks, LifecycleObserver {
public class AppStartTrace
implements ActivityLifecycleCallbacks, LifecycleObserver, SessionAwareObject {

private static final @NonNull Timer PERF_CLASS_LOAD_TIME = new Clock().getTime();
private static final long MAX_LATENCY_BEFORE_UI_INIT = TimeUnit.MINUTES.toMicros(1);
Expand All @@ -97,6 +99,9 @@ public class AppStartTrace implements ActivityLifecycleCallbacks, LifecycleObser
*/
private WeakReference<Activity> appStartActivity;

// Start tracking session information changes for app start trace.
private final WeakReference<SessionAwareObject> sessionAwareObject = new WeakReference<>(this);

/**
* If the time difference between app starts and creation of any Activity is larger than
* MAX_LATENCY_BEFORE_UI_INIT, set mTooLateToInitUI to true and we don't send AppStart Trace.
Expand Down Expand Up @@ -365,7 +370,14 @@ public synchronized void onActivityResumed(Activity activity) {
appStartActivity = new WeakReference<Activity>(activity);

onResumeTime = clock.getTime();
this.startSession = SessionManager.getInstance().perfSession();
PerfSession perfSession = SessionManager.getInstance().perfSession();
if (perfSession.sessionId().isEmpty()) {
SessionManager.getInstance().registerForSessionUpdates(sessionAwareObject);
} else {
this.startSession = SessionManager.getInstance().perfSession();
dispatchAppStartTrace();
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this call updateSession(perfSession) instead?

AndroidLogger.getInstance()
.debug(
"onResume(): "
Expand All @@ -374,15 +386,26 @@ public synchronized void onActivityResumed(Activity activity) {
+ getClassLoadTimeCompat().getDurationMicros(onResumeTime)
+ " microseconds");

// Log the app start trace in a non-main thread.
executorService.execute(this::logAppStartTrace);

if (!isExperimentTTIDEnabled) {
// After AppStart trace is logged, we can unregister this callback.
unregisterActivityLifecycleCallbacks();
}
}

private void dispatchAppStartTrace() {
// Log the app start trace in a non-main thread.
executorService.execute(this::logAppStartTrace);
}

@Override
public void updateSession(PerfSession session) {
if (!session.sessionId().isEmpty()) {
SessionManager.getInstance().unregisterForSessionUpdates(sessionAwareObject);
this.startSession = session;
dispatchAppStartTrace();
}
}

/** Helper for logging all experiments in one trace. */
private void logExperimentTrace(TraceMetric.Builder metric) {
if (this.preDrawPostTime == null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.google.firebase.Firebase
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import com.google.firebase.initialize
import com.google.firebase.sessions.settings.SessionsSettings
import org.junit.After
import org.junit.Before
import org.junit.Test
Expand Down Expand Up @@ -57,6 +58,13 @@ class FirebaseSessionsTests {
assertThat(FirebaseSessions.instance).isNotNull()
}

@Test
fun firebaseSessionsDependenciesDoInitialize() {
assertThat(SessionFirelogPublisher.instance).isNotNull()
assertThat(SessionGenerator.instance).isNotNull()
assertThat(SessionsSettings.instance).isNotNull()
}

companion object {
private const val APP_ID = "1:1:android:1a"
private const val API_KEY = "API-KEY-API-KEY-API-KEY-API-KEY-API-KEY"
Expand Down
4 changes: 4 additions & 0 deletions firebase-sessions/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
<!--<uses-sdk android:minSdkVersion="16"/>-->

<application>
<service
android:name="com.google.firebase.sessions.SessionLifecycleService"
android:enabled="true"
android:exported="false" />
<service android:name="com.google.firebase.components.ComponentDiscoveryService"
android:exported="false">
<meta-data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,9 @@ package com.google.firebase.sessions

import android.app.Application
import android.util.Log
import com.google.android.datatransport.TransportFactory
import com.google.firebase.Firebase
import com.google.firebase.FirebaseApp
import com.google.firebase.app
import com.google.firebase.inject.Provider
import com.google.firebase.installations.FirebaseInstallationsApi
import com.google.firebase.sessions.api.FirebaseSessionsDependencies
import com.google.firebase.sessions.api.SessionSubscriber
import com.google.firebase.sessions.settings.SessionsSettings
Expand All @@ -33,47 +30,30 @@ import kotlinx.coroutines.CoroutineDispatcher
class FirebaseSessions
internal constructor(
private val firebaseApp: FirebaseApp,
firebaseInstallations: FirebaseInstallationsApi,
backgroundDispatcher: CoroutineDispatcher,
blockingDispatcher: CoroutineDispatcher,
transportFactoryProvider: Provider<TransportFactory>,
internal val backgroundDispatcher: CoroutineDispatcher,
private val sessionFirelogPublisher: SessionFirelogPublisher,
sessionGenerator: SessionGenerator,
private val sessionSettings: SessionsSettings,
) {
private val applicationInfo = SessionEvents.getApplicationInfo(firebaseApp)
private val sessionSettings =
SessionsSettings(
firebaseApp.applicationContext,
blockingDispatcher,
backgroundDispatcher,
firebaseInstallations,
applicationInfo,
)
private val timeProvider: TimeProvider = Time()
private val sessionGenerator: SessionGenerator
private val eventGDTLogger = EventGDTLogger(transportFactoryProvider)
private val sessionCoordinator = SessionCoordinator(firebaseInstallations, eventGDTLogger)

// TODO: This needs to be moved into the service to be consistent across multiple processes.
private val collectEvents: Boolean

init {
sessionGenerator = SessionGenerator(collectEvents = shouldCollectEvents(), timeProvider)

val sessionInitiateListener =
object : SessionInitiateListener {
// Avoid making a public function in FirebaseSessions for onInitiateSession.
override suspend fun onInitiateSession(sessionDetails: SessionDetails) {
initiateSessionStart(sessionDetails)
}
}
collectEvents = shouldCollectEvents()

val sessionInitiator =
SessionInitiator(
timeProvider,
timeProvider = WallClock,
backgroundDispatcher,
sessionInitiateListener,
::initiateSessionStart,
sessionSettings,
sessionGenerator,
)

val appContext = firebaseApp.applicationContext.applicationContext
if (appContext is Application) {
SessionLifecycleClient.bindToService(appContext)
appContext.registerActivityLifecycleCallbacks(sessionInitiator.activityLifecycleCallbacks)

firebaseApp.addLifecycleEventListener { _, _ ->
Expand All @@ -97,14 +77,6 @@ internal constructor(
"Registering Sessions SDK subscriber with name: ${subscriber.sessionSubscriberName}, " +
"data collection enabled: ${subscriber.isDataCollectionEnabled}"
)

// Immediately call the callback if Sessions generated a session before the
// subscriber subscribed, otherwise subscribers might miss the first session.
if (sessionGenerator.hasGenerateSession) {
subscriber.onSessionChanged(
SessionSubscriber.SessionDetails(sessionGenerator.currentSession.sessionId)
)
}
}

private suspend fun initiateSessionStart(sessionDetails: SessionDetails) {
Expand Down Expand Up @@ -138,23 +110,10 @@ internal constructor(
return
}

if (!sessionGenerator.collectEvents) {
if (!collectEvents) {
Log.d(TAG, "Sessions SDK has dropped this session due to sampling.")
return
}

try {
val sessionEvent =
SessionEvents.startSession(firebaseApp, sessionDetails, sessionSettings, subscribers)
sessionCoordinator.attemptLoggingSessionEvent(sessionEvent)
} catch (ex: IllegalStateException) {
// This can happen if the app suddenly deletes the instance of FirebaseApp.
Log.w(
TAG,
"FirebaseApp is not initialized. Sessions library will not collect session data.",
ex
)
}
}

/** Calculate whether we should sample events using [sessionSettings] data. */
Expand All @@ -169,9 +128,18 @@ internal constructor(

@JvmStatic
val instance: FirebaseSessions
get() = getInstance(Firebase.app)
get() = Firebase.app.get(FirebaseSessions::class.java)

@JvmStatic
fun getInstance(app: FirebaseApp): FirebaseSessions = app.get(FirebaseSessions::class.java)
@Deprecated(
"Firebase Sessions only supports the Firebase default app.",
ReplaceWith("FirebaseSessions.instance"),
)
fun getInstance(app: FirebaseApp): FirebaseSessions =
if (app == Firebase.app) {
app.get(FirebaseSessions::class.java)
} else {
throw IllegalArgumentException("Firebase Sessions only supports the Firebase default app.")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ import com.google.firebase.components.Qualified.qualified
import com.google.firebase.components.Qualified.unqualified
import com.google.firebase.installations.FirebaseInstallationsApi
import com.google.firebase.platforminfo.LibraryVersionComponent
import com.google.firebase.sessions.settings.SessionsSettings
import kotlinx.coroutines.CoroutineDispatcher

/**
* [ComponentRegistrar] for setting up [FirebaseSessions].
* [ComponentRegistrar] for setting up [FirebaseSessions] and its internal dependencies.
*
* @hide
*/
Expand All @@ -40,24 +41,60 @@ internal class FirebaseSessionsRegistrar : ComponentRegistrar {
Component.builder(FirebaseSessions::class.java)
.name(LIBRARY_NAME)
.add(Dependency.required(firebaseApp))
.add(Dependency.required(firebaseInstallationsApi))
.add(Dependency.required(backgroundDispatcher))
.add(Dependency.required(blockingDispatcher))
.add(Dependency.requiredProvider(transportFactory))
.add(Dependency.required(sessionFirelogPublisher))
.add(Dependency.required(sessionGenerator))
.add(Dependency.required(sessionsSettings))
.factory { container ->
FirebaseSessions(
container.get(firebaseApp),
container.get(backgroundDispatcher),
container.get(sessionFirelogPublisher),
container.get(sessionGenerator),
container.get(sessionsSettings),
)
}
.build(),
Component.builder(SessionGenerator::class.java)
.name("session-generator")
.factory { SessionGenerator(timeProvider = WallClock) }
.build(),
Component.builder(SessionFirelogPublisher::class.java)
.name("session-publisher")
.add(Dependency.required(firebaseApp))
.add(Dependency.required(firebaseInstallationsApi))
.add(Dependency.required(sessionsSettings))
.add(Dependency.requiredProvider(transportFactory))
.add(Dependency.required(backgroundDispatcher))
.factory { container ->
SessionFirelogPublisher(
container.get(firebaseApp),
container.get(firebaseInstallationsApi),
container.get(sessionsSettings),
EventGDTLogger(container.getProvider(transportFactory)),
container.get(backgroundDispatcher),
)
}
.build(),
Component.builder(SessionsSettings::class.java)
.name("sessions-settings")
.add(Dependency.required(firebaseApp))
.add(Dependency.required(blockingDispatcher))
.add(Dependency.required(backgroundDispatcher))
.add(Dependency.required(firebaseInstallationsApi))
.factory { container ->
SessionsSettings(
container.get(firebaseApp),
container.get(blockingDispatcher),
container.getProvider(transportFactory),
container.get(backgroundDispatcher),
container.get(firebaseInstallationsApi),
)
}
.build(),
LibraryVersionComponent.create(LIBRARY_NAME, BuildConfig.VERSION_NAME)
LibraryVersionComponent.create(LIBRARY_NAME, BuildConfig.VERSION_NAME),
)

companion object {
private companion object {
private const val LIBRARY_NAME = "fire-sessions"

private val firebaseApp = unqualified(FirebaseApp::class.java)
Expand All @@ -67,5 +104,8 @@ internal class FirebaseSessionsRegistrar : ComponentRegistrar {
private val blockingDispatcher =
qualified(Blocking::class.java, CoroutineDispatcher::class.java)
private val transportFactory = unqualified(TransportFactory::class.java)
private val sessionFirelogPublisher = unqualified(SessionFirelogPublisher::class.java)
private val sessionGenerator = unqualified(SessionGenerator::class.java)
private val sessionsSettings = unqualified(SessionsSettings::class.java)
}
}

This file was deleted.

Loading