Skip to content

Commit c1a097b

Browse files
committed
Implement a SessionSubscriber for Firebase Performance (#6683)
This PR doesn't change the use of session ID to AQS - except in GaugeMetadata. I've added TODOs to identify the missing locations.
1 parent b14a2f4 commit c1a097b

File tree

8 files changed

+82
-215
lines changed

8 files changed

+82
-215
lines changed

firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfRegistrar.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public class FirebasePerfRegistrar implements ComponentRegistrar {
5050
private static final String EARLY_LIBRARY_NAME = "fire-perf-early";
5151

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

firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerformance.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,6 @@ public static FirebasePerformance getInstance() {
138138
// to false if it's been force disabled or it is set to null if neither.
139139
@Nullable private Boolean mPerformanceCollectionForceEnabledState = null;
140140

141-
private final FirebaseApp firebaseApp;
142-
private final Provider<RemoteConfigComponent> firebaseRemoteConfigProvider;
143-
private final FirebaseInstallationsApi firebaseInstallationsApi;
144-
private final Provider<TransportFactory> transportFactoryProvider;
145-
146141
/**
147142
* Constructs the FirebasePerformance class and allows injecting dependencies.
148143
*
@@ -168,11 +163,6 @@ public static FirebasePerformance getInstance() {
168163
ConfigResolver configResolver,
169164
SessionManager sessionManager) {
170165

171-
this.firebaseApp = firebaseApp;
172-
this.firebaseRemoteConfigProvider = firebaseRemoteConfigProvider;
173-
this.firebaseInstallationsApi = firebaseInstallationsApi;
174-
this.transportFactoryProvider = transportFactoryProvider;
175-
176166
if (firebaseApp == null) {
177167
this.mPerformanceCollectionForceEnabledState = false;
178168
this.configResolver = configResolver;
@@ -197,6 +187,9 @@ public static FirebasePerformance getInstance() {
197187
sessionManager.setApplicationContext(appContext);
198188

199189
mPerformanceCollectionForceEnabledState = configResolver.getIsPerformanceCollectionEnabled();
190+
FirebaseSessionsDependencies.register(
191+
new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled()));
192+
200193
if (logger.isLogcatEnabled() && isPerformanceCollectionEnabled()) {
201194
logger.info(
202195
String.format(
@@ -287,7 +280,7 @@ public synchronized void setPerformanceCollectionEnabled(@Nullable Boolean enabl
287280
return;
288281
}
289282

290-
if (configResolver.getIsPerformanceCollectionDeactivated()) {
283+
if (Boolean.TRUE.equals(configResolver.getIsPerformanceCollectionDeactivated())) {
291284
logger.info("Firebase Performance is permanently disabled");
292285
return;
293286
}
Lines changed: 34 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,46 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package com.google.firebase.perf.session
218

3-
import com.google.firebase.perf.logging.AndroidLogger
419
import com.google.firebase.perf.session.gauges.GaugeManager
20+
import com.google.firebase.perf.v1.ApplicationProcessState
521
import com.google.firebase.sessions.api.SessionSubscriber
22+
import java.util.UUID
623

7-
class FirebasePerformanceSessionSubscriber() : SessionSubscriber {
8-
private val perfSessionToAqs: MutableMap<String, SessionSubscriber.SessionDetails?> =
9-
mutableMapOf()
10-
11-
// TODO(b/394127311): Identify a way to ste this value after ConfigResolver has relevant metadata.
12-
override val isDataCollectionEnabled: Boolean
13-
get() = true
24+
class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: Boolean) :
25+
SessionSubscriber {
1426

15-
override val sessionSubscriberName: SessionSubscriber.Name
16-
get() = SessionSubscriber.Name.PERFORMANCE
27+
override val sessionSubscriberName: SessionSubscriber.Name = SessionSubscriber.Name.PERFORMANCE
1728

1829
override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) {
19-
val perfSessionId = SessionManager.getInstance().perfSession().sessionId()
20-
AndroidLogger.getInstance()
21-
.debug("CFPRS AQS Session Changed: $sessionDetails, PerfSession: $perfSessionId")
22-
23-
// There can be situations where a new [PerfSession] was created, but an AQS wasn't
24-
// available (during cold start).
25-
if (perfSessionToAqs[perfSessionId] == null) {
26-
perfSessionToAqs[perfSessionId] = sessionDetails
27-
} else {
28-
val newSession = PerfSession.createNewSession()
29-
SessionManager.getInstance().updatePerfSession(newSession)
30-
perfSessionToAqs[newSession.sessionId()] = sessionDetails
30+
val currentPerfSession = SessionManager.getInstance().perfSession()
31+
32+
// A [PerfSession] was created before a session was started.
33+
if (currentPerfSession.aqsSessionId() == null) {
34+
currentPerfSession.setAQSId(sessionDetails)
35+
GaugeManager.getInstance()
36+
.logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND)
37+
return
3138
}
3239

33-
// Always log GaugeMetadata when a session changes.
34-
GaugeManager.getInstance().logGaugeMetadata(sessionDetails.sessionId)
35-
}
36-
37-
fun reportPerfSession(perfSessionId: String) {
38-
perfSessionToAqs[perfSessionId] = null
39-
}
40-
41-
fun getAqsMappedToPerfSession(perfSessionId: String): String {
42-
AndroidLogger.getInstance()
43-
.debug("AQS for perf session $perfSessionId is ${perfSessionToAqs[perfSessionId]?.sessionId}")
44-
return perfSessionToAqs[perfSessionId]?.sessionId ?: perfSessionId
45-
}
46-
47-
fun clearSessionForTest() {
48-
perfSessionToAqs.clear()
49-
}
50-
51-
companion object {
52-
val instance: FirebasePerformanceSessionSubscriber by lazy {
53-
FirebasePerformanceSessionSubscriber()
54-
}
40+
val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString())
41+
updatedSession.setAQSId(sessionDetails)
42+
SessionManager.getInstance().updatePerfSession(updatedSession)
43+
GaugeManager.getInstance()
44+
.logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND)
5545
}
5646
}

firebase-perf/src/main/java/com/google/firebase/perf/session/PerfSession.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.firebase.perf.util.Clock;
2525
import com.google.firebase.perf.util.Timer;
2626
import com.google.firebase.perf.v1.SessionVerbosity;
27+
import com.google.firebase.sessions.api.SessionSubscriber;
2728
import java.util.List;
2829
import java.util.UUID;
2930
import java.util.concurrent.TimeUnit;
@@ -34,6 +35,7 @@ public class PerfSession implements Parcelable {
3435
private static final String SESSION_ID_PREFIX = "FPR";
3536
private final String sessionId;
3637
private final Timer creationTime;
38+
@Nullable private String aqsSessionId;
3739

3840
private boolean isGaugeAndEventCollectionEnabled = false;
3941

@@ -67,7 +69,7 @@ private PerfSession(@NonNull Parcel in) {
6769
creationTime = in.readParcelable(Timer.class.getClassLoader());
6870
}
6971

70-
/** Returns the sessionId of the object. */
72+
/** Returns the sessionId of the session. */
7173
public String sessionId() {
7274
return this.sessionId;
7375
}
@@ -77,6 +79,19 @@ private String aqsSessionId() {
7779
.getAqsMappedToPerfSession(this.sessionId);
7880
}
7981

82+
/** Returns the AQS sessionId for the given session. */
83+
@Nullable
84+
public String aqsSessionId() {
85+
return aqsSessionId;
86+
}
87+
88+
/** Sets the AQS sessionId for the given session. */
89+
public void setAQSId(SessionSubscriber.SessionDetails aqs) {
90+
if (aqsSessionId == null) {
91+
aqsSessionId = aqs.getSessionId();
92+
}
93+
}
94+
8095
/**
8196
* Returns a timer object that has been seeded with the system time at which the session began.
8297
*/
@@ -126,6 +141,7 @@ public boolean isSessionRunningTooLong() {
126141

127142
/** Creates and returns the proto object for PerfSession object. */
128143
public com.google.firebase.perf.v1.PerfSession build() {
144+
// TODO(b/394127311): Switch to using AQS.
129145
com.google.firebase.perf.v1.PerfSession.Builder sessionMetric =
130146
com.google.firebase.perf.v1.PerfSession.newBuilder().setSessionId(aqsSessionId());
131147

firebase-perf/src/main/java/com/google/firebase/perf/session/SessionManager.java

Lines changed: 4 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,20 @@
1919
import androidx.annotation.Keep;
2020
import androidx.annotation.VisibleForTesting;
2121
import com.google.firebase.perf.application.AppStateMonitor;
22-
import com.google.firebase.perf.application.AppStateUpdateHandler;
2322
import com.google.firebase.perf.session.gauges.GaugeManager;
2423
import com.google.firebase.perf.v1.ApplicationProcessState;
2524
import com.google.firebase.perf.v1.GaugeMetadata;
2625
import com.google.firebase.perf.v1.GaugeMetric;
2726
import java.lang.ref.WeakReference;
2827
import java.util.HashSet;
2928
import java.util.Iterator;
29+
import java.util.Objects;
3030
import java.util.Set;
3131
import java.util.UUID;
32-
import java.util.concurrent.ExecutorService;
33-
import java.util.concurrent.Executors;
34-
import java.util.concurrent.Future;
3532

3633
/** Session manager to generate sessionIDs and broadcast to the application. */
3734
@Keep // Needed because of b/117526359.
38-
public class SessionManager extends AppStateUpdateHandler {
35+
public class SessionManager {
3936

4037
@SuppressLint("StaticFieldLeak")
4138
private static final SessionManager instance = new SessionManager();
@@ -45,7 +42,6 @@ public class SessionManager extends AppStateUpdateHandler {
4542
private final Set<WeakReference<SessionAwareObject>> clients = new HashSet<>();
4643

4744
private PerfSession perfSession;
48-
private Future syncInitFuture;
4945

5046
/** Returns the singleton instance of SessionManager. */
5147
public static SessionManager getInstance() {
@@ -71,50 +67,14 @@ public SessionManager(
7167
this.gaugeManager = gaugeManager;
7268
this.perfSession = perfSession;
7369
this.appStateMonitor = appStateMonitor;
74-
registerForAppState();
7570
}
7671

7772
/**
7873
* Finalizes gauge initialization during cold start. This must be called before app start finishes
7974
* (currently that is before onResume finishes) to ensure gauge collection starts on time.
8075
*/
8176
public void setApplicationContext(final Context appContext) {
82-
// TODO(b/258263016): Migrate to go/firebase-android-executors
83-
@SuppressLint("ThreadPoolCreation")
84-
ExecutorService executorService = Executors.newSingleThreadExecutor();
85-
syncInitFuture =
86-
executorService.submit(
87-
() -> {
88-
gaugeManager.initializeGaugeMetadataManager(appContext);
89-
});
90-
}
91-
92-
@Override
93-
public void onUpdateAppState(ApplicationProcessState newAppState) {
94-
super.onUpdateAppState(newAppState);
95-
96-
if (appStateMonitor.isColdStart()) {
97-
// We want the Session to remain unchanged if this is a cold start of the app since we already
98-
// update the PerfSession in FirebasePerfProvider#onAttachInfo().
99-
return;
100-
}
101-
102-
if (newAppState == ApplicationProcessState.FOREGROUND) {
103-
// A new foregrounding of app will force a new sessionID generation.
104-
PerfSession session = PerfSession.createWithId(UUID.randomUUID().toString());
105-
updatePerfSession(session);
106-
} else {
107-
// If the session is running for too long, generate a new session and collect gauges as
108-
// necessary.
109-
if (perfSession.isSessionRunningTooLong()) {
110-
PerfSession session = PerfSession.createWithId(UUID.randomUUID().toString());
111-
updatePerfSession(session);
112-
} else {
113-
// For any other state change of the application, modify gauge collection state as
114-
// necessary.
115-
startOrStopCollectingGauges(newAppState);
116-
}
117-
}
77+
gaugeManager.initializeGaugeMetadataManager(appContext);
11878
}
11979

12080
/**
@@ -138,7 +98,7 @@ public void stopGaugeCollectionIfSessionRunningTooLong() {
13898
*/
13999
public void updatePerfSession(PerfSession perfSession) {
140100
// Do not update the perf session if it is the exact same sessionId.
141-
if (perfSession.sessionId() == this.perfSession.sessionId()) {
101+
if (Objects.equals(perfSession.sessionId(), this.perfSession.sessionId())) {
142102
return;
143103
}
144104

@@ -207,9 +167,4 @@ private void startOrStopCollectingGauges(ApplicationProcessState appState) {
207167
public void setPerfSession(PerfSession perfSession) {
208168
this.perfSession = perfSession;
209169
}
210-
211-
@VisibleForTesting
212-
public Future getSyncInitFuture() {
213-
return this.syncInitFuture;
214-
}
215170
}

firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/GaugeManager.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ public void startCollectingGauges(
136136
final String sessionIdForScheduledTask = sessionId;
137137
final ApplicationProcessState applicationProcessStateForScheduledTask = applicationProcessState;
138138

139+
// TODO(b/394127311): Switch to using AQS.
139140
try {
140141
gaugeManagerDataCollectionJob =
141142
gaugeManagerExecutor
@@ -204,6 +205,7 @@ public void stopCollectingGauges() {
204205
gaugeManagerDataCollectionJob.cancel(false);
205206
}
206207

208+
// TODO(b/394127311): Switch to using AQS.
207209
// Flush any data that was collected for this session one last time.
208210
@SuppressWarnings("FutureReturnValueIgnored")
209211
ScheduledFuture unusedFuture =
@@ -242,6 +244,7 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) {
242244
}
243245

244246
// Adding Session ID info.
247+
// TODO(b/394127311): Switch to using AQS.
245248
gaugeMetricBuilder.setSessionId(sessionId);
246249

247250
transportManager.log(gaugeMetricBuilder.build(), appState);
@@ -250,17 +253,16 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) {
250253
/**
251254
* Log the Gauge Metadata information to the transport.
252255
*
253-
* @param sessionId The {@link PerfSession#sessionId()} to which the collected Gauge Metrics
256+
* @param aqsSessionId The {@link PerfSession#aqsSessionId()} ()} to which the collected Gauge Metrics
254257
* should be associated with.
255258
* @param appState The {@link ApplicationProcessState} for which these gauges are collected.
256259
* @return true if GaugeMetadata was logged, false otherwise.
257260
*/
258-
public boolean logGaugeMetadata(String sessionId, ApplicationProcessState appState) {
259-
// TODO(b/394127311): Re-introduce logging of metadata for AQS.
261+
public boolean logGaugeMetadata(String aqsSessionId, ApplicationProcessState appState) {
260262
if (gaugeMetadataManager != null) {
261263
GaugeMetric gaugeMetric =
262264
GaugeMetric.newBuilder()
263-
.setSessionId(sessionId)
265+
.setSessionId(aqsSessionId)
264266
.setGaugeMetadata(getGaugeMetadata())
265267
.build();
266268
transportManager.log(gaugeMetric, appState);

firebase-perf/src/main/java/com/google/firebase/perf/transport/TransportManager.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ public void log(final GaugeMetric gaugeMetric) {
354354
* {@link #isAllowedToDispatch(PerfMetric)}).
355355
*/
356356
public void log(final GaugeMetric gaugeMetric, final ApplicationProcessState appState) {
357+
// TODO(b/394127311): This *might* potentially be the right place to get AQS.
357358
executorService.execute(
358359
() -> syncLog(PerfMetric.newBuilder().setGaugeMetric(gaugeMetric), appState));
359360
}

0 commit comments

Comments
 (0)