Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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,18 @@

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)
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.Companion.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.Companion.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.Companion.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,29 @@
/*
* 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 {
return "abc$suffix"
}
Loading
Loading