diff --git a/firebase-perf/firebase-perf.gradle b/firebase-perf/firebase-perf.gradle index c0fd6df6056..49c921edeb0 100644 --- a/firebase-perf/firebase-perf.gradle +++ b/firebase-perf/firebase-perf.gradle @@ -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' diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfEarly.java b/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfEarly.java index 5b89deaad82..969031a773f 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfEarly.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfEarly.java @@ -20,6 +20,7 @@ import com.google.firebase.StartupTime; import com.google.firebase.perf.application.AppStateMonitor; import com.google.firebase.perf.config.ConfigResolver; +import com.google.firebase.perf.logging.AndroidLogger; import com.google.firebase.perf.metrics.AppStartTrace; import com.google.firebase.perf.session.SessionManager; import java.util.concurrent.Executor; @@ -51,12 +52,11 @@ public FirebasePerfEarly( uiExecutor.execute(new AppStartTrace.StartFromBackgroundRunnable(appStartTrace)); } - // TODO: Bring back Firebase Sessions dependency to watch for updates to sessions. - // 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. + AndroidLogger.getInstance().debug("Initializing Gauge Collection"); SessionManager.getInstance().initializeGaugeCollection(); } } diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfRegistrar.java b/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfRegistrar.java index c01f035af1f..0754eddcd51 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfRegistrar.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfRegistrar.java @@ -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; @@ -47,6 +49,10 @@ public class FirebasePerfRegistrar implements ComponentRegistrar { private static final String LIBRARY_NAME = "fire-perf"; private static final String EARLY_LIBRARY_NAME = "fire-perf-early"; + static { + FirebaseSessionsDependencies.addDependency(SessionSubscriber.Name.PERFORMANCE); + } + @Override @Keep public List> getComponents() { diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerformance.java b/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerformance.java index 40468566225..db01b113f40 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerformance.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerformance.java @@ -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; @@ -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 firebaseRemoteConfigProvider; - private final FirebaseInstallationsApi firebaseInstallationsApi; - private final Provider transportFactoryProvider; - /** * Constructs the FirebasePerformance class and allows injecting dependencies. * @@ -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; @@ -178,11 +170,14 @@ public static FirebasePerformance getInstance() { return; } + // Prioritize registering the FirebaseSession dependency to have the session + // `setApplicationContext`. + FirebaseSessionsDependencies.register( + FirebasePerformanceSessionSubscriber.Companion.getInstance()); 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); @@ -192,6 +187,9 @@ public static FirebasePerformance getInstance() { sessionManager.setApplicationContext(appContext); mPerformanceCollectionForceEnabledState = configResolver.getIsPerformanceCollectionEnabled(); + FirebaseSessionsDependencies.register( + new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled())); + if (logger.isLogcatEnabled() && isPerformanceCollectionEnabled()) { logger.info( String.format( @@ -199,6 +197,9 @@ public static FirebasePerformance getInstance() { ConsoleUrlGenerator.generateDashboardUrl( firebaseApp.getOptions().getProjectId(), appContext.getPackageName()))); } + + SessionManagerKt sessionSubscriber = new SessionManagerKt(isPerformanceCollectionEnabled()); + FirebaseSessionsDependencies.register(sessionSubscriber); } /** @@ -282,7 +283,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; } diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/config/ConfigResolver.java b/firebase-perf/src/main/java/com/google/firebase/perf/config/ConfigResolver.java index 1ee9d395e03..7e321515141 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/config/ConfigResolver.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/config/ConfigResolver.java @@ -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(); } @@ -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; } @@ -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; } diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebasePerformanceSessionSubscriber.kt b/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebasePerformanceSessionSubscriber.kt new file mode 100644 index 00000000000..189c7b6b661 --- /dev/null +++ b/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebasePerformanceSessionSubscriber.kt @@ -0,0 +1,58 @@ +package com.google.firebase.perf.session + +import com.google.firebase.perf.config.ConfigResolver +import com.google.firebase.perf.logging.AndroidLogger +import com.google.firebase.perf.session.gauges.GaugeManager +import com.google.firebase.sessions.api.SessionSubscriber + +class FirebasePerformanceSessionSubscriber(private val dataCollectionEnabled: Boolean) : + SessionSubscriber { + private val perfSessionToAqs: MutableMap = + mutableMapOf() + + override val isDataCollectionEnabled: Boolean + get() = dataCollectionEnabled + + override val sessionSubscriberName: SessionSubscriber.Name + get() = SessionSubscriber.Name.PERFORMANCE + + override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { + AndroidLogger.getInstance().debug("AQS Session Changed: $sessionDetails") + val currentInternalSessionId = SessionManager.getInstance().perfSession().internalSessionId + + // There can be situations where a new [PerfSession] was created, but an AQS wasn't + // available (during cold start). + if (perfSessionToAqs[currentInternalSessionId] == null) { + perfSessionToAqs[currentInternalSessionId] = sessionDetails + } else { + val newSession = PerfSession.createNewSession() + SessionManager.getInstance().updatePerfSession(newSession) + perfSessionToAqs[newSession.internalSessionId] = sessionDetails + } + + // Always log GaugeMetadata when a session changes. + GaugeManager.getInstance().logGaugeMetadata(sessionDetails.sessionId) + } + + fun reportPerfSession(perfSessionId: String) { + perfSessionToAqs[perfSessionId] = null + } + + fun getAqsMappedToPerfSession(perfSessionId: String): String { + AndroidLogger.getInstance() + .debug("AQS for perf session $perfSessionId is ${perfSessionToAqs[perfSessionId]?.sessionId}") + return perfSessionToAqs[perfSessionId]?.sessionId ?: perfSessionId + } + + fun clearSessionForTest() { + perfSessionToAqs.clear() + } + + companion object { + val instance: FirebasePerformanceSessionSubscriber by lazy { + FirebasePerformanceSessionSubscriber( + ConfigResolver.getInstance().isPerformanceMonitoringEnabled + ) + } + } +} diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/session/PerfSession.java b/firebase-perf/src/main/java/com/google/firebase/perf/session/PerfSession.java index 160a4507560..93ce4ab899c 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/session/PerfSession.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/session/PerfSession.java @@ -23,25 +23,28 @@ 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.UUID; import java.util.concurrent.TimeUnit; /** Details of a session including a unique Id and related information. */ public class PerfSession implements Parcelable { + private static final String SESSION_ID_PREFIX = "fireperf-session"; private final String sessionId; private final Timer creationTime; + @Nullable private String aqsSessionId; private boolean isGaugeAndEventCollectionEnabled = false; /* * Creates a PerfSession object and decides what metrics to collect. */ - public static PerfSession createWithId(@NonNull String sessionId) { - String prunedSessionId = sessionId.replace("-", ""); + public static PerfSession createNewSession() { + String prunedSessionId = SESSION_ID_PREFIX + UUID.randomUUID().toString().replace("-", ""); PerfSession session = new PerfSession(prunedSessionId, new Clock()); session.setGaugeAndEventCollectionEnabled(shouldCollectGaugesAndEvents()); - return session; } @@ -50,6 +53,11 @@ public static PerfSession createWithId(@NonNull String sessionId) { public PerfSession(String sessionId, Clock clock) { this.sessionId = sessionId; creationTime = clock.getTime(); + // Every time a PerfSession is created, it sets the AQS to null. Once an AQS is received, + // SessionManagerKt verifies if this is an active session, and sets the AQS session ID. + // The assumption is that new PerfSessions *should* be limited to either App Start, or through + // AQS. + FirebasePerformanceSessionSubscriber.Companion.getInstance().reportPerfSession(sessionId); } private PerfSession(@NonNull Parcel in) { @@ -59,9 +67,27 @@ 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; + return this.sessionId; + } + + private String aqsSessionId() { + return FirebasePerformanceSessionSubscriber.Companion.getInstance() + .getAqsMappedToPerfSession(this.sessionId); + } + + /** Returns the AQS sessionId for the given session. */ + @Nullable + public String aqsSessionId() { + return aqsSessionId; + } + + /** Sets the AQS sessionId for the given session. */ + public void setAQSId(SessionSubscriber.SessionDetails aqs) { + if (aqsSessionId == null) { + aqsSessionId = aqs.getSessionId(); + } } /** @@ -113,8 +139,9 @@ public boolean isSessionRunningTooLong() { /** Creates and returns the proto object for PerfSession object. */ public com.google.firebase.perf.v1.PerfSession build() { + // TODO(b/394127311): Switch to using AQS. com.google.firebase.perf.v1.PerfSession.Builder sessionMetric = - com.google.firebase.perf.v1.PerfSession.newBuilder().setSessionId(sessionId); + com.google.firebase.perf.v1.PerfSession.newBuilder().setSessionId(aqsSessionId()); // If gauge collection is enabled, enable gauge collection verbosity. if (isGaugeAndEventCollectionEnabled) { diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/session/SessionManager.java b/firebase-perf/src/main/java/com/google/firebase/perf/session/SessionManager.java index 79d034b9b0b..70c01d25a4f 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/session/SessionManager.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/session/SessionManager.java @@ -19,7 +19,7 @@ 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.logging.AndroidLogger; import com.google.firebase.perf.session.gauges.GaugeManager; import com.google.firebase.perf.v1.ApplicationProcessState; import com.google.firebase.perf.v1.GaugeMetadata; @@ -27,15 +27,13 @@ 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(); @@ -59,10 +57,7 @@ public final PerfSession perfSession() { private SessionManager() { // Generate a new sessionID for every cold start. - this( - GaugeManager.getInstance(), - PerfSession.createWithId(UUID.randomUUID().toString()), - AppStateMonitor.getInstance()); + this(GaugeManager.getInstance(), PerfSession.createNewSession(), AppStateMonitor.getInstance()); } @VisibleForTesting @@ -71,7 +66,6 @@ public SessionManager( this.gaugeManager = gaugeManager; this.perfSession = perfSession; this.appStateMonitor = appStateMonitor; - registerForAppState(); } /** @@ -79,49 +73,7 @@ public SessionManager( * (currently that is before onResume finishes) to ensure gauge collection starts on time. */ public void setApplicationContext(final Context appContext) { - // Get PerfSession in main thread first, because it is possible that app changes fg/bg state - // which creates a new perfSession, before the following is executed in background thread - final PerfSession appStartSession = perfSession; - // TODO(b/258263016): Migrate to go/firebase-android-executors - @SuppressLint("ThreadPoolCreation") - ExecutorService executorService = Executors.newSingleThreadExecutor(); - syncInitFuture = - executorService.submit( - () -> { - gaugeManager.initializeGaugeMetadataManager(appContext); - if (appStartSession.isGaugeAndEventCollectionEnabled()) { - gaugeManager.logGaugeMetadata( - appStartSession.sessionId(), ApplicationProcessState.FOREGROUND); - } - }); - } - - @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, ApplicationProcessState.FOREGROUND); } /** @@ -145,10 +97,12 @@ 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; } + AndroidLogger.getInstance().debug("Perf Session Changed: " + perfSession); + this.perfSession = perfSession; synchronized (clients) { @@ -164,9 +118,6 @@ public void updatePerfSession(PerfSession perfSession) { } } - // Log the gauge metadata event if data collection is enabled. - logGaugeMetadataIfCollectionEnabled(appStateMonitor.getAppState()); - // Start of stop the gauge data collection. startOrStopCollectingGauges(appStateMonitor.getAppState()); } @@ -178,7 +129,6 @@ public void updatePerfSession(PerfSession perfSession) { * this does not reset the perfSession. */ public void initializeGaugeCollection() { - logGaugeMetadataIfCollectionEnabled(ApplicationProcessState.FOREGROUND); startOrStopCollectingGauges(ApplicationProcessState.FOREGROUND); } @@ -206,12 +156,6 @@ public void unregisterForSessionUpdates(WeakReference client } } - private void logGaugeMetadataIfCollectionEnabled(ApplicationProcessState appState) { - if (perfSession.isGaugeAndEventCollectionEnabled()) { - gaugeManager.logGaugeMetadata(perfSession.sessionId(), appState); - } - } - private void startOrStopCollectingGauges(ApplicationProcessState appState) { if (perfSession.isGaugeAndEventCollectionEnabled()) { gaugeManager.startCollectingGauges(perfSession, appState); diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/CpuGaugeCollector.java b/firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/CpuGaugeCollector.java index e33d363c0aa..ceb636d56b3 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/CpuGaugeCollector.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/CpuGaugeCollector.java @@ -17,8 +17,6 @@ import static android.system.Os.sysconf; import android.annotation.SuppressLint; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.system.OsConstants; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -163,7 +161,7 @@ private synchronized void scheduleCpuMetricCollectionWithRate( this.cpuMetricCollectionRateMs = cpuMetricCollectionRate; try { cpuMetricCollectorJob = - cpuMetricCollectorExecutor.scheduleAtFixedRate( + cpuMetricCollectorExecutor.scheduleWithFixedDelay( () -> { CpuMetricReading currCpuReading = syncCollectCpuMetric(referenceTime); if (currCpuReading != null) { @@ -181,7 +179,7 @@ private synchronized void scheduleCpuMetricCollectionWithRate( private synchronized void scheduleCpuMetricCollectionOnce(Timer referenceTime) { try { @SuppressWarnings("FutureReturnValueIgnored") - ScheduledFuture unusedFuture = + ScheduledFuture unusedFuture = cpuMetricCollectorExecutor.schedule( () -> { CpuMetricReading currCpuReading = syncCollectCpuMetric(referenceTime); @@ -227,12 +225,7 @@ private CpuMetricReading syncCollectCpuMetric(Timer referenceTime) { } private long getClockTicksPerSecond() { - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - return sysconf(OsConstants._SC_CLK_TCK); - } else { - // TODO(b/110779408): Figure out how to collect this info for Android API 20 and below. - return INVALID_SC_PER_CPU_CLOCK_TICK; - } + return sysconf(OsConstants._SC_CLK_TCK); } private long convertClockTicksToMicroseconds(long clockTicks) { diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/GaugeManager.java b/firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/GaugeManager.java index 7f6182a9c15..65e55e49c11 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/GaugeManager.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/GaugeManager.java @@ -22,6 +22,7 @@ import com.google.firebase.components.Lazy; import com.google.firebase.perf.config.ConfigResolver; import com.google.firebase.perf.logging.AndroidLogger; +import com.google.firebase.perf.session.FirebasePerformanceSessionSubscriber; import com.google.firebase.perf.session.PerfSession; import com.google.firebase.perf.transport.TransportManager; import com.google.firebase.perf.util.Timer; @@ -59,7 +60,7 @@ public class GaugeManager { private final TransportManager transportManager; @Nullable private GaugeMetadataManager gaugeMetadataManager; - @Nullable private ScheduledFuture gaugeManagerDataCollectionJob = null; + @Nullable private ScheduledFuture gaugeManagerDataCollectionJob = null; @Nullable private String sessionId = null; private ApplicationProcessState applicationProcessState = ApplicationProcessState.APPLICATION_PROCESS_STATE_UNKNOWN; @@ -72,8 +73,8 @@ private GaugeManager() { TransportManager.getInstance(), ConfigResolver.getInstance(), null, - new Lazy<>(() -> new CpuGaugeCollector()), - new Lazy<>(() -> new MemoryGaugeCollector())); + new Lazy<>(CpuGaugeCollector::new), + new Lazy<>(MemoryGaugeCollector::new)); } @VisibleForTesting @@ -81,7 +82,7 @@ private GaugeManager() { Lazy gaugeManagerExecutor, TransportManager transportManager, ConfigResolver configResolver, - GaugeMetadataManager gaugeMetadataManager, + @Nullable GaugeMetadataManager gaugeMetadataManager, Lazy cpuGaugeCollector, Lazy memoryGaugeCollector) { @@ -94,8 +95,10 @@ private GaugeManager() { } /** Initializes GaugeMetadataManager which requires application context. */ - public void initializeGaugeMetadataManager(Context appContext) { + public void initializeGaugeMetadataManager( + Context appContext, ApplicationProcessState applicationProcessState) { this.gaugeMetadataManager = new GaugeMetadataManager(appContext); + this.applicationProcessState = applicationProcessState; } /** Returns the singleton instance of this class. */ @@ -140,7 +143,7 @@ public void startCollectingGauges( gaugeManagerDataCollectionJob = gaugeManagerExecutor .get() - .scheduleAtFixedRate( + .scheduleWithFixedDelay( () -> { syncFlush(sessionIdForScheduledTask, applicationProcessStateForScheduledTask); }, @@ -206,7 +209,7 @@ public void stopCollectingGauges() { // Flush any data that was collected for this session one last time. @SuppressWarnings("FutureReturnValueIgnored") - ScheduledFuture unusedFuture = + ScheduledFuture unusedFuture = gaugeManagerExecutor .get() .schedule( @@ -242,7 +245,11 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) { } // Adding Session ID info. - gaugeMetricBuilder.setSessionId(sessionId); + String aqsSessionId = + FirebasePerformanceSessionSubscriber.Companion.getInstance() + .getAqsMappedToPerfSession(sessionId); + gaugeMetricBuilder.setSessionId(aqsSessionId); + AndroidLogger.getInstance().debug("CFPR syncFlush: " + sessionId + " AQS: " + aqsSessionId); transportManager.log(gaugeMetricBuilder.build(), appState); } @@ -250,22 +257,20 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) { /** * Log the Gauge Metadata information to the transport. * - * @param sessionId The {@link PerfSession#sessionId()} to which the collected Gauge Metrics + * @param aqsSessionId The {@link FirebasePerformanceSessionSubscriber#getAqsMappedToPerfSession(String)} to which the collected Gauge Metrics * should be associated with. - * @param appState The {@link ApplicationProcessState} for which these gauges are collected. * @return true if GaugeMetadata was logged, false otherwise. */ - public boolean logGaugeMetadata(String sessionId, ApplicationProcessState appState) { - if (gaugeMetadataManager != null) { - GaugeMetric gaugeMetric = - GaugeMetric.newBuilder() - .setSessionId(sessionId) - .setGaugeMetadata(getGaugeMetadata()) - .build(); - transportManager.log(gaugeMetric, appState); - return true; - } - return false; + public void logGaugeMetadata(String aqsSessionId) { + // TODO(b/394127311): This can now throw an NPE. Explore if there's anything that should be + // verified. + AndroidLogger.getInstance().debug("CFPR logGaugeMetadata: " + aqsSessionId); + GaugeMetric gaugeMetric = + GaugeMetric.newBuilder() + .setSessionId(aqsSessionId) + .setGaugeMetadata(getGaugeMetadata()) + .build(); + transportManager.log(gaugeMetric, this.applicationProcessState); } private GaugeMetadata getGaugeMetadata() { diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/GaugeMetadataManager.java b/firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/GaugeMetadataManager.java index 6b4466dfc35..ed38dd8f38d 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/GaugeMetadataManager.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/GaugeMetadataManager.java @@ -17,18 +17,11 @@ import android.app.ActivityManager; import android.app.ActivityManager.MemoryInfo; import android.content.Context; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import androidx.annotation.VisibleForTesting; import com.google.firebase.perf.logging.AndroidLogger; import com.google.firebase.perf.util.StorageUnit; import com.google.firebase.perf.util.Utils; import com.google.firebase.perf.v1.GaugeMetadata; -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * The {@code GaugeMetadataManager} class is responsible for collecting {@link GaugeMetadata} @@ -41,7 +34,6 @@ class GaugeMetadataManager { private final Runtime runtime; private final ActivityManager activityManager; private final MemoryInfo memoryInfo; - private final Context appContext; GaugeMetadataManager(Context appContext) { this(Runtime.getRuntime(), appContext); @@ -50,7 +42,6 @@ class GaugeMetadataManager { @VisibleForTesting GaugeMetadataManager(Runtime runtime, Context appContext) { this.runtime = runtime; - this.appContext = appContext; this.activityManager = (ActivityManager) appContext.getSystemService(Context.ACTIVITY_SERVICE); memoryInfo = new ActivityManager.MemoryInfo(); activityManager.getMemoryInfo(memoryInfo); @@ -75,29 +66,6 @@ public int getMaxEncouragedAppJavaHeapMemoryKb() { /** Returns the total memory (in kilobytes) accessible by the kernel (called the RAM size). */ public int getDeviceRamSizeKb() { - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { - return Utils.saturatedIntCast(StorageUnit.BYTES.toKilobytes(memoryInfo.totalMem)); - } - - return readTotalRAM(/* procFileName= */ "/proc/meminfo"); - } - - /** Returns the total ram size of the device (in kilobytes) by reading the "proc/meminfo" file. */ - @VisibleForTesting - int readTotalRAM(String procFileName) { - try (BufferedReader br = new BufferedReader(new FileReader(procFileName))) { - for (String s = br.readLine(); s != null; s = br.readLine()) { - if (s.startsWith("MemTotal")) { - Matcher m = Pattern.compile("\\d+").matcher(s); - return m.find() ? Integer.parseInt(m.group()) : 0; - } - } - } catch (IOException ioe) { - logger.warn("Unable to read '" + procFileName + "' file: " + ioe.getMessage()); - } catch (NumberFormatException nfe) { - logger.warn("Unable to parse '" + procFileName + "' file: " + nfe.getMessage()); - } - - return 0; + return Utils.saturatedIntCast(StorageUnit.BYTES.toKilobytes(memoryInfo.totalMem)); } } diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/MemoryGaugeCollector.java b/firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/MemoryGaugeCollector.java index eeaf4eb7c80..a7b4b40002a 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/MemoryGaugeCollector.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/session/gauges/MemoryGaugeCollector.java @@ -50,7 +50,7 @@ public class MemoryGaugeCollector { public final ConcurrentLinkedQueue memoryMetricReadings; private final Runtime runtime; - @Nullable private ScheduledFuture memoryMetricCollectorJob = null; + @Nullable private ScheduledFuture memoryMetricCollectorJob = null; private long memoryMetricCollectionRateMs = UNSET_MEMORY_METRIC_COLLECTION_RATE; // TODO(b/258263016): Migrate to go/firebase-android-executors @@ -124,7 +124,7 @@ private synchronized void scheduleMemoryMetricCollectionWithRate( try { memoryMetricCollectorJob = - memoryMetricCollectorExecutor.scheduleAtFixedRate( + memoryMetricCollectorExecutor.scheduleWithFixedDelay( () -> { AndroidMemoryReading memoryReading = syncCollectMemoryMetric(referenceTime); if (memoryReading != null) { @@ -142,7 +142,7 @@ private synchronized void scheduleMemoryMetricCollectionWithRate( private synchronized void scheduleMemoryMetricCollectionOnce(Timer referenceTime) { try { @SuppressWarnings("FutureReturnValueIgnored") - ScheduledFuture unusedFuture = + ScheduledFuture unusedFuture = memoryMetricCollectorExecutor.schedule( () -> { AndroidMemoryReading memoryReading = syncCollectMemoryMetric(referenceTime); diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/transport/TransportManager.java b/firebase-perf/src/main/java/com/google/firebase/perf/transport/TransportManager.java index 9600b099a6d..159af53d3d3 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/transport/TransportManager.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/transport/TransportManager.java @@ -354,6 +354,7 @@ public void log(final GaugeMetric gaugeMetric) { * {@link #isAllowedToDispatch(PerfMetric)}). */ public void log(final GaugeMetric gaugeMetric, final ApplicationProcessState appState) { + // TODO(b/394127311): This *might* potentially be the right place to get AQS. executorService.execute( () -> syncLog(PerfMetric.newBuilder().setGaugeMetric(gaugeMetric), appState)); } diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/FirebasePerformanceTestBase.java b/firebase-perf/src/test/java/com/google/firebase/perf/FirebasePerformanceTestBase.java index b31696d963b..1a045b3f1b9 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/FirebasePerformanceTestBase.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/FirebasePerformanceTestBase.java @@ -23,9 +23,10 @@ import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.perf.config.ConfigResolver; -import com.google.firebase.perf.session.PerfSession; -import com.google.firebase.perf.session.SessionManager; +import com.google.firebase.perf.session.FirebasePerformanceSessionSubscriber; import com.google.firebase.perf.util.ImmutableBundle; +import com.google.firebase.sessions.api.SessionSubscriber; +import java.util.UUID; import org.junit.After; import org.junit.Before; import org.robolectric.shadows.ShadowPackageManager; @@ -52,6 +53,8 @@ public class FirebasePerformanceTestBase { protected static final String FAKE_FIREBASE_DB_URL = "https://fir-perftestapp.firebaseio.com"; protected static final String FAKE_FIREBASE_PROJECT_ID = "fir-perftestapp"; + protected static final String FAKE_AQS_SESSION_PREFIX = "AIzaSyBcE"; + protected Context appContext; @Before @@ -72,11 +75,13 @@ public void setUpFirebaseApp() { .setProjectId(FAKE_FIREBASE_PROJECT_ID) .build(); FirebaseApp.initializeApp(appContext, options); + triggerAqsSession(); } @After public void tearDownFirebaseApp() { FirebaseApp.clearInstancesForTest(); + FirebasePerformanceSessionSubscriber.Companion.getInstance().clearSessionForTest(); } protected static void forceSessionsFeatureDisabled() { @@ -93,11 +98,16 @@ protected static void forceNonVerboseSession() { forceVerboseSessionWithSamplingPercentage(0); } + protected static void triggerAqsSession() { + FirebasePerformanceSessionSubscriber.Companion.getInstance() + .onSessionChanged( + new SessionSubscriber.SessionDetails(FAKE_AQS_SESSION_PREFIX + UUID.randomUUID())); + } + private static void forceVerboseSessionWithSamplingPercentage(long samplingPercentage) { Bundle bundle = new Bundle(); bundle.putFloat("sessions_sampling_percentage", samplingPercentage); ConfigResolver.getInstance().setMetadataBundle(new ImmutableBundle(bundle)); - - SessionManager.getInstance().setPerfSession(PerfSession.createWithId("sessionId")); + triggerAqsSession(); } } diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java index 36ae3d10116..47569cb4a2d 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/AppStartTraceTest.java @@ -96,7 +96,7 @@ public Timer answer(InvocationOnMock invocationOnMock) throws Throwable { @After public void reset() { - SessionManager.getInstance().updatePerfSession(PerfSession.createWithId("randomSessionId")); + SessionManager.getInstance().updatePerfSession(PerfSession.createNewSession()); } /** Test activity sequentially goes through onCreate()->onStart()->onResume() state change. */ diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilderTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilderTest.java index 61b3823741d..3f6a3f929e2 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilderTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/NetworkRequestMetricBuilderTest.java @@ -225,7 +225,7 @@ public void testSessionIdAdditionInNetworkRequestMetric() { assertThat(this.networkMetricBuilder.getSessions()).isEmpty(); int numberOfSessionIds = metricBuilder.getSessions().size(); - PerfSession perfSession = PerfSession.createWithId("testSessionId"); + PerfSession perfSession = PerfSession.createNewSession(); SessionManager.getInstance().updatePerfSession(perfSession); assertThat(metricBuilder.getSessions().size()).isEqualTo(numberOfSessionIds + 1); @@ -328,7 +328,7 @@ public void testUpdateSessionWithValidSessionIsAdded() { networkMetricBuilder.setRequestStartTimeMicros(/* time= */ 2000); assertThat(networkMetricBuilder.getSessions()).hasSize(1); - networkMetricBuilder.updateSession(PerfSession.createWithId("testSessionId")); + networkMetricBuilder.updateSession(PerfSession.createNewSession()); assertThat(networkMetricBuilder.getSessions()).hasSize(2); } diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/TraceTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/TraceTest.java index 0be443031f2..020bf433560 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/metrics/TraceTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/metrics/TraceTest.java @@ -1016,7 +1016,7 @@ public void testSessionIdAdditionInTrace() { int numberOfSessionIds = trace.getSessions().size(); - PerfSession perfSession = PerfSession.createWithId("test_session_id"); + PerfSession perfSession = PerfSession.createNewSession(); SessionManager.getInstance().updatePerfSession(perfSession); assertThat(trace.getSessions()).hasSize(numberOfSessionIds + 1); @@ -1071,7 +1071,7 @@ public void testUpdateSessionWithValidSessionIsAdded() { trace.start(); assertThat(trace.getSessions()).hasSize(1); - trace.updateSession(PerfSession.createWithId("test_session_id")); + trace.updateSession(PerfSession.createNewSession()); assertThat(trace.getSessions()).hasSize(2); trace.stop(); diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/session/PerfSessionTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/session/PerfSessionTest.java index 43257987b0f..9551f1d11ae 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/session/PerfSessionTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/session/PerfSessionTest.java @@ -160,21 +160,21 @@ public void testPerfSessionConversionWithoutVerbosity() { @Test public void testPerfSessionsCreateDisabledGaugeCollectionWhenVerboseSessionForceDisabled() { forceNonVerboseSession(); - PerfSession testPerfSession = PerfSession.createWithId("sessionId"); + PerfSession testPerfSession = PerfSession.createNewSession(); assertThat(testPerfSession.isGaugeAndEventCollectionEnabled()).isFalse(); } @Test public void testPerfSessionsCreateDisabledGaugeCollectionWhenSessionsFeatureDisabled() { forceSessionsFeatureDisabled(); - PerfSession testPerfSession = PerfSession.createWithId("sessionId"); + PerfSession testPerfSession = PerfSession.createNewSession(); assertThat(testPerfSession.isGaugeAndEventCollectionEnabled()).isFalse(); } @Test public void testPerfSessionsCreateEnablesGaugeCollectionWhenVerboseSessionForceEnabled() { forceVerboseSession(); - PerfSession testPerfSession = PerfSession.createWithId("sessionId"); + PerfSession testPerfSession = PerfSession.createNewSession(); assertThat(testPerfSession.isGaugeAndEventCollectionEnabled()).isTrue(); } @@ -185,16 +185,16 @@ public void testBuildAndSortMovesTheVerboseSessionToTop() { // Next, create 3 non-verbose sessions List sessions = new ArrayList<>(); - sessions.add(PerfSession.createWithId("sessionId1")); - sessions.add(PerfSession.createWithId("sessionId2")); - sessions.add(PerfSession.createWithId("sessionId3")); + sessions.add(PerfSession.createNewSession()); + sessions.add(PerfSession.createNewSession()); + sessions.add(PerfSession.createNewSession()); // Force all the sessions from now onwards to be verbose forceVerboseSession(); // Next, create 2 verbose sessions - sessions.add(PerfSession.createWithId("sessionId4")); - sessions.add(PerfSession.createWithId("sessionId5")); + sessions.add(PerfSession.createNewSession()); + sessions.add(PerfSession.createNewSession()); // Verify that the first session in the list of sessions was not verbose assertThat(sessions.get(0).isVerbose()).isFalse(); diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/session/SessionManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/session/SessionManagerTest.java index f3e3795f3f8..d105594f4ce 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/session/SessionManagerTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/session/SessionManagerTest.java @@ -136,20 +136,6 @@ public void testOnUpdateAppStateGeneratesNewSessionIdOnBackgroundStateIfPerfSess assertThat(oldSessionId).isNotEqualTo(testSessionManager.perfSession().sessionId()); } - @Test - public void - testOnUpdateAppStateMakesGaugeManagerLogGaugeMetadataOnForegroundStateIfSessionIsVerbose() { - forceVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.FOREGROUND); - - verify(mockGaugeManager) - .logGaugeMetadata( - anyString(), nullable(com.google.firebase.perf.v1.ApplicationProcessState.class)); - } - @Test public void testOnUpdateAppStateDoesntMakeGaugeManagerLogGaugeMetadataOnForegroundStateIfSessionIsNonVerbose() { @@ -178,21 +164,6 @@ public void testOnUpdateAppStateGeneratesNewSessionIdOnBackgroundStateIfPerfSess anyString(), nullable(com.google.firebase.perf.v1.ApplicationProcessState.class)); } - @Test - public void - testOnUpdateAppStateMakesGaugeManagerLogGaugeMetadataOnBackgroundAppStateIfSessionIsVerboseAndTimedOut() { - when(mockPerfSession.isSessionRunningTooLong()).thenReturn(true); - forceVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.BACKGROUND); - - verify(mockGaugeManager) - .logGaugeMetadata( - anyString(), nullable(com.google.firebase.perf.v1.ApplicationProcessState.class)); - } - @Test public void testOnUpdateAppStateMakesGaugeManagerStartCollectingGaugesIfSessionIsVerbose() { forceVerboseSession(); @@ -232,32 +203,6 @@ public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesWhenSession verify(mockGaugeManager).stopCollectingGauges(); } - @Test - public void testGaugeMetadataIsFlushedOnlyWhenNewVerboseSessionIsCreated() { - when(mockPerfSession.isSessionRunningTooLong()).thenReturn(false); - - // Start with a non verbose session - forceNonVerboseSession(); - SessionManager testSessionManager = - new SessionManager( - mockGaugeManager, PerfSession.createWithId("testSessionId1"), mockAppStateMonitor); - - verify(mockGaugeManager, times(0)) - .logGaugeMetadata( - eq("testSessionId1"), - eq(com.google.firebase.perf.v1.ApplicationProcessState.FOREGROUND)); - - // Forcing a verbose session will enable Gauge collection - forceVerboseSession(); - testSessionManager.updatePerfSession(PerfSession.createWithId("testSessionId2")); - verify(mockGaugeManager, times(1)).logGaugeMetadata(eq("testSessionId2"), any()); - - // Force a non-verbose session and verify if we are not logging metadata - forceVerboseSession(); - testSessionManager.updatePerfSession(PerfSession.createWithId("testSessionId3")); - verify(mockGaugeManager, times(1)).logGaugeMetadata(eq("testSessionId3"), any()); - } - @Test public void testSessionIdDoesNotUpdateIfPerfSessionRunsTooLong() { Timer mockTimer = mock(Timer.class); diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/session/gauges/GaugeManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/session/gauges/GaugeManagerTest.java index 5090d66c8b9..696b90ed466 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/session/gauges/GaugeManagerTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/session/gauges/GaugeManagerTest.java @@ -641,7 +641,7 @@ public void testLogGaugeMetadataSendDataToTransport() { when(fakeGaugeMetadataManager.getMaxAppJavaHeapMemoryKb()).thenReturn(1000); when(fakeGaugeMetadataManager.getMaxEncouragedAppJavaHeapMemoryKb()).thenReturn(800); - testGaugeManager.logGaugeMetadata("sessionId", ApplicationProcessState.FOREGROUND); + testGaugeManager.logGaugeMetadata("sessionId"); GaugeMetric recordedGaugeMetric = getLastRecordedGaugeMetric(ApplicationProcessState.FOREGROUND, 1); @@ -668,35 +668,6 @@ public void testLogGaugeMetadataDoesntLogWhenGaugeMetadataManagerNotAvailable() /* gaugeMetadataManager= */ null, new Lazy<>(() -> fakeCpuGaugeCollector), new Lazy<>(() -> fakeMemoryGaugeCollector)); - - assertThat(testGaugeManager.logGaugeMetadata("sessionId", ApplicationProcessState.FOREGROUND)) - .isFalse(); - } - - @Test - public void testLogGaugeMetadataLogsAfterApplicationContextIsSet() { - - testGaugeManager = - new GaugeManager( - new Lazy<>(() -> fakeScheduledExecutorService), - mockTransportManager, - mockConfigResolver, - /* gaugeMetadataManager= */ null, - new Lazy<>(() -> fakeCpuGaugeCollector), - new Lazy<>(() -> fakeMemoryGaugeCollector)); - - assertThat(testGaugeManager.logGaugeMetadata("sessionId", ApplicationProcessState.FOREGROUND)) - .isFalse(); - - testGaugeManager.initializeGaugeMetadataManager(ApplicationProvider.getApplicationContext()); - assertThat(testGaugeManager.logGaugeMetadata("sessionId", ApplicationProcessState.FOREGROUND)) - .isTrue(); - - GaugeMetric recordedGaugeMetric = - getLastRecordedGaugeMetric(ApplicationProcessState.FOREGROUND, 1); - GaugeMetadata recordedGaugeMetadata = recordedGaugeMetric.getGaugeMetadata(); - - assertThat(recordedGaugeMetric.getSessionId()).isEqualTo("sessionId"); } @Test diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/session/gauges/GaugeMetadataManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/session/gauges/GaugeMetadataManagerTest.java index 292747121dd..1592f77e5ad 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/session/gauges/GaugeMetadataManagerTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/session/gauges/GaugeMetadataManagerTest.java @@ -15,26 +15,20 @@ package com.google.firebase.perf.session.gauges; import static com.google.common.truth.Truth.assertThat; -import static java.nio.charset.StandardCharsets.UTF_8; import static org.mockito.MockitoAnnotations.initMocks; import static org.robolectric.Shadows.shadowOf; import android.app.ActivityManager; import android.content.Context; -import android.os.Environment; import androidx.test.core.app.ApplicationProvider; import com.google.firebase.perf.FirebasePerformanceTestBase; import com.google.firebase.perf.util.StorageUnit; -import java.io.File; import java.io.IOException; -import java.io.Writer; -import java.nio.file.Files; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.robolectric.RobolectricTestRunner; -import org.robolectric.shadows.ShadowEnvironment; /** Unit tests for {@link com.google.firebase.perf.session.gauges.GaugeMetadataManager} */ @RunWith(RobolectricTestRunner.class) @@ -49,12 +43,11 @@ public class GaugeMetadataManagerTest extends FirebasePerformanceTestBase { @Mock private Runtime runtime; private ActivityManager activityManager; - private Context appContext; @Before public void setUp() { initMocks(this); - appContext = ApplicationProvider.getApplicationContext(); + Context appContext = ApplicationProvider.getApplicationContext(); activityManager = (ActivityManager) appContext.getSystemService(Context.ACTIVITY_SERVICE); mockMemory(); @@ -90,62 +83,5 @@ public void testGetDeviceRamSize_returnsExpectedValue() throws IOException { int ramSize = testGaugeMetadataManager.getDeviceRamSizeKb(); assertThat(ramSize).isEqualTo(StorageUnit.BYTES.toKilobytes(DEVICE_RAM_SIZE_BYTES)); - assertThat(ramSize).isEqualTo(testGaugeMetadataManager.readTotalRAM(createFakeMemInfoFile())); } - - /** @return The file path of this fake file which can be used to read the file. */ - private String createFakeMemInfoFile() throws IOException { - // Due to file permission issues on forge, it's easiest to just write this file to the emulated - // robolectric external storage. - ShadowEnvironment.setExternalStorageState(Environment.MEDIA_MOUNTED); - - File file = new File(Environment.getExternalStorageDirectory(), "FakeProcMemInfoFile"); - Writer fileWriter; - - fileWriter = Files.newBufferedWriter(file.toPath(), UTF_8); - fileWriter.write(MEM_INFO_CONTENTS); - fileWriter.close(); - - return file.getAbsolutePath(); - } - - private static final String MEM_INFO_CONTENTS = - "MemTotal: " - + DEVICE_RAM_SIZE_KB - + " kB\n" - + "MemFree: 542404 kB\n" - + "MemAvailable: 1392324 kB\n" - + "Buffers: 64292 kB\n" - + "Cached: 826180 kB\n" - + "SwapCached: 4196 kB\n" - + "Active: 934768 kB\n" - + "Inactive: 743812 kB\n" - + "Active(anon): 582132 kB\n" - + "Inactive(anon): 241500 kB\n" - + "Active(file): 352636 kB\n" - + "Inactive(file): 502312 kB\n" - + "Unevictable: 5148 kB\n" - + "Mlocked: 256 kB\n" - + "SwapTotal: 524284 kB\n" - + "SwapFree: 484800 kB\n" - + "Dirty: 4 kB\n" - + "Writeback: 0 kB\n" - + "AnonPages: 789404 kB\n" - + "Mapped: 241928 kB\n" - + "Shmem: 30632 kB\n" - + "Slab: 122320 kB\n" - + "SReclaimable: 42552 kB\n" - + "SUnreclaim: 79768 kB\n" - + "KernelStack: 22816 kB\n" - + "PageTables: 35344 kB\n" - + "NFS_Unstable: 0 kB\n" - + "Bounce: 0 kB\n" - + "WritebackTmp: 0 kB\n" - + "CommitLimit: 2042280 kB\n" - + "Committed_AS: 76623352 kB\n" - + "VmallocTotal: 251658176 kB\n" - + "VmallocUsed: 232060 kB\n" - + "VmallocChunk: 251347444 kB\n" - + "NvMapMemFree: 48640 kB\n" - + "NvMapMemUsed: 471460 kB\n"; } diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/api/FirebaseSessionsDependencies.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/api/FirebaseSessionsDependencies.kt index 8d3548c8f4b..4b636a155e0 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/api/FirebaseSessionsDependencies.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/api/FirebaseSessionsDependencies.kt @@ -40,19 +40,6 @@ object FirebaseSessionsDependencies { */ @JvmStatic fun addDependency(subscriberName: SessionSubscriber.Name) { - if (subscriberName == SessionSubscriber.Name.PERFORMANCE) { - throw IllegalArgumentException( - """ - Incompatible versions of Firebase Perf and Firebase Sessions. - A safe combination would be: - firebase-sessions:1.1.0 - firebase-crashlytics:18.5.0 - firebase-perf:20.5.0 - For more information contact Firebase Support. - """ - .trimIndent() - ) - } if (dependencies.containsKey(subscriberName)) { Log.d(TAG, "Dependency $subscriberName already added.") return diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SessionConfigs.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SessionConfigs.kt new file mode 100644 index 00000000000..ab310ebed8a --- /dev/null +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SessionConfigs.kt @@ -0,0 +1,58 @@ +/* + * 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.sessions.settings + +import androidx.datastore.core.CorruptionException +import androidx.datastore.core.Serializer +import java.io.InputStream +import java.io.OutputStream +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json + +/** Session configs data for caching. */ +@Serializable +internal data class SessionConfigs( + val sessionsEnabled: Boolean?, + val sessionSamplingRate: Double?, + val sessionTimeoutSeconds: Int?, + val cacheDurationSeconds: Int?, + val cacheUpdatedTimeSeconds: Long?, +) + +/** DataStore json [Serializer] for [SessionConfigs]. */ +internal object SessionConfigsSerializer : Serializer { + override val defaultValue = + SessionConfigs( + sessionsEnabled = null, + sessionSamplingRate = null, + sessionTimeoutSeconds = null, + cacheDurationSeconds = null, + cacheUpdatedTimeSeconds = null, + ) + + override suspend fun readFrom(input: InputStream): SessionConfigs = + try { + Json.decodeFromString(input.readBytes().decodeToString()) + } catch (ex: Exception) { + throw CorruptionException("Cannot parse session configs", ex) + } + + override suspend fun writeTo(t: SessionConfigs, output: OutputStream) { + @Suppress("BlockingMethodInNonBlockingContext") // blockingDispatcher is safe for blocking calls + output.write(Json.encodeToString(SessionConfigs.serializer(), t).encodeToByteArray()) + } +} diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionDatastoreTest.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionDatastoreTest.kt new file mode 100644 index 00000000000..efe7bb27a97 --- /dev/null +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionDatastoreTest.kt @@ -0,0 +1,59 @@ +/* + * 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.sessions + +import android.content.Context +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.dataStoreFile +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class SessionDatastoreTest { + private val appContext: Context = ApplicationProvider.getApplicationContext() + + @Test + fun getCurrentSessionId_returnsLatest() = runTest { + val sessionDatastore = + SessionDatastoreImpl( + backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), + sessionDataStore = + DataStoreFactory.create( + serializer = SessionDataSerializer, + scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")), + produceFile = { appContext.dataStoreFile("sessionDataStore.data") }, + ), + ) + + sessionDatastore.updateSessionId("sessionId1") + sessionDatastore.updateSessionId("sessionId2") + sessionDatastore.updateSessionId("sessionId3") + + runCurrent() + + assertThat(sessionDatastore.getCurrentSessionId()).isEqualTo("sessionId3") + } +} diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/FakeSettingsCache.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/FakeSettingsCache.kt new file mode 100644 index 00000000000..2c58ef22d7d --- /dev/null +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/FakeSettingsCache.kt @@ -0,0 +1,52 @@ +/* + * 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.sessions.testing + +import com.google.firebase.sessions.TimeProvider +import com.google.firebase.sessions.settings.SessionConfigs +import com.google.firebase.sessions.settings.SessionConfigsSerializer +import com.google.firebase.sessions.settings.SettingsCache + +/** Fake implementation of [SettingsCache]. */ +internal class FakeSettingsCache( + private val timeProvider: TimeProvider = FakeTimeProvider(), + private var sessionConfigs: SessionConfigs = SessionConfigsSerializer.defaultValue, +) : SettingsCache { + override fun hasCacheExpired(): Boolean { + val cacheUpdatedTimeSeconds = sessionConfigs.cacheUpdatedTimeSeconds + val cacheDurationSeconds = sessionConfigs.cacheDurationSeconds + + if (cacheUpdatedTimeSeconds != null && cacheDurationSeconds != null) { + val timeDifferenceSeconds = timeProvider.currentTime().seconds - cacheUpdatedTimeSeconds + if (timeDifferenceSeconds < cacheDurationSeconds) { + return false + } + } + + return true + } + + override fun sessionsEnabled(): Boolean? = sessionConfigs.sessionsEnabled + + override fun sessionSamplingRate(): Double? = sessionConfigs.sessionSamplingRate + + override fun sessionRestartTimeout(): Int? = sessionConfigs.sessionTimeoutSeconds + + override suspend fun updateConfigs(sessionConfigs: SessionConfigs) { + this.sessionConfigs = sessionConfigs + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 44079b349e8..5fa68f6cc03 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -91,6 +91,7 @@ androidx-cardview = { module = "androidx.cardview:cardview", version.ref = "card androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" } androidx-core = { module = "androidx.core:core", version = "1.13.1" } androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" } +androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" } androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" } androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" } androidx-espresso-idling-resource = { module = "androidx.test.espresso:espresso-idling-resource", version.ref = "espressoCore" }