Skip to content

Update PerfSession and SessionManager to identify Legacy sessions. #6867

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 8 commits into from
Apr 14, 2025
Merged
Show file tree
Hide file tree
Changes from all 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: 2 additions & 0 deletions 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
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
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.DebugEnforcementCheck;
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;
Expand All @@ -44,8 +44,8 @@
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.BuildConfig;
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 @@ -96,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 @@ -169,9 +171,10 @@ public static FirebasePerformance getInstance() {
this.mPerformanceCollectionForceEnabledState = false;
this.configResolver = configResolver;
this.mMetadataBundle = new ImmutableBundle(new Bundle());
this.sessionSubscriber = new FirebasePerformanceSessionSubscriber(false);
return;
}
DebugEnforcementCheck.setEnforcement(BuildConfig.DEBUG);
FirebaseSessionsEnforcementCheck.setEnforcement(BuildConfig.ENFORCE_LEGACY_SESSIONS);

TransportManager.getInstance()
.initialize(firebaseApp, firebaseInstallationsApi, transportFactoryProvider);
Expand All @@ -186,8 +189,8 @@ public static FirebasePerformance getInstance() {
sessionManager.setApplicationContext(appContext);

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

if (logger.isLogcatEnabled() && isPerformanceCollectionEnabled()) {
logger.info(
Expand Down Expand Up @@ -463,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
Expand Up @@ -16,15 +16,19 @@

package com.google.firebase.perf.logging

class DebugEnforcementCheck {
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()

public fun checkSession(isAqsAvailable: Boolean, failureMessage: String) {
if (!isAqsAvailable) {
Companion.logger.debug(failureMessage)
@JvmStatic
fun checkSession(session: PerfSession, failureMessage: String) {
if (session.isLegacy()) {
logger.debug("legacy session ${session.sessionId()}: $failureMessage")
assert(!enforcement) { failureMessage }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@

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
import java.util.UUID

class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: Boolean) :
SessionSubscriber {
Expand All @@ -28,15 +28,10 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled:

override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) {
val currentPerfSession = SessionManager.getInstance().perfSession()
// TODO(b/394127311): Add logic to deal with app start gauges.
FirebaseSessionsEnforcementCheck.checkSession(currentPerfSession, "onSessionChanged")

// A [PerfSession] was created before a session was started.
if (!currentPerfSession.isAqsReady) {
GaugeManager.getInstance()
.logGaugeMetadata(currentPerfSession.sessionId(), ApplicationProcessState.FOREGROUND)
return
}

val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString())
val updatedSession = PerfSession.createWithId(sessionDetails.sessionId)
SessionManager.getInstance().updatePerfSession(updatedSession)
GaugeManager.getInstance()
.logGaugeMetadata(updatedSession.sessionId(), ApplicationProcessState.FOREGROUND)
Expand Down
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 @@ -24,51 +24,43 @@
import com.google.firebase.perf.util.Timer;
import com.google.firebase.perf.v1.SessionVerbosity;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/** Details of a session including a unique Id and related information. */
public class PerfSession implements Parcelable {
private final Timer creationTime;
private final String sessionId;
private boolean isGaugeAndEventCollectionEnabled = false;
public final boolean isAqsReady;

/*
* Creates a PerfSession object and decides what metrics to collect.
*/
public static PerfSession createWithId(@Nullable String aqsSessionId) {
String sessionId;
Boolean isAqsReady;
if (aqsSessionId != null) {
sessionId = aqsSessionId;
isAqsReady = true;
} else {
sessionId = UUID.randomUUID().toString().replace("-", "");
isAqsReady = false;
String sessionId = aqsSessionId;
if (sessionId == null) {
sessionId = FirebaseSessionsHelperKt.createLegacySessionId();
}
PerfSession session = new PerfSession(sessionId, new Clock(), isAqsReady);
session.setGaugeAndEventCollectionEnabled(shouldCollectGaugesAndEvents(sessionId));
PerfSession session = new PerfSession(sessionId, new Clock());
session.setGaugeAndEventCollectionEnabled(session.shouldCollectGaugesAndEvents());
return session;
}

/** Creates a PerfSession with the provided {@code sessionId} and {@code clock}. */
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public PerfSession(String sessionId, Clock clock, boolean isAqsReady) {
public PerfSession(String sessionId, Clock clock) {
this.sessionId = sessionId;
this.isAqsReady = isAqsReady;
creationTime = clock.getTime();
}

private PerfSession(@NonNull Parcel in) {
super();
sessionId = in.readString();
isGaugeAndEventCollectionEnabled = in.readByte() != 0;
isAqsReady = in.readByte() != 0;
creationTime = in.readParcelable(Timer.class.getClassLoader());
}

/** Returns the sessionId for the given session. */
@NonNull
public String sessionId() {
return sessionId;
}
Expand Down Expand Up @@ -160,10 +152,11 @@ public static com.google.firebase.perf.v1.PerfSession[] buildAndSort(
}

/** If true, Session Gauge collection is enabled. */
public static boolean shouldCollectGaugesAndEvents(String sessionId) {
public boolean shouldCollectGaugesAndEvents() {
ConfigResolver configResolver = ConfigResolver.getInstance();
return configResolver.isPerformanceMonitoringEnabled()
&& (Math.abs(sessionId.hashCode() % 100) < configResolver.getSessionsSamplingRate() * 100);
&& (Math.abs(this.sessionId.hashCode() % 100)
< configResolver.getSessionsSamplingRate() * 100);
}

/**
Expand All @@ -187,7 +180,6 @@ public int describeContents() {
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeString(sessionId);
out.writeByte((byte) (isGaugeAndEventCollectionEnabled ? 1 : 0));
out.writeByte((byte) (isAqsReady ? 1 : 0));
out.writeParcelable(creationTime, 0);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import androidx.annotation.Keep;
import androidx.annotation.VisibleForTesting;
import com.google.firebase.perf.application.AppStateMonitor;
import com.google.firebase.perf.logging.DebugEnforcementCheck;
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.perf.v1.GaugeMetadata;
Expand Down Expand Up @@ -49,8 +49,8 @@ public static SessionManager getInstance() {

/** Returns the currently active PerfSession. */
public final PerfSession perfSession() {
DebugEnforcementCheck.Companion.checkSession(
perfSession.isAqsReady, "Access perf session from manger without aqs ready");
FirebaseSessionsEnforcementCheck.checkSession(
perfSession, "Access perf session from manger without aqs ready");

return perfSession;
}
Expand Down Expand Up @@ -82,8 +82,8 @@ public void setApplicationContext(final Context appContext) {
* @see PerfSession#isSessionRunningTooLong()
*/
public void stopGaugeCollectionIfSessionRunningTooLong() {
DebugEnforcementCheck.Companion.checkSession(
perfSession.isAqsReady,
FirebaseSessionsEnforcementCheck.checkSession(
perfSession,
"Session is not ready while trying to stopGaugeCollectionIfSessionRunningTooLong");

if (perfSession.isSessionRunningTooLong()) {
Expand All @@ -107,6 +107,8 @@ public void updatePerfSession(PerfSession perfSession) {

this.perfSession = perfSession;

// TODO(b/394127311): Update/verify behavior for Firebase Sessions.

synchronized (clients) {
for (Iterator<WeakReference<SessionAwareObject>> i = clients.iterator(); i.hasNext(); ) {
SessionAwareObject callback = i.next().get();
Expand Down Expand Up @@ -159,8 +161,8 @@ public void unregisterForSessionUpdates(WeakReference<SessionAwareObject> client
}

private void startOrStopCollectingGauges(ApplicationProcessState appState) {
DebugEnforcementCheck.Companion.checkSession(
perfSession.isAqsReady, "Session is not ready while trying to startOrStopCollectingGauges");
FirebaseSessionsEnforcementCheck.checkSession(
perfSession, "Session is not ready while trying to startOrStopCollectingGauges");

if (perfSession.isGaugeAndEventCollectionEnabled()) {
gaugeManager.startCollectingGauges(perfSession, appState);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public class Constants {
public static final String PREFS_NAME = "FirebasePerfSharedPrefs";
public static final String ENABLE_DISABLE = "isEnabled";

// A non-hex character guarantees it isn't an AQS generated UUID.
// https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.uuid/-uuid/
public static final String UNDEFINED_AQS_ID_PREFIX = "z";

public static final double MIN_SAMPLING_RATE = 0.0;
public static final double MAX_SAMPLING_RATE = 1.0;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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.Clock

fun createTestSession(suffix: Int): PerfSession {
// TODO(b/394127311): Add a method to verify legacy behavior.
// only hex characters and so it's AQS.
return PerfSession(testSessionId(suffix), Clock())
}

fun testSessionId(suffix: Int): String = "abc$suffix"
Loading
Loading