Skip to content

Implement a SessionSubscriber for Firebase Performance #6683

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 13 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
2 changes: 1 addition & 1 deletion firebase-perf/firebase-perf.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ dependencies {
api("com.google.firebase:firebase-components:18.0.0")
api("com.google.firebase:firebase-config:21.5.0")
api("com.google.firebase:firebase-installations:17.2.0")
api("com.google.firebase:firebase-sessions:2.0.7") {
api(project(":firebase-sessions")) {
exclude group: 'com.google.firebase', module: 'firebase-common'
exclude group: 'com.google.firebase', module: 'firebase-common-ktx'
exclude group: 'com.google.firebase', module: 'firebase-components'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import com.google.firebase.perf.injection.modules.FirebasePerformanceModule;
import com.google.firebase.platforminfo.LibraryVersionComponent;
import com.google.firebase.remoteconfig.RemoteConfigComponent;
import com.google.firebase.sessions.api.FirebaseSessionsDependencies;
import com.google.firebase.sessions.api.SessionSubscriber;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executor;
Expand All @@ -47,6 +49,11 @@ public class FirebasePerfRegistrar implements ComponentRegistrar {
private static final String LIBRARY_NAME = "fire-perf";
private static final String EARLY_LIBRARY_NAME = "fire-perf-early";

static {
// Add Firebase Performance as a dependency of Sessions when this class is loaded into memory.
FirebaseSessionsDependencies.addDependency(SessionSubscriber.Name.PERFORMANCE);
}

@Override
@Keep
public List<Component<?>> getComponents() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@
import com.google.firebase.perf.logging.ConsoleUrlGenerator;
import com.google.firebase.perf.metrics.HttpMetric;
import com.google.firebase.perf.metrics.Trace;
import com.google.firebase.perf.session.FirebasePerformanceSessionSubscriber;
import com.google.firebase.perf.session.SessionManager;
import com.google.firebase.perf.transport.TransportManager;
import com.google.firebase.perf.util.Constants;
import com.google.firebase.perf.util.ImmutableBundle;
import com.google.firebase.perf.util.Timer;
import com.google.firebase.remoteconfig.RemoteConfigComponent;
import com.google.firebase.sessions.api.FirebaseSessionsDependencies;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.URL;
Expand Down Expand Up @@ -136,11 +138,6 @@ public static FirebasePerformance getInstance() {
// to false if it's been force disabled or it is set to null if neither.
@Nullable private Boolean mPerformanceCollectionForceEnabledState = null;

private final FirebaseApp firebaseApp;
private final Provider<RemoteConfigComponent> firebaseRemoteConfigProvider;
private final FirebaseInstallationsApi firebaseInstallationsApi;
private final Provider<TransportFactory> transportFactoryProvider;

/**
* Constructs the FirebasePerformance class and allows injecting dependencies.
*
Expand All @@ -166,11 +163,6 @@ public static FirebasePerformance getInstance() {
ConfigResolver configResolver,
SessionManager sessionManager) {

this.firebaseApp = firebaseApp;
this.firebaseRemoteConfigProvider = firebaseRemoteConfigProvider;
this.firebaseInstallationsApi = firebaseInstallationsApi;
this.transportFactoryProvider = transportFactoryProvider;

if (firebaseApp == null) {
this.mPerformanceCollectionForceEnabledState = false;
this.configResolver = configResolver;
Expand All @@ -191,6 +183,9 @@ public static FirebasePerformance getInstance() {
sessionManager.setApplicationContext(appContext);

mPerformanceCollectionForceEnabledState = configResolver.getIsPerformanceCollectionEnabled();
FirebaseSessionsDependencies.register(
new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled()));

if (logger.isLogcatEnabled() && isPerformanceCollectionEnabled()) {
logger.info(
String.format(
Expand Down Expand Up @@ -281,7 +276,7 @@ public synchronized void setPerformanceCollectionEnabled(@Nullable Boolean enabl
return;
}

if (configResolver.getIsPerformanceCollectionDeactivated()) {
if (Boolean.TRUE.equals(configResolver.getIsPerformanceCollectionDeactivated())) {
logger.info("Firebase Performance is permanently disabled");
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public void setMetadataBundle(ImmutableBundle bundle) {
/** Default API to call for whether performance monitoring is currently silent. */
public boolean isPerformanceMonitoringEnabled() {
Boolean isPerformanceCollectionEnabled = getIsPerformanceCollectionEnabled();
return (isPerformanceCollectionEnabled == null || isPerformanceCollectionEnabled == true)
return (isPerformanceCollectionEnabled == null || isPerformanceCollectionEnabled)
&& getIsServiceCollectionEnabled();
}

Expand All @@ -131,7 +131,7 @@ public Boolean getIsPerformanceCollectionEnabled() {
// return developer config.
// 4. Else, return null. Because Firebase Performance will read highlevel Firebase flag in this
// case.
if (getIsPerformanceCollectionDeactivated()) {
if (Boolean.TRUE.equals(getIsPerformanceCollectionDeactivated())) {
// 1. If developer has deactivated Firebase Performance in Manifest, return false.
return false;
}
Expand Down Expand Up @@ -186,7 +186,7 @@ public void setIsPerformanceCollectionEnabled(Boolean isEnabled) {
// 2. Otherwise, save this configuration in device cache.

// If collection is deactivated, skip the action to save user configuration.
if (getIsPerformanceCollectionDeactivated()) {
if (Boolean.TRUE.equals(getIsPerformanceCollectionDeactivated())) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.google.firebase.perf.session

import com.google.firebase.perf.session.gauges.GaugeManager
import com.google.firebase.perf.v1.ApplicationProcessState
import com.google.firebase.sessions.api.SessionSubscriber
import java.util.UUID

class FirebasePerformanceSessionSubscriber(private val dataCollectionEnabled: Boolean) :
SessionSubscriber {
override val isDataCollectionEnabled: Boolean
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you just put override val isDataCollectionEnabled: Boolean in the ctor?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

get() = dataCollectionEnabled

override val sessionSubscriberName: SessionSubscriber.Name
get() = SessionSubscriber.Name.PERFORMANCE
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think you need the explicit getter. Can you just say something like override val sessionSubscriberName = SessionSubscriber.Name.PERFORMANCE?

Copy link
Contributor

Choose a reason for hiding this comment

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

If you copied this from Crashlytics, it's likely a result of Java Kotlin interop

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.


override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) {
val currentPerfSession = SessionManager.getInstance().perfSession()

// A [PerfSession] was created before a session was started.
if (currentPerfSession.aqsSessionId() == null) {
currentPerfSession.setAQSId(sessionDetails)
GaugeManager.getInstance()
.logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND)
return
}

val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString())
updatedSession.setAQSId(sessionDetails)
SessionManager.getInstance().updatePerfSession(updatedSession)
GaugeManager.getInstance()
.logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.google.firebase.perf.util.Clock;
import com.google.firebase.perf.util.Timer;
import com.google.firebase.perf.v1.SessionVerbosity;
import com.google.firebase.sessions.api.SessionSubscriber;
import java.util.List;
import java.util.concurrent.TimeUnit;

Expand All @@ -31,6 +32,7 @@ public class PerfSession implements Parcelable {

private final String sessionId;
private final Timer creationTime;
@Nullable private String aqsSessionId;

private boolean isGaugeAndEventCollectionEnabled = false;

Expand Down Expand Up @@ -59,11 +61,23 @@ private PerfSession(@NonNull Parcel in) {
creationTime = in.readParcelable(Timer.class.getClassLoader());
}

/** Returns the sessionId of the object. */
/** Returns the sessionId of the session. */
public String sessionId() {
return sessionId;
}

/** Returns the AQS sessionId for the given session. */
public String aqsSessionId() {
Copy link
Contributor

Choose a reason for hiding this comment

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

@Nullable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

return aqsSessionId;
}

/** Returns the AQS sessionId for the given session. */
Copy link
Contributor

Choose a reason for hiding this comment

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

Sets

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

public void setAQSId(SessionSubscriber.SessionDetails aqs) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want the Perf session to have the id of the current AQS session when the Perf session starts, but then allow AQS to have different sessions due to background foreground etc? Because Crashlytics will have the current AQS session id at crash time added to the report.

Copy link
Contributor

Choose a reason for hiding this comment

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

AQS knows the "first session id" and the current session id. If that is your concern, I think it's better to have the current aqs session id as the perf session id, and then if we need to do lookups we can join it to aqs data and know the first session id.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed the behaviour a little compared to #6665.

This retains the PerfSession ID as is, which is used to identify sessions for gauge collection.

However, my plan is to use the AQS session ID to log the gauges. Whenever there is an AQS change, it will also result in a new PerfSession.

if (aqsSessionId == null) {
aqsSessionId = aqs.getSessionId();
}
}

/**
* Returns a timer object that has been seeded with the system time at which the session began.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,21 @@
import androidx.annotation.Keep;
import androidx.annotation.VisibleForTesting;
import com.google.firebase.perf.application.AppStateMonitor;
import com.google.firebase.perf.application.AppStateUpdateHandler;
import com.google.firebase.perf.session.gauges.GaugeManager;
import com.google.firebase.perf.v1.ApplicationProcessState;
import com.google.firebase.perf.v1.GaugeMetadata;
import com.google.firebase.perf.v1.GaugeMetric;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/** Session manager to generate sessionIDs and broadcast to the application. */
@Keep // Needed because of b/117526359.
public class SessionManager extends AppStateUpdateHandler {
public class SessionManager {

@SuppressLint("StaticFieldLeak")
private static final SessionManager instance = new SessionManager();
Expand Down Expand Up @@ -71,50 +69,14 @@ public SessionManager(
this.gaugeManager = gaugeManager;
this.perfSession = perfSession;
this.appStateMonitor = appStateMonitor;
registerForAppState();
}

/**
* Finalizes gauge initialization during cold start. This must be called before app start finishes
* (currently that is before onResume finishes) to ensure gauge collection starts on time.
*/
public void setApplicationContext(final Context appContext) {
// TODO(b/258263016): Migrate to go/firebase-android-executors
@SuppressLint("ThreadPoolCreation")
ExecutorService executorService = Executors.newSingleThreadExecutor();
syncInitFuture =
executorService.submit(
() -> {
gaugeManager.initializeGaugeMetadataManager(appContext);
});
}

@Override
public void onUpdateAppState(ApplicationProcessState newAppState) {
super.onUpdateAppState(newAppState);

if (appStateMonitor.isColdStart()) {
// We want the Session to remain unchanged if this is a cold start of the app since we already
// update the PerfSession in FirebasePerfProvider#onAttachInfo().
return;
}

if (newAppState == ApplicationProcessState.FOREGROUND) {
// A new foregrounding of app will force a new sessionID generation.
PerfSession session = PerfSession.createWithId(UUID.randomUUID().toString());
updatePerfSession(session);
} else {
// If the session is running for too long, generate a new session and collect gauges as
// necessary.
if (perfSession.isSessionRunningTooLong()) {
PerfSession session = PerfSession.createWithId(UUID.randomUUID().toString());
updatePerfSession(session);
} else {
// For any other state change of the application, modify gauge collection state as
// necessary.
startOrStopCollectingGauges(newAppState);
}
}
gaugeManager.initializeGaugeMetadataManager(appContext);
}

/**
Expand All @@ -138,7 +100,7 @@ public void stopGaugeCollectionIfSessionRunningTooLong() {
*/
public void updatePerfSession(PerfSession perfSession) {
// Do not update the perf session if it is the exact same sessionId.
if (perfSession.sessionId() == this.perfSession.sessionId()) {
if (Objects.equals(perfSession.sessionId(), this.perfSession.sessionId())) {
return;
}

Expand Down
Loading
Loading