Skip to content
4 changes: 3 additions & 1 deletion firebase-perf/firebase-perf.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,15 @@ android {
buildConfigField("String", "TRANSPORT_LOG_SRC", "String.valueOf(\"FIREPERF\")")
buildConfigField("Boolean", "ENFORCE_DEFAULT_LOG_SRC", "Boolean.valueOf(false)")
buildConfigField("String", "FIREPERF_VERSION_NAME", "String.valueOf(\"" + property("version") + "\")")
buildConfigField("Boolean", "ENFORCE_LEGACY_SESSIONS", "Boolean.valueOf(false)")

if (project.hasProperty("fireperfBuildForAutopush")) {
// This allows the SDK to be built for "Autopush" env when the mentioned flag
// (-PfireperfBuildForAutopush) is passed in the gradle build command (of either the
// SDK or the Test App).
buildConfigField("String", "TRANSPORT_LOG_SRC", "String.valueOf(\"FIREPERF_AUTOPUSH\")")
buildConfigField("Boolean", "ENFORCE_DEFAULT_LOG_SRC", "Boolean.valueOf(true)")
buildConfigField("Boolean", "ENFORCE_LEGACY_SESSIONS", "Boolean.valueOf(true)")
}

minSdkVersion project.minSdkVersion
Expand Down Expand Up @@ -118,7 +120,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 @@ -51,12 +51,8 @@ public FirebasePerfEarly(
uiExecutor.execute(new AppStartTrace.StartFromBackgroundRunnable(appStartTrace));
}

// TODO: Bring back Firebase Sessions dependency to watch for updates to sessions.
// In the case of a cold start, we initialize gauge collection with a legacy PerfSession.

// In the case of cold start, we create a session and start collecting gauges as early as
// possible.
// There is code in SessionManager that prevents us from resetting the session twice in case
// of app cold start.
SessionManager.getInstance().initializeGaugeCollection();
}
}
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 @@ -34,14 +34,18 @@
import com.google.firebase.perf.config.RemoteConfigManager;
import com.google.firebase.perf.logging.AndroidLogger;
import com.google.firebase.perf.logging.ConsoleUrlGenerator;
import com.google.firebase.perf.logging.FirebaseSessionsEnforcementCheck;
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 com.google.firebase.sessions.api.SessionSubscriber;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.URL;
Expand Down Expand Up @@ -92,6 +96,8 @@ public class FirebasePerformance implements FirebasePerformanceAttributable {
// once during initialization and cache it.
private final ImmutableBundle mMetadataBundle;

private final SessionSubscriber sessionSubscriber;

/** Valid HttpMethods for manual network APIs */
@StringDef({
HttpMethod.GET,
Expand Down Expand Up @@ -136,11 +142,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,23 +167,19 @@ 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;
this.mMetadataBundle = new ImmutableBundle(new Bundle());
this.sessionSubscriber = new FirebasePerformanceSessionSubscriber(false);
return;
}
FirebaseSessionsEnforcementCheck.setEnforcement(BuildConfig.ENFORCE_LEGACY_SESSIONS);

TransportManager.getInstance()
.initialize(firebaseApp, firebaseInstallationsApi, transportFactoryProvider);

Context appContext = firebaseApp.getApplicationContext();
// TODO(b/110178816): Explore moving off of main thread.
mMetadataBundle = extractMetadata(appContext);

remoteConfigManager.setFirebaseRemoteConfigProvider(firebaseRemoteConfigProvider);
Expand All @@ -192,6 +189,9 @@ public static FirebasePerformance getInstance() {
sessionManager.setApplicationContext(appContext);

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

if (logger.isLogcatEnabled() && isPerformanceCollectionEnabled()) {
logger.info(
String.format(
Expand Down Expand Up @@ -282,7 +282,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 Expand Up @@ -466,4 +466,9 @@ private static ImmutableBundle extractMetadata(Context appContext) {
Boolean getPerformanceCollectionForceEnabledState() {
return mPerformanceCollectionForceEnabledState;
}

@VisibleForTesting
SessionSubscriber getSessionSubscriber() {
return sessionSubscriber;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.firebase.perf.logging

import com.google.firebase.perf.session.PerfSession
import com.google.firebase.perf.session.isLegacy

class FirebaseSessionsEnforcementCheck {
companion object {
/** When enabled, failed preconditions will cause assertion errors for debugging. */
@JvmStatic var enforcement: Boolean = false
private var logger: AndroidLogger = AndroidLogger.getInstance()

@JvmStatic
fun checkSession(session: PerfSession, failureMessage: String) {
if (session.isLegacy()) {
logger.debug("legacy session ${session.sessionId()}: $failureMessage")
assert(!enforcement) { failureMessage }
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.firebase.perf.session

import com.google.firebase.perf.logging.FirebaseSessionsEnforcementCheck
import com.google.firebase.perf.session.gauges.GaugeManager
import com.google.firebase.perf.v1.ApplicationProcessState
import com.google.firebase.sessions.api.SessionSubscriber

class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: Boolean) :
SessionSubscriber {

override val sessionSubscriberName: SessionSubscriber.Name = SessionSubscriber.Name.PERFORMANCE

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

if (currentPerfSession.isLegacy() && currentPerfSession.isVerbose) {
GaugeManager.getInstance()
.logGaugeMetadata(sessionDetails.sessionId, ApplicationProcessState.FOREGROUND)
GaugeManager.getInstance()
.logExistingGaugeMetrics(sessionDetails.sessionId, ApplicationProcessState.FOREGROUND)
}

val updatedSession = PerfSession.createWithId(sessionDetails.sessionId)
SessionManager.getInstance().updatePerfSession(updatedSession)
GaugeManager.getInstance()
.logGaugeMetadata(updatedSession.sessionId(), ApplicationProcessState.FOREGROUND)
SessionManager.getInstance().updateGaugeCollectionOnNewSession()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.firebase.perf.session

import com.google.firebase.perf.util.Constants
import java.util.UUID

/** Identifies whether the [PerfSession] is legacy or not. */
fun PerfSession.isLegacy(): Boolean {
return this.sessionId().isLegacy()
}

/** Identifies whether the string is from a legacy [PerfSession]. */
fun String.isLegacy(): Boolean {
return this.startsWith(Constants.UNDEFINED_AQS_ID_PREFIX)
}

/** Creates a valid session ID for [PerfSession] that can be predictably identified as legacy. */
fun createLegacySessionId(): String {
val uuid = UUID.randomUUID().toString().replace("-", "")
return uuid.replaceRange(
0,
Constants.UNDEFINED_AQS_ID_PREFIX.length,
Constants.UNDEFINED_AQS_ID_PREFIX
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@

/** Details of a session including a unique Id and related information. */
public class PerfSession implements Parcelable {

private final String sessionId;
private final Timer creationTime;

private boolean isGaugeAndEventCollectionEnabled = false;
private final String sessionId;
private boolean isGaugeAndEventCollectionEnabled = true;

/*
* Creates a PerfSession object and decides what metrics to collect.
*/
public static PerfSession createWithId(@NonNull String sessionId) {
String prunedSessionId = sessionId.replace("-", "");
PerfSession session = new PerfSession(prunedSessionId, new Clock());
session.setGaugeAndEventCollectionEnabled(shouldCollectGaugesAndEvents());

public static PerfSession createWithId(@Nullable String aqsSessionId) {
String sessionId = aqsSessionId;
if (sessionId == null) {
sessionId = FirebaseSessionsHelperKt.createLegacySessionId();
}
PerfSession session = new PerfSession(sessionId, new Clock());
session.setGaugeAndEventCollectionEnabled(session.shouldCollectGaugesAndEvents());
return session;
}

Expand All @@ -59,7 +59,8 @@ private PerfSession(@NonNull Parcel in) {
creationTime = in.readParcelable(Timer.class.getClassLoader());
}

/** Returns the sessionId of the object. */
/** Returns the sessionId for the given session. */
@NonNull
public String sessionId() {
return sessionId;
}
Expand Down Expand Up @@ -90,18 +91,6 @@ public boolean isVerbose() {
return isGaugeAndEventCollectionEnabled;
}

/** Checks if the current {@link com.google.firebase.perf.v1.PerfSession} is verbose or not. */
@VisibleForTesting
static boolean isVerbose(@NonNull com.google.firebase.perf.v1.PerfSession perfSession) {
for (SessionVerbosity sessionVerbosity : perfSession.getSessionVerbosityList()) {
if (sessionVerbosity == SessionVerbosity.GAUGES_AND_SYSTEM_EVENTS) {
return true;
}
}

return false;
}

/**
* Checks if it has been more than {@link ConfigResolver#getSessionsMaxDurationMinutes()} time
* since the creation time of the current session.
Expand Down Expand Up @@ -163,11 +152,11 @@ public static com.google.firebase.perf.v1.PerfSession[] buildAndSort(
}

/** If true, Session Gauge collection is enabled. */
public static boolean shouldCollectGaugesAndEvents() {
public boolean shouldCollectGaugesAndEvents() {
ConfigResolver configResolver = ConfigResolver.getInstance();

return configResolver.isPerformanceMonitoringEnabled()
&& Math.random() < configResolver.getSessionsSamplingRate();
&& (Math.abs(this.sessionId.hashCode() % 100)
< configResolver.getSessionsSamplingRate() * 100);
}

/**
Expand Down Expand Up @@ -208,4 +197,16 @@ public PerfSession[] newArray(int size) {
return new PerfSession[size];
}
};

/** Checks if the current {@link com.google.firebase.perf.v1.PerfSession} is verbose or not. */
@VisibleForTesting
static boolean isVerbose(@NonNull com.google.firebase.perf.v1.PerfSession perfSession) {
for (SessionVerbosity sessionVerbosity : perfSession.getSessionVerbosityList()) {
if (sessionVerbosity == SessionVerbosity.GAUGES_AND_SYSTEM_EVENTS) {
return true;
}
}

return false;
}
}
Loading
Loading