From 9c03b7b84b1cb78b5f69773dc5be584887bc6956 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 7 Feb 2025 14:58:37 -0500 Subject: [PATCH 01/76] Remove the logging of GaugeMetadata to allow using AQS (#6678) Based on the behaviour of AQS w/ Fireperf, an AQS session isn't available when (currently) logging gauge metadata. Changes: - Remove the current logging of gauge metadata - will be re-introduced in a future PR. - Switch Gauge collection from `scheduleAtFixedRate` to `scheduleWithFixedDelay`. As [documented](https://stackoverflow.com/a/78405653), this *should* prevent a potentially large amounts of gauge collection if a process is cached, and then restored during a verbose session - which *should* make it work better w/ AQS. - Remove API restricted behaviour which is no longer relevant. --- .../firebase/perf/FirebasePerformance.java | 1 - .../firebase/perf/session/SessionManager.java | 17 ----- .../session/gauges/CpuGaugeCollector.java | 13 +--- .../perf/session/gauges/GaugeManager.java | 9 +-- .../session/gauges/GaugeMetadataManager.java | 34 +--------- .../session/gauges/MemoryGaugeCollector.java | 6 +- .../perf/session/SessionManagerTest.java | 58 +--------------- .../gauges/GaugeMetadataManagerTest.java | 66 +------------------ 8 files changed, 14 insertions(+), 190 deletions(-) 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..3cc49896ce0 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 @@ -182,7 +182,6 @@ public static FirebasePerformance getInstance() { .initialize(firebaseApp, firebaseInstallationsApi, transportFactoryProvider); Context appContext = firebaseApp.getApplicationContext(); - // TODO(b/110178816): Explore moving off of main thread. mMetadataBundle = extractMetadata(appContext); remoteConfigManager.setFirebaseRemoteConfigProvider(firebaseRemoteConfigProvider); 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..29ffb988ba0 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 @@ -79,9 +79,6 @@ 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(); @@ -89,10 +86,6 @@ public void setApplicationContext(final Context appContext) { executorService.submit( () -> { gaugeManager.initializeGaugeMetadataManager(appContext); - if (appStartSession.isGaugeAndEventCollectionEnabled()) { - gaugeManager.logGaugeMetadata( - appStartSession.sessionId(), ApplicationProcessState.FOREGROUND); - } }); } @@ -164,9 +157,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 +168,6 @@ public void updatePerfSession(PerfSession perfSession) { * this does not reset the perfSession. */ public void initializeGaugeCollection() { - logGaugeMetadataIfCollectionEnabled(ApplicationProcessState.FOREGROUND); startOrStopCollectingGauges(ApplicationProcessState.FOREGROUND); } @@ -206,12 +195,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..30da2f0160f 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 @@ -72,8 +72,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 +81,7 @@ private GaugeManager() { Lazy gaugeManagerExecutor, TransportManager transportManager, ConfigResolver configResolver, - GaugeMetadataManager gaugeMetadataManager, + @Nullable GaugeMetadataManager gaugeMetadataManager, Lazy cpuGaugeCollector, Lazy memoryGaugeCollector) { @@ -140,7 +140,7 @@ public void startCollectingGauges( gaugeManagerDataCollectionJob = gaugeManagerExecutor .get() - .scheduleAtFixedRate( + .scheduleWithFixedDelay( () -> { syncFlush(sessionIdForScheduledTask, applicationProcessStateForScheduledTask); }, @@ -256,6 +256,7 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) { * @return true if GaugeMetadata was logged, false otherwise. */ public boolean logGaugeMetadata(String sessionId, ApplicationProcessState appState) { + // TODO(b/394127311): Re-introduce logging of metadata for AQS. if (gaugeMetadataManager != null) { GaugeMetric gaugeMetric = GaugeMetric.newBuilder() 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/test/java/com/google/firebase/perf/session/SessionManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/session/SessionManagerTest.java index f3e3795f3f8..37b9ff7215b 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 @@ -74,7 +74,7 @@ public void testInstanceCreation() { } @Test - public void setApplicationContext_logGaugeMetadata_afterGaugeMetadataManagerIsInitialized() + public void setApplicationContext_initializeGaugeMetadataManager() throws ExecutionException, InterruptedException { when(mockPerfSession.isGaugeAndEventCollectionEnabled()).thenReturn(true); InOrder inOrder = Mockito.inOrder(mockGaugeManager); @@ -84,7 +84,6 @@ public void setApplicationContext_logGaugeMetadata_afterGaugeMetadataManagerIsIn testSessionManager.getSyncInitFuture().get(); inOrder.verify(mockGaugeManager).initializeGaugeMetadataManager(any()); - inOrder.verify(mockGaugeManager).logGaugeMetadata(any(), any()); } @Test @@ -136,20 +135,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 +163,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 +202,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/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"; } From 134398ce06985f3f3d5c4355cde29dbd2efea3e5 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 7 Feb 2025 15:20:31 -0500 Subject: [PATCH 02/76] Add a SessionSubscriber --- .../firebase/perf/FirebasePerfRegistrar.java | 8 +++++ .../firebase/perf/FirebasePerformance.java | 22 ++++++------- .../firebase/perf/config/ConfigResolver.java | 6 ++-- .../FirebasePerformanceSessionSubscriber.kt | 31 +++++++++++++++++++ .../firebase/perf/session/PerfSession.java | 17 +++++++++- .../firebase/perf/session/SessionManager.java | 9 +----- 6 files changed, 68 insertions(+), 25 deletions(-) create mode 100644 firebase-perf/src/main/java/com/google/firebase/perf/session/FirebasePerformanceSessionSubscriber.kt 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..af5a0af622b 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,9 @@ 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 +50,11 @@ public class FirebasePerfRegistrar implements ComponentRegistrar { private static final String LIBRARY_NAME = "fire-perf"; private static final String EARLY_LIBRARY_NAME = "fire-perf-early"; + static { + // Add Firebase Performance as a dependency of Sessions when this class is loaded into memory. + FirebaseSessionsDependencies.addDependency(SessionSubscriber.Name.PERFORMANCE); + } + @Override @Keep public List> 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 3cc49896ce0..f825ab4610b 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,15 @@ 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; @@ -52,6 +55,7 @@ import javax.inject.Inject; import javax.inject.Singleton; + /** * The Firebase Performance Monitoring API. * @@ -92,7 +96,7 @@ public class FirebasePerformance implements FirebasePerformanceAttributable { // once during initialization and cache it. private final ImmutableBundle mMetadataBundle; - /** Valid HttpMethods for manual network APIs */ + /** Valid HttpMethods for manual network APIs */ @StringDef({ HttpMethod.GET, HttpMethod.PUT, @@ -136,12 +140,7 @@ 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. * *

TODO(b/172007278): Initialize SDK components in a background thread to avoid cases of cyclic @@ -166,11 +165,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; @@ -191,6 +185,8 @@ public static FirebasePerformance getInstance() { sessionManager.setApplicationContext(appContext); mPerformanceCollectionForceEnabledState = configResolver.getIsPerformanceCollectionEnabled(); + FirebaseSessionsDependencies.register(new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled())); + if (logger.isLogcatEnabled() && isPerformanceCollectionEnabled()) { logger.info( String.format( @@ -281,7 +277,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..203b2680873 --- /dev/null +++ b/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebasePerformanceSessionSubscriber.kt @@ -0,0 +1,31 @@ +package com.google.firebase.perf.session + +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.sessions.api.SessionSubscriber +import java.util.UUID + +class FirebasePerformanceSessionSubscriber(private val dataCollectionEnabled: Boolean) : SessionSubscriber { + override val isDataCollectionEnabled: Boolean + get() = dataCollectionEnabled + + override val sessionSubscriberName: SessionSubscriber.Name + get() = SessionSubscriber.Name.PERFORMANCE + + override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { + val currentPerfSession = SessionManager.getInstance().perfSession() + + // A [PerfSession] was created before a session was started. + if (currentPerfSession.aqsSessionId() == null) { + currentPerfSession.setAQSId(sessionDetails) + GaugeManager.getInstance().logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + return + } + + val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()); + updatedSession.setAQSId(sessionDetails) + SessionManager.getInstance().updatePerfSession(updatedSession) + GaugeManager.getInstance().logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + } +} \ No newline at end of file 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..b1bb08fd001 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,6 +23,8 @@ 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.concurrent.TimeUnit; @@ -31,6 +33,7 @@ public class PerfSession implements Parcelable { private final String sessionId; private final Timer creationTime; + @Nullable private String aqsSessionId; private boolean isGaugeAndEventCollectionEnabled = false; @@ -59,11 +62,23 @@ 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; } + /** Returns the AQS sessionId for the given session. */ + public String aqsSessionId() { + return aqsSessionId; + } + + /** Returns the AQS sessionId for the given session. */ + public void setAQSId(SessionSubscriber.SessionDetails aqs) { + if (aqsSessionId == null) { + aqsSessionId = aqs.getSessionId(); + } + } + /** * Returns a timer object that has been seeded with the system time at which the session began. */ 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 29ffb988ba0..d4c729d9610 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 @@ -79,14 +79,7 @@ public SessionManager( * (currently that is before onResume finishes) to ensure gauge collection starts on time. */ public void setApplicationContext(final Context appContext) { - // TODO(b/258263016): Migrate to go/firebase-android-executors - @SuppressLint("ThreadPoolCreation") - ExecutorService executorService = Executors.newSingleThreadExecutor(); - syncInitFuture = - executorService.submit( - () -> { - gaugeManager.initializeGaugeMetadataManager(appContext); - }); + gaugeManager.initializeGaugeMetadataManager(appContext); } @Override From 99a541e2a13f9cb7150a5c860c42c3e50844f2bd Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 7 Feb 2025 15:22:01 -0500 Subject: [PATCH 03/76] style --- .../firebase/perf/FirebasePerfRegistrar.java | 1 - .../firebase/perf/FirebasePerformance.java | 9 ++-- .../FirebasePerformanceSessionSubscriber.kt | 42 ++++++++++--------- .../firebase/perf/session/PerfSession.java | 1 - .../firebase/perf/session/SessionManager.java | 2 - 5 files changed, 26 insertions(+), 29 deletions(-) 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 af5a0af622b..daffc2de81a 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 @@ -32,7 +32,6 @@ 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; 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 f825ab4610b..e4ddfcd600c 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 @@ -44,7 +44,6 @@ 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; @@ -55,7 +54,6 @@ import javax.inject.Inject; import javax.inject.Singleton; - /** * The Firebase Performance Monitoring API. * @@ -96,7 +94,7 @@ public class FirebasePerformance implements FirebasePerformanceAttributable { // once during initialization and cache it. private final ImmutableBundle mMetadataBundle; - /** Valid HttpMethods for manual network APIs */ + /** Valid HttpMethods for manual network APIs */ @StringDef({ HttpMethod.GET, HttpMethod.PUT, @@ -140,7 +138,7 @@ 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; - /** + /** * Constructs the FirebasePerformance class and allows injecting dependencies. * *

TODO(b/172007278): Initialize SDK components in a background thread to avoid cases of cyclic @@ -185,7 +183,8 @@ public static FirebasePerformance getInstance() { sessionManager.setApplicationContext(appContext); mPerformanceCollectionForceEnabledState = configResolver.getIsPerformanceCollectionEnabled(); - FirebaseSessionsDependencies.register(new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled())); + FirebaseSessionsDependencies.register( + new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled())); if (logger.isLogcatEnabled() && isPerformanceCollectionEnabled()) { logger.info( 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 index 203b2680873..79dae6cf8d7 100644 --- 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 @@ -1,31 +1,33 @@ package com.google.firebase.perf.session -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.sessions.api.SessionSubscriber import java.util.UUID -class FirebasePerformanceSessionSubscriber(private val dataCollectionEnabled: Boolean) : SessionSubscriber { - override val isDataCollectionEnabled: Boolean - get() = dataCollectionEnabled +class FirebasePerformanceSessionSubscriber(private val dataCollectionEnabled: Boolean) : + SessionSubscriber { + override val isDataCollectionEnabled: Boolean + get() = dataCollectionEnabled - override val sessionSubscriberName: SessionSubscriber.Name - get() = SessionSubscriber.Name.PERFORMANCE + override val sessionSubscriberName: SessionSubscriber.Name + get() = SessionSubscriber.Name.PERFORMANCE - override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { - val currentPerfSession = SessionManager.getInstance().perfSession() + override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { + val currentPerfSession = SessionManager.getInstance().perfSession() - // A [PerfSession] was created before a session was started. - if (currentPerfSession.aqsSessionId() == null) { - currentPerfSession.setAQSId(sessionDetails) - GaugeManager.getInstance().logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) - return - } - - val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()); - updatedSession.setAQSId(sessionDetails) - SessionManager.getInstance().updatePerfSession(updatedSession) - GaugeManager.getInstance().logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + // A [PerfSession] was created before a session was started. + if (currentPerfSession.aqsSessionId() == null) { + currentPerfSession.setAQSId(sessionDetails) + GaugeManager.getInstance() + .logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + return } -} \ No newline at end of file + + val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()) + updatedSession.setAQSId(sessionDetails) + SessionManager.getInstance().updatePerfSession(updatedSession) + GaugeManager.getInstance() + .logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + } +} 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 b1bb08fd001..d70ee0eb2b6 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 @@ -24,7 +24,6 @@ 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.concurrent.TimeUnit; 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 d4c729d9610..ee71a4bb29d 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 @@ -29,8 +29,6 @@ import java.util.Iterator; 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. */ From 09a9deca6bdc7d787db234c66306612629c87d57 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 7 Feb 2025 17:20:41 -0500 Subject: [PATCH 04/76] Remove app updates from session manager and add unit tests --- firebase-perf/firebase-perf.gradle | 2 +- .../firebase/perf/session/SessionManager.java | 35 +---- .../perf/session/SessionManagerTest.java | 120 +++--------------- .../api/FirebaseSessionsDependencies.kt | 13 -- 4 files changed, 19 insertions(+), 151 deletions(-) diff --git a/firebase-perf/firebase-perf.gradle b/firebase-perf/firebase-perf.gradle index b6028e75b61..3dc2fd5a38d 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/session/SessionManager.java b/firebase-perf/src/main/java/com/google/firebase/perf/session/SessionManager.java index ee71a4bb29d..7e97d9b1d16 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,6 @@ 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.session.gauges.GaugeManager; import com.google.firebase.perf.v1.ApplicationProcessState; import com.google.firebase.perf.v1.GaugeMetadata; @@ -27,13 +26,14 @@ 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.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(); @@ -69,7 +69,6 @@ public SessionManager( this.gaugeManager = gaugeManager; this.perfSession = perfSession; this.appStateMonitor = appStateMonitor; - registerForAppState(); } /** @@ -80,34 +79,6 @@ public void setApplicationContext(final Context appContext) { gaugeManager.initializeGaugeMetadataManager(appContext); } - @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); - } - } - } - /** * Checks if the current {@link PerfSession} is expired/timed out. If so, stop collecting gauges. * @@ -129,7 +100,7 @@ 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; } 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 37b9ff7215b..8c8ee679f99 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 @@ -16,9 +16,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -40,7 +37,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.AdditionalMatchers; import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.Mock; @@ -86,101 +82,12 @@ public void setApplicationContext_initializeGaugeMetadataManager() inOrder.verify(mockGaugeManager).initializeGaugeMetadataManager(any()); } - @Test - public void testOnUpdateAppStateDoesNothingDuringAppStart() { - String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - - AppStateMonitor.getInstance().setIsColdStart(true); - - SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.FOREGROUND); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - } - - @Test - public void testOnUpdateAppStateGeneratesNewSessionIdOnForegroundState() { - String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - - SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.FOREGROUND); - assertThat(oldSessionId).isNotEqualTo(SessionManager.getInstance().perfSession().sessionId()); - } - - @Test - public void testOnUpdateAppStateDoesntGenerateNewSessionIdOnBackgroundState() { - String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - - SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.BACKGROUND); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - } - - @Test - public void testOnUpdateAppStateGeneratesNewSessionIdOnBackgroundStateIfPerfSessionExpires() { - when(mockPerfSession.isSessionRunningTooLong()).thenReturn(true); - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - String oldSessionId = testSessionManager.perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(testSessionManager.perfSession().sessionId()); - - testSessionManager.onUpdateAppState(ApplicationProcessState.BACKGROUND); - assertThat(oldSessionId).isNotEqualTo(testSessionManager.perfSession().sessionId()); - } - - @Test - public void - testOnUpdateAppStateDoesntMakeGaugeManagerLogGaugeMetadataOnForegroundStateIfSessionIsNonVerbose() { - forceNonVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.FOREGROUND); - - verify(mockGaugeManager, never()) - .logGaugeMetadata( - anyString(), nullable(com.google.firebase.perf.v1.ApplicationProcessState.class)); - } - - @Test - public void - testOnUpdateAppStateDoesntMakeGaugeManagerLogGaugeMetadataOnBackgroundStateEvenIfSessionIsVerbose() { - forceVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.BACKGROUND); - - verify(mockGaugeManager, never()) - .logGaugeMetadata( - anyString(), nullable(com.google.firebase.perf.v1.ApplicationProcessState.class)); - } - - @Test - public void testOnUpdateAppStateMakesGaugeManagerStartCollectingGaugesIfSessionIsVerbose() { - forceVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.FOREGROUND); - - verify(mockGaugeManager) - .startCollectingGauges(AdditionalMatchers.not(eq(mockPerfSession)), any()); - } - // LogGaugeData on new perf session when Verbose // NotLogGaugeData on new perf session when not Verbose // Mark Session as expired after time limit. @Test - public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesIfSessionIsNonVerbose() { + public void testUpdatePerfSessionMakesGaugeManagerStopCollectingGaugesIfSessionIsNonVerbose() { forceNonVerboseSession(); SessionManager testSessionManager = @@ -191,7 +98,7 @@ public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesIfSessionIs } @Test - public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesWhenSessionsDisabled() { + public void testUpdatePerfSessionMakesGaugeManagerStopCollectingGaugesWhenSessionsDisabled() { forceSessionsFeatureDisabled(); SessionManager testSessionManager = @@ -221,22 +128,25 @@ public void testSessionIdDoesNotUpdateIfPerfSessionRunsTooLong() { } @Test - public void testPerfSessionExpiredMakesGaugeManagerStopsCollectingGaugesIfSessionIsVerbose() { - forceVerboseSession(); + public void testUpdatePerfSessionStartsCollectingGaugesIfSessionIsVerbose() { Timer mockTimer = mock(Timer.class); when(mockClock.getTime()).thenReturn(mockTimer); + when(mockAppStateMonitor.getAppState()).thenReturn(ApplicationProcessState.FOREGROUND); - PerfSession session = new PerfSession("sessionId", mockClock); - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, session, mockAppStateMonitor); + PerfSession previousSession = new PerfSession("previousSession", mockClock); + previousSession.setGaugeAndEventCollectionEnabled(false); - assertThat(session.isSessionRunningTooLong()).isFalse(); + PerfSession newSession = new PerfSession("newSession", mockClock); + newSession.setGaugeAndEventCollectionEnabled(true); - when(mockTimer.getDurationMicros()) - .thenReturn(TimeUnit.HOURS.toMicros(5)); // Default Max Session Length is 4 hours + SessionManager testSessionManager = + new SessionManager(mockGaugeManager, previousSession, mockAppStateMonitor); + testSessionManager.updatePerfSession(newSession); + testSessionManager.setApplicationContext(mockApplicationContext); - assertThat(session.isSessionRunningTooLong()).isTrue(); - verify(mockGaugeManager, times(0)).logGaugeMetadata(any(), any()); + verify(mockGaugeManager, times(1)).initializeGaugeMetadataManager(mockApplicationContext); + verify(mockGaugeManager, times(1)) + .startCollectingGauges(newSession, ApplicationProcessState.FOREGROUND); } @Test 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 From f37408d572c0117847e5d454379d527d32ea7900 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Mon, 10 Feb 2025 16:56:10 -0500 Subject: [PATCH 05/76] Fix unit test --- .../com/google/firebase/perf/session/SessionManager.java | 7 ------- .../google/firebase/perf/session/SessionManagerTest.java | 1 - 2 files changed, 8 deletions(-) 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 7e97d9b1d16..cf99c1e52ea 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 @@ -29,7 +29,6 @@ import java.util.Objects; import java.util.Set; import java.util.UUID; -import java.util.concurrent.Future; /** Session manager to generate sessionIDs and broadcast to the application. */ @Keep // Needed because of b/117526359. @@ -43,7 +42,6 @@ public class SessionManager { private final Set> clients = new HashSet<>(); private PerfSession perfSession; - private Future syncInitFuture; /** Returns the singleton instance of SessionManager. */ public static SessionManager getInstance() { @@ -169,9 +167,4 @@ private void startOrStopCollectingGauges(ApplicationProcessState appState) { public void setPerfSession(PerfSession perfSession) { this.perfSession = perfSession; } - - @VisibleForTesting - public Future getSyncInitFuture() { - return this.syncInitFuture; - } } 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 8c8ee679f99..954b0ae88d3 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 @@ -78,7 +78,6 @@ public void setApplicationContext_initializeGaugeMetadataManager() new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); testSessionManager.setApplicationContext(mockApplicationContext); - testSessionManager.getSyncInitFuture().get(); inOrder.verify(mockGaugeManager).initializeGaugeMetadataManager(any()); } From 765425128779b8aa4605d2616da5b92e7d609ab9 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Mon, 10 Feb 2025 16:59:01 -0500 Subject: [PATCH 06/76] Add license header --- .../FirebasePerformanceSessionSubscriber.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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 index 79dae6cf8d7..7506172967d 100644 --- 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 @@ -1,3 +1,19 @@ +/* + * Copyright 2024 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.session.gauges.GaugeManager From a0e9493623cb5f8e4d1da321e89d763005230f0c Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Mon, 10 Feb 2025 17:09:01 -0500 Subject: [PATCH 07/76] Revert changes in ConfigResolver --- .../com/google/firebase/perf/config/ConfigResolver.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 7e321515141..1ee9d395e03 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) + return (isPerformanceCollectionEnabled == null || isPerformanceCollectionEnabled == true) && 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 (Boolean.TRUE.equals(getIsPerformanceCollectionDeactivated())) { + if (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 (Boolean.TRUE.equals(getIsPerformanceCollectionDeactivated())) { + if (getIsPerformanceCollectionDeactivated()) { return; } From b3ba68d993e1003f60a428415c14196e0db322b2 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Mon, 10 Feb 2025 17:14:56 -0500 Subject: [PATCH 08/76] Use AQS Session ID for building a perf session --- .../main/java/com/google/firebase/perf/session/PerfSession.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d70ee0eb2b6..3748f3e3983 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 @@ -128,7 +128,7 @@ public boolean isSessionRunningTooLong() { /** Creates and returns the proto object for PerfSession object. */ public com.google.firebase.perf.v1.PerfSession build() { 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) { From 5e5caf58b03a33ad6b29b4a5ef3fa518e4766600 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Mon, 10 Feb 2025 17:20:06 -0500 Subject: [PATCH 09/76] Update TODOs --- .../google/firebase/perf/session/gauges/GaugeManager.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 30da2f0160f..92b1cd94959 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 @@ -242,6 +242,7 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) { } // Adding Session ID info. + // TODO(b/394127311): Switch to using AQS. gaugeMetricBuilder.setSessionId(sessionId); transportManager.log(gaugeMetricBuilder.build(), appState); @@ -250,17 +251,16 @@ 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 PerfSession#aqsSessionId()} ()} 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) { - // TODO(b/394127311): Re-introduce logging of metadata for AQS. + public boolean logGaugeMetadata(String aqsSessionId, ApplicationProcessState appState) { if (gaugeMetadataManager != null) { GaugeMetric gaugeMetric = GaugeMetric.newBuilder() - .setSessionId(sessionId) + .setSessionId(aqsSessionId) .setGaugeMetadata(getGaugeMetadata()) .build(); transportManager.log(gaugeMetric, appState); From eb9856ec91a1d6e1fb6b8b52433f3f4742cd2a30 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Mon, 10 Feb 2025 17:23:55 -0500 Subject: [PATCH 10/76] More TODO --- .../com/google/firebase/perf/session/gauges/GaugeManager.java | 1 + 1 file changed, 1 insertion(+) 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 92b1cd94959..fae270d1e1a 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 @@ -136,6 +136,7 @@ public void startCollectingGauges( final String sessionIdForScheduledTask = sessionId; final ApplicationProcessState applicationProcessStateForScheduledTask = applicationProcessState; + // TODO(b/394127311): Switch to using AQS. try { gaugeManagerDataCollectionJob = gaugeManagerExecutor From 8efea1b5828a30a7250c5d7f359a4cc49497256e Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Mon, 10 Feb 2025 17:25:47 -0500 Subject: [PATCH 11/76] More TODO --- .../com/google/firebase/perf/session/gauges/GaugeManager.java | 1 + 1 file changed, 1 insertion(+) 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 fae270d1e1a..1c06ceac9dd 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 @@ -205,6 +205,7 @@ public void stopCollectingGauges() { gaugeManagerDataCollectionJob.cancel(false); } + // TODO(b/394127311): Switch to using AQS. // Flush any data that was collected for this session one last time. @SuppressWarnings("FutureReturnValueIgnored") ScheduledFuture unusedFuture = From 8e510b16b1572ee93a875d43e6b9b772bc37cbb0 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Mon, 10 Feb 2025 17:29:23 -0500 Subject: [PATCH 12/76] More TODOs --- .../com/google/firebase/perf/transport/TransportManager.java | 1 + 1 file changed, 1 insertion(+) 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)); } From 3145a71a456cd8acd7eda02eccc9ce38e2bd15a7 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 09:11:32 -0500 Subject: [PATCH 13/76] Revert adding AQS in traces --- .../java/com/google/firebase/perf/session/PerfSession.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 3748f3e3983..b7972f0d70f 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 @@ -127,8 +127,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(aqsSessionId); + com.google.firebase.perf.v1.PerfSession.newBuilder().setSessionId(sessionId); // If gauge collection is enabled, enable gauge collection verbosity. if (isGaugeAndEventCollectionEnabled) { From 03e2b103496998a8d9758fd486e5bf8a3a3bd92b Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 09:32:35 -0500 Subject: [PATCH 14/76] Allow using session subscriber for unit tests --- .../google/firebase/perf/FirebasePerformance.java | 15 +++++++++++++-- .../google/firebase/perf/session/PerfSession.java | 2 +- .../perf/FirebasePerformanceTestBase.java | 14 +++++++++++--- 3 files changed, 25 insertions(+), 6 deletions(-) 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 e4ddfcd600c..7152124050f 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 @@ -44,6 +44,8 @@ import com.google.firebase.perf.util.Timer; import com.google.firebase.remoteconfig.RemoteConfigComponent; import com.google.firebase.sessions.api.FirebaseSessionsDependencies; +import com.google.firebase.sessions.api.SessionSubscriber; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.URL; @@ -94,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, @@ -167,6 +171,8 @@ public static FirebasePerformance getInstance() { this.mPerformanceCollectionForceEnabledState = false; this.configResolver = configResolver; this.mMetadataBundle = new ImmutableBundle(new Bundle()); + this.sessionSubscriber = new FirebasePerformanceSessionSubscriber(false); + FirebaseSessionsDependencies.register(sessionSubscriber); return; } @@ -183,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( @@ -460,4 +466,9 @@ private static ImmutableBundle extractMetadata(Context appContext) { Boolean getPerformanceCollectionForceEnabledState() { return mPerformanceCollectionForceEnabledState; } + + @VisibleForTesting + SessionSubscriber getSessionSubscriber() { + return sessionSubscriber; + } } 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 b7972f0d70f..763b161c6d8 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 @@ -129,7 +129,7 @@ public boolean isSessionRunningTooLong() { 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/test/java/com/google/firebase/perf/FirebasePerformanceTestBase.java b/firebase-perf/src/test/java/com/google/firebase/perf/FirebasePerformanceTestBase.java index b31696d963b..9835b7bfa98 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 @@ -26,12 +26,15 @@ import com.google.firebase.perf.session.PerfSession; import com.google.firebase.perf.session.SessionManager; import com.google.firebase.perf.util.ImmutableBundle; +import com.google.firebase.sessions.FirebaseSessions; +import com.google.firebase.sessions.api.FirebaseSessionsDependencies; +import com.google.firebase.sessions.api.SessionSubscriber; + import org.junit.After; import org.junit.Before; import org.robolectric.shadows.ShadowPackageManager; public class FirebasePerformanceTestBase { - /** * The following values are needed by Firebase to identify the project and application that all * data stored in Firebase databases gets associated with. This is important to determine data @@ -72,6 +75,7 @@ public void setUpFirebaseApp() { .setProjectId(FAKE_FIREBASE_PROJECT_ID) .build(); FirebaseApp.initializeApp(appContext, options); + forceAppQualitySession(); } @After @@ -93,11 +97,15 @@ protected static void forceNonVerboseSession() { forceVerboseSessionWithSamplingPercentage(0); } + protected static void forceAppQualitySession() { + SessionSubscriber sessionSubscriber = FirebasePerformance.getInstance().getSessionSubscriber(); + sessionSubscriber.onSessionChanged(new SessionSubscriber.SessionDetails("testAqsSession")); + } + 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")); + forceAppQualitySession(); } } From 3c3fab61e4d70fba56233fb6606200bcbe4e7325 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 09:33:01 -0500 Subject: [PATCH 15/76] Remove TODO --- .../main/java/com/google/firebase/perf/session/PerfSession.java | 1 - 1 file changed, 1 deletion(-) 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 763b161c6d8..3748f3e3983 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 @@ -127,7 +127,6 @@ 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(aqsSessionId); From d598282444909924ee843f5fa1dc589553993ca9 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 09:34:16 -0500 Subject: [PATCH 16/76] Style --- .../java/com/google/firebase/perf/FirebasePerformance.java | 1 - .../google/firebase/perf/FirebasePerformanceTestBase.java | 5 ----- 2 files changed, 6 deletions(-) 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 7152124050f..ac38c7aa214 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 @@ -45,7 +45,6 @@ import com.google.firebase.remoteconfig.RemoteConfigComponent; import com.google.firebase.sessions.api.FirebaseSessionsDependencies; import com.google.firebase.sessions.api.SessionSubscriber; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.URL; 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 9835b7bfa98..903bef01ee2 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,13 +23,8 @@ 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.util.ImmutableBundle; -import com.google.firebase.sessions.FirebaseSessions; -import com.google.firebase.sessions.api.FirebaseSessionsDependencies; import com.google.firebase.sessions.api.SessionSubscriber; - import org.junit.After; import org.junit.Before; import org.robolectric.shadows.ShadowPackageManager; From 800b33b7c499645deb0e5116cd89ddc68ca3a587 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 09:57:52 -0500 Subject: [PATCH 17/76] Try and update test --- .../google/firebase/perf/transport/TransportManagerTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java index 5376265aa0e..1a85e219a23 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java @@ -105,7 +105,8 @@ public void setUp() { @After public void tearDown() { reset(mockFirebaseInstallationsApi); - FirebaseApp.clearInstancesForTest(); + reset(mockConfigResolver); + reset(mockRateLimiter); } // region Transport Initialization From afb080408d5149bce64c3bc6ecb13e6c9ecc06ef Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 10:03:24 -0500 Subject: [PATCH 18/76] Address code review comments --- .../perf/session/FirebasePerformanceSessionSubscriber.kt | 9 +++------ .../com/google/firebase/perf/session/PerfSession.java | 3 ++- 2 files changed, 5 insertions(+), 7 deletions(-) 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 index 7506172967d..b6a3d30c139 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * 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. @@ -21,13 +21,10 @@ import com.google.firebase.perf.v1.ApplicationProcessState import com.google.firebase.sessions.api.SessionSubscriber import java.util.UUID -class FirebasePerformanceSessionSubscriber(private val dataCollectionEnabled: Boolean) : +class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: Boolean) : SessionSubscriber { - override val isDataCollectionEnabled: Boolean - get() = dataCollectionEnabled - override val sessionSubscriberName: SessionSubscriber.Name - get() = SessionSubscriber.Name.PERFORMANCE + override val sessionSubscriberName: SessionSubscriber.Name = SessionSubscriber.Name.PERFORMANCE override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { val currentPerfSession = SessionManager.getInstance().perfSession() 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 b7972f0d70f..075848ab747 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 @@ -67,11 +67,12 @@ public String sessionId() { } /** Returns the AQS sessionId for the given session. */ + @Nullable public String aqsSessionId() { return aqsSessionId; } - /** Returns the AQS sessionId for the given session. */ + /** Sets the AQS sessionId for the given session. */ public void setAQSId(SessionSubscriber.SessionDetails aqs) { if (aqsSessionId == null) { aqsSessionId = aqs.getSessionId(); From 166ed7eb70eb3d6272ed91ee19edd08123c378af Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 11:42:05 -0500 Subject: [PATCH 19/76] Update tests --- .../firebase/perf/ktx/PerformanceTests.kt | 1 + .../firebase/perf/FirebasePerformance.java | 3 +- .../perf/FirebasePerformanceTestBase.java | 8 +++- .../google/firebase/perf/PerformanceTests.kt | 4 ++ .../firebase/perf/ktx/PerformanceTests.kt | 1 + .../firebase/perf/metrics/TraceTest.java | 6 +-- .../perf/session/PerfSessionTest.java | 37 +++++++++++-------- .../perf/transport/TransportManagerTest.java | 18 +++++---- 8 files changed, 50 insertions(+), 28 deletions(-) diff --git a/firebase-perf/ktx/src/test/kotlin/com/google/firebase/perf/ktx/PerformanceTests.kt b/firebase-perf/ktx/src/test/kotlin/com/google/firebase/perf/ktx/PerformanceTests.kt index 2a477986908..be54bcb6fc8 100644 --- a/firebase-perf/ktx/src/test/kotlin/com/google/firebase/perf/ktx/PerformanceTests.kt +++ b/firebase-perf/ktx/src/test/kotlin/com/google/firebase/perf/ktx/PerformanceTests.kt @@ -38,6 +38,7 @@ import com.google.firebase.perf.v1.ApplicationProcessState import com.google.firebase.perf.v1.NetworkRequestMetric import com.google.firebase.perf.v1.TraceMetric import com.google.firebase.platforminfo.UserAgentPublisher +import com.google.firebase.sessions.api.SessionSubscriber import org.junit.After import org.junit.Before import org.junit.Test 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 ac38c7aa214..39b6241d803 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 @@ -466,8 +466,9 @@ Boolean getPerformanceCollectionForceEnabledState() { return mPerformanceCollectionForceEnabledState; } + @NonNull @VisibleForTesting - SessionSubscriber getSessionSubscriber() { + protected SessionSubscriber getSessionSubscriber() { return sessionSubscriber; } } 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 903bef01ee2..e8b5386ae98 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,8 +23,11 @@ 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.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; @@ -93,8 +96,11 @@ protected static void forceNonVerboseSession() { } protected static void forceAppQualitySession() { + PerfSession existingPerfSession = PerfSession.createWithId(UUID.randomUUID().toString()); + existingPerfSession.setAQSId(new SessionSubscriber.SessionDetails("notnull")); + SessionManager.getInstance().setPerfSession(existingPerfSession); SessionSubscriber sessionSubscriber = FirebasePerformance.getInstance().getSessionSubscriber(); - sessionSubscriber.onSessionChanged(new SessionSubscriber.SessionDetails("testAqsSession")); + sessionSubscriber.onSessionChanged(new SessionSubscriber.SessionDetails("fakeAQS")); } private static void forceVerboseSessionWithSamplingPercentage(long samplingPercentage) { diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/PerformanceTests.kt b/firebase-perf/src/test/java/com/google/firebase/perf/PerformanceTests.kt index e8844e0cc43..a90ddbc108c 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/PerformanceTests.kt +++ b/firebase-perf/src/test/java/com/google/firebase/perf/PerformanceTests.kt @@ -37,6 +37,7 @@ import com.google.firebase.perf.v1.ApplicationProcessState import com.google.firebase.perf.v1.NetworkRequestMetric import com.google.firebase.perf.v1.TraceMetric import com.google.firebase.platforminfo.UserAgentPublisher +import com.google.firebase.sessions.api.SessionSubscriber import org.junit.After import org.junit.Before import org.junit.Test @@ -84,6 +85,9 @@ abstract class BaseTestCase { .build(), EXISTING_APP ) + Firebase.performance.sessionSubscriber.onSessionChanged( + SessionSubscriber.SessionDetails("fakeAQSSession") + ) } @After diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/ktx/PerformanceTests.kt b/firebase-perf/src/test/java/com/google/firebase/perf/ktx/PerformanceTests.kt index f5d02ea6b35..8eb63acc2e4 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/ktx/PerformanceTests.kt +++ b/firebase-perf/src/test/java/com/google/firebase/perf/ktx/PerformanceTests.kt @@ -31,6 +31,7 @@ import com.google.firebase.perf.metrics.HttpMetric import com.google.firebase.perf.metrics.Trace import com.google.firebase.perf.metrics.getTraceCounter import com.google.firebase.perf.metrics.getTraceCounterCount +import com.google.firebase.perf.performance import com.google.firebase.perf.transport.TransportManager import com.google.firebase.perf.util.Clock import com.google.firebase.perf.util.Timer 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..27fb9fde775 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 @@ -32,7 +32,6 @@ import com.google.firebase.perf.application.AppStateMonitor; import com.google.firebase.perf.config.ConfigResolver; import com.google.firebase.perf.config.DeviceCacheManager; -import com.google.firebase.perf.session.PerfSession; import com.google.firebase.perf.session.SessionManager; import com.google.firebase.perf.session.gauges.GaugeManager; import com.google.firebase.perf.transport.TransportManager; @@ -1016,8 +1015,7 @@ public void testSessionIdAdditionInTrace() { int numberOfSessionIds = trace.getSessions().size(); - PerfSession perfSession = PerfSession.createWithId("test_session_id"); - SessionManager.getInstance().updatePerfSession(perfSession); + forceAppQualitySession(); assertThat(trace.getSessions()).hasSize(numberOfSessionIds + 1); trace.stop(); @@ -1071,7 +1069,7 @@ public void testUpdateSessionWithValidSessionIsAdded() { trace.start(); assertThat(trace.getSessions()).hasSize(1); - trace.updateSession(PerfSession.createWithId("test_session_id")); + forceAppQualitySession(); 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..2c46b45bbd8 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 @@ -30,6 +30,7 @@ import com.google.firebase.perf.util.ImmutableBundle; import com.google.firebase.perf.util.Timer; import com.google.firebase.perf.v1.SessionVerbosity; +import com.google.firebase.sessions.api.SessionSubscriber; import com.google.testing.timing.FakeDirectExecutorService; import java.util.ArrayList; import java.util.List; @@ -140,10 +141,11 @@ public void shouldCollectGaugesAndEvents_perfMonDeactivated_sessionNotVerbose() @Test public void testPerfSessionConversion() { PerfSession session1 = new PerfSession("sessionId", mockClock); + session1.setAQSId(new SessionSubscriber.SessionDetails("aqsSessionId")); session1.setGaugeAndEventCollectionEnabled(true); com.google.firebase.perf.v1.PerfSession perfSession = session1.build(); - Assert.assertEquals(session1.sessionId(), perfSession.getSessionId()); + Assert.assertEquals(session1.aqsSessionId(), perfSession.getSessionId()); Assert.assertEquals( SessionVerbosity.GAUGES_AND_SYSTEM_EVENTS, perfSession.getSessionVerbosity(0)); } @@ -151,9 +153,10 @@ public void testPerfSessionConversion() { @Test public void testPerfSessionConversionWithoutVerbosity() { PerfSession session1 = new PerfSession("sessionId", mockClock); + session1.setAQSId(new SessionSubscriber.SessionDetails("aqsSessionId")); com.google.firebase.perf.v1.PerfSession perfSession = session1.build(); - Assert.assertEquals(session1.sessionId(), perfSession.getSessionId()); + assertThat(perfSession.getSessionId()).isEqualTo(session1.aqsSessionId()); assertThat(perfSession.getSessionVerbosityList()).isEmpty(); } @@ -180,21 +183,15 @@ public void testPerfSessionsCreateEnablesGaugeCollectionWhenVerboseSessionForceE @Test public void testBuildAndSortMovesTheVerboseSessionToTop() { - // Force all the sessions from now onwards to be non-verbose - forceNonVerboseSession(); - - // Next, create 3 non-verbose sessions + // Create 3 non-verbose sessions List sessions = new ArrayList<>(); - sessions.add(PerfSession.createWithId("sessionId1")); - sessions.add(PerfSession.createWithId("sessionId2")); - sessions.add(PerfSession.createWithId("sessionId3")); - - // Force all the sessions from now onwards to be verbose - forceVerboseSession(); + sessions.add(createSession(false)); + sessions.add(createSession(false)); + sessions.add(createSession(false)); - // Next, create 2 verbose sessions - sessions.add(PerfSession.createWithId("sessionId4")); - sessions.add(PerfSession.createWithId("sessionId5")); + // Create 2 verbose sessions + sessions.add(createSession(true)); + sessions.add(createSession(true)); // Verify that the first session in the list of sessions was not verbose assertThat(sessions.get(0).isVerbose()).isFalse(); @@ -241,4 +238,14 @@ public void testIsExpiredReturnsTrueWhenCurrentSessionLengthIsGreaterThanMaxSess PerfSession session = new PerfSession("sessionId", mockClock); assertThat(session.isSessionRunningTooLong()).isTrue(); } + + private PerfSession createSession(boolean verbose) { + if (verbose) { + forceVerboseSession(); + return SessionManager.getInstance().perfSession(); + } + + forceNonVerboseSession(); + return SessionManager.getInstance().perfSession(); + } } diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java index 1a85e219a23..a965f732f1b 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java @@ -40,7 +40,6 @@ import com.google.firebase.perf.config.ConfigResolver; import com.google.firebase.perf.session.SessionManager; import com.google.firebase.perf.shadows.ShadowPreconditions; -import com.google.firebase.perf.util.Clock; import com.google.firebase.perf.util.Constants; import com.google.firebase.perf.util.Constants.CounterNames; import com.google.firebase.perf.v1.AndroidMemoryReading; @@ -53,6 +52,7 @@ import com.google.firebase.perf.v1.PerfMetric; import com.google.firebase.perf.v1.PerfSession; import com.google.firebase.perf.v1.TraceMetric; +import com.google.firebase.sessions.api.SessionSubscriber; import com.google.testing.timing.FakeScheduledExecutorService; import java.util.ArrayList; import java.util.List; @@ -1169,8 +1169,10 @@ public void logGaugeMetric_globalCustomAttributesAreNotAdded() { public void logTraceMetric_sessionEnabled_doesNotStripOffSessionId() { TraceMetric.Builder validTrace = createValidTraceMetric().toBuilder(); List perfSessions = new ArrayList<>(); - perfSessions.add( - new com.google.firebase.perf.session.PerfSession("fakeSessionId", new Clock()).build()); + com.google.firebase.perf.session.PerfSession testSession = + com.google.firebase.perf.session.PerfSession.createWithId("fakeSessionId"); + testSession.setAQSId(new SessionSubscriber.SessionDetails("fakeAqsSessionId")); + perfSessions.add(testSession.build()); validTrace.addAllPerfSessions(perfSessions); testTransportManager.log(validTrace.build()); @@ -1179,7 +1181,7 @@ public void logTraceMetric_sessionEnabled_doesNotStripOffSessionId() { PerfMetric loggedPerfMetric = getLastLoggedEvent(times(1)); assertThat(loggedPerfMetric.getTraceMetric().getPerfSessionsCount()).isEqualTo(1); assertThat(loggedPerfMetric.getTraceMetric().getPerfSessions(0).getSessionId()) - .isEqualTo("fakeSessionId"); + .isEqualTo("fakeAqsSessionId"); } @Test @@ -1187,8 +1189,10 @@ public void logNetworkMetric_sessionEnabled_doesNotStripOffSessionId() { NetworkRequestMetric.Builder validNetworkRequest = createValidNetworkRequestMetric().toBuilder(); List perfSessions = new ArrayList<>(); - perfSessions.add( - new com.google.firebase.perf.session.PerfSession("fakeSessionId", new Clock()).build()); + com.google.firebase.perf.session.PerfSession testSession = + com.google.firebase.perf.session.PerfSession.createWithId("fakeSessionId"); + testSession.setAQSId(new SessionSubscriber.SessionDetails("fakeAqsSessionId")); + perfSessions.add(testSession.build()); validNetworkRequest.clearPerfSessions().addAllPerfSessions(perfSessions); testTransportManager.log(validNetworkRequest.build()); @@ -1197,7 +1201,7 @@ public void logNetworkMetric_sessionEnabled_doesNotStripOffSessionId() { PerfMetric loggedPerfMetric = getLastLoggedEvent(times(1)); assertThat(loggedPerfMetric.getNetworkRequestMetric().getPerfSessionsCount()).isEqualTo(1); assertThat(loggedPerfMetric.getNetworkRequestMetric().getPerfSessions(0).getSessionId()) - .isEqualTo("fakeSessionId"); + .isEqualTo("fakeAqsSessionId"); } @Test From 030cd9ad05efa1f04bc95542787d2a689a38b6b9 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 12:02:42 -0500 Subject: [PATCH 20/76] style --- .../test/kotlin/com/google/firebase/perf/ktx/PerformanceTests.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/firebase-perf/ktx/src/test/kotlin/com/google/firebase/perf/ktx/PerformanceTests.kt b/firebase-perf/ktx/src/test/kotlin/com/google/firebase/perf/ktx/PerformanceTests.kt index be54bcb6fc8..2a477986908 100644 --- a/firebase-perf/ktx/src/test/kotlin/com/google/firebase/perf/ktx/PerformanceTests.kt +++ b/firebase-perf/ktx/src/test/kotlin/com/google/firebase/perf/ktx/PerformanceTests.kt @@ -38,7 +38,6 @@ import com.google.firebase.perf.v1.ApplicationProcessState import com.google.firebase.perf.v1.NetworkRequestMetric import com.google.firebase.perf.v1.TraceMetric import com.google.firebase.platforminfo.UserAgentPublisher -import com.google.firebase.sessions.api.SessionSubscriber import org.junit.After import org.junit.Before import org.junit.Test From a9a36d3fee39fc332e8a00e6e17c0dbf078371cd Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 12:17:55 -0500 Subject: [PATCH 21/76] Switch to usage of AQS instead of sessions in most places --- .../com/google/firebase/perf/FirebasePerfEarly.java | 9 --------- .../session/FirebasePerformanceSessionSubscriber.kt | 2 ++ .../firebase/perf/session/gauges/GaugeManager.java | 11 ++++++----- 3 files changed, 8 insertions(+), 14 deletions(-) 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..2d4d99da7bf 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 @@ -21,7 +21,6 @@ import com.google.firebase.perf.application.AppStateMonitor; import com.google.firebase.perf.config.ConfigResolver; import com.google.firebase.perf.metrics.AppStartTrace; -import com.google.firebase.perf.session.SessionManager; import java.util.concurrent.Executor; /** @@ -50,13 +49,5 @@ public FirebasePerfEarly( appStartTrace.registerActivityLifecycleCallbacks(context); 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. - SessionManager.getInstance().initializeGaugeCollection(); } } 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 index b6a3d30c139..aa698bec44c 100644 --- 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 @@ -34,6 +34,8 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: currentPerfSession.setAQSId(sessionDetails) GaugeManager.getInstance() .logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + // TODO(b/394127311): Explore deciding whether to collect gauges or not via. AQS. + SessionManager.getInstance().initializeGaugeCollection() return } 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 1c06ceac9dd..088b7c65c01 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 @@ -59,8 +59,9 @@ 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; + @Nullable private String aqsSessionId = null; private ApplicationProcessState applicationProcessState = ApplicationProcessState.APPLICATION_PROCESS_STATE_UNKNOWN; @@ -130,10 +131,11 @@ public void startCollectingGauges( } this.sessionId = session.sessionId(); + this.aqsSessionId = session.aqsSessionId(); this.applicationProcessState = applicationProcessState; // This is needed, otherwise the Runnable might use a stale value. - final String sessionIdForScheduledTask = sessionId; + final String sessionIdForScheduledTask = aqsSessionId; final ApplicationProcessState applicationProcessStateForScheduledTask = applicationProcessState; // TODO(b/394127311): Switch to using AQS. @@ -195,7 +197,7 @@ public void stopCollectingGauges() { } // This is needed, otherwise the Runnable might use a stale value. - final String sessionIdForScheduledTask = sessionId; + final String sessionIdForScheduledTask = aqsSessionId; final ApplicationProcessState applicationProcessStateForScheduledTask = applicationProcessState; cpuGaugeCollector.get().stopCollecting(); @@ -205,10 +207,9 @@ public void stopCollectingGauges() { gaugeManagerDataCollectionJob.cancel(false); } - // TODO(b/394127311): Switch to using AQS. // Flush any data that was collected for this session one last time. @SuppressWarnings("FutureReturnValueIgnored") - ScheduledFuture unusedFuture = + ScheduledFuture unusedFuture = gaugeManagerExecutor .get() .schedule( From b9816ffdbcbd8e3dbb5a010f5052e3840f65fe57 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 13:06:38 -0500 Subject: [PATCH 22/76] 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. --- firebase-perf/firebase-perf.gradle | 2 +- .../firebase/perf/FirebasePerfRegistrar.java | 7 + .../firebase/perf/FirebasePerformance.java | 17 +-- .../FirebasePerformanceSessionSubscriber.kt | 46 +++++++ .../firebase/perf/session/PerfSession.java | 18 ++- .../firebase/perf/session/SessionManager.java | 53 +------- .../perf/session/gauges/GaugeManager.java | 10 +- .../perf/transport/TransportManager.java | 1 + .../perf/session/SessionManagerTest.java | 121 +++--------------- .../api/FirebaseSessionsDependencies.kt | 13 -- 10 files changed, 103 insertions(+), 185 deletions(-) create mode 100644 firebase-perf/src/main/java/com/google/firebase/perf/session/FirebasePerformanceSessionSubscriber.kt diff --git a/firebase-perf/firebase-perf.gradle b/firebase-perf/firebase-perf.gradle index b6028e75b61..3dc2fd5a38d 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/FirebasePerfRegistrar.java b/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfRegistrar.java index c01f035af1f..daffc2de81a 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,11 @@ public class FirebasePerfRegistrar implements ComponentRegistrar { private static final String LIBRARY_NAME = "fire-perf"; private static final String EARLY_LIBRARY_NAME = "fire-perf-early"; + static { + // Add Firebase Performance as a dependency of Sessions when this class is loaded into memory. + FirebaseSessionsDependencies.addDependency(SessionSubscriber.Name.PERFORMANCE); + } + @Override @Keep public List> 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 3cc49896ce0..e4ddfcd600c 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; @@ -191,6 +183,9 @@ public static FirebasePerformance getInstance() { sessionManager.setApplicationContext(appContext); mPerformanceCollectionForceEnabledState = configResolver.getIsPerformanceCollectionEnabled(); + FirebaseSessionsDependencies.register( + new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled())); + if (logger.isLogcatEnabled() && isPerformanceCollectionEnabled()) { logger.info( String.format( @@ -281,7 +276,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/session/FirebasePerformanceSessionSubscriber.kt b/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebasePerformanceSessionSubscriber.kt new file mode 100644 index 00000000000..b6a3d30c139 --- /dev/null +++ b/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebasePerformanceSessionSubscriber.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.perf.session + +import com.google.firebase.perf.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 { + + override val sessionSubscriberName: SessionSubscriber.Name = SessionSubscriber.Name.PERFORMANCE + + override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { + val currentPerfSession = SessionManager.getInstance().perfSession() + + // A [PerfSession] was created before a session was started. + if (currentPerfSession.aqsSessionId() == null) { + currentPerfSession.setAQSId(sessionDetails) + GaugeManager.getInstance() + .logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + return + } + + val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()) + updatedSession.setAQSId(sessionDetails) + SessionManager.getInstance().updatePerfSession(updatedSession) + GaugeManager.getInstance() + .logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + } +} 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..075848ab747 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,6 +23,7 @@ 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.concurrent.TimeUnit; @@ -31,6 +32,7 @@ public class PerfSession implements Parcelable { private final String sessionId; private final Timer creationTime; + @Nullable private String aqsSessionId; private boolean isGaugeAndEventCollectionEnabled = false; @@ -59,11 +61,24 @@ 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; } + /** 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(); + } + } + /** * Returns a timer object that has been seeded with the system time at which the session began. */ @@ -113,6 +128,7 @@ 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); 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 29ffb988ba0..cf99c1e52ea 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,6 @@ 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.session.gauges.GaugeManager; import com.google.firebase.perf.v1.ApplicationProcessState; import com.google.firebase.perf.v1.GaugeMetadata; @@ -27,15 +26,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(); @@ -45,7 +42,6 @@ public class SessionManager extends AppStateUpdateHandler { private final Set> clients = new HashSet<>(); private PerfSession perfSession; - private Future syncInitFuture; /** Returns the singleton instance of SessionManager. */ public static SessionManager getInstance() { @@ -71,7 +67,6 @@ public SessionManager( this.gaugeManager = gaugeManager; this.perfSession = perfSession; this.appStateMonitor = appStateMonitor; - registerForAppState(); } /** @@ -79,42 +74,7 @@ public SessionManager( * (currently that is before onResume finishes) to ensure gauge collection starts on time. */ public void setApplicationContext(final Context appContext) { - // TODO(b/258263016): Migrate to go/firebase-android-executors - @SuppressLint("ThreadPoolCreation") - ExecutorService executorService = Executors.newSingleThreadExecutor(); - syncInitFuture = - executorService.submit( - () -> { - gaugeManager.initializeGaugeMetadataManager(appContext); - }); - } - - @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); } /** @@ -138,7 +98,7 @@ 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; } @@ -207,9 +167,4 @@ private void startOrStopCollectingGauges(ApplicationProcessState appState) { public void setPerfSession(PerfSession perfSession) { this.perfSession = perfSession; } - - @VisibleForTesting - public Future getSyncInitFuture() { - return this.syncInitFuture; - } } 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 30da2f0160f..1c06ceac9dd 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 @@ -136,6 +136,7 @@ public void startCollectingGauges( final String sessionIdForScheduledTask = sessionId; final ApplicationProcessState applicationProcessStateForScheduledTask = applicationProcessState; + // TODO(b/394127311): Switch to using AQS. try { gaugeManagerDataCollectionJob = gaugeManagerExecutor @@ -204,6 +205,7 @@ public void stopCollectingGauges() { gaugeManagerDataCollectionJob.cancel(false); } + // TODO(b/394127311): Switch to using AQS. // Flush any data that was collected for this session one last time. @SuppressWarnings("FutureReturnValueIgnored") ScheduledFuture unusedFuture = @@ -242,6 +244,7 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) { } // Adding Session ID info. + // TODO(b/394127311): Switch to using AQS. gaugeMetricBuilder.setSessionId(sessionId); transportManager.log(gaugeMetricBuilder.build(), appState); @@ -250,17 +253,16 @@ 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 PerfSession#aqsSessionId()} ()} 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) { - // TODO(b/394127311): Re-introduce logging of metadata for AQS. + public boolean logGaugeMetadata(String aqsSessionId, ApplicationProcessState appState) { if (gaugeMetadataManager != null) { GaugeMetric gaugeMetric = GaugeMetric.newBuilder() - .setSessionId(sessionId) + .setSessionId(aqsSessionId) .setGaugeMetadata(getGaugeMetadata()) .build(); transportManager.log(gaugeMetric, appState); 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/session/SessionManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/session/SessionManagerTest.java index 37b9ff7215b..954b0ae88d3 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 @@ -16,9 +16,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -40,7 +37,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.AdditionalMatchers; import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.Mock; @@ -82,105 +78,15 @@ public void setApplicationContext_initializeGaugeMetadataManager() new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); testSessionManager.setApplicationContext(mockApplicationContext); - testSessionManager.getSyncInitFuture().get(); inOrder.verify(mockGaugeManager).initializeGaugeMetadataManager(any()); } - @Test - public void testOnUpdateAppStateDoesNothingDuringAppStart() { - String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - - AppStateMonitor.getInstance().setIsColdStart(true); - - SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.FOREGROUND); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - } - - @Test - public void testOnUpdateAppStateGeneratesNewSessionIdOnForegroundState() { - String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - - SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.FOREGROUND); - assertThat(oldSessionId).isNotEqualTo(SessionManager.getInstance().perfSession().sessionId()); - } - - @Test - public void testOnUpdateAppStateDoesntGenerateNewSessionIdOnBackgroundState() { - String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - - SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.BACKGROUND); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - } - - @Test - public void testOnUpdateAppStateGeneratesNewSessionIdOnBackgroundStateIfPerfSessionExpires() { - when(mockPerfSession.isSessionRunningTooLong()).thenReturn(true); - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - String oldSessionId = testSessionManager.perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(testSessionManager.perfSession().sessionId()); - - testSessionManager.onUpdateAppState(ApplicationProcessState.BACKGROUND); - assertThat(oldSessionId).isNotEqualTo(testSessionManager.perfSession().sessionId()); - } - - @Test - public void - testOnUpdateAppStateDoesntMakeGaugeManagerLogGaugeMetadataOnForegroundStateIfSessionIsNonVerbose() { - forceNonVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.FOREGROUND); - - verify(mockGaugeManager, never()) - .logGaugeMetadata( - anyString(), nullable(com.google.firebase.perf.v1.ApplicationProcessState.class)); - } - - @Test - public void - testOnUpdateAppStateDoesntMakeGaugeManagerLogGaugeMetadataOnBackgroundStateEvenIfSessionIsVerbose() { - forceVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.BACKGROUND); - - verify(mockGaugeManager, never()) - .logGaugeMetadata( - anyString(), nullable(com.google.firebase.perf.v1.ApplicationProcessState.class)); - } - - @Test - public void testOnUpdateAppStateMakesGaugeManagerStartCollectingGaugesIfSessionIsVerbose() { - forceVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.FOREGROUND); - - verify(mockGaugeManager) - .startCollectingGauges(AdditionalMatchers.not(eq(mockPerfSession)), any()); - } - // LogGaugeData on new perf session when Verbose // NotLogGaugeData on new perf session when not Verbose // Mark Session as expired after time limit. @Test - public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesIfSessionIsNonVerbose() { + public void testUpdatePerfSessionMakesGaugeManagerStopCollectingGaugesIfSessionIsNonVerbose() { forceNonVerboseSession(); SessionManager testSessionManager = @@ -191,7 +97,7 @@ public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesIfSessionIs } @Test - public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesWhenSessionsDisabled() { + public void testUpdatePerfSessionMakesGaugeManagerStopCollectingGaugesWhenSessionsDisabled() { forceSessionsFeatureDisabled(); SessionManager testSessionManager = @@ -221,22 +127,25 @@ public void testSessionIdDoesNotUpdateIfPerfSessionRunsTooLong() { } @Test - public void testPerfSessionExpiredMakesGaugeManagerStopsCollectingGaugesIfSessionIsVerbose() { - forceVerboseSession(); + public void testUpdatePerfSessionStartsCollectingGaugesIfSessionIsVerbose() { Timer mockTimer = mock(Timer.class); when(mockClock.getTime()).thenReturn(mockTimer); + when(mockAppStateMonitor.getAppState()).thenReturn(ApplicationProcessState.FOREGROUND); - PerfSession session = new PerfSession("sessionId", mockClock); - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, session, mockAppStateMonitor); + PerfSession previousSession = new PerfSession("previousSession", mockClock); + previousSession.setGaugeAndEventCollectionEnabled(false); - assertThat(session.isSessionRunningTooLong()).isFalse(); + PerfSession newSession = new PerfSession("newSession", mockClock); + newSession.setGaugeAndEventCollectionEnabled(true); - when(mockTimer.getDurationMicros()) - .thenReturn(TimeUnit.HOURS.toMicros(5)); // Default Max Session Length is 4 hours + SessionManager testSessionManager = + new SessionManager(mockGaugeManager, previousSession, mockAppStateMonitor); + testSessionManager.updatePerfSession(newSession); + testSessionManager.setApplicationContext(mockApplicationContext); - assertThat(session.isSessionRunningTooLong()).isTrue(); - verify(mockGaugeManager, times(0)).logGaugeMetadata(any(), any()); + verify(mockGaugeManager, times(1)).initializeGaugeMetadataManager(mockApplicationContext); + verify(mockGaugeManager, times(1)) + .startCollectingGauges(newSession, ApplicationProcessState.FOREGROUND); } @Test 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 From e5bc537c40cc67efff430bae372e210dbf07ce0e Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 15:26:09 -0500 Subject: [PATCH 23/76] Add back Session Subscriber variable --- .../google/firebase/perf/FirebasePerformance.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) 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 e4ddfcd600c..3db3d1acfeb 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 @@ -37,6 +37,7 @@ 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.sessions.api.SessionSubscriber; import com.google.firebase.perf.session.SessionManager; import com.google.firebase.perf.transport.TransportManager; import com.google.firebase.perf.util.Constants; @@ -94,6 +95,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, @@ -167,6 +170,7 @@ public static FirebasePerformance getInstance() { this.mPerformanceCollectionForceEnabledState = false; this.configResolver = configResolver; this.mMetadataBundle = new ImmutableBundle(new Bundle()); + this.sessionSubscriber = new FirebasePerformanceSessionSubscriber(false); return; } @@ -183,8 +187,8 @@ public static FirebasePerformance getInstance() { sessionManager.setApplicationContext(appContext); mPerformanceCollectionForceEnabledState = configResolver.getIsPerformanceCollectionEnabled(); - FirebaseSessionsDependencies.register( - new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled())); + this.sessionSubscriber = new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled()); + FirebaseSessionsDependencies.register(this.sessionSubscriber); if (logger.isLogcatEnabled() && isPerformanceCollectionEnabled()) { logger.info( @@ -460,4 +464,10 @@ private static ImmutableBundle extractMetadata(Context appContext) { Boolean getPerformanceCollectionForceEnabledState() { return mPerformanceCollectionForceEnabledState; } + + @NonNull + @VisibleForTesting + protected SessionSubscriber getSessionSubscriber() { + return sessionSubscriber; + } } From 2d357bce16753856922486fd7d50b11c3d884b93 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 16:49:01 -0500 Subject: [PATCH 24/76] Upload some changes for gauge manager changes --- .../firebase/perf/FirebasePerformance.java | 5 +- .../firebase/perf/session/SessionManager.java | 28 ++++- .../perf/session/gauges/GaugeManager.java | 98 +++++++++------ .../perf/session/SessionManagerTest.java | 4 +- .../perf/session/gauges/GaugeManagerTest.java | 118 +++++++++--------- 5 files changed, 148 insertions(+), 105 deletions(-) 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 3db3d1acfeb..2c41d402cd0 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 @@ -37,7 +37,6 @@ 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.sessions.api.SessionSubscriber; import com.google.firebase.perf.session.SessionManager; import com.google.firebase.perf.transport.TransportManager; import com.google.firebase.perf.util.Constants; @@ -45,6 +44,7 @@ import com.google.firebase.perf.util.Timer; import com.google.firebase.remoteconfig.RemoteConfigComponent; import com.google.firebase.sessions.api.FirebaseSessionsDependencies; +import com.google.firebase.sessions.api.SessionSubscriber; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.URL; @@ -187,7 +187,8 @@ public static FirebasePerformance getInstance() { sessionManager.setApplicationContext(appContext); mPerformanceCollectionForceEnabledState = configResolver.getIsPerformanceCollectionEnabled(); - this.sessionSubscriber = new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled()); + this.sessionSubscriber = + new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled()); FirebaseSessionsDependencies.register(this.sessionSubscriber); if (logger.isLogcatEnabled() && isPerformanceCollectionEnabled()) { 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 cf99c1e52ea..6f37ca12124 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,6 +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.session.gauges.GaugeManager; import com.google.firebase.perf.v1.ApplicationProcessState; import com.google.firebase.perf.v1.GaugeMetadata; @@ -32,7 +33,7 @@ /** Session manager to generate sessionIDs and broadcast to the application. */ @Keep // Needed because of b/117526359. -public class SessionManager { +public class SessionManager extends AppStateUpdateHandler { @SuppressLint("StaticFieldLeak") private static final SessionManager instance = new SessionManager(); @@ -69,6 +70,18 @@ public SessionManager( this.appStateMonitor = appStateMonitor; } + @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; + } + + updateGaugeCollection(newAppState); + } + /** * Finalizes gauge initialization during cold start. This must be called before app start finishes * (currently that is before onResume finishes) to ensure gauge collection starts on time. @@ -118,7 +131,8 @@ public void updatePerfSession(PerfSession perfSession) { } // Start of stop the gauge data collection. - startOrStopCollectingGauges(appStateMonitor.getAppState()); + startOrStopCollectingGauges(); + updateGaugeCollection(appStateMonitor.getAppState()); } /** @@ -128,7 +142,7 @@ public void updatePerfSession(PerfSession perfSession) { * this does not reset the perfSession. */ public void initializeGaugeCollection() { - startOrStopCollectingGauges(ApplicationProcessState.FOREGROUND); + startOrStopCollectingGauges(); } /** @@ -155,14 +169,18 @@ public void unregisterForSessionUpdates(WeakReference client } } - private void startOrStopCollectingGauges(ApplicationProcessState appState) { + private void startOrStopCollectingGauges() { if (perfSession.isGaugeAndEventCollectionEnabled()) { - gaugeManager.startCollectingGauges(perfSession, appState); + gaugeManager.startCollectingGauges(perfSession); } else { gaugeManager.stopCollectingGauges(); } } + private void updateGaugeCollection(ApplicationProcessState applicationProcessState) { + gaugeManager.updateGaugeCollection(applicationProcessState); + } + @VisibleForTesting public void setPerfSession(PerfSession perfSession) { this.perfSession = perfSession; 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 772f4cf294a..06c954e2838 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 @@ -60,10 +60,11 @@ public class GaugeManager { @Nullable private GaugeMetadataManager gaugeMetadataManager; @Nullable private ScheduledFuture gaugeManagerDataCollectionJob = null; - @Nullable private String sessionId = null; - @Nullable private String aqsSessionId = null; - private ApplicationProcessState applicationProcessState = - ApplicationProcessState.APPLICATION_PROCESS_STATE_UNKNOWN; + @Nullable private PerfSession session = null; + + // The default value for application process state is Foreground. This will be updated based on + // app state changes. + private ApplicationProcessState applicationProcessState = ApplicationProcessState.FOREGROUND; // TODO(b/258263016): Migrate to go/firebase-android-executors @SuppressLint("ThreadPoolCreation") @@ -104,48 +105,34 @@ public static synchronized GaugeManager getInstance() { return instance; } - /** - * Starts the collection of available gauges for the given {@code sessionId} and {@code - * applicationProcessState}. The collected Gauge Metrics will be flushed at regular intervals. - * - *

GaugeManager can only collect gauges for one session at a time, and if this method is called - * again with the same or new sessionId while it's already collecting gauges, all future gauges - * will then be associated with the same or new sessionId and applicationProcessState. - * - * @param session The {@link PerfSession} to which the collected gauges will be associated with. - * @param applicationProcessState The {@link ApplicationProcessState} the collected GaugeMetrics - * will be associated with. - * @note: This method is NOT thread safe - {@link this.startCollectingGauges()} and {@link - * this.stopCollectingGauges()} should always be called from the same thread. - */ - public void startCollectingGauges( - PerfSession session, ApplicationProcessState applicationProcessState) { - if (this.sessionId != null) { - stopCollectingGauges(); + public void updateGaugeCollection(ApplicationProcessState applicationProcessState) { + if (gaugeManagerDataCollectionJob != null) { + gaugeManagerDataCollectionJob.cancel(false); } - long collectionFrequency = startCollectingGauges(applicationProcessState, session.getTimer()); - if (collectionFrequency == INVALID_GAUGE_COLLECTION_FREQUENCY) { - logger.warn("Invalid gauge collection frequency. Unable to start collecting Gauges."); + if (session == null || !session.isGaugeAndEventCollectionEnabled()) { + logger.warn("Not starting gauge collection."); + stopCollectingGauges(); return; } - this.sessionId = session.sessionId(); - this.aqsSessionId = session.aqsSessionId(); - this.applicationProcessState = applicationProcessState; + if (session.aqsSessionId() == null) { + logger.warn("Not starting gauge collection."); + } - // This is needed, otherwise the Runnable might use a stale value. - final String sessionIdForScheduledTask = aqsSessionId; - final ApplicationProcessState applicationProcessStateForScheduledTask = applicationProcessState; + long collectionFrequency = + Math.min( + getCpuGaugeCollectionFrequencyMs(applicationProcessState), + getMemoryGaugeCollectionFrequencyMs(applicationProcessState)); + String sessionIdForScheduledTask = session.aqsSessionId(); - // TODO(b/394127311): Switch to using AQS. try { gaugeManagerDataCollectionJob = gaugeManagerExecutor .get() .scheduleWithFixedDelay( () -> { - syncFlush(sessionIdForScheduledTask, applicationProcessStateForScheduledTask); + syncFlush(sessionIdForScheduledTask, applicationProcessState); }, /* initialDelay= */ collectionFrequency * APPROX_NUMBER_OF_DATA_POINTS_PER_GAUGE_METRIC, @@ -155,6 +142,36 @@ public void startCollectingGauges( } catch (RejectedExecutionException e) { logger.warn("Unable to start collecting Gauges: " + e.getMessage()); } + + if (this.applicationProcessState != applicationProcessState) { + this.applicationProcessState = applicationProcessState; + + // If there's a change in App State - update the gauge collection frequency as well. + stopCollectingGauges(); + startCollectingGauges(applicationProcessState, this.session.getTimer()); + } + } + + /** + * Starts the collection of available gauges for the given {@code session}. + * + *

GaugeManager can only collect gauges for one session at a time, and if this method is called + * again with the same or new sessionId while it's already collecting gauges, all future gauges + * will then be associated with the same or new sessionId and applicationProcessState. + * + * @param session The {@link PerfSession} to which the collected gauges will be associated with. + * @note: This method is NOT thread safe - {@link this.startCollectingGauges()} and {@link + * this.stopCollectingGauges()} should always be called from the same thread. + */ + public void startCollectingGauges(PerfSession session) { + if (this.session != null) { + stopCollectingGauges(); + } + + long collectionFrequency = startCollectingGauges(applicationProcessState, session.getTimer()); + if (collectionFrequency == INVALID_GAUGE_COLLECTION_FREQUENCY) { + logger.warn("Invalid gauge collection frequency. Unable to start collecting Gauges."); + } } /** @@ -192,12 +209,14 @@ private long startCollectingGauges(ApplicationProcessState appState, Timer refer * this.stopCollectingGauges()} should always be called from the same thread. */ public void stopCollectingGauges() { - if (this.sessionId == null) { + if (this.session == null) { return; } // This is needed, otherwise the Runnable might use a stale value. - final String sessionIdForScheduledTask = aqsSessionId; + + // AQS is guaranteed to be available when stopping gauge collection. + final String sessionIdForScheduledTask = session.aqsSessionId(); final ApplicationProcessState applicationProcessStateForScheduledTask = applicationProcessState; cpuGaugeCollector.get().stopCollecting(); @@ -207,7 +226,6 @@ public void stopCollectingGauges() { gaugeManagerDataCollectionJob.cancel(false); } - // TODO(b/394127311): Switch to using AQS. // Flush any data that was collected for this session one last time. @SuppressWarnings("FutureReturnValueIgnored") ScheduledFuture unusedFuture = @@ -220,8 +238,7 @@ public void stopCollectingGauges() { TIME_TO_WAIT_BEFORE_FLUSHING_GAUGES_QUEUE_MS, TimeUnit.MILLISECONDS); - this.sessionId = null; - this.applicationProcessState = ApplicationProcessState.APPLICATION_PROCESS_STATE_UNKNOWN; + this.session = null; } /** @@ -407,4 +424,9 @@ private long getMemoryGaugeCollectionFrequencyMs( return memoryGaugeCollectionFrequency; } } + + @VisibleForTesting + void setApplicationProcessState(ApplicationProcessState applicationProcessState) { + this.applicationProcessState = applicationProcessState; + } } 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 954b0ae88d3..deff47ef7f4 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 @@ -144,8 +144,8 @@ public void testUpdatePerfSessionStartsCollectingGaugesIfSessionIsVerbose() { testSessionManager.setApplicationContext(mockApplicationContext); verify(mockGaugeManager, times(1)).initializeGaugeMetadataManager(mockApplicationContext); - verify(mockGaugeManager, times(1)) - .startCollectingGauges(newSession, ApplicationProcessState.FOREGROUND); + verify(mockGaugeManager, times(1)).startCollectingGauges(newSession); + verify(mockGaugeManager, times(1)).updateGaugeCollection(ApplicationProcessState.FOREGROUND); } @Test 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..26be1fde301 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 @@ -123,23 +123,9 @@ public void setUp() { } @Test - public void testStartCollectingGaugesStartsCollectingMetricsInBackgroundState() { + public void testStartCollectingGaugesStartsCollectingMetrics() { PerfSession fakeSession = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); - verify(fakeCpuGaugeCollector) - .startCollecting( - eq(DEFAULT_CPU_GAUGE_COLLECTION_FREQUENCY_BG_MS), - ArgumentMatchers.nullable(Timer.class)); - verify(fakeMemoryGaugeCollector) - .startCollecting( - eq(DEFAULT_MEMORY_GAUGE_COLLECTION_FREQUENCY_BG_MS), - ArgumentMatchers.nullable(Timer.class)); - } - - @Test - public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.FOREGROUND); + testGaugeManager.startCollectingGauges(fakeSession); verify(fakeCpuGaugeCollector) .startCollecting( eq(DEFAULT_CPU_GAUGE_COLLECTION_FREQUENCY_FG_MS), @@ -150,25 +136,13 @@ public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() ArgumentMatchers.nullable(Timer.class)); } - @Test - public void - testStartCollectingGaugesDoesNotStartCollectingMetricsWithUnknownApplicationProcessState() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges( - fakeSession, ApplicationProcessState.APPLICATION_PROCESS_STATE_UNKNOWN); - verify(fakeCpuGaugeCollector, never()) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.nullable(Timer.class)); - verify(fakeMemoryGaugeCollector, never()) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.nullable(Timer.class)); - } - @Test public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithValidFrequencyInBackground() { // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyBackgroundMs(); PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.BACKGROUND); + testGaugeManager.startCollectingGauges(fakeSession1); // Verify that Cpu metric collection is not started verify(fakeCpuGaugeCollector, never()) @@ -181,7 +155,8 @@ public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyBackgroundMs(); PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.BACKGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); + testGaugeManager.startCollectingGauges(fakeSession2); // Verify that Cpu metric collection is not started verify(fakeCpuGaugeCollector, never()) @@ -198,7 +173,8 @@ public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyBackgroundMs(); PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.BACKGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); + testGaugeManager.startCollectingGauges(fakeSession1); // Verify that Memory metric collection is not started verify(fakeMemoryGaugeCollector, never()) @@ -211,7 +187,8 @@ public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyBackgroundMs(); PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.BACKGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); + testGaugeManager.startCollectingGauges(fakeSession2); // Verify that Memory metric collection is not started verify(fakeMemoryGaugeCollector, never()) @@ -227,7 +204,8 @@ public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithV // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.FOREGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); + testGaugeManager.startCollectingGauges(fakeSession1); // Verify that Cpu metric collection is not started verify(fakeCpuGaugeCollector, never()) @@ -240,7 +218,8 @@ public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithV // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); + testGaugeManager.startCollectingGauges(fakeSession2); // Verify that Cpu metric collection is not started verify(fakeCpuGaugeCollector, never()) @@ -257,7 +236,8 @@ public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithV // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.FOREGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); + testGaugeManager.startCollectingGauges(fakeSession1); // Verify that Memory metric collection is not started verify(fakeMemoryGaugeCollector, never()) @@ -270,7 +250,8 @@ public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithV // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); + testGaugeManager.startCollectingGauges(fakeSession2); // Verify that Memory metric collection is not started verify(fakeMemoryGaugeCollector, never()) @@ -284,8 +265,9 @@ public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithV @Test public void testStartCollectingGaugesDoesNotStartAJobToConsumeMetricsWithUnknownAppState() { PerfSession fakeSession = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges( - fakeSession, ApplicationProcessState.APPLICATION_PROCESS_STATE_UNKNOWN); + testGaugeManager.setApplicationProcessState( + ApplicationProcessState.APPLICATION_PROCESS_STATE_UNKNOWN); + testGaugeManager.startCollectingGauges(fakeSession); assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); } @@ -295,14 +277,16 @@ public void stopCollectingCPUMetrics_invalidCPUCaptureFrequency_appInForegrounf( doReturn(0L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.FOREGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); + testGaugeManager.startCollectingGauges(fakeSession1); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); + testGaugeManager.startCollectingGauges(fakeSession2); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); } @@ -312,14 +296,16 @@ public void stopCollectingGauges_invalidMemoryCollectionFrequency_appInForegroun doReturn(0L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.FOREGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); + testGaugeManager.startCollectingGauges(fakeSession1); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); + testGaugeManager.startCollectingGauges(fakeSession2); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); } @@ -330,7 +316,8 @@ public void stopCollectingGauges_invalidGaugeCollectionFrequency_appInForeground doReturn(0L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.FOREGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); + testGaugeManager.startCollectingGauges(fakeSession1); assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); // PASS 2: Test with -ve value @@ -338,7 +325,8 @@ public void stopCollectingGauges_invalidGaugeCollectionFrequency_appInForeground doReturn(-25L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); + testGaugeManager.startCollectingGauges(fakeSession2); assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); } @@ -348,17 +336,22 @@ public void startCollectingGauges_validGaugeCollectionFrequency_appInForeground( doReturn(15L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); PerfSession fakeSession = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.FOREGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); + testGaugeManager.startCollectingGauges(fakeSession); - assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); - assertThat(fakeScheduledExecutorService.getDelayToNextTask(TimeUnit.MILLISECONDS)) - .isEqualTo(15L * APPROX_NUMBER_OF_DATA_POINTS_PER_GAUGE_METRIC); + assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); + verify(fakeCpuGaugeCollector) + .startCollecting(eq(DEFAULT_CPU_GAUGE_COLLECTION_FREQUENCY_FG_MS), ArgumentMatchers.any()); + verify(fakeMemoryGaugeCollector) + .startCollecting( + eq(DEFAULT_MEMORY_GAUGE_COLLECTION_FREQUENCY_FG_MS), ArgumentMatchers.any()); } @Test public void testStartCollectingGaugesStartsAJobToConsumeTheGeneratedMetrics() { PerfSession fakeSession = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); + testGaugeManager.startCollectingGauges(fakeSession); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); assertThat(fakeScheduledExecutorService.getDelayToNextTask(TimeUnit.MILLISECONDS)) @@ -393,7 +386,8 @@ public void testStartCollectingGaugesStartsAJobToConsumeTheGeneratedMetrics() { public void testStopCollectingGaugesStopsCollectingAllGaugeMetrics() { PerfSession fakeSession = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); + testGaugeManager.startCollectingGauges(fakeSession); verify(fakeCpuGaugeCollector) .startCollecting(eq(DEFAULT_CPU_GAUGE_COLLECTION_FREQUENCY_BG_MS), ArgumentMatchers.any()); @@ -406,7 +400,8 @@ public void testStopCollectingGaugesStopsCollectingAllGaugeMetrics() { @Test public void testStopCollectingGaugesCreatesOneLastJobToConsumeAnyPendingMetrics() { PerfSession fakeSession = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); + testGaugeManager.startCollectingGauges(fakeSession); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); testGaugeManager.stopCollectingGauges(); @@ -435,7 +430,8 @@ public void testStopCollectingGaugesCreatesOneLastJobToConsumeAnyPendingMetrics( public void testGaugeManagerClearsTheQueueEachRun() { PerfSession fakeSession = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); + testGaugeManager.startCollectingGauges(fakeSession); fakeCpuGaugeCollector.cpuMetricReadings.add(createFakeCpuMetricReading(200, 100)); fakeCpuGaugeCollector.cpuMetricReadings.add(createFakeCpuMetricReading(300, 400)); @@ -468,7 +464,8 @@ public void testStartingGaugeManagerWithNewSessionIdButSameAppState() { PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); // Start collecting Gauges. - testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.BACKGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); + testGaugeManager.startCollectingGauges(fakeSession1); CpuMetricReading fakeCpuMetricReading1 = createFakeCpuMetricReading(200, 100); fakeCpuGaugeCollector.cpuMetricReadings.add(fakeCpuMetricReading1); AndroidMemoryReading fakeMemoryMetricReading1 = @@ -493,7 +490,8 @@ public void testStartingGaugeManagerWithNewSessionIdButSameAppState() { PerfSession fakeSession2 = new PerfSession("sessionId2", new Clock()); // Start collecting gauges for new session, but same app state. - testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.BACKGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); + testGaugeManager.startCollectingGauges(fakeSession2); // The next sweep conducted by GaugeManager still associates metrics to old sessionId and state. fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); @@ -526,7 +524,8 @@ public void testStartGaugeManagerWithSameSessionIdButDifferentAppState() { PerfSession fakeSession = new PerfSession("sessionId", new Clock()); // Start collecting Gauges. - testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); + testGaugeManager.startCollectingGauges(fakeSession); CpuMetricReading fakeCpuMetricReading1 = createFakeCpuMetricReading(200, 100); fakeCpuGaugeCollector.cpuMetricReadings.add(fakeCpuMetricReading1); AndroidMemoryReading fakeMemoryMetricReading1 = @@ -549,7 +548,8 @@ public void testStartGaugeManagerWithSameSessionIdButDifferentAppState() { fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading2); // Start collecting gauges for same session, but new app state - testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.FOREGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); + testGaugeManager.startCollectingGauges(fakeSession); // The next sweep conducted by GaugeManager still associates metrics to old sessionId and state. fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); @@ -582,7 +582,8 @@ public void testStartGaugeManagerWithNewSessionIdAndNewAppState() { PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); // Start collecting Gauges. - testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.BACKGROUND); + testGaugeManager.startCollectingGauges(fakeSession1); + testGaugeManager.updateGaugeCollection(ApplicationProcessState.BACKGROUND); CpuMetricReading fakeCpuMetricReading1 = createFakeCpuMetricReading(200, 100); fakeCpuGaugeCollector.cpuMetricReadings.add(fakeCpuMetricReading1); AndroidMemoryReading fakeMemoryMetricReading1 = @@ -607,7 +608,8 @@ public void testStartGaugeManagerWithNewSessionIdAndNewAppState() { PerfSession fakeSession2 = new PerfSession("sessionId2", new Clock()); // Start collecting gauges for new session and new app state - testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); + testGaugeManager.startCollectingGauges(fakeSession2); + testGaugeManager.updateGaugeCollection(ApplicationProcessState.FOREGROUND); // The next sweep conducted by GaugeManager still associates metrics to old sessionId and state. fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); From f379bc06a4c4b9a6d67b538344d8d9123260a60a Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 17:24:46 -0500 Subject: [PATCH 25/76] More changes --- .../firebase/perf/session/SessionManager.java | 3 ++- .../perf/session/gauges/GaugeManager.java | 16 ++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) 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 6f37ca12124..7da4196ca5c 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 @@ -79,6 +79,8 @@ public void onUpdateAppState(ApplicationProcessState newAppState) { return; } + // While the change in app state doesn't start or stop gauge collection, it updates the upload + // frequency. updateGaugeCollection(newAppState); } @@ -130,7 +132,6 @@ public void updatePerfSession(PerfSession perfSession) { } } - // Start of stop the gauge data collection. startOrStopCollectingGauges(); updateGaugeCollection(appStateMonitor.getAppState()); } 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 06c954e2838..c4703581828 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 @@ -110,16 +110,13 @@ public void updateGaugeCollection(ApplicationProcessState applicationProcessStat gaugeManagerDataCollectionJob.cancel(false); } - if (session == null || !session.isGaugeAndEventCollectionEnabled()) { + if (session == null + || !session.isGaugeAndEventCollectionEnabled() + || session.aqsSessionId() == null) { logger.warn("Not starting gauge collection."); - stopCollectingGauges(); return; } - if (session.aqsSessionId() == null) { - logger.warn("Not starting gauge collection."); - } - long collectionFrequency = Math.min( getCpuGaugeCollectionFrequencyMs(applicationProcessState), @@ -168,6 +165,13 @@ public void startCollectingGauges(PerfSession session) { stopCollectingGauges(); } + // Updates the session associated w/ Gauge Manager when starting collecting gauges. + this.session = session; + + if (!session.isGaugeAndEventCollectionEnabled()) { + return; + } + long collectionFrequency = startCollectingGauges(applicationProcessState, session.getTimer()); if (collectionFrequency == INVALID_GAUGE_COLLECTION_FREQUENCY) { logger.warn("Invalid gauge collection frequency. Unable to start collecting Gauges."); From fc403c0daa8068eb98fa85c40541c5a6f7410f6e Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 17:34:23 -0500 Subject: [PATCH 26/76] Revert initializing gauge collection in FirebasePerfEarly --- .../java/com/google/firebase/perf/FirebasePerfEarly.java | 7 +++++++ .../perf/session/FirebasePerformanceSessionSubscriber.kt | 6 +++--- .../com/google/firebase/perf/session/SessionManager.java | 3 ++- .../google/firebase/perf/session/gauges/GaugeManager.java | 1 + 4 files changed, 13 insertions(+), 4 deletions(-) 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 2d4d99da7bf..52ca3016df5 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 @@ -21,6 +21,8 @@ import com.google.firebase.perf.application.AppStateMonitor; import com.google.firebase.perf.config.ConfigResolver; import com.google.firebase.perf.metrics.AppStartTrace; +import com.google.firebase.perf.session.SessionManager; + import java.util.concurrent.Executor; /** @@ -49,5 +51,10 @@ public FirebasePerfEarly( appStartTrace.registerActivityLifecycleCallbacks(context); uiExecutor.execute(new AppStartTrace.StartFromBackgroundRunnable(appStartTrace)); } + + // In the case of cold start, we create a session and start collecting gauges as early as + // possible. + // Uploading the gauges however only starts once AQS is initialized. + SessionManager.getInstance().initializeGaugeCollection(); } } 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 index aa698bec44c..03178a7d92d 100644 --- 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 @@ -28,14 +28,14 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { val currentPerfSession = SessionManager.getInstance().perfSession() + val gaugeManager = GaugeManager.getInstance() // A [PerfSession] was created before a session was started. if (currentPerfSession.aqsSessionId() == null) { currentPerfSession.setAQSId(sessionDetails) - GaugeManager.getInstance() + gaugeManager .logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) - // TODO(b/394127311): Explore deciding whether to collect gauges or not via. AQS. - SessionManager.getInstance().initializeGaugeCollection() + gaugeManager.updateGaugeCollection(ApplicationProcessState.FOREGROUND) return } 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 7da4196ca5c..9698f302134 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 @@ -133,7 +133,8 @@ public void updatePerfSession(PerfSession perfSession) { } startOrStopCollectingGauges(); - updateGaugeCollection(appStateMonitor.getAppState()); + // A session is *only* updated in Foreground. + updateGaugeCollection(ApplicationProcessState.FOREGROUND); } /** 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 c4703581828..cde70a98e52 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 @@ -114,6 +114,7 @@ public void updateGaugeCollection(ApplicationProcessState applicationProcessStat || !session.isGaugeAndEventCollectionEnabled() || session.aqsSessionId() == null) { logger.warn("Not starting gauge collection."); + stopCollectingGauges(); return; } From 20fb963096b5ce05c084ca948ec6704eab7ec43d Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 17:36:20 -0500 Subject: [PATCH 27/76] style changes --- .../perf/session/FirebasePerformanceSessionSubscriber.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 index 03178a7d92d..37dff221aea 100644 --- 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 @@ -27,7 +27,8 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: override val sessionSubscriberName: SessionSubscriber.Name = SessionSubscriber.Name.PERFORMANCE override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { - val currentPerfSession = SessionManager.getInstance().perfSession() + val sessionManager = SessionManager.getInstance() + val currentPerfSession = sessionManager.perfSession() val gaugeManager = GaugeManager.getInstance() // A [PerfSession] was created before a session was started. @@ -41,8 +42,8 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()) updatedSession.setAQSId(sessionDetails) - SessionManager.getInstance().updatePerfSession(updatedSession) - GaugeManager.getInstance() + sessionManager.updatePerfSession(updatedSession) + gaugeManager .logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) } } From f692e14b84eec5aaf554b14d5a530b065525ccd9 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 17:37:26 -0500 Subject: [PATCH 28/76] comment changes --- .../java/com/google/firebase/perf/session/SessionManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9698f302134..27f19ee704c 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 @@ -80,7 +80,7 @@ public void onUpdateAppState(ApplicationProcessState newAppState) { } // While the change in app state doesn't start or stop gauge collection, it updates the upload - // frequency. + // and gauge collection frequency. updateGaugeCollection(newAppState); } From 6ee06c592329f7b90be81ad951d24dea7470d906 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 25 Feb 2025 12:08:45 -0500 Subject: [PATCH 29/76] Update comment --- .../com/google/firebase/perf/session/gauges/GaugeManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 cde70a98e52..221c80fbc75 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 @@ -180,7 +180,9 @@ public void startCollectingGauges(PerfSession session) { } /** - * Starts the collection of available Gauges for the given {@code appState}. + * Starts the collection of available Gauges for the given {@code appState}. Do note that + * uploading the gauges *only* starts after calling + * {@link #updateGaugeCollection(ApplicationProcessState)} * * @param appState The app state to which the collected gauges are associated. * @param referenceTime The time off which the system time is calculated when collecting gauges. From 49f00082c40c115178ead5fc47c412011c9ca6e0 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 7 Feb 2025 14:58:37 -0500 Subject: [PATCH 30/76] Remove the logging of GaugeMetadata to allow using AQS (#6678) Based on the behaviour of AQS w/ Fireperf, an AQS session isn't available when (currently) logging gauge metadata. Changes: - Remove the current logging of gauge metadata - will be re-introduced in a future PR. - Switch Gauge collection from `scheduleAtFixedRate` to `scheduleWithFixedDelay`. As [documented](https://stackoverflow.com/a/78405653), this *should* prevent a potentially large amounts of gauge collection if a process is cached, and then restored during a verbose session - which *should* make it work better w/ AQS. - Remove API restricted behaviour which is no longer relevant. --- .../firebase/perf/FirebasePerformance.java | 1 - .../firebase/perf/session/SessionManager.java | 17 ----- .../session/gauges/CpuGaugeCollector.java | 13 +--- .../perf/session/gauges/GaugeManager.java | 9 +-- .../session/gauges/GaugeMetadataManager.java | 34 +--------- .../session/gauges/MemoryGaugeCollector.java | 6 +- .../perf/session/SessionManagerTest.java | 58 +--------------- .../gauges/GaugeMetadataManagerTest.java | 66 +------------------ 8 files changed, 14 insertions(+), 190 deletions(-) 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..3cc49896ce0 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 @@ -182,7 +182,6 @@ public static FirebasePerformance getInstance() { .initialize(firebaseApp, firebaseInstallationsApi, transportFactoryProvider); Context appContext = firebaseApp.getApplicationContext(); - // TODO(b/110178816): Explore moving off of main thread. mMetadataBundle = extractMetadata(appContext); remoteConfigManager.setFirebaseRemoteConfigProvider(firebaseRemoteConfigProvider); 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..29ffb988ba0 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 @@ -79,9 +79,6 @@ 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(); @@ -89,10 +86,6 @@ public void setApplicationContext(final Context appContext) { executorService.submit( () -> { gaugeManager.initializeGaugeMetadataManager(appContext); - if (appStartSession.isGaugeAndEventCollectionEnabled()) { - gaugeManager.logGaugeMetadata( - appStartSession.sessionId(), ApplicationProcessState.FOREGROUND); - } }); } @@ -164,9 +157,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 +168,6 @@ public void updatePerfSession(PerfSession perfSession) { * this does not reset the perfSession. */ public void initializeGaugeCollection() { - logGaugeMetadataIfCollectionEnabled(ApplicationProcessState.FOREGROUND); startOrStopCollectingGauges(ApplicationProcessState.FOREGROUND); } @@ -206,12 +195,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..30da2f0160f 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 @@ -72,8 +72,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 +81,7 @@ private GaugeManager() { Lazy gaugeManagerExecutor, TransportManager transportManager, ConfigResolver configResolver, - GaugeMetadataManager gaugeMetadataManager, + @Nullable GaugeMetadataManager gaugeMetadataManager, Lazy cpuGaugeCollector, Lazy memoryGaugeCollector) { @@ -140,7 +140,7 @@ public void startCollectingGauges( gaugeManagerDataCollectionJob = gaugeManagerExecutor .get() - .scheduleAtFixedRate( + .scheduleWithFixedDelay( () -> { syncFlush(sessionIdForScheduledTask, applicationProcessStateForScheduledTask); }, @@ -256,6 +256,7 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) { * @return true if GaugeMetadata was logged, false otherwise. */ public boolean logGaugeMetadata(String sessionId, ApplicationProcessState appState) { + // TODO(b/394127311): Re-introduce logging of metadata for AQS. if (gaugeMetadataManager != null) { GaugeMetric gaugeMetric = GaugeMetric.newBuilder() 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/test/java/com/google/firebase/perf/session/SessionManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/session/SessionManagerTest.java index f3e3795f3f8..37b9ff7215b 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 @@ -74,7 +74,7 @@ public void testInstanceCreation() { } @Test - public void setApplicationContext_logGaugeMetadata_afterGaugeMetadataManagerIsInitialized() + public void setApplicationContext_initializeGaugeMetadataManager() throws ExecutionException, InterruptedException { when(mockPerfSession.isGaugeAndEventCollectionEnabled()).thenReturn(true); InOrder inOrder = Mockito.inOrder(mockGaugeManager); @@ -84,7 +84,6 @@ public void setApplicationContext_logGaugeMetadata_afterGaugeMetadataManagerIsIn testSessionManager.getSyncInitFuture().get(); inOrder.verify(mockGaugeManager).initializeGaugeMetadataManager(any()); - inOrder.verify(mockGaugeManager).logGaugeMetadata(any(), any()); } @Test @@ -136,20 +135,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 +163,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 +202,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/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"; } From c5e9b9b6ffb91245ac7d25846813bbcc1428fb3e Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 13:06:38 -0500 Subject: [PATCH 31/76] 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. --- firebase-perf/firebase-perf.gradle | 2 +- .../firebase/perf/FirebasePerfRegistrar.java | 7 + .../firebase/perf/FirebasePerformance.java | 17 +-- .../FirebasePerformanceSessionSubscriber.kt | 46 +++++++ .../firebase/perf/session/PerfSession.java | 18 ++- .../firebase/perf/session/SessionManager.java | 53 +------- .../perf/session/gauges/GaugeManager.java | 10 +- .../perf/transport/TransportManager.java | 1 + .../perf/session/SessionManagerTest.java | 121 +++--------------- .../api/FirebaseSessionsDependencies.kt | 13 -- 10 files changed, 103 insertions(+), 185 deletions(-) create mode 100644 firebase-perf/src/main/java/com/google/firebase/perf/session/FirebasePerformanceSessionSubscriber.kt 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/FirebasePerfRegistrar.java b/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfRegistrar.java index c01f035af1f..daffc2de81a 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,11 @@ public class FirebasePerfRegistrar implements ComponentRegistrar { private static final String LIBRARY_NAME = "fire-perf"; private static final String EARLY_LIBRARY_NAME = "fire-perf-early"; + static { + // Add Firebase Performance as a dependency of Sessions when this class is loaded into memory. + FirebaseSessionsDependencies.addDependency(SessionSubscriber.Name.PERFORMANCE); + } + @Override @Keep public List> 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 3cc49896ce0..e4ddfcd600c 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; @@ -191,6 +183,9 @@ public static FirebasePerformance getInstance() { sessionManager.setApplicationContext(appContext); mPerformanceCollectionForceEnabledState = configResolver.getIsPerformanceCollectionEnabled(); + FirebaseSessionsDependencies.register( + new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled())); + if (logger.isLogcatEnabled() && isPerformanceCollectionEnabled()) { logger.info( String.format( @@ -281,7 +276,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/session/FirebasePerformanceSessionSubscriber.kt b/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebasePerformanceSessionSubscriber.kt new file mode 100644 index 00000000000..b6a3d30c139 --- /dev/null +++ b/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebasePerformanceSessionSubscriber.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.perf.session + +import com.google.firebase.perf.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 { + + override val sessionSubscriberName: SessionSubscriber.Name = SessionSubscriber.Name.PERFORMANCE + + override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { + val currentPerfSession = SessionManager.getInstance().perfSession() + + // A [PerfSession] was created before a session was started. + if (currentPerfSession.aqsSessionId() == null) { + currentPerfSession.setAQSId(sessionDetails) + GaugeManager.getInstance() + .logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + return + } + + val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()) + updatedSession.setAQSId(sessionDetails) + SessionManager.getInstance().updatePerfSession(updatedSession) + GaugeManager.getInstance() + .logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + } +} 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..075848ab747 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,6 +23,7 @@ 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.concurrent.TimeUnit; @@ -31,6 +32,7 @@ public class PerfSession implements Parcelable { private final String sessionId; private final Timer creationTime; + @Nullable private String aqsSessionId; private boolean isGaugeAndEventCollectionEnabled = false; @@ -59,11 +61,24 @@ 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; } + /** 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(); + } + } + /** * Returns a timer object that has been seeded with the system time at which the session began. */ @@ -113,6 +128,7 @@ 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); 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 29ffb988ba0..cf99c1e52ea 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,6 @@ 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.session.gauges.GaugeManager; import com.google.firebase.perf.v1.ApplicationProcessState; import com.google.firebase.perf.v1.GaugeMetadata; @@ -27,15 +26,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(); @@ -45,7 +42,6 @@ public class SessionManager extends AppStateUpdateHandler { private final Set> clients = new HashSet<>(); private PerfSession perfSession; - private Future syncInitFuture; /** Returns the singleton instance of SessionManager. */ public static SessionManager getInstance() { @@ -71,7 +67,6 @@ public SessionManager( this.gaugeManager = gaugeManager; this.perfSession = perfSession; this.appStateMonitor = appStateMonitor; - registerForAppState(); } /** @@ -79,42 +74,7 @@ public SessionManager( * (currently that is before onResume finishes) to ensure gauge collection starts on time. */ public void setApplicationContext(final Context appContext) { - // TODO(b/258263016): Migrate to go/firebase-android-executors - @SuppressLint("ThreadPoolCreation") - ExecutorService executorService = Executors.newSingleThreadExecutor(); - syncInitFuture = - executorService.submit( - () -> { - gaugeManager.initializeGaugeMetadataManager(appContext); - }); - } - - @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); } /** @@ -138,7 +98,7 @@ 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; } @@ -207,9 +167,4 @@ private void startOrStopCollectingGauges(ApplicationProcessState appState) { public void setPerfSession(PerfSession perfSession) { this.perfSession = perfSession; } - - @VisibleForTesting - public Future getSyncInitFuture() { - return this.syncInitFuture; - } } 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 30da2f0160f..1c06ceac9dd 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 @@ -136,6 +136,7 @@ public void startCollectingGauges( final String sessionIdForScheduledTask = sessionId; final ApplicationProcessState applicationProcessStateForScheduledTask = applicationProcessState; + // TODO(b/394127311): Switch to using AQS. try { gaugeManagerDataCollectionJob = gaugeManagerExecutor @@ -204,6 +205,7 @@ public void stopCollectingGauges() { gaugeManagerDataCollectionJob.cancel(false); } + // TODO(b/394127311): Switch to using AQS. // Flush any data that was collected for this session one last time. @SuppressWarnings("FutureReturnValueIgnored") ScheduledFuture unusedFuture = @@ -242,6 +244,7 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) { } // Adding Session ID info. + // TODO(b/394127311): Switch to using AQS. gaugeMetricBuilder.setSessionId(sessionId); transportManager.log(gaugeMetricBuilder.build(), appState); @@ -250,17 +253,16 @@ 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 PerfSession#aqsSessionId()} ()} 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) { - // TODO(b/394127311): Re-introduce logging of metadata for AQS. + public boolean logGaugeMetadata(String aqsSessionId, ApplicationProcessState appState) { if (gaugeMetadataManager != null) { GaugeMetric gaugeMetric = GaugeMetric.newBuilder() - .setSessionId(sessionId) + .setSessionId(aqsSessionId) .setGaugeMetadata(getGaugeMetadata()) .build(); transportManager.log(gaugeMetric, appState); 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/session/SessionManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/session/SessionManagerTest.java index 37b9ff7215b..954b0ae88d3 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 @@ -16,9 +16,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -40,7 +37,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.AdditionalMatchers; import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.Mock; @@ -82,105 +78,15 @@ public void setApplicationContext_initializeGaugeMetadataManager() new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); testSessionManager.setApplicationContext(mockApplicationContext); - testSessionManager.getSyncInitFuture().get(); inOrder.verify(mockGaugeManager).initializeGaugeMetadataManager(any()); } - @Test - public void testOnUpdateAppStateDoesNothingDuringAppStart() { - String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - - AppStateMonitor.getInstance().setIsColdStart(true); - - SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.FOREGROUND); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - } - - @Test - public void testOnUpdateAppStateGeneratesNewSessionIdOnForegroundState() { - String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - - SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.FOREGROUND); - assertThat(oldSessionId).isNotEqualTo(SessionManager.getInstance().perfSession().sessionId()); - } - - @Test - public void testOnUpdateAppStateDoesntGenerateNewSessionIdOnBackgroundState() { - String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - - SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.BACKGROUND); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - } - - @Test - public void testOnUpdateAppStateGeneratesNewSessionIdOnBackgroundStateIfPerfSessionExpires() { - when(mockPerfSession.isSessionRunningTooLong()).thenReturn(true); - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - String oldSessionId = testSessionManager.perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(testSessionManager.perfSession().sessionId()); - - testSessionManager.onUpdateAppState(ApplicationProcessState.BACKGROUND); - assertThat(oldSessionId).isNotEqualTo(testSessionManager.perfSession().sessionId()); - } - - @Test - public void - testOnUpdateAppStateDoesntMakeGaugeManagerLogGaugeMetadataOnForegroundStateIfSessionIsNonVerbose() { - forceNonVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.FOREGROUND); - - verify(mockGaugeManager, never()) - .logGaugeMetadata( - anyString(), nullable(com.google.firebase.perf.v1.ApplicationProcessState.class)); - } - - @Test - public void - testOnUpdateAppStateDoesntMakeGaugeManagerLogGaugeMetadataOnBackgroundStateEvenIfSessionIsVerbose() { - forceVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.BACKGROUND); - - verify(mockGaugeManager, never()) - .logGaugeMetadata( - anyString(), nullable(com.google.firebase.perf.v1.ApplicationProcessState.class)); - } - - @Test - public void testOnUpdateAppStateMakesGaugeManagerStartCollectingGaugesIfSessionIsVerbose() { - forceVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.FOREGROUND); - - verify(mockGaugeManager) - .startCollectingGauges(AdditionalMatchers.not(eq(mockPerfSession)), any()); - } - // LogGaugeData on new perf session when Verbose // NotLogGaugeData on new perf session when not Verbose // Mark Session as expired after time limit. @Test - public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesIfSessionIsNonVerbose() { + public void testUpdatePerfSessionMakesGaugeManagerStopCollectingGaugesIfSessionIsNonVerbose() { forceNonVerboseSession(); SessionManager testSessionManager = @@ -191,7 +97,7 @@ public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesIfSessionIs } @Test - public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesWhenSessionsDisabled() { + public void testUpdatePerfSessionMakesGaugeManagerStopCollectingGaugesWhenSessionsDisabled() { forceSessionsFeatureDisabled(); SessionManager testSessionManager = @@ -221,22 +127,25 @@ public void testSessionIdDoesNotUpdateIfPerfSessionRunsTooLong() { } @Test - public void testPerfSessionExpiredMakesGaugeManagerStopsCollectingGaugesIfSessionIsVerbose() { - forceVerboseSession(); + public void testUpdatePerfSessionStartsCollectingGaugesIfSessionIsVerbose() { Timer mockTimer = mock(Timer.class); when(mockClock.getTime()).thenReturn(mockTimer); + when(mockAppStateMonitor.getAppState()).thenReturn(ApplicationProcessState.FOREGROUND); - PerfSession session = new PerfSession("sessionId", mockClock); - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, session, mockAppStateMonitor); + PerfSession previousSession = new PerfSession("previousSession", mockClock); + previousSession.setGaugeAndEventCollectionEnabled(false); - assertThat(session.isSessionRunningTooLong()).isFalse(); + PerfSession newSession = new PerfSession("newSession", mockClock); + newSession.setGaugeAndEventCollectionEnabled(true); - when(mockTimer.getDurationMicros()) - .thenReturn(TimeUnit.HOURS.toMicros(5)); // Default Max Session Length is 4 hours + SessionManager testSessionManager = + new SessionManager(mockGaugeManager, previousSession, mockAppStateMonitor); + testSessionManager.updatePerfSession(newSession); + testSessionManager.setApplicationContext(mockApplicationContext); - assertThat(session.isSessionRunningTooLong()).isTrue(); - verify(mockGaugeManager, times(0)).logGaugeMetadata(any(), any()); + verify(mockGaugeManager, times(1)).initializeGaugeMetadataManager(mockApplicationContext); + verify(mockGaugeManager, times(1)) + .startCollectingGauges(newSession, ApplicationProcessState.FOREGROUND); } @Test 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 From c71fd96f4adfaf6e099a4ea08654227c9c9e08d6 Mon Sep 17 00:00:00 2001 From: Matthew Robertson Date: Thu, 20 Mar 2025 10:28:52 -0600 Subject: [PATCH 32/76] Use multi-process DataStore instead of Preferences DataStore (#6781) Use multi-process DataStore instead of Preferences DataStore. This change allows multiple processes to share the same datastore file safely. This reduces settings fetch to one per app run, not one per process. Also updated the TimeProvider to provide an object with explicit time units. This will make time less error prone. Removed all instances of `System.currentTimeMillis()` from tests, making them deterministic. --- firebase-sessions/CHANGELOG.md | 14 +- .../firebase-sessions.gradle.kts | 4 +- .../sessions/FirebaseSessionsComponent.kt | 60 +++--- .../sessions/FirebaseSessionsRegistrar.kt | 4 +- .../sessions/SessionDataStoreConfigs.kt | 40 ---- .../firebase/sessions/SessionDatastore.kt | 65 +++--- .../firebase/sessions/SessionGenerator.kt | 2 +- .../google/firebase/sessions/TimeProvider.kt | 15 +- .../sessions/settings/RemoteSettings.kt | 52 ++--- .../settings/RemoteSettingsFetcher.kt | 4 +- .../sessions/settings/SessionConfigs.kt | 58 ++++++ .../sessions/settings/SettingsCache.kt | 123 ++++------- .../firebase/sessions/SessionDatastoreTest.kt | 59 ++++++ .../firebase/sessions/SessionGeneratorTest.kt | 13 +- .../sessions/settings/RemoteSettingsTest.kt | 196 ++++++++---------- .../sessions/settings/SessionsSettingsTest.kt | 48 ++--- .../sessions/settings/SettingsCacheTest.kt | 169 ++++++++------- .../sessions/testing/FakeSettingsCache.kt | 52 +++++ .../sessions/testing/FakeTimeProvider.kt | 10 +- .../sessions/testing/TestDataStores.kt | 50 +++++ .../sessions/testing/TestSessionEventData.kt | 16 +- gradle/libs.versions.toml | 1 + 22 files changed, 575 insertions(+), 480 deletions(-) delete mode 100644 firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDataStoreConfigs.kt create mode 100644 firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SessionConfigs.kt create mode 100644 firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionDatastoreTest.kt create mode 100644 firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/FakeSettingsCache.kt create mode 100644 firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/TestDataStores.kt diff --git a/firebase-sessions/CHANGELOG.md b/firebase-sessions/CHANGELOG.md index 8353fbf9029..f73a860ee60 100644 --- a/firebase-sessions/CHANGELOG.md +++ b/firebase-sessions/CHANGELOG.md @@ -1,5 +1,5 @@ # Unreleased - +* [changed] Use multi-process DataStore instead of Preferences DataStore # 2.1.0 * [changed] Add warning for known issue b/328687152 @@ -7,21 +7,9 @@ * [changed] Updated datastore dependency to v1.1.3 to fix [CVE-2024-7254](https://github.com/advisories/GHSA-735f-pc8j-v9w8). - -## Kotlin -The Kotlin extensions library transitively includes the updated -`firebase-sessions` library. The Kotlin extensions library has no additional -updates. - # 2.0.9 * [fixed] Make AQS resilient to background init in multi-process apps. - -## Kotlin -The Kotlin extensions library transitively includes the updated -`firebase-sessions` library. The Kotlin extensions library has no additional -updates. - # 2.0.7 * [fixed] Removed extraneous logs that risk leaking internal identifiers. diff --git a/firebase-sessions/firebase-sessions.gradle.kts b/firebase-sessions/firebase-sessions.gradle.kts index b136a281660..23edc952d5e 100644 --- a/firebase-sessions/firebase-sessions.gradle.kts +++ b/firebase-sessions/firebase-sessions.gradle.kts @@ -21,6 +21,7 @@ plugins { id("firebase-vendor") id("kotlin-android") id("kotlin-kapt") + id("kotlinx-serialization") } firebaseLibrary { @@ -76,7 +77,8 @@ dependencies { implementation("com.google.android.datatransport:transport-api:3.2.0") implementation(libs.javax.inject) implementation(libs.androidx.annotation) - implementation(libs.androidx.datastore.preferences) + implementation(libs.androidx.datastore) + implementation(libs.kotlinx.serialization.json) vendor(libs.dagger.dagger) { exclude(group = "javax.inject", module = "javax.inject") } diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt index 5680c9cc0ec..99de9e4a3fc 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt @@ -19,23 +19,24 @@ package com.google.firebase.sessions import android.content.Context import android.util.Log import androidx.datastore.core.DataStore +import androidx.datastore.core.MultiProcessDataStoreFactory import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler -import androidx.datastore.preferences.core.PreferenceDataStoreFactory -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.emptyPreferences -import androidx.datastore.preferences.preferencesDataStoreFile +import androidx.datastore.dataStoreFile import com.google.android.datatransport.TransportFactory import com.google.firebase.FirebaseApp import com.google.firebase.annotations.concurrent.Background import com.google.firebase.annotations.concurrent.Blocking import com.google.firebase.inject.Provider import com.google.firebase.installations.FirebaseInstallationsApi -import com.google.firebase.sessions.ProcessDetailsProvider.getProcessName import com.google.firebase.sessions.settings.CrashlyticsSettingsFetcher import com.google.firebase.sessions.settings.LocalOverrideSettings import com.google.firebase.sessions.settings.RemoteSettings import com.google.firebase.sessions.settings.RemoteSettingsFetcher +import com.google.firebase.sessions.settings.SessionConfigs +import com.google.firebase.sessions.settings.SessionConfigsSerializer import com.google.firebase.sessions.settings.SessionsSettings +import com.google.firebase.sessions.settings.SettingsCache +import com.google.firebase.sessions.settings.SettingsCacheImpl import com.google.firebase.sessions.settings.SettingsProvider import dagger.Binds import dagger.BindsInstance @@ -45,10 +46,7 @@ import dagger.Provides import javax.inject.Qualifier import javax.inject.Singleton import kotlin.coroutines.CoroutineContext - -@Qualifier internal annotation class SessionConfigsDataStore - -@Qualifier internal annotation class SessionDetailsDataStore +import kotlinx.coroutines.CoroutineScope @Qualifier internal annotation class LocalOverrideSettingsProvider @@ -119,6 +117,8 @@ internal interface FirebaseSessionsComponent { @RemoteSettingsProvider fun remoteSettings(impl: RemoteSettings): SettingsProvider + @Binds @Singleton fun settingsCache(impl: SettingsCacheImpl): SettingsCache + companion object { private const val TAG = "FirebaseSessions" @@ -133,31 +133,37 @@ internal interface FirebaseSessionsComponent { @Provides @Singleton - @SessionConfigsDataStore - fun sessionConfigsDataStore(appContext: Context): DataStore = - PreferenceDataStoreFactory.create( + fun sessionConfigsDataStore( + appContext: Context, + @Blocking blockingDispatcher: CoroutineContext, + ): DataStore = + MultiProcessDataStoreFactory.create( + serializer = SessionConfigsSerializer, corruptionHandler = ReplaceFileCorruptionHandler { ex -> - Log.w(TAG, "CorruptionException in settings DataStore in ${getProcessName()}.", ex) - emptyPreferences() - } - ) { - appContext.preferencesDataStoreFile(SessionDataStoreConfigs.SETTINGS_CONFIG_NAME) - } + Log.w(TAG, "CorruptionException in session configs DataStore", ex) + SessionConfigsSerializer.defaultValue + }, + scope = CoroutineScope(blockingDispatcher), + produceFile = { appContext.dataStoreFile("aqs/sessionConfigsDataStore.data") }, + ) @Provides @Singleton - @SessionDetailsDataStore - fun sessionDetailsDataStore(appContext: Context): DataStore = - PreferenceDataStoreFactory.create( + fun sessionDataStore( + appContext: Context, + @Blocking blockingDispatcher: CoroutineContext, + ): DataStore = + MultiProcessDataStoreFactory.create( + serializer = SessionDataSerializer, corruptionHandler = ReplaceFileCorruptionHandler { ex -> - Log.w(TAG, "CorruptionException in sessions DataStore in ${getProcessName()}.", ex) - emptyPreferences() - } - ) { - appContext.preferencesDataStoreFile(SessionDataStoreConfigs.SESSIONS_CONFIG_NAME) - } + Log.w(TAG, "CorruptionException in session data DataStore", ex) + SessionDataSerializer.defaultValue + }, + scope = CoroutineScope(blockingDispatcher), + produceFile = { appContext.dataStoreFile("aqs/sessionDataStore.data") }, + ) } } } diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsRegistrar.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsRegistrar.kt index 5cb8de7a182..76c0c6330f4 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsRegistrar.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsRegistrar.kt @@ -19,7 +19,7 @@ package com.google.firebase.sessions import android.content.Context import android.util.Log import androidx.annotation.Keep -import androidx.datastore.preferences.preferencesDataStore +import androidx.datastore.core.MultiProcessDataStoreFactory import com.google.android.datatransport.TransportFactory import com.google.firebase.FirebaseApp import com.google.firebase.annotations.concurrent.Background @@ -84,7 +84,7 @@ internal class FirebaseSessionsRegistrar : ComponentRegistrar { init { try { - ::preferencesDataStore.javaClass + MultiProcessDataStoreFactory.javaClass } catch (ex: NoClassDefFoundError) { Log.w( TAG, diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDataStoreConfigs.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDataStoreConfigs.kt deleted file mode 100644 index 109e980e666..00000000000 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDataStoreConfigs.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2023 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.util.Base64 -import com.google.firebase.sessions.settings.SessionsSettings - -/** - * Util object for handling DataStore configs in multi-process apps safely. - * - * This can be removed when datastore-preferences:1.1.0 becomes stable. - */ -internal object SessionDataStoreConfigs { - /** Sanitized process name to use in config filenames. */ - private val PROCESS_NAME = - Base64.encodeToString( - ProcessDetailsProvider.getProcessName().encodeToByteArray(), - Base64.NO_WRAP or Base64.URL_SAFE, // URL safe is also filename safe. - ) - - /** Config name for [SessionDatastore] */ - val SESSIONS_CONFIG_NAME = "firebase_session_${PROCESS_NAME}_data" - - /** Config name for [SessionsSettings] */ - val SETTINGS_CONFIG_NAME = "firebase_session_${PROCESS_NAME}_settings" -} diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDatastore.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDatastore.kt index 2c4f243f942..b3b72b4d4d7 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDatastore.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDatastore.kt @@ -17,15 +17,15 @@ package com.google.firebase.sessions import android.util.Log +import androidx.datastore.core.CorruptionException import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.edit -import androidx.datastore.preferences.core.emptyPreferences -import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.core.Serializer import com.google.firebase.Firebase import com.google.firebase.annotations.concurrent.Background import com.google.firebase.app import java.io.IOException +import java.io.InputStream +import java.io.OutputStream import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject import javax.inject.Singleton @@ -33,11 +33,29 @@ import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json -/** Datastore for sessions information */ -internal data class FirebaseSessionsData(val sessionId: String?) +/** Data for sessions information */ +@Serializable internal data class SessionData(val sessionId: String?) + +/** DataStore json [Serializer] for [SessionData]. */ +internal object SessionDataSerializer : Serializer { + override val defaultValue = SessionData(sessionId = null) + + override suspend fun readFrom(input: InputStream): SessionData = + try { + Json.decodeFromString(input.readBytes().decodeToString()) + } catch (ex: Exception) { + throw CorruptionException("Cannot parse session data", ex) + } + + override suspend fun writeTo(t: SessionData, output: OutputStream) { + @Suppress("BlockingMethodInNonBlockingContext") // blockingDispatcher is safe for blocking calls + output.write(Json.encodeToString(SessionData.serializer(), t).encodeToByteArray()) + } +} /** Handles reading to and writing from the [DataStore]. */ internal interface SessionDatastore { @@ -61,23 +79,17 @@ internal class SessionDatastoreImpl @Inject constructor( @Background private val backgroundDispatcher: CoroutineContext, - @SessionDetailsDataStore private val dataStore: DataStore, + private val sessionDataStore: DataStore, ) : SessionDatastore { /** Most recent session from datastore is updated asynchronously whenever it changes */ - private val currentSessionFromDatastore = AtomicReference() + private val currentSessionFromDatastore = AtomicReference() - private object FirebaseSessionDataKeys { - val SESSION_ID = stringPreferencesKey("session_id") - } - - private val firebaseSessionDataFlow: Flow = - dataStore.data - .catch { exception -> - Log.e(TAG, "Error reading stored session data.", exception) - emit(emptyPreferences()) - } - .map { preferences -> mapSessionsData(preferences) } + private val firebaseSessionDataFlow: Flow = + sessionDataStore.data.catch { ex -> + Log.e(TAG, "Error reading stored session data.", ex) + emit(SessionDataSerializer.defaultValue) + } init { CoroutineScope(backgroundDispatcher).launch { @@ -88,19 +100,14 @@ constructor( override fun updateSessionId(sessionId: String) { CoroutineScope(backgroundDispatcher).launch { try { - dataStore.edit { preferences -> - preferences[FirebaseSessionDataKeys.SESSION_ID] = sessionId - } - } catch (e: IOException) { - Log.w(TAG, "Failed to update session Id: $e") + sessionDataStore.updateData { SessionData(sessionId) } + } catch (ex: IOException) { + Log.w(TAG, "Failed to update session Id", ex) } } } - override fun getCurrentSessionId() = currentSessionFromDatastore.get()?.sessionId - - private fun mapSessionsData(preferences: Preferences): FirebaseSessionsData = - FirebaseSessionsData(preferences[FirebaseSessionDataKeys.SESSION_ID]) + override fun getCurrentSessionId(): String? = currentSessionFromDatastore.get()?.sessionId private companion object { private const val TAG = "FirebaseSessionsRepo" diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionGenerator.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionGenerator.kt index 4c4775e8b24..409f9989348 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionGenerator.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionGenerator.kt @@ -60,7 +60,7 @@ constructor(private val timeProvider: TimeProvider, private val uuidGenerator: U sessionId = if (sessionIndex == 0) firstSessionId else generateSessionId(), firstSessionId, sessionIndex, - sessionStartTimestampUs = timeProvider.currentTimeUs(), + sessionStartTimestampUs = timeProvider.currentTime().us, ) return currentSession } diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/TimeProvider.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/TimeProvider.kt index b66b09af19f..869b64b2ff2 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/TimeProvider.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/TimeProvider.kt @@ -20,11 +20,17 @@ import android.os.SystemClock import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds +/** Time with accessors for microseconds, milliseconds, and seconds. */ +internal data class Time(val ms: Long) { + val us = ms * 1_000 + val seconds = ms / 1_000 +} + /** Time provider interface, for testing purposes. */ internal interface TimeProvider { fun elapsedRealtime(): Duration - fun currentTimeUs(): Long + fun currentTime(): Time } /** "Wall clock" time provider implementation. */ @@ -38,14 +44,11 @@ internal object TimeProviderImpl : TimeProvider { override fun elapsedRealtime(): Duration = SystemClock.elapsedRealtime().milliseconds /** - * Gets the current "wall clock" time in microseconds. + * Gets the current "wall clock" time. * * This clock can be set by the user or the phone network, so the time may jump backwards or * forwards unpredictably. This clock should only be used when correspondence with real-world * dates and times is important, such as in a calendar or alarm clock application. */ - override fun currentTimeUs(): Long = System.currentTimeMillis() * US_PER_MILLIS - - /** Microseconds per millisecond. */ - private const val US_PER_MILLIS = 1000L + override fun currentTime(): Time = Time(ms = System.currentTimeMillis()) } diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettings.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettings.kt index 67a48bc7924..b715cd9f79c 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettings.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettings.kt @@ -19,18 +19,17 @@ package com.google.firebase.sessions.settings import android.os.Build import android.util.Log import androidx.annotation.VisibleForTesting -import com.google.firebase.annotations.concurrent.Background import com.google.firebase.installations.FirebaseInstallationsApi import com.google.firebase.sessions.ApplicationInfo import com.google.firebase.sessions.InstallationId +import com.google.firebase.sessions.TimeProvider import dagger.Lazy import javax.inject.Inject import javax.inject.Singleton -import kotlin.coroutines.CoroutineContext import kotlin.time.Duration +import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.json.JSONException @@ -40,7 +39,7 @@ import org.json.JSONObject internal class RemoteSettings @Inject constructor( - @Background private val backgroundDispatcher: CoroutineContext, + private val timeProvider: TimeProvider, private val firebaseInstallationsApi: FirebaseInstallationsApi, private val appInfo: ApplicationInfo, private val configsFetcher: CrashlyticsSettingsFetcher, @@ -90,10 +89,9 @@ constructor( val options = mapOf( "X-Crashlytics-Installation-ID" to installationId, - "X-Crashlytics-Device-Model" to - removeForwardSlashesIn(String.format("%s/%s", Build.MANUFACTURER, Build.MODEL)), - "X-Crashlytics-OS-Build-Version" to removeForwardSlashesIn(Build.VERSION.INCREMENTAL), - "X-Crashlytics-OS-Display-Version" to removeForwardSlashesIn(Build.VERSION.RELEASE), + "X-Crashlytics-Device-Model" to sanitize("${Build.MANUFACTURER}${Build.MODEL}"), + "X-Crashlytics-OS-Build-Version" to sanitize(Build.VERSION.INCREMENTAL), + "X-Crashlytics-OS-Display-Version" to sanitize(Build.VERSION.RELEASE), "X-Crashlytics-API-Client-Version" to appInfo.sessionSdkVersion, ) @@ -129,22 +127,19 @@ constructor( } } - sessionsEnabled?.let { settingsCache.updateSettingsEnabled(sessionsEnabled) } - - sessionTimeoutSeconds?.let { - settingsCache.updateSessionRestartTimeout(sessionTimeoutSeconds) - } - - sessionSamplingRate?.let { settingsCache.updateSamplingRate(sessionSamplingRate) } - - cacheDuration?.let { settingsCache.updateSessionCacheDuration(cacheDuration) } - ?: let { settingsCache.updateSessionCacheDuration(86400) } - - settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) + settingsCache.updateConfigs( + SessionConfigs( + sessionsEnabled = sessionsEnabled, + sessionTimeoutSeconds = sessionTimeoutSeconds, + sessionSamplingRate = sessionSamplingRate, + cacheDurationSeconds = cacheDuration ?: defaultCacheDuration, + cacheUpdatedTimeMs = timeProvider.currentTime().ms, + ) + ) }, onFailure = { msg -> // Network request failed here. - Log.e(TAG, "Error failing to fetch the remote configs: $msg") + Log.e(TAG, "Error failed to fetch the remote configs: $msg") }, ) } @@ -153,18 +148,17 @@ constructor( override fun isSettingsStale(): Boolean = settingsCache.hasCacheExpired() @VisibleForTesting - internal fun clearCachedSettings() { - val scope = CoroutineScope(backgroundDispatcher) - scope.launch { settingsCache.removeConfigs() } + internal fun clearCachedSettings() = runBlocking { + settingsCache.updateConfigs(SessionConfigsSerializer.defaultValue) } - private fun removeForwardSlashesIn(s: String): String { - return s.replace(FORWARD_SLASH_STRING.toRegex(), "") - } + private fun sanitize(s: String) = s.replace(sanitizeRegex, "") private companion object { const val TAG = "SessionConfigFetcher" - const val FORWARD_SLASH_STRING: String = "/" + val defaultCacheDuration = 24.hours.inWholeSeconds.toInt() + + val sanitizeRegex = "/".toRegex() } } diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettingsFetcher.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettingsFetcher.kt index 92d530f2fa1..bd45ec8fb24 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettingsFetcher.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettingsFetcher.kt @@ -17,7 +17,7 @@ package com.google.firebase.sessions.settings import android.net.Uri -import com.google.firebase.annotations.concurrent.Background +import com.google.firebase.annotations.concurrent.Blocking import com.google.firebase.sessions.ApplicationInfo import java.io.BufferedReader import java.io.InputStreamReader @@ -42,7 +42,7 @@ internal class RemoteSettingsFetcher @Inject constructor( private val appInfo: ApplicationInfo, - @Background private val blockingDispatcher: CoroutineContext, + @Blocking private val blockingDispatcher: CoroutineContext, ) : CrashlyticsSettingsFetcher { @Suppress("BlockingMethodInNonBlockingContext") // blockingDispatcher is safe for blocking calls. override suspend fun doConfigFetch( 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..8d7e2484675 --- /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 cacheUpdatedTimeMs: Long?, +) + +/** DataStore json [Serializer] for [SessionConfigs]. */ +internal object SessionConfigsSerializer : Serializer { + override val defaultValue = + SessionConfigs( + sessionsEnabled = null, + sessionSamplingRate = null, + sessionTimeoutSeconds = null, + cacheDurationSeconds = null, + cacheUpdatedTimeMs = 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/main/kotlin/com/google/firebase/sessions/settings/SettingsCache.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SettingsCache.kt index 2e60e51650a..468bbad6b7a 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SettingsCache.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SettingsCache.kt @@ -17,128 +17,77 @@ package com.google.firebase.sessions.settings import android.util.Log -import androidx.annotation.VisibleForTesting import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.booleanPreferencesKey -import androidx.datastore.preferences.core.doublePreferencesKey -import androidx.datastore.preferences.core.edit -import androidx.datastore.preferences.core.intPreferencesKey -import androidx.datastore.preferences.core.longPreferencesKey -import com.google.firebase.sessions.SessionConfigsDataStore +import com.google.firebase.sessions.TimeProvider import java.io.IOException import javax.inject.Inject import javax.inject.Singleton import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking -internal data class SessionConfigs( - val sessionEnabled: Boolean?, - val sessionSamplingRate: Double?, - val sessionRestartTimeout: Int?, - val cacheDuration: Int?, - val cacheUpdatedTime: Long?, -) +internal interface SettingsCache { + fun hasCacheExpired(): Boolean + + fun sessionsEnabled(): Boolean? + + fun sessionSamplingRate(): Double? + + fun sessionRestartTimeout(): Int? + + suspend fun updateConfigs(sessionConfigs: SessionConfigs) +} @Singleton -internal class SettingsCache +internal class SettingsCacheImpl @Inject -constructor(@SessionConfigsDataStore private val dataStore: DataStore) { - private lateinit var sessionConfigs: SessionConfigs +constructor( + private val timeProvider: TimeProvider, + private val sessionConfigsDataStore: DataStore, +) : SettingsCache { + private var sessionConfigs: SessionConfigs init { // Block until the cache is loaded from disk to ensure cache // values are valid and readable from the main thread on init. - runBlocking { updateSessionConfigs(dataStore.data.first().toPreferences()) } + runBlocking { sessionConfigs = sessionConfigsDataStore.data.first() } } - /** Update session configs from the given [preferences]. */ - private fun updateSessionConfigs(preferences: Preferences) { - sessionConfigs = - SessionConfigs( - sessionEnabled = preferences[SESSIONS_ENABLED], - sessionSamplingRate = preferences[SAMPLING_RATE], - sessionRestartTimeout = preferences[RESTART_TIMEOUT_SECONDS], - cacheDuration = preferences[CACHE_DURATION_SECONDS], - cacheUpdatedTime = preferences[CACHE_UPDATED_TIME], - ) - } + override fun hasCacheExpired(): Boolean { + val cacheUpdatedTimeMs = sessionConfigs.cacheUpdatedTimeMs + val cacheDurationSeconds = sessionConfigs.cacheDurationSeconds - internal fun hasCacheExpired(): Boolean { - val cacheUpdatedTime = sessionConfigs.cacheUpdatedTime - val cacheDuration = sessionConfigs.cacheDuration - - if (cacheUpdatedTime != null && cacheDuration != null) { - val timeDifferenceSeconds = (System.currentTimeMillis() - cacheUpdatedTime) / 1000 - if (timeDifferenceSeconds < cacheDuration) { + if (cacheUpdatedTimeMs != null && cacheDurationSeconds != null) { + val timeDifferenceSeconds = (timeProvider.currentTime().ms - cacheUpdatedTimeMs) / 1000 + if (timeDifferenceSeconds < cacheDurationSeconds) { return false } } return true } - fun sessionsEnabled(): Boolean? = sessionConfigs.sessionEnabled - - fun sessionSamplingRate(): Double? = sessionConfigs.sessionSamplingRate - - fun sessionRestartTimeout(): Int? = sessionConfigs.sessionRestartTimeout - - suspend fun updateSettingsEnabled(enabled: Boolean?) { - updateConfigValue(SESSIONS_ENABLED, enabled) - } - - suspend fun updateSamplingRate(rate: Double?) { - updateConfigValue(SAMPLING_RATE, rate) - } - - suspend fun updateSessionRestartTimeout(timeoutInSeconds: Int?) { - updateConfigValue(RESTART_TIMEOUT_SECONDS, timeoutInSeconds) - } + override fun sessionsEnabled(): Boolean? = sessionConfigs.sessionsEnabled - suspend fun updateSessionCacheDuration(cacheDurationInSeconds: Int?) { - updateConfigValue(CACHE_DURATION_SECONDS, cacheDurationInSeconds) - } + override fun sessionSamplingRate(): Double? = sessionConfigs.sessionSamplingRate - suspend fun updateSessionCacheUpdatedTime(cacheUpdatedTime: Long?) { - updateConfigValue(CACHE_UPDATED_TIME, cacheUpdatedTime) - } + override fun sessionRestartTimeout(): Int? = sessionConfigs.sessionTimeoutSeconds - @VisibleForTesting - internal suspend fun removeConfigs() { + override suspend fun updateConfigs(sessionConfigs: SessionConfigs) { try { - dataStore.edit { preferences -> - preferences.clear() - updateSessionConfigs(preferences) - } - } catch (e: IOException) { - Log.w(TAG, "Failed to remove config values: $e") + sessionConfigsDataStore.updateData { sessionConfigs } + this.sessionConfigs = sessionConfigs + } catch (ex: IOException) { + Log.w(TAG, "Failed to update config values: $ex") } } - /** Updated the config value, or remove the key if the value is null. */ - private suspend fun updateConfigValue(key: Preferences.Key, value: T?) { - // TODO(mrober): Refactor these to update all the values in one transaction. + internal suspend fun removeConfigs() = try { - dataStore.edit { preferences -> - if (value != null) { - preferences[key] = value - } else { - preferences.remove(key) - } - updateSessionConfigs(preferences) - } + sessionConfigsDataStore.updateData { SessionConfigsSerializer.defaultValue } } catch (ex: IOException) { - Log.w(TAG, "Failed to update cache config value: $ex") + Log.w(TAG, "Failed to remove config values: $ex") } - } private companion object { const val TAG = "SettingsCache" - - val SESSIONS_ENABLED = booleanPreferencesKey("firebase_sessions_enabled") - val SAMPLING_RATE = doublePreferencesKey("firebase_sessions_sampling_rate") - val RESTART_TIMEOUT_SECONDS = intPreferencesKey("firebase_sessions_restart_timeout") - val CACHE_DURATION_SECONDS = intPreferencesKey("firebase_sessions_cache_duration") - val CACHE_UPDATED_TIME = longPreferencesKey("firebase_sessions_cache_updated_time") } } 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..7e94eb3113e --- /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("sessionDataTestDataStore.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/SessionGeneratorTest.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionGeneratorTest.kt index 7126bae4dbf..bf260e73a4f 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionGeneratorTest.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionGeneratorTest.kt @@ -16,12 +16,15 @@ package com.google.firebase.sessions +import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import com.google.firebase.sessions.testing.FakeTimeProvider import com.google.firebase.sessions.testing.FakeUuidGenerator -import com.google.firebase.sessions.testing.TestSessionEventData.TEST_SESSION_TIMESTAMP_US +import com.google.firebase.sessions.testing.TestSessionEventData.TEST_SESSION_TIMESTAMP import org.junit.Test +import org.junit.runner.RunWith +@RunWith(AndroidJUnit4::class) class SessionGeneratorTest { private fun isValidSessionId(sessionId: String): Boolean { if (sessionId.length != 32) { @@ -96,7 +99,7 @@ class SessionGeneratorTest { sessionId = SESSION_ID_1, firstSessionId = SESSION_ID_1, sessionIndex = 0, - sessionStartTimestampUs = TEST_SESSION_TIMESTAMP_US, + sessionStartTimestampUs = TEST_SESSION_TIMESTAMP.us, ) ) } @@ -119,7 +122,7 @@ class SessionGeneratorTest { sessionId = SESSION_ID_1, firstSessionId = SESSION_ID_1, sessionIndex = 0, - sessionStartTimestampUs = TEST_SESSION_TIMESTAMP_US, + sessionStartTimestampUs = TEST_SESSION_TIMESTAMP.us, ) ) @@ -135,7 +138,7 @@ class SessionGeneratorTest { sessionId = SESSION_ID_2, firstSessionId = SESSION_ID_1, sessionIndex = 1, - sessionStartTimestampUs = TEST_SESSION_TIMESTAMP_US, + sessionStartTimestampUs = TEST_SESSION_TIMESTAMP.us, ) ) @@ -151,7 +154,7 @@ class SessionGeneratorTest { sessionId = SESSION_ID_3, firstSessionId = SESSION_ID_1, sessionIndex = 2, - sessionStartTimestampUs = TEST_SESSION_TIMESTAMP_US, + sessionStartTimestampUs = TEST_SESSION_TIMESTAMP.us, ) ) } diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/RemoteSettingsTest.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/RemoteSettingsTest.kt index e4fb0b00148..ccaf4f8954d 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/RemoteSettingsTest.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/RemoteSettingsTest.kt @@ -16,24 +16,22 @@ package com.google.firebase.sessions.settings -import androidx.datastore.preferences.core.PreferenceDataStoreFactory -import androidx.datastore.preferences.preferencesDataStoreFile import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import com.google.firebase.FirebaseApp -import com.google.firebase.concurrent.TestOnlyExecutors import com.google.firebase.installations.FirebaseInstallationsApi import com.google.firebase.sessions.ApplicationInfo import com.google.firebase.sessions.SessionEvents +import com.google.firebase.sessions.TimeProvider import com.google.firebase.sessions.testing.FakeFirebaseApp import com.google.firebase.sessions.testing.FakeFirebaseInstallations import com.google.firebase.sessions.testing.FakeRemoteConfigFetcher -import kotlin.coroutines.CoroutineContext +import com.google.firebase.sessions.testing.FakeSettingsCache +import com.google.firebase.sessions.testing.FakeTimeProvider import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -53,22 +51,16 @@ class RemoteSettingsTest { fun remoteSettings_successfulFetchCachesValues() = runTest(UnconfinedTestDispatcher()) { val firebaseApp = FakeFirebaseApp().firebaseApp - val context = firebaseApp.applicationContext val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") val fakeFetcher = FakeRemoteConfigFetcher() val remoteSettings = buildRemoteSettings( - TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext, + FakeTimeProvider(), firebaseInstallations, SessionEvents.getApplicationInfo(firebaseApp), fakeFetcher, - SettingsCache( - PreferenceDataStoreFactory.create( - scope = this, - produceFile = { context.preferencesDataStoreFile(SESSION_TEST_CONFIGS_NAME) }, - ) - ), + FakeSettingsCache(), ) runCurrent() @@ -90,120 +82,100 @@ class RemoteSettingsTest { } @Test - fun remoteSettings_successfulFetchWithLessConfigsCachesOnlyReceivedValues() = - runTest(UnconfinedTestDispatcher()) { - val firebaseApp = FakeFirebaseApp().firebaseApp - val context = firebaseApp.applicationContext - val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") - val fakeFetcher = FakeRemoteConfigFetcher() - - val remoteSettings = - buildRemoteSettings( - TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext, - firebaseInstallations, - SessionEvents.getApplicationInfo(firebaseApp), - fakeFetcher, - SettingsCache( - PreferenceDataStoreFactory.create( - scope = this, - produceFile = { context.preferencesDataStoreFile(SESSION_TEST_CONFIGS_NAME) }, - ) - ), - ) - - runCurrent() - - assertThat(remoteSettings.sessionEnabled).isNull() - assertThat(remoteSettings.samplingRate).isNull() - assertThat(remoteSettings.sessionRestartTimeout).isNull() - - val fetchedResponse = JSONObject(VALID_RESPONSE) - fetchedResponse.getJSONObject("app_quality").remove("sessions_enabled") - fakeFetcher.responseJSONObject = fetchedResponse - remoteSettings.updateSettings() - - runCurrent() - - assertThat(remoteSettings.sessionEnabled).isNull() - assertThat(remoteSettings.samplingRate).isEqualTo(0.75) - assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) - - remoteSettings.clearCachedSettings() - } + fun remoteSettings_successfulFetchWithLessConfigsCachesOnlyReceivedValues() = runTest { + val firebaseApp = FakeFirebaseApp().firebaseApp + val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") + val fakeFetcher = FakeRemoteConfigFetcher() + + val remoteSettings = + buildRemoteSettings( + FakeTimeProvider(), + firebaseInstallations, + SessionEvents.getApplicationInfo(firebaseApp), + fakeFetcher, + FakeSettingsCache(), + ) + + runCurrent() + + assertThat(remoteSettings.sessionEnabled).isNull() + assertThat(remoteSettings.samplingRate).isNull() + assertThat(remoteSettings.sessionRestartTimeout).isNull() + + val fetchedResponse = JSONObject(VALID_RESPONSE) + fetchedResponse.getJSONObject("app_quality").remove("sessions_enabled") + fakeFetcher.responseJSONObject = fetchedResponse + remoteSettings.updateSettings() + + runCurrent() + + assertThat(remoteSettings.sessionEnabled).isNull() + assertThat(remoteSettings.samplingRate).isEqualTo(0.75) + assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) + + remoteSettings.clearCachedSettings() + } @Test - fun remoteSettings_successfulReFetchUpdatesCache() = - runTest(UnconfinedTestDispatcher()) { - val firebaseApp = FakeFirebaseApp().firebaseApp - val context = firebaseApp.applicationContext - val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") - val fakeFetcher = FakeRemoteConfigFetcher() + fun remoteSettings_successfulReFetchUpdatesCache() = runTest { + val firebaseApp = FakeFirebaseApp().firebaseApp + val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") + val fakeFetcher = FakeRemoteConfigFetcher() + val fakeTimeProvider = FakeTimeProvider() - val remoteSettings = - buildRemoteSettings( - TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext, - firebaseInstallations, - SessionEvents.getApplicationInfo(firebaseApp), - fakeFetcher, - SettingsCache( - PreferenceDataStoreFactory.create( - scope = this, - produceFile = { context.preferencesDataStoreFile(SESSION_TEST_CONFIGS_NAME) }, - ) - ), - ) + val remoteSettings = + buildRemoteSettings( + fakeTimeProvider, + firebaseInstallations, + SessionEvents.getApplicationInfo(firebaseApp), + fakeFetcher, + FakeSettingsCache(fakeTimeProvider), + ) - val fetchedResponse = JSONObject(VALID_RESPONSE) - fetchedResponse.getJSONObject("app_quality").put("cache_duration", 1) - fakeFetcher.responseJSONObject = fetchedResponse - remoteSettings.updateSettings() + val fetchedResponse = JSONObject(VALID_RESPONSE) + fetchedResponse.getJSONObject("app_quality").put("cache_duration", 1) + fakeFetcher.responseJSONObject = fetchedResponse + remoteSettings.updateSettings() - runCurrent() + runCurrent() - assertThat(remoteSettings.sessionEnabled).isFalse() - assertThat(remoteSettings.samplingRate).isEqualTo(0.75) - assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) + assertThat(remoteSettings.sessionEnabled).isFalse() + assertThat(remoteSettings.samplingRate).isEqualTo(0.75) + assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) - fetchedResponse.getJSONObject("app_quality").put("sessions_enabled", true) - fetchedResponse.getJSONObject("app_quality").put("sampling_rate", 0.25) - fetchedResponse.getJSONObject("app_quality").put("session_timeout_seconds", 1200) + fetchedResponse.getJSONObject("app_quality").put("sessions_enabled", true) + fetchedResponse.getJSONObject("app_quality").put("sampling_rate", 0.25) + fetchedResponse.getJSONObject("app_quality").put("session_timeout_seconds", 1200) - // TODO(mrober): Fix these so we don't need to sleep. Maybe use FakeTime? - // Sleep for a second before updating configs - Thread.sleep(2000) + fakeTimeProvider.addInterval(31.minutes) - fakeFetcher.responseJSONObject = fetchedResponse - remoteSettings.updateSettings() + fakeFetcher.responseJSONObject = fetchedResponse + remoteSettings.updateSettings() - runCurrent() + runCurrent() - assertThat(remoteSettings.sessionEnabled).isTrue() - assertThat(remoteSettings.samplingRate).isEqualTo(0.25) - assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(20.minutes) + assertThat(remoteSettings.sessionEnabled).isTrue() + assertThat(remoteSettings.samplingRate).isEqualTo(0.25) + assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(20.minutes) - remoteSettings.clearCachedSettings() - } + remoteSettings.clearCachedSettings() + } @Test fun remoteSettings_successfulFetchWithEmptyConfigRetainsOldConfigs() = runTest(UnconfinedTestDispatcher()) { val firebaseApp = FakeFirebaseApp().firebaseApp - val context = firebaseApp.applicationContext val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") val fakeFetcher = FakeRemoteConfigFetcher() + val fakeTimeProvider = FakeTimeProvider() val remoteSettings = buildRemoteSettings( - TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext, + fakeTimeProvider, firebaseInstallations, SessionEvents.getApplicationInfo(firebaseApp), fakeFetcher, - SettingsCache( - PreferenceDataStoreFactory.create( - scope = this, - produceFile = { context.preferencesDataStoreFile(SESSION_TEST_CONFIGS_NAME) }, - ) - ), + FakeSettingsCache(), ) val fetchedResponse = JSONObject(VALID_RESPONSE) @@ -212,6 +184,7 @@ class RemoteSettingsTest { remoteSettings.updateSettings() runCurrent() + fakeTimeProvider.addInterval(31.seconds) assertThat(remoteSettings.sessionEnabled).isFalse() assertThat(remoteSettings.samplingRate).isEqualTo(0.75) @@ -226,6 +199,7 @@ class RemoteSettingsTest { remoteSettings.updateSettings() runCurrent() + Thread.sleep(30) assertThat(remoteSettings.sessionEnabled).isFalse() assertThat(remoteSettings.samplingRate).isEqualTo(0.75) @@ -249,7 +223,6 @@ class RemoteSettingsTest { // - Third fetch should exit even earlier, never having gone into the mutex. val firebaseApp = FakeFirebaseApp().firebaseApp - val context = firebaseApp.applicationContext val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") val fakeFetcherWithDelay = FakeRemoteConfigFetcher(JSONObject(VALID_RESPONSE), networkDelay = 3.seconds) @@ -260,16 +233,11 @@ class RemoteSettingsTest { val remoteSettingsWithDelay = buildRemoteSettings( - TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext, + FakeTimeProvider(), firebaseInstallations, SessionEvents.getApplicationInfo(firebaseApp), - fakeFetcherWithDelay, - SettingsCache( - PreferenceDataStoreFactory.create( - scope = this, - produceFile = { context.preferencesDataStoreFile(SESSION_TEST_CONFIGS_NAME) }, - ) - ), + configsFetcher = fakeFetcherWithDelay, + FakeSettingsCache(), ) // Do the first fetch. This one should fetched the configsFetcher. @@ -298,8 +266,6 @@ class RemoteSettingsTest { } internal companion object { - const val SESSION_TEST_CONFIGS_NAME = "firebase_session_settings_test" - const val VALID_RESPONSE = """ { @@ -329,14 +295,14 @@ class RemoteSettingsTest { * the test code. */ fun buildRemoteSettings( - backgroundDispatcher: CoroutineContext, + timeProvider: TimeProvider, firebaseInstallationsApi: FirebaseInstallationsApi, appInfo: ApplicationInfo, configsFetcher: CrashlyticsSettingsFetcher, settingsCache: SettingsCache, ): RemoteSettings = RemoteSettings_Factory.create( - { backgroundDispatcher }, + { timeProvider }, { firebaseInstallationsApi }, { appInfo }, { configsFetcher }, diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SessionsSettingsTest.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SessionsSettingsTest.kt index 12f40e7cca8..f87d773b970 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SessionsSettingsTest.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SessionsSettingsTest.kt @@ -17,20 +17,18 @@ package com.google.firebase.sessions.settings import android.os.Bundle -import androidx.datastore.preferences.core.PreferenceDataStoreFactory -import androidx.datastore.preferences.preferencesDataStoreFile import com.google.common.truth.Truth.assertThat import com.google.firebase.FirebaseApp -import com.google.firebase.concurrent.TestOnlyExecutors -import com.google.firebase.sessions.SessionDataStoreConfigs import com.google.firebase.sessions.SessionEvents +import com.google.firebase.sessions.settings.RemoteSettingsTest.Companion.buildRemoteSettings import com.google.firebase.sessions.testing.FakeFirebaseApp import com.google.firebase.sessions.testing.FakeFirebaseInstallations import com.google.firebase.sessions.testing.FakeRemoteConfigFetcher +import com.google.firebase.sessions.testing.FakeSettingsCache import com.google.firebase.sessions.testing.FakeSettingsProvider +import com.google.firebase.sessions.testing.FakeTimeProvider import kotlin.time.Duration.Companion.minutes import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -107,17 +105,12 @@ class SessionsSettingsTest { val fakeFetcher = FakeRemoteConfigFetcher(JSONObject(VALID_RESPONSE)) val remoteSettings = - RemoteSettingsTest.buildRemoteSettings( - TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext, + buildRemoteSettings( + FakeTimeProvider(), firebaseInstallations, SessionEvents.getApplicationInfo(firebaseApp), fakeFetcher, - SettingsCache( - PreferenceDataStoreFactory.create( - scope = this, - produceFile = { context.preferencesDataStoreFile(SESSION_TEST_CONFIGS_NAME) }, - ) - ), + FakeSettingsCache(), ) val sessionsSettings = @@ -150,17 +143,12 @@ class SessionsSettingsTest { val fakeFetcher = FakeRemoteConfigFetcher(JSONObject(VALID_RESPONSE)) val remoteSettings = - RemoteSettingsTest.buildRemoteSettings( - TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext, + buildRemoteSettings( + FakeTimeProvider(), firebaseInstallations, SessionEvents.getApplicationInfo(firebaseApp), fakeFetcher, - SettingsCache( - PreferenceDataStoreFactory.create( - scope = this, - produceFile = { context.preferencesDataStoreFile(SESSION_TEST_CONFIGS_NAME) }, - ) - ), + FakeSettingsCache(), ) val sessionsSettings = @@ -199,17 +187,12 @@ class SessionsSettingsTest { fakeFetcher.responseJSONObject = JSONObject(invalidResponse) val remoteSettings = - RemoteSettingsTest.buildRemoteSettings( - TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext, + buildRemoteSettings( + FakeTimeProvider(), firebaseInstallations, SessionEvents.getApplicationInfo(firebaseApp), fakeFetcher, - SettingsCache( - PreferenceDataStoreFactory.create( - scope = this, - produceFile = { context.preferencesDataStoreFile(SESSION_TEST_CONFIGS_NAME) }, - ) - ), + FakeSettingsCache(), ) val sessionsSettings = @@ -229,19 +212,12 @@ class SessionsSettingsTest { remoteSettings.clearCachedSettings() } - @Test - fun sessionSettings_dataStorePreferencesNameIsFilenameSafe() { - assertThat(SessionDataStoreConfigs.SESSIONS_CONFIG_NAME).matches("^[a-zA-Z0-9_=]+\$") - } - @After fun cleanUp() { FirebaseApp.clearInstancesForTest() } private companion object { - const val SESSION_TEST_CONFIGS_NAME = "firebase_session_settings_test" - const val VALID_RESPONSE = """ { diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SettingsCacheTest.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SettingsCacheTest.kt index c4d35c86456..729208c33ca 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SettingsCacheTest.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SettingsCacheTest.kt @@ -16,30 +16,23 @@ package com.google.firebase.sessions.settings -import android.content.Context -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.preferencesDataStore import com.google.common.truth.Truth.assertThat import com.google.firebase.FirebaseApp -import com.google.firebase.sessions.testing.FakeFirebaseApp -import kotlinx.coroutines.ExperimentalCoroutinesApi +import com.google.firebase.sessions.testing.FakeTimeProvider +import com.google.firebase.sessions.testing.TestDataStores import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner -@OptIn(ExperimentalCoroutinesApi::class) @RunWith(RobolectricTestRunner::class) class SettingsCacheTest { - private val Context.dataStore: DataStore by - preferencesDataStore(name = SESSION_TEST_CONFIGS_NAME) @Test fun sessionCache_returnsEmptyCache() = runTest { - val context = FakeFirebaseApp().firebaseApp.applicationContext - val settingsCache = SettingsCache(context.dataStore) + val fakeTimeProvider = FakeTimeProvider() + val settingsCache = SettingsCacheImpl(fakeTimeProvider, TestDataStores.sessionConfigsDataStore) assertThat(settingsCache.sessionSamplingRate()).isNull() assertThat(settingsCache.sessionsEnabled()).isNull() @@ -49,14 +42,18 @@ class SettingsCacheTest { @Test fun settingConfigsReturnsCachedValue() = runTest { - val context = FakeFirebaseApp().firebaseApp.applicationContext - val settingsCache = SettingsCache(context.dataStore) - - settingsCache.updateSettingsEnabled(false) - settingsCache.updateSamplingRate(0.25) - settingsCache.updateSessionRestartTimeout(600) - settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) - settingsCache.updateSessionCacheDuration(1000) + val fakeTimeProvider = FakeTimeProvider() + val settingsCache = SettingsCacheImpl(fakeTimeProvider, TestDataStores.sessionConfigsDataStore) + + settingsCache.updateConfigs( + SessionConfigs( + sessionsEnabled = false, + sessionSamplingRate = 0.25, + sessionTimeoutSeconds = 600, + cacheUpdatedTimeMs = fakeTimeProvider.currentTime().ms, + cacheDurationSeconds = 1000, + ) + ) assertThat(settingsCache.sessionsEnabled()).isFalse() assertThat(settingsCache.sessionSamplingRate()).isEqualTo(0.25) @@ -69,17 +66,22 @@ class SettingsCacheTest { @Test fun settingConfigsReturnsPreviouslyStoredValue() = runTest { - val context = FakeFirebaseApp().firebaseApp.applicationContext - val settingsCache = SettingsCache(context.dataStore) - - settingsCache.updateSettingsEnabled(false) - settingsCache.updateSamplingRate(0.25) - settingsCache.updateSessionRestartTimeout(600) - settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) - settingsCache.updateSessionCacheDuration(1000) + val fakeTimeProvider = FakeTimeProvider() + val settingsCache = SettingsCacheImpl(fakeTimeProvider, TestDataStores.sessionConfigsDataStore) + + settingsCache.updateConfigs( + SessionConfigs( + sessionsEnabled = false, + sessionSamplingRate = 0.25, + sessionTimeoutSeconds = 600, + cacheUpdatedTimeMs = fakeTimeProvider.currentTime().ms, + cacheDurationSeconds = 1000, + ) + ) // Create a new instance to imitate a second app launch. - val newSettingsCache = SettingsCache(context.dataStore) + val newSettingsCache = + SettingsCacheImpl(fakeTimeProvider, TestDataStores.sessionConfigsDataStore) assertThat(newSettingsCache.sessionsEnabled()).isFalse() assertThat(newSettingsCache.sessionSamplingRate()).isEqualTo(0.25) @@ -93,14 +95,18 @@ class SettingsCacheTest { @Test fun settingConfigsReturnsCacheExpiredWithShortCacheDuration() = runTest { - val context = FakeFirebaseApp().firebaseApp.applicationContext - val settingsCache = SettingsCache(context.dataStore) - - settingsCache.updateSettingsEnabled(false) - settingsCache.updateSamplingRate(0.25) - settingsCache.updateSessionRestartTimeout(600) - settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) - settingsCache.updateSessionCacheDuration(0) + val fakeTimeProvider = FakeTimeProvider() + val settingsCache = SettingsCacheImpl(fakeTimeProvider, TestDataStores.sessionConfigsDataStore) + + settingsCache.updateConfigs( + SessionConfigs( + sessionsEnabled = false, + sessionSamplingRate = 0.25, + sessionTimeoutSeconds = 600, + cacheUpdatedTimeMs = fakeTimeProvider.currentTime().ms, + cacheDurationSeconds = 0, + ) + ) assertThat(settingsCache.sessionSamplingRate()).isEqualTo(0.25) assertThat(settingsCache.sessionsEnabled()).isFalse() @@ -112,13 +118,18 @@ class SettingsCacheTest { @Test fun settingConfigsReturnsCachedValueWithPartialConfigs() = runTest { - val context = FakeFirebaseApp().firebaseApp.applicationContext - val settingsCache = SettingsCache(context.dataStore) - - settingsCache.updateSettingsEnabled(false) - settingsCache.updateSamplingRate(0.25) - settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) - settingsCache.updateSessionCacheDuration(1000) + val fakeTimeProvider = FakeTimeProvider() + val settingsCache = SettingsCacheImpl(fakeTimeProvider, TestDataStores.sessionConfigsDataStore) + + settingsCache.updateConfigs( + SessionConfigs( + sessionsEnabled = false, + sessionSamplingRate = 0.25, + sessionTimeoutSeconds = null, + cacheUpdatedTimeMs = fakeTimeProvider.currentTime().ms, + cacheDurationSeconds = 1000, + ) + ) assertThat(settingsCache.sessionSamplingRate()).isEqualTo(0.25) assertThat(settingsCache.sessionsEnabled()).isFalse() @@ -130,25 +141,33 @@ class SettingsCacheTest { @Test fun settingConfigsAllowsUpdateConfigsAndCachesValues() = runTest { - val context = FakeFirebaseApp().firebaseApp.applicationContext - val settingsCache = SettingsCache(context.dataStore) - - settingsCache.updateSettingsEnabled(false) - settingsCache.updateSamplingRate(0.25) - settingsCache.updateSessionRestartTimeout(600) - settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) - settingsCache.updateSessionCacheDuration(1000) + val fakeTimeProvider = FakeTimeProvider() + val settingsCache = SettingsCacheImpl(fakeTimeProvider, TestDataStores.sessionConfigsDataStore) + + settingsCache.updateConfigs( + SessionConfigs( + sessionsEnabled = false, + sessionSamplingRate = 0.25, + sessionTimeoutSeconds = 600, + cacheUpdatedTimeMs = fakeTimeProvider.currentTime().ms, + cacheDurationSeconds = 1000, + ) + ) assertThat(settingsCache.sessionSamplingRate()).isEqualTo(0.25) assertThat(settingsCache.sessionsEnabled()).isFalse() assertThat(settingsCache.sessionRestartTimeout()).isEqualTo(600) assertThat(settingsCache.hasCacheExpired()).isFalse() - settingsCache.updateSettingsEnabled(true) - settingsCache.updateSamplingRate(0.33) - settingsCache.updateSessionRestartTimeout(100) - settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) - settingsCache.updateSessionCacheDuration(0) + settingsCache.updateConfigs( + SessionConfigs( + sessionsEnabled = true, + sessionSamplingRate = 0.33, + sessionTimeoutSeconds = 100, + cacheUpdatedTimeMs = fakeTimeProvider.currentTime().ms, + cacheDurationSeconds = 0, + ) + ) assertThat(settingsCache.sessionSamplingRate()).isEqualTo(0.33) assertThat(settingsCache.sessionsEnabled()).isTrue() @@ -160,25 +179,33 @@ class SettingsCacheTest { @Test fun settingConfigsCleansCacheForNullValues() = runTest { - val context = FakeFirebaseApp().firebaseApp.applicationContext - val settingsCache = SettingsCache(context.dataStore) - - settingsCache.updateSettingsEnabled(false) - settingsCache.updateSamplingRate(0.25) - settingsCache.updateSessionRestartTimeout(600) - settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) - settingsCache.updateSessionCacheDuration(1000) + val fakeTimeProvider = FakeTimeProvider() + val settingsCache = SettingsCacheImpl(fakeTimeProvider, TestDataStores.sessionConfigsDataStore) + + settingsCache.updateConfigs( + SessionConfigs( + sessionsEnabled = false, + sessionSamplingRate = 0.25, + sessionTimeoutSeconds = 600, + cacheUpdatedTimeMs = fakeTimeProvider.currentTime().ms, + cacheDurationSeconds = 1000, + ) + ) assertThat(settingsCache.sessionSamplingRate()).isEqualTo(0.25) assertThat(settingsCache.sessionsEnabled()).isFalse() assertThat(settingsCache.sessionRestartTimeout()).isEqualTo(600) assertThat(settingsCache.hasCacheExpired()).isFalse() - settingsCache.updateSettingsEnabled(null) - settingsCache.updateSamplingRate(0.33) - settingsCache.updateSessionRestartTimeout(null) - settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) - settingsCache.updateSessionCacheDuration(1000) + settingsCache.updateConfigs( + SessionConfigs( + sessionsEnabled = null, + sessionSamplingRate = 0.33, + sessionTimeoutSeconds = null, + cacheUpdatedTimeMs = fakeTimeProvider.currentTime().ms, + cacheDurationSeconds = 1000, + ) + ) assertThat(settingsCache.sessionSamplingRate()).isEqualTo(0.33) assertThat(settingsCache.sessionsEnabled()).isNull() @@ -192,8 +219,4 @@ class SettingsCacheTest { fun cleanUp() { FirebaseApp.clearInstancesForTest() } - - private companion object { - const val SESSION_TEST_CONFIGS_NAME = "firebase_test_session_settings" - } } 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..2a3e28c00b9 --- /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 cacheUpdatedTimeMs = sessionConfigs.cacheUpdatedTimeMs + val cacheDurationSeconds = sessionConfigs.cacheDurationSeconds + + if (cacheUpdatedTimeMs != null && cacheDurationSeconds != null) { + val timeDifferenceSeconds = (timeProvider.currentTime().ms - cacheUpdatedTimeMs) / 1000 + 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/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/FakeTimeProvider.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/FakeTimeProvider.kt index 35010de415a..295600cf48e 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/FakeTimeProvider.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/FakeTimeProvider.kt @@ -16,17 +16,19 @@ package com.google.firebase.sessions.testing +import com.google.firebase.sessions.Time import com.google.firebase.sessions.TimeProvider -import com.google.firebase.sessions.testing.TestSessionEventData.TEST_SESSION_TIMESTAMP_US +import com.google.firebase.sessions.testing.TestSessionEventData.TEST_SESSION_TIMESTAMP import kotlin.time.Duration -import kotlin.time.DurationUnit +import kotlin.time.DurationUnit.MILLISECONDS /** * Fake [TimeProvider] that allows programmatically elapsing time forward. * * Default [elapsedRealtime] is [Duration.ZERO] until the time is moved using [addInterval]. */ -class FakeTimeProvider(private val initialTimeUs: Long = TEST_SESSION_TIMESTAMP_US) : TimeProvider { +internal class FakeTimeProvider(private val initialTime: Time = TEST_SESSION_TIMESTAMP) : + TimeProvider { private var elapsed = Duration.ZERO fun addInterval(interval: Duration) { @@ -38,5 +40,5 @@ class FakeTimeProvider(private val initialTimeUs: Long = TEST_SESSION_TIMESTAMP_ override fun elapsedRealtime(): Duration = elapsed - override fun currentTimeUs(): Long = initialTimeUs + elapsed.toLong(DurationUnit.MICROSECONDS) + override fun currentTime(): Time = Time(ms = initialTime.ms + elapsed.toLong(MILLISECONDS)) } diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/TestDataStores.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/TestDataStores.kt new file mode 100644 index 00000000000..d7cc3a7f67d --- /dev/null +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/TestDataStores.kt @@ -0,0 +1,50 @@ +/* + * 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 android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.dataStoreFile +import androidx.test.core.app.ApplicationProvider +import com.google.firebase.sessions.SessionData +import com.google.firebase.sessions.SessionDataSerializer +import com.google.firebase.sessions.settings.SessionConfigs +import com.google.firebase.sessions.settings.SessionConfigsSerializer + +/** + * Container of instances of [DataStore] for testing. + * + * Note these do not pass the test scheduler to the instances, so won't work with `runCurrent`. + */ +internal object TestDataStores { + private val appContext: Context = ApplicationProvider.getApplicationContext() + + val sessionConfigsDataStore: DataStore by lazy { + DataStoreFactory.create( + serializer = SessionConfigsSerializer, + produceFile = { appContext.dataStoreFile("sessionConfigsTestDataStore.data") }, + ) + } + + val sessionDataStore: DataStore by lazy { + DataStoreFactory.create( + serializer = SessionDataSerializer, + produceFile = { appContext.dataStoreFile("sessionDataTestDataStore.data") }, + ) + } +} diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/TestSessionEventData.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/TestSessionEventData.kt index 7619bc12588..105950a37f4 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/TestSessionEventData.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/TestSessionEventData.kt @@ -30,23 +30,24 @@ import com.google.firebase.sessions.ProcessDetails import com.google.firebase.sessions.SessionDetails import com.google.firebase.sessions.SessionEvent import com.google.firebase.sessions.SessionInfo +import com.google.firebase.sessions.Time internal object TestSessionEventData { - const val TEST_SESSION_TIMESTAMP_US: Long = 12340000 + val TEST_SESSION_TIMESTAMP: Time = Time(ms = 12340) val TEST_SESSION_DETAILS = SessionDetails( sessionId = "a1b2c3", firstSessionId = "a1a1a1", sessionIndex = 3, - sessionStartTimestampUs = TEST_SESSION_TIMESTAMP_US + sessionStartTimestampUs = TEST_SESSION_TIMESTAMP.us, ) val TEST_DATA_COLLECTION_STATUS = DataCollectionStatus( performance = DataCollectionState.COLLECTION_SDK_NOT_INSTALLED, crashlytics = DataCollectionState.COLLECTION_SDK_NOT_INSTALLED, - sessionSamplingRate = 1.0 + sessionSamplingRate = 1.0, ) val TEST_SESSION_DATA = @@ -54,19 +55,14 @@ internal object TestSessionEventData { sessionId = "a1b2c3", firstSessionId = "a1a1a1", sessionIndex = 3, - eventTimestampUs = TEST_SESSION_TIMESTAMP_US, + eventTimestampUs = TEST_SESSION_TIMESTAMP.us, dataCollectionStatus = TEST_DATA_COLLECTION_STATUS, firebaseInstallationId = "", firebaseAuthenticationToken = "", ) val TEST_PROCESS_DETAILS = - ProcessDetails( - processName = "com.google.firebase.sessions.test", - 0, - 100, - false, - ) + ProcessDetails(processName = "com.google.firebase.sessions.test", 0, 100, false) val TEST_APP_PROCESS_DETAILS = listOf(TEST_PROCESS_DETAILS) 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" } From fcd270c1f5717590a3764efa81f34302cfeecf7f Mon Sep 17 00:00:00 2001 From: Matthew Robertson Date: Mon, 24 Mar 2025 07:11:04 -0600 Subject: [PATCH 33/76] Share settings cache between running processes (#6788) With the multi-process data store change, all processes will read the settings cache from the same file safely. This means if a second process started, it would read the cache the first process persisted. But if 2 processes were already running, and one fetched and cached new settings, it wouldn't automatically share it with the other running process. This change fixes that by having all processes watch the file. Also cleaned up settings a bit, and made everything in seconds to avoid converting units. Cleaned up unit tests. Also removed the need to lazy load the cache by doing a double check in the getter instead. There is more potential to clean up, but let's do it later. --- .../sessions/FirebaseSessionsTests.kt | 34 +-- .../sessions/settings/RemoteSettings.kt | 11 +- .../sessions/settings/SessionConfigs.kt | 4 +- .../sessions/settings/SettingsCache.kt | 37 ++- .../firebase/sessions/SessionDatastoreTest.kt | 2 +- .../sessions/settings/RemoteSettingsTest.kt | 268 +++++++----------- .../sessions/settings/SessionsSettingsTest.kt | 218 +++++++------- .../sessions/settings/SettingsCacheTest.kt | 123 ++++++-- .../sessions/testing/FakeSettingsCache.kt | 6 +- .../sessions/testing/TestDataStores.kt | 50 ---- 10 files changed, 365 insertions(+), 388 deletions(-) delete mode 100644 firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/TestDataStores.kt diff --git a/firebase-sessions/src/androidTest/kotlin/com/google/firebase/sessions/FirebaseSessionsTests.kt b/firebase-sessions/src/androidTest/kotlin/com/google/firebase/sessions/FirebaseSessionsTests.kt index 1cf67e0c5e1..c74b6e4e329 100644 --- a/firebase-sessions/src/androidTest/kotlin/com/google/firebase/sessions/FirebaseSessionsTests.kt +++ b/firebase-sessions/src/androidTest/kotlin/com/google/firebase/sessions/FirebaseSessionsTests.kt @@ -20,12 +20,10 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import com.google.firebase.Firebase -import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseOptions import com.google.firebase.initialize import com.google.firebase.sessions.settings.SessionsSettings -import org.junit.After -import org.junit.Before +import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith @@ -36,23 +34,6 @@ import org.junit.runner.RunWith */ @RunWith(AndroidJUnit4::class) class FirebaseSessionsTests { - @Before - fun setUp() { - Firebase.initialize( - ApplicationProvider.getApplicationContext(), - FirebaseOptions.Builder() - .setApplicationId(APP_ID) - .setApiKey(API_KEY) - .setProjectId(PROJECT_ID) - .build() - ) - } - - @After - fun cleanUp() { - FirebaseApp.clearInstancesForTest() - } - @Test fun firebaseSessionsDoesInitialize() { assertThat(FirebaseSessions.instance).isNotNull() @@ -69,5 +50,18 @@ class FirebaseSessionsTests { private const val APP_ID = "1:1:android:1a" private const val API_KEY = "API-KEY-API-KEY-API-KEY-API-KEY-API-KEY" private const val PROJECT_ID = "PROJECT-ID" + + @BeforeClass + @JvmStatic + fun setUp() { + Firebase.initialize( + ApplicationProvider.getApplicationContext(), + FirebaseOptions.Builder() + .setApplicationId(APP_ID) + .setApiKey(API_KEY) + .setProjectId(PROJECT_ID) + .build(), + ) + } } } diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettings.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettings.kt index b715cd9f79c..1079577e03c 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettings.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettings.kt @@ -23,13 +23,11 @@ import com.google.firebase.installations.FirebaseInstallationsApi import com.google.firebase.sessions.ApplicationInfo import com.google.firebase.sessions.InstallationId import com.google.firebase.sessions.TimeProvider -import dagger.Lazy import javax.inject.Inject import javax.inject.Singleton import kotlin.time.Duration import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.json.JSONException @@ -43,11 +41,8 @@ constructor( private val firebaseInstallationsApi: FirebaseInstallationsApi, private val appInfo: ApplicationInfo, private val configsFetcher: CrashlyticsSettingsFetcher, - private val lazySettingsCache: Lazy, + private val settingsCache: SettingsCache, ) : SettingsProvider { - private val settingsCache: SettingsCache - get() = lazySettingsCache.get() - private val fetchInProgress = Mutex() override val sessionEnabled: Boolean? @@ -133,7 +128,7 @@ constructor( sessionTimeoutSeconds = sessionTimeoutSeconds, sessionSamplingRate = sessionSamplingRate, cacheDurationSeconds = cacheDuration ?: defaultCacheDuration, - cacheUpdatedTimeMs = timeProvider.currentTime().ms, + cacheUpdatedTimeSeconds = timeProvider.currentTime().seconds, ) ) }, @@ -148,7 +143,7 @@ constructor( override fun isSettingsStale(): Boolean = settingsCache.hasCacheExpired() @VisibleForTesting - internal fun clearCachedSettings() = runBlocking { + internal suspend fun clearCachedSettings() { settingsCache.updateConfigs(SessionConfigsSerializer.defaultValue) } 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 index 8d7e2484675..ab310ebed8a 100644 --- 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 @@ -30,7 +30,7 @@ internal data class SessionConfigs( val sessionSamplingRate: Double?, val sessionTimeoutSeconds: Int?, val cacheDurationSeconds: Int?, - val cacheUpdatedTimeMs: Long?, + val cacheUpdatedTimeSeconds: Long?, ) /** DataStore json [Serializer] for [SessionConfigs]. */ @@ -41,7 +41,7 @@ internal object SessionConfigsSerializer : Serializer { sessionSamplingRate = null, sessionTimeoutSeconds = null, cacheDurationSeconds = null, - cacheUpdatedTimeMs = null, + cacheUpdatedTimeSeconds = null, ) override suspend fun readFrom(input: InputStream): SessionConfigs = diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SettingsCache.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SettingsCache.kt index 468bbad6b7a..1640a5c7b7a 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SettingsCache.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SettingsCache.kt @@ -17,12 +17,18 @@ package com.google.firebase.sessions.settings import android.util.Log +import androidx.annotation.VisibleForTesting import androidx.datastore.core.DataStore +import com.google.firebase.annotations.concurrent.Background import com.google.firebase.sessions.TimeProvider import java.io.IOException +import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject import javax.inject.Singleton +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking internal interface SettingsCache { @@ -41,23 +47,38 @@ internal interface SettingsCache { internal class SettingsCacheImpl @Inject constructor( + @Background private val backgroundDispatcher: CoroutineContext, private val timeProvider: TimeProvider, private val sessionConfigsDataStore: DataStore, ) : SettingsCache { - private var sessionConfigs: SessionConfigs + private val sessionConfigsAtomicReference = AtomicReference() + + private val sessionConfigs: SessionConfigs + get() { + // Ensure configs are loaded from disk before the first access + if (sessionConfigsAtomicReference.get() == null) { + // Double check to avoid the `runBlocking` unless necessary + sessionConfigsAtomicReference.compareAndSet( + null, + runBlocking { sessionConfigsDataStore.data.first() }, + ) + } + + return sessionConfigsAtomicReference.get() + } init { - // Block until the cache is loaded from disk to ensure cache - // values are valid and readable from the main thread on init. - runBlocking { sessionConfigs = sessionConfigsDataStore.data.first() } + CoroutineScope(backgroundDispatcher).launch { + sessionConfigsDataStore.data.collect(sessionConfigsAtomicReference::set) + } } override fun hasCacheExpired(): Boolean { - val cacheUpdatedTimeMs = sessionConfigs.cacheUpdatedTimeMs + val cacheUpdatedTimeSeconds = sessionConfigs.cacheUpdatedTimeSeconds val cacheDurationSeconds = sessionConfigs.cacheDurationSeconds - if (cacheUpdatedTimeMs != null && cacheDurationSeconds != null) { - val timeDifferenceSeconds = (timeProvider.currentTime().ms - cacheUpdatedTimeMs) / 1000 + if (cacheUpdatedTimeSeconds != null && cacheDurationSeconds != null) { + val timeDifferenceSeconds = timeProvider.currentTime().seconds - cacheUpdatedTimeSeconds if (timeDifferenceSeconds < cacheDurationSeconds) { return false } @@ -74,12 +95,12 @@ constructor( override suspend fun updateConfigs(sessionConfigs: SessionConfigs) { try { sessionConfigsDataStore.updateData { sessionConfigs } - this.sessionConfigs = sessionConfigs } catch (ex: IOException) { Log.w(TAG, "Failed to update config values: $ex") } } + @VisibleForTesting internal suspend fun removeConfigs() = try { sessionConfigsDataStore.updateData { SessionConfigsSerializer.defaultValue } 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 index 7e94eb3113e..efe7bb27a97 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionDatastoreTest.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionDatastoreTest.kt @@ -44,7 +44,7 @@ class SessionDatastoreTest { DataStoreFactory.create( serializer = SessionDataSerializer, scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")), - produceFile = { appContext.dataStoreFile("sessionDataTestDataStore.data") }, + produceFile = { appContext.dataStoreFile("sessionDataStore.data") }, ), ) diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/RemoteSettingsTest.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/RemoteSettingsTest.kt index ccaf4f8954d..74df328ae57 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/RemoteSettingsTest.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/RemoteSettingsTest.kt @@ -19,10 +19,7 @@ package com.google.firebase.sessions.settings import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import com.google.firebase.FirebaseApp -import com.google.firebase.installations.FirebaseInstallationsApi -import com.google.firebase.sessions.ApplicationInfo import com.google.firebase.sessions.SessionEvents -import com.google.firebase.sessions.TimeProvider import com.google.firebase.sessions.testing.FakeFirebaseApp import com.google.firebase.sessions.testing.FakeFirebaseInstallations import com.google.firebase.sessions.testing.FakeRemoteConfigFetcher @@ -31,11 +28,8 @@ import com.google.firebase.sessions.testing.FakeTimeProvider import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withTimeout import org.json.JSONObject @@ -43,43 +37,37 @@ import org.junit.After import org.junit.Test import org.junit.runner.RunWith -@OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) class RemoteSettingsTest { @Test - fun remoteSettings_successfulFetchCachesValues() = - runTest(UnconfinedTestDispatcher()) { - val firebaseApp = FakeFirebaseApp().firebaseApp - val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") - val fakeFetcher = FakeRemoteConfigFetcher() - - val remoteSettings = - buildRemoteSettings( - FakeTimeProvider(), - firebaseInstallations, - SessionEvents.getApplicationInfo(firebaseApp), - fakeFetcher, - FakeSettingsCache(), - ) - - runCurrent() + fun remoteSettings_successfulFetchCachesValues() = runTest { + val firebaseApp = FakeFirebaseApp().firebaseApp + val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") + val fakeFetcher = FakeRemoteConfigFetcher() - assertThat(remoteSettings.sessionEnabled).isNull() - assertThat(remoteSettings.samplingRate).isNull() - assertThat(remoteSettings.sessionRestartTimeout).isNull() + val remoteSettings = + RemoteSettings( + FakeTimeProvider(), + firebaseInstallations, + SessionEvents.getApplicationInfo(firebaseApp), + fakeFetcher, + FakeSettingsCache(), + ) - fakeFetcher.responseJSONObject = JSONObject(VALID_RESPONSE) - remoteSettings.updateSettings() + assertThat(remoteSettings.sessionEnabled).isNull() + assertThat(remoteSettings.samplingRate).isNull() + assertThat(remoteSettings.sessionRestartTimeout).isNull() - runCurrent() + fakeFetcher.responseJSONObject = JSONObject(VALID_RESPONSE) + remoteSettings.updateSettings() - assertThat(remoteSettings.sessionEnabled).isFalse() - assertThat(remoteSettings.samplingRate).isEqualTo(0.75) - assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) + assertThat(remoteSettings.sessionEnabled).isFalse() + assertThat(remoteSettings.samplingRate).isEqualTo(0.75) + assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) - remoteSettings.clearCachedSettings() - } + remoteSettings.clearCachedSettings() + } @Test fun remoteSettings_successfulFetchWithLessConfigsCachesOnlyReceivedValues() = runTest { @@ -88,7 +76,7 @@ class RemoteSettingsTest { val fakeFetcher = FakeRemoteConfigFetcher() val remoteSettings = - buildRemoteSettings( + RemoteSettings( FakeTimeProvider(), firebaseInstallations, SessionEvents.getApplicationInfo(firebaseApp), @@ -96,8 +84,6 @@ class RemoteSettingsTest { FakeSettingsCache(), ) - runCurrent() - assertThat(remoteSettings.sessionEnabled).isNull() assertThat(remoteSettings.samplingRate).isNull() assertThat(remoteSettings.sessionRestartTimeout).isNull() @@ -107,8 +93,6 @@ class RemoteSettingsTest { fakeFetcher.responseJSONObject = fetchedResponse remoteSettings.updateSettings() - runCurrent() - assertThat(remoteSettings.sessionEnabled).isNull() assertThat(remoteSettings.samplingRate).isEqualTo(0.75) assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) @@ -124,7 +108,7 @@ class RemoteSettingsTest { val fakeTimeProvider = FakeTimeProvider() val remoteSettings = - buildRemoteSettings( + RemoteSettings( fakeTimeProvider, firebaseInstallations, SessionEvents.getApplicationInfo(firebaseApp), @@ -137,8 +121,6 @@ class RemoteSettingsTest { fakeFetcher.responseJSONObject = fetchedResponse remoteSettings.updateSettings() - runCurrent() - assertThat(remoteSettings.sessionEnabled).isFalse() assertThat(remoteSettings.samplingRate).isEqualTo(0.75) assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) @@ -152,8 +134,6 @@ class RemoteSettingsTest { fakeFetcher.responseJSONObject = fetchedResponse remoteSettings.updateSettings() - runCurrent() - assertThat(remoteSettings.sessionEnabled).isTrue() assertThat(remoteSettings.samplingRate).isEqualTo(0.25) assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(20.minutes) @@ -162,110 +142,99 @@ class RemoteSettingsTest { } @Test - fun remoteSettings_successfulFetchWithEmptyConfigRetainsOldConfigs() = - runTest(UnconfinedTestDispatcher()) { - val firebaseApp = FakeFirebaseApp().firebaseApp - val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") - val fakeFetcher = FakeRemoteConfigFetcher() - val fakeTimeProvider = FakeTimeProvider() - - val remoteSettings = - buildRemoteSettings( - fakeTimeProvider, - firebaseInstallations, - SessionEvents.getApplicationInfo(firebaseApp), - fakeFetcher, - FakeSettingsCache(), - ) - - val fetchedResponse = JSONObject(VALID_RESPONSE) - fetchedResponse.getJSONObject("app_quality").put("cache_duration", 1) - fakeFetcher.responseJSONObject = fetchedResponse - remoteSettings.updateSettings() - - runCurrent() - fakeTimeProvider.addInterval(31.seconds) - - assertThat(remoteSettings.sessionEnabled).isFalse() - assertThat(remoteSettings.samplingRate).isEqualTo(0.75) - assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) - - fetchedResponse.remove("app_quality") - - // Sleep for a second before updating configs - Thread.sleep(2000) - - fakeFetcher.responseJSONObject = fetchedResponse - remoteSettings.updateSettings() - - runCurrent() - Thread.sleep(30) - - assertThat(remoteSettings.sessionEnabled).isFalse() - assertThat(remoteSettings.samplingRate).isEqualTo(0.75) - assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) - - remoteSettings.clearCachedSettings() - } + fun remoteSettings_successfulFetchWithEmptyConfigRetainsOldConfigs() = runTest { + val firebaseApp = FakeFirebaseApp().firebaseApp + val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") + val fakeFetcher = FakeRemoteConfigFetcher() + val fakeTimeProvider = FakeTimeProvider() + + val remoteSettings = + RemoteSettings( + fakeTimeProvider, + firebaseInstallations, + SessionEvents.getApplicationInfo(firebaseApp), + fakeFetcher, + FakeSettingsCache(), + ) + + val fetchedResponse = JSONObject(VALID_RESPONSE) + fetchedResponse.getJSONObject("app_quality").put("cache_duration", 1) + fakeFetcher.responseJSONObject = fetchedResponse + remoteSettings.updateSettings() + + fakeTimeProvider.addInterval(31.seconds) + + assertThat(remoteSettings.sessionEnabled).isFalse() + assertThat(remoteSettings.samplingRate).isEqualTo(0.75) + assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) + + fetchedResponse.remove("app_quality") + + fakeFetcher.responseJSONObject = fetchedResponse + remoteSettings.updateSettings() + + assertThat(remoteSettings.sessionEnabled).isFalse() + assertThat(remoteSettings.samplingRate).isEqualTo(0.75) + assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) + + remoteSettings.clearCachedSettings() + } @Test - fun remoteSettings_fetchWhileFetchInProgress() = - runTest(UnconfinedTestDispatcher()) { - // This test does: - // 1. Do a fetch with a fake fetcher that will block for 3 seconds. - // 2. While that is happening, do a second fetch. - // - First fetch is still fetching, so second fetch should fall through to the mutex. - // - Second fetch will be blocked until first completes. - // - First fetch returns, should unblock the second fetch. - // - Second fetch should go into mutex, sees cache is valid in "double check," exist early. - // 3. After a fetch completes, do a third fetch. - // - First fetch should have have updated the cache. - // - Third fetch should exit even earlier, never having gone into the mutex. - - val firebaseApp = FakeFirebaseApp().firebaseApp - val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") - val fakeFetcherWithDelay = - FakeRemoteConfigFetcher(JSONObject(VALID_RESPONSE), networkDelay = 3.seconds) - - fakeFetcherWithDelay.responseJSONObject - .getJSONObject("app_quality") - .put("sampling_rate", 0.125) - - val remoteSettingsWithDelay = - buildRemoteSettings( - FakeTimeProvider(), - firebaseInstallations, - SessionEvents.getApplicationInfo(firebaseApp), - configsFetcher = fakeFetcherWithDelay, - FakeSettingsCache(), - ) - - // Do the first fetch. This one should fetched the configsFetcher. - val firstFetch = launch(Dispatchers.Default) { remoteSettingsWithDelay.updateSettings() } - - // Wait a second, and then do the second fetch while first is still running. - // This one should block until the first fetch completes, but then exit early. - launch(Dispatchers.Default) { - delay(1.seconds) - remoteSettingsWithDelay.updateSettings() - } + fun remoteSettings_fetchWhileFetchInProgress() = runTest { + // This test does: + // 1. Do a fetch with a fake fetcher that will block for 3 seconds. + // 2. While that is happening, do a second fetch. + // - First fetch is still fetching, so second fetch should fall through to the mutex. + // - Second fetch will be blocked until first completes. + // - First fetch returns, should unblock the second fetch. + // - Second fetch should go into mutex, sees cache is valid in "double check," exist early. + // 3. After a fetch completes, do a third fetch. + // - First fetch should have have updated the cache. + // - Third fetch should exit even earlier, never having gone into the mutex. + + val firebaseApp = FakeFirebaseApp().firebaseApp + val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") + val fakeFetcherWithDelay = + FakeRemoteConfigFetcher(JSONObject(VALID_RESPONSE), networkDelay = 3.seconds) + + fakeFetcherWithDelay.responseJSONObject.getJSONObject("app_quality").put("sampling_rate", 0.125) + + val remoteSettingsWithDelay = + RemoteSettings( + FakeTimeProvider(), + firebaseInstallations, + SessionEvents.getApplicationInfo(firebaseApp), + configsFetcher = fakeFetcherWithDelay, + FakeSettingsCache(), + ) - // Wait until the first fetch is done, then do a third fetch. - // This one should not even block, and exit early. - firstFetch.join() - withTimeout(1.seconds) { remoteSettingsWithDelay.updateSettings() } + // Do the first fetch. This one should fetched the configsFetcher. + val firstFetch = launch(Dispatchers.Default) { remoteSettingsWithDelay.updateSettings() } - // Assert that the configsFetcher was fetched exactly once. - assertThat(fakeFetcherWithDelay.timesCalled).isEqualTo(1) - assertThat(remoteSettingsWithDelay.samplingRate).isEqualTo(0.125) + // Wait a second, and then do the second fetch while first is still running. + // This one should block until the first fetch completes, but then exit early. + launch(Dispatchers.Default) { + delay(1.seconds) + remoteSettingsWithDelay.updateSettings() } + // Wait until the first fetch is done, then do a third fetch. + // This one should not even block, and exit early. + firstFetch.join() + withTimeout(1.seconds) { remoteSettingsWithDelay.updateSettings() } + + // Assert that the configsFetcher was fetched exactly once. + assertThat(fakeFetcherWithDelay.timesCalled).isEqualTo(1) + assertThat(remoteSettingsWithDelay.samplingRate).isEqualTo(0.125) + } + @After fun cleanUp() { FirebaseApp.clearInstancesForTest() } - internal companion object { + private companion object { const val VALID_RESPONSE = """ { @@ -284,30 +253,5 @@ class RemoteSettingsTest { } } """ - - /** - * Build an instance of [RemoteSettings] using the Dagger factory. - * - * This is needed because the SDK vendors Dagger to a difference namespace, but it does not for - * these unit tests. The [RemoteSettings.lazySettingsCache] has type [dagger.Lazy] in these - * tests, but type `com.google.firebase.sessions.dagger.Lazy` in the SDK. This method to build - * the instance is the easiest I could find that does not need any reference to [dagger.Lazy] in - * the test code. - */ - fun buildRemoteSettings( - timeProvider: TimeProvider, - firebaseInstallationsApi: FirebaseInstallationsApi, - appInfo: ApplicationInfo, - configsFetcher: CrashlyticsSettingsFetcher, - settingsCache: SettingsCache, - ): RemoteSettings = - RemoteSettings_Factory.create( - { timeProvider }, - { firebaseInstallationsApi }, - { appInfo }, - { configsFetcher }, - { settingsCache }, - ) - .get() } } diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SessionsSettingsTest.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SessionsSettingsTest.kt index f87d773b970..146857ae7f4 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SessionsSettingsTest.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SessionsSettingsTest.kt @@ -20,7 +20,6 @@ import android.os.Bundle import com.google.common.truth.Truth.assertThat import com.google.firebase.FirebaseApp import com.google.firebase.sessions.SessionEvents -import com.google.firebase.sessions.settings.RemoteSettingsTest.Companion.buildRemoteSettings import com.google.firebase.sessions.testing.FakeFirebaseApp import com.google.firebase.sessions.testing.FakeFirebaseInstallations import com.google.firebase.sessions.testing.FakeRemoteConfigFetcher @@ -28,9 +27,6 @@ import com.google.firebase.sessions.testing.FakeSettingsCache import com.google.firebase.sessions.testing.FakeSettingsProvider import com.google.firebase.sessions.testing.FakeTimeProvider import kotlin.time.Duration.Companion.minutes -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.json.JSONObject import org.junit.After @@ -38,7 +34,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner -@OptIn(ExperimentalCoroutinesApi::class) @RunWith(RobolectricTestRunner::class) class SessionsSettingsTest { @@ -89,128 +84,117 @@ class SessionsSettingsTest { remoteSettings = FakeSettingsProvider(), ) - runCurrent() - assertThat(sessionsSettings.sessionsEnabled).isFalse() assertThat(sessionsSettings.samplingRate).isEqualTo(0.5) assertThat(sessionsSettings.sessionRestartTimeout).isEqualTo(30.minutes) } @Test - fun sessionSettings_remoteSettingsOverrideDefaultsWhenPresent() = - runTest(UnconfinedTestDispatcher()) { - val firebaseApp = FakeFirebaseApp().firebaseApp - val context = firebaseApp.applicationContext - val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") - val fakeFetcher = FakeRemoteConfigFetcher(JSONObject(VALID_RESPONSE)) - - val remoteSettings = - buildRemoteSettings( - FakeTimeProvider(), - firebaseInstallations, - SessionEvents.getApplicationInfo(firebaseApp), - fakeFetcher, - FakeSettingsCache(), - ) - - val sessionsSettings = - SessionsSettings( - localOverrideSettings = LocalOverrideSettings(context), - remoteSettings = remoteSettings, - ) - - sessionsSettings.updateSettings() - - runCurrent() - - assertThat(sessionsSettings.sessionsEnabled).isFalse() - assertThat(sessionsSettings.samplingRate).isEqualTo(0.75) - assertThat(sessionsSettings.sessionRestartTimeout).isEqualTo(40.minutes) - - remoteSettings.clearCachedSettings() - } + fun sessionSettings_remoteSettingsOverrideDefaultsWhenPresent() = runTest { + val firebaseApp = FakeFirebaseApp().firebaseApp + val context = firebaseApp.applicationContext + val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") + val fakeFetcher = FakeRemoteConfigFetcher(JSONObject(VALID_RESPONSE)) + + val remoteSettings = + RemoteSettings( + FakeTimeProvider(), + firebaseInstallations, + SessionEvents.getApplicationInfo(firebaseApp), + fakeFetcher, + FakeSettingsCache(), + ) + + val sessionsSettings = + SessionsSettings( + localOverrideSettings = LocalOverrideSettings(context), + remoteSettings = remoteSettings, + ) + + sessionsSettings.updateSettings() + + assertThat(sessionsSettings.sessionsEnabled).isFalse() + assertThat(sessionsSettings.samplingRate).isEqualTo(0.75) + assertThat(sessionsSettings.sessionRestartTimeout).isEqualTo(40.minutes) + + remoteSettings.clearCachedSettings() + } @Test - fun sessionSettings_manifestOverridesRemoteSettingsAndDefaultsWhenPresent() = - runTest(UnconfinedTestDispatcher()) { - val metadata = Bundle() - metadata.putBoolean("firebase_sessions_enabled", true) - metadata.putDouble("firebase_sessions_sampling_rate", 0.5) - metadata.putInt("firebase_sessions_sessions_restart_timeout", 180) - val firebaseApp = FakeFirebaseApp(metadata).firebaseApp - val context = firebaseApp.applicationContext - val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") - val fakeFetcher = FakeRemoteConfigFetcher(JSONObject(VALID_RESPONSE)) - - val remoteSettings = - buildRemoteSettings( - FakeTimeProvider(), - firebaseInstallations, - SessionEvents.getApplicationInfo(firebaseApp), - fakeFetcher, - FakeSettingsCache(), - ) - - val sessionsSettings = - SessionsSettings( - localOverrideSettings = LocalOverrideSettings(context), - remoteSettings = remoteSettings, - ) - - sessionsSettings.updateSettings() - - runCurrent() - - assertThat(sessionsSettings.sessionsEnabled).isTrue() - assertThat(sessionsSettings.samplingRate).isEqualTo(0.5) - assertThat(sessionsSettings.sessionRestartTimeout).isEqualTo(3.minutes) - - remoteSettings.clearCachedSettings() - } + fun sessionSettings_manifestOverridesRemoteSettingsAndDefaultsWhenPresent() = runTest { + val metadata = Bundle() + metadata.putBoolean("firebase_sessions_enabled", true) + metadata.putDouble("firebase_sessions_sampling_rate", 0.5) + metadata.putInt("firebase_sessions_sessions_restart_timeout", 180) + val firebaseApp = FakeFirebaseApp(metadata).firebaseApp + val context = firebaseApp.applicationContext + val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") + val fakeFetcher = FakeRemoteConfigFetcher(JSONObject(VALID_RESPONSE)) + + val remoteSettings = + RemoteSettings( + FakeTimeProvider(), + firebaseInstallations, + SessionEvents.getApplicationInfo(firebaseApp), + fakeFetcher, + FakeSettingsCache(), + ) + + val sessionsSettings = + SessionsSettings( + localOverrideSettings = LocalOverrideSettings(context), + remoteSettings = remoteSettings, + ) + + sessionsSettings.updateSettings() + + assertThat(sessionsSettings.sessionsEnabled).isTrue() + assertThat(sessionsSettings.samplingRate).isEqualTo(0.5) + assertThat(sessionsSettings.sessionRestartTimeout).isEqualTo(3.minutes) + + remoteSettings.clearCachedSettings() + } @Test - fun sessionSettings_invalidManifestConfigsDoNotOverride() = - runTest(UnconfinedTestDispatcher()) { - val metadata = Bundle() - metadata.putBoolean("firebase_sessions_enabled", false) - metadata.putDouble("firebase_sessions_sampling_rate", -0.2) // Invalid - metadata.putInt("firebase_sessions_sessions_restart_timeout", -2) // Invalid - val firebaseApp = FakeFirebaseApp(metadata).firebaseApp - val context = firebaseApp.applicationContext - val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") - val fakeFetcher = FakeRemoteConfigFetcher() - val invalidResponse = - VALID_RESPONSE.replace( - "\"sampling_rate\":0.75,", - "\"sampling_rate\":1.2,", // Invalid - ) - fakeFetcher.responseJSONObject = JSONObject(invalidResponse) - - val remoteSettings = - buildRemoteSettings( - FakeTimeProvider(), - firebaseInstallations, - SessionEvents.getApplicationInfo(firebaseApp), - fakeFetcher, - FakeSettingsCache(), - ) - - val sessionsSettings = - SessionsSettings( - localOverrideSettings = LocalOverrideSettings(context), - remoteSettings = remoteSettings, - ) - - sessionsSettings.updateSettings() - - runCurrent() - - assertThat(sessionsSettings.sessionsEnabled).isFalse() // Manifest - assertThat(sessionsSettings.samplingRate).isEqualTo(1.0) // SDK default - assertThat(sessionsSettings.sessionRestartTimeout).isEqualTo(40.minutes) // Remote - - remoteSettings.clearCachedSettings() - } + fun sessionSettings_invalidManifestConfigsDoNotOverride() = runTest { + val metadata = Bundle() + metadata.putBoolean("firebase_sessions_enabled", false) + metadata.putDouble("firebase_sessions_sampling_rate", -0.2) // Invalid + metadata.putInt("firebase_sessions_sessions_restart_timeout", -2) // Invalid + val firebaseApp = FakeFirebaseApp(metadata).firebaseApp + val context = firebaseApp.applicationContext + val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") + val fakeFetcher = FakeRemoteConfigFetcher() + val invalidResponse = + VALID_RESPONSE.replace( + "\"sampling_rate\":0.75,", + "\"sampling_rate\":1.2,", // Invalid + ) + fakeFetcher.responseJSONObject = JSONObject(invalidResponse) + + val remoteSettings = + RemoteSettings( + FakeTimeProvider(), + firebaseInstallations, + SessionEvents.getApplicationInfo(firebaseApp), + fakeFetcher, + FakeSettingsCache(), + ) + + val sessionsSettings = + SessionsSettings( + localOverrideSettings = LocalOverrideSettings(context), + remoteSettings = remoteSettings, + ) + + sessionsSettings.updateSettings() + + assertThat(sessionsSettings.sessionsEnabled).isFalse() // Manifest + assertThat(sessionsSettings.samplingRate).isEqualTo(1.0) // SDK default + assertThat(sessionsSettings.sessionRestartTimeout).isEqualTo(40.minutes) // Remote + + remoteSettings.clearCachedSettings() + } @After fun cleanUp() { diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SettingsCacheTest.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SettingsCacheTest.kt index 729208c33ca..a8d8429b5a8 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SettingsCacheTest.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SettingsCacheTest.kt @@ -16,23 +16,44 @@ package com.google.firebase.sessions.settings +import android.content.Context +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.dataStoreFile +import androidx.test.core.app.ApplicationProvider import com.google.common.truth.Truth.assertThat import com.google.firebase.FirebaseApp import com.google.firebase.sessions.testing.FakeTimeProvider -import com.google.firebase.sessions.testing.TestDataStores +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.After import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner +@OptIn(ExperimentalCoroutinesApi::class) @RunWith(RobolectricTestRunner::class) class SettingsCacheTest { + private val appContext: Context = ApplicationProvider.getApplicationContext() @Test fun sessionCache_returnsEmptyCache() = runTest { val fakeTimeProvider = FakeTimeProvider() - val settingsCache = SettingsCacheImpl(fakeTimeProvider, TestDataStores.sessionConfigsDataStore) + val settingsCache = + SettingsCacheImpl( + backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), + timeProvider = fakeTimeProvider, + sessionConfigsDataStore = + DataStoreFactory.create( + serializer = SessionConfigsSerializer, + scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")), + produceFile = { appContext.dataStoreFile("sessionConfigsDataStore.data") }, + ), + ) + + runCurrent() assertThat(settingsCache.sessionSamplingRate()).isNull() assertThat(settingsCache.sessionsEnabled()).isNull() @@ -43,14 +64,24 @@ class SettingsCacheTest { @Test fun settingConfigsReturnsCachedValue() = runTest { val fakeTimeProvider = FakeTimeProvider() - val settingsCache = SettingsCacheImpl(fakeTimeProvider, TestDataStores.sessionConfigsDataStore) + val settingsCache = + SettingsCacheImpl( + backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), + timeProvider = fakeTimeProvider, + sessionConfigsDataStore = + DataStoreFactory.create( + serializer = SessionConfigsSerializer, + scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")), + produceFile = { appContext.dataStoreFile("sessionConfigsDataStore.data") }, + ), + ) settingsCache.updateConfigs( SessionConfigs( sessionsEnabled = false, sessionSamplingRate = 0.25, sessionTimeoutSeconds = 600, - cacheUpdatedTimeMs = fakeTimeProvider.currentTime().ms, + cacheUpdatedTimeSeconds = fakeTimeProvider.currentTime().seconds, cacheDurationSeconds = 1000, ) ) @@ -66,22 +97,40 @@ class SettingsCacheTest { @Test fun settingConfigsReturnsPreviouslyStoredValue() = runTest { + val sessionConfigsDataStore = + DataStoreFactory.create( + serializer = SessionConfigsSerializer, + scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")), + produceFile = { appContext.dataStoreFile("sessionConfigsDataStore.data") }, + ) + val fakeTimeProvider = FakeTimeProvider() - val settingsCache = SettingsCacheImpl(fakeTimeProvider, TestDataStores.sessionConfigsDataStore) + val settingsCache = + SettingsCacheImpl( + backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), + timeProvider = fakeTimeProvider, + sessionConfigsDataStore = sessionConfigsDataStore, + ) settingsCache.updateConfigs( SessionConfigs( sessionsEnabled = false, sessionSamplingRate = 0.25, sessionTimeoutSeconds = 600, - cacheUpdatedTimeMs = fakeTimeProvider.currentTime().ms, + cacheUpdatedTimeSeconds = fakeTimeProvider.currentTime().seconds, cacheDurationSeconds = 1000, ) ) // Create a new instance to imitate a second app launch. val newSettingsCache = - SettingsCacheImpl(fakeTimeProvider, TestDataStores.sessionConfigsDataStore) + SettingsCacheImpl( + backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), + timeProvider = fakeTimeProvider, + sessionConfigsDataStore = sessionConfigsDataStore, + ) + + runCurrent() assertThat(newSettingsCache.sessionsEnabled()).isFalse() assertThat(newSettingsCache.sessionSamplingRate()).isEqualTo(0.25) @@ -96,14 +145,24 @@ class SettingsCacheTest { @Test fun settingConfigsReturnsCacheExpiredWithShortCacheDuration() = runTest { val fakeTimeProvider = FakeTimeProvider() - val settingsCache = SettingsCacheImpl(fakeTimeProvider, TestDataStores.sessionConfigsDataStore) + val settingsCache = + SettingsCacheImpl( + backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), + timeProvider = fakeTimeProvider, + sessionConfigsDataStore = + DataStoreFactory.create( + serializer = SessionConfigsSerializer, + scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")), + produceFile = { appContext.dataStoreFile("sessionConfigsDataStore.data") }, + ), + ) settingsCache.updateConfigs( SessionConfigs( sessionsEnabled = false, sessionSamplingRate = 0.25, sessionTimeoutSeconds = 600, - cacheUpdatedTimeMs = fakeTimeProvider.currentTime().ms, + cacheUpdatedTimeSeconds = fakeTimeProvider.currentTime().seconds, cacheDurationSeconds = 0, ) ) @@ -119,14 +178,24 @@ class SettingsCacheTest { @Test fun settingConfigsReturnsCachedValueWithPartialConfigs() = runTest { val fakeTimeProvider = FakeTimeProvider() - val settingsCache = SettingsCacheImpl(fakeTimeProvider, TestDataStores.sessionConfigsDataStore) + val settingsCache = + SettingsCacheImpl( + backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), + timeProvider = fakeTimeProvider, + sessionConfigsDataStore = + DataStoreFactory.create( + serializer = SessionConfigsSerializer, + scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")), + produceFile = { appContext.dataStoreFile("sessionConfigsDataStore.data") }, + ), + ) settingsCache.updateConfigs( SessionConfigs( sessionsEnabled = false, sessionSamplingRate = 0.25, sessionTimeoutSeconds = null, - cacheUpdatedTimeMs = fakeTimeProvider.currentTime().ms, + cacheUpdatedTimeSeconds = fakeTimeProvider.currentTime().seconds, cacheDurationSeconds = 1000, ) ) @@ -142,14 +211,24 @@ class SettingsCacheTest { @Test fun settingConfigsAllowsUpdateConfigsAndCachesValues() = runTest { val fakeTimeProvider = FakeTimeProvider() - val settingsCache = SettingsCacheImpl(fakeTimeProvider, TestDataStores.sessionConfigsDataStore) + val settingsCache = + SettingsCacheImpl( + backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), + timeProvider = fakeTimeProvider, + sessionConfigsDataStore = + DataStoreFactory.create( + serializer = SessionConfigsSerializer, + scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")), + produceFile = { appContext.dataStoreFile("sessionConfigsDataStore.data") }, + ), + ) settingsCache.updateConfigs( SessionConfigs( sessionsEnabled = false, sessionSamplingRate = 0.25, sessionTimeoutSeconds = 600, - cacheUpdatedTimeMs = fakeTimeProvider.currentTime().ms, + cacheUpdatedTimeSeconds = fakeTimeProvider.currentTime().seconds, cacheDurationSeconds = 1000, ) ) @@ -164,7 +243,7 @@ class SettingsCacheTest { sessionsEnabled = true, sessionSamplingRate = 0.33, sessionTimeoutSeconds = 100, - cacheUpdatedTimeMs = fakeTimeProvider.currentTime().ms, + cacheUpdatedTimeSeconds = fakeTimeProvider.currentTime().seconds, cacheDurationSeconds = 0, ) ) @@ -180,14 +259,24 @@ class SettingsCacheTest { @Test fun settingConfigsCleansCacheForNullValues() = runTest { val fakeTimeProvider = FakeTimeProvider() - val settingsCache = SettingsCacheImpl(fakeTimeProvider, TestDataStores.sessionConfigsDataStore) + val settingsCache = + SettingsCacheImpl( + backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), + timeProvider = fakeTimeProvider, + sessionConfigsDataStore = + DataStoreFactory.create( + serializer = SessionConfigsSerializer, + scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")), + produceFile = { appContext.dataStoreFile("sessionConfigsDataStore.data") }, + ), + ) settingsCache.updateConfigs( SessionConfigs( sessionsEnabled = false, sessionSamplingRate = 0.25, sessionTimeoutSeconds = 600, - cacheUpdatedTimeMs = fakeTimeProvider.currentTime().ms, + cacheUpdatedTimeSeconds = fakeTimeProvider.currentTime().seconds, cacheDurationSeconds = 1000, ) ) @@ -202,7 +291,7 @@ class SettingsCacheTest { sessionsEnabled = null, sessionSamplingRate = 0.33, sessionTimeoutSeconds = null, - cacheUpdatedTimeMs = fakeTimeProvider.currentTime().ms, + cacheUpdatedTimeSeconds = fakeTimeProvider.currentTime().seconds, cacheDurationSeconds = 1000, ) ) 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 index 2a3e28c00b9..2c58ef22d7d 100644 --- 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 @@ -27,11 +27,11 @@ internal class FakeSettingsCache( private var sessionConfigs: SessionConfigs = SessionConfigsSerializer.defaultValue, ) : SettingsCache { override fun hasCacheExpired(): Boolean { - val cacheUpdatedTimeMs = sessionConfigs.cacheUpdatedTimeMs + val cacheUpdatedTimeSeconds = sessionConfigs.cacheUpdatedTimeSeconds val cacheDurationSeconds = sessionConfigs.cacheDurationSeconds - if (cacheUpdatedTimeMs != null && cacheDurationSeconds != null) { - val timeDifferenceSeconds = (timeProvider.currentTime().ms - cacheUpdatedTimeMs) / 1000 + if (cacheUpdatedTimeSeconds != null && cacheDurationSeconds != null) { + val timeDifferenceSeconds = timeProvider.currentTime().seconds - cacheUpdatedTimeSeconds if (timeDifferenceSeconds < cacheDurationSeconds) { return false } diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/TestDataStores.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/TestDataStores.kt deleted file mode 100644 index d7cc3a7f67d..00000000000 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/TestDataStores.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 android.content.Context -import androidx.datastore.core.DataStore -import androidx.datastore.core.DataStoreFactory -import androidx.datastore.dataStoreFile -import androidx.test.core.app.ApplicationProvider -import com.google.firebase.sessions.SessionData -import com.google.firebase.sessions.SessionDataSerializer -import com.google.firebase.sessions.settings.SessionConfigs -import com.google.firebase.sessions.settings.SessionConfigsSerializer - -/** - * Container of instances of [DataStore] for testing. - * - * Note these do not pass the test scheduler to the instances, so won't work with `runCurrent`. - */ -internal object TestDataStores { - private val appContext: Context = ApplicationProvider.getApplicationContext() - - val sessionConfigsDataStore: DataStore by lazy { - DataStoreFactory.create( - serializer = SessionConfigsSerializer, - produceFile = { appContext.dataStoreFile("sessionConfigsTestDataStore.data") }, - ) - } - - val sessionDataStore: DataStore by lazy { - DataStoreFactory.create( - serializer = SessionDataSerializer, - produceFile = { appContext.dataStoreFile("sessionDataTestDataStore.data") }, - ) - } -} From 45f625266ea41c1350714b6151a8a81cfcec1049 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 1 Apr 2025 12:45:22 -0400 Subject: [PATCH 34/76] Switch AQS ID to an always known ID --- .../com/google/firebase/perf/FirebasePerfEarly.java | 2 +- .../session/FirebasePerformanceSessionSubscriber.kt | 12 +++++++----- .../google/firebase/perf/session/PerfSession.java | 7 ++++--- .../firebase/perf/session/gauges/GaugeManager.java | 5 ++--- .../com/google/firebase/perf/util/Constants.java | 2 ++ 5 files changed, 16 insertions(+), 12 deletions(-) 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 52ca3016df5..766fd7daa95 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 @@ -22,7 +22,6 @@ import com.google.firebase.perf.config.ConfigResolver; import com.google.firebase.perf.metrics.AppStartTrace; import com.google.firebase.perf.session.SessionManager; - import java.util.concurrent.Executor; /** @@ -55,6 +54,7 @@ public FirebasePerfEarly( // In the case of cold start, we create a session and start collecting gauges as early as // possible. // Uploading the gauges however only starts once AQS is initialized. + // TODO(b/394127311): Update this to use changes in AQS initialization, SessionManager.getInstance().initializeGaugeCollection(); } } 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 index 37dff221aea..bd2ff2a32e7 100644 --- 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 @@ -17,6 +17,7 @@ package com.google.firebase.perf.session import com.google.firebase.perf.session.gauges.GaugeManager +import com.google.firebase.perf.util.Constants import com.google.firebase.perf.v1.ApplicationProcessState import com.google.firebase.sessions.api.SessionSubscriber import java.util.UUID @@ -32,10 +33,12 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: val gaugeManager = GaugeManager.getInstance() // A [PerfSession] was created before a session was started. - if (currentPerfSession.aqsSessionId() == null) { + if (currentPerfSession.aqsSessionId().equals(Constants.UNDEFINED_AQS_ID)) { currentPerfSession.setAQSId(sessionDetails) - gaugeManager - .logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + gaugeManager.logGaugeMetadata( + currentPerfSession.aqsSessionId(), + ApplicationProcessState.FOREGROUND + ) gaugeManager.updateGaugeCollection(ApplicationProcessState.FOREGROUND) return } @@ -43,7 +46,6 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()) updatedSession.setAQSId(sessionDetails) sessionManager.updatePerfSession(updatedSession) - gaugeManager - .logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + gaugeManager.logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) } } 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 103c1c38ad9..5a3d79a30a8 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 @@ -21,6 +21,7 @@ import androidx.annotation.VisibleForTesting; import com.google.firebase.perf.config.ConfigResolver; import com.google.firebase.perf.util.Clock; +import com.google.firebase.perf.util.Constants; import com.google.firebase.perf.util.Timer; import com.google.firebase.perf.v1.SessionVerbosity; import com.google.firebase.sessions.api.SessionSubscriber; @@ -32,7 +33,8 @@ public class PerfSession implements Parcelable { private final String sessionId; private final Timer creationTime; - @Nullable private String aqsSessionId; + // TODO(b/394127311): Remove this once this isn't needed. + private String aqsSessionId = Constants.UNDEFINED_AQS_ID; private boolean isGaugeAndEventCollectionEnabled = false; @@ -67,14 +69,13 @@ public String 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) { + if (aqsSessionId.equals(Constants.UNDEFINED_AQS_ID)) { aqsSessionId = aqs.getSessionId(); } } 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 65034c68469..1912e39a17a 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 @@ -24,6 +24,7 @@ import com.google.firebase.perf.logging.AndroidLogger; import com.google.firebase.perf.session.PerfSession; import com.google.firebase.perf.transport.TransportManager; +import com.google.firebase.perf.util.Constants; import com.google.firebase.perf.util.Timer; import com.google.firebase.perf.v1.AndroidMemoryReading; import com.google.firebase.perf.v1.ApplicationProcessState; @@ -112,7 +113,7 @@ public void updateGaugeCollection(ApplicationProcessState applicationProcessStat if (session == null || !session.isGaugeAndEventCollectionEnabled() - || session.aqsSessionId() == null) { + || session.aqsSessionId().equals(Constants.UNDEFINED_AQS_ID)) { logger.warn("Not starting gauge collection."); stopCollectingGauges(); return; @@ -124,7 +125,6 @@ public void updateGaugeCollection(ApplicationProcessState applicationProcessStat getMemoryGaugeCollectionFrequencyMs(applicationProcessState)); String sessionIdForScheduledTask = session.aqsSessionId(); - // TODO(b/394127311): Switch to using AQS. try { gaugeManagerDataCollectionJob = gaugeManagerExecutor @@ -272,7 +272,6 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) { } // Adding Session ID info. - // TODO(b/394127311): Switch to using AQS. gaugeMetricBuilder.setSessionId(sessionId); transportManager.log(gaugeMetricBuilder.build(), appState); diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/util/Constants.java b/firebase-perf/src/main/java/com/google/firebase/perf/util/Constants.java index f2704b903ce..95d95a9ccd6 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/util/Constants.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/util/Constants.java @@ -22,6 +22,8 @@ public class Constants { public static final String PREFS_NAME = "FirebasePerfSharedPrefs"; public static final String ENABLE_DISABLE = "isEnabled"; + public static final String UNDEFINED_AQS_ID = "_uaqsid"; + public static final double MIN_SAMPLING_RATE = 0.0; public static final double MAX_SAMPLING_RATE = 1.0; From d307f57c6e46e56e23e81ce9734b715acde73c4f Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 1 Apr 2025 16:37:39 -0400 Subject: [PATCH 35/76] More changes --- .../com/google/firebase/perf/session/PerfSession.java | 5 +++++ .../firebase/perf/session/gauges/GaugeManager.java | 10 +++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) 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 5a3d79a30a8..93985e67436 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 @@ -70,6 +70,11 @@ public String sessionId() { /** Returns the AQS sessionId for the given session. */ public String aqsSessionId() { + // This is a fallback for if/when the AQS ID is undefined. + if (aqsSessionId.equals(Constants.UNDEFINED_AQS_ID)) { + // TODO(b/394127311): Explore returning a valid - but different ID. + return Constants.UNDEFINED_AQS_ID; + } return aqsSessionId; } 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 1912e39a17a..b22ab028b06 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 @@ -111,9 +111,7 @@ public void updateGaugeCollection(ApplicationProcessState applicationProcessStat gaugeManagerDataCollectionJob.cancel(false); } - if (session == null - || !session.isGaugeAndEventCollectionEnabled() - || session.aqsSessionId().equals(Constants.UNDEFINED_AQS_ID)) { + if (shouldStopCollectingGauges()) { logger.warn("Not starting gauge collection."); stopCollectingGauges(); return; @@ -433,6 +431,12 @@ private long getMemoryGaugeCollectionFrequencyMs( } } + private boolean shouldStopCollectingGauges() { + return session == null + || !session.isGaugeAndEventCollectionEnabled() + || session.aqsSessionId().equals(Constants.UNDEFINED_AQS_ID); + } + @VisibleForTesting void setApplicationProcessState(ApplicationProcessState applicationProcessState) { this.applicationProcessState = applicationProcessState; From 54f56020db9ecdcba428ea03cdfb5731d4a05548 Mon Sep 17 00:00:00 2001 From: Matthew Robertson Date: Wed, 2 Apr 2025 09:32:49 -0600 Subject: [PATCH 36/76] Fix unit tests showing warning about datastore_shared_counter (#6830) Fix Robolectric tests showing warning about datastore_shared_counter by falling back to the normal DataStore instance. This is a known issue, see [b/352047731](http://b/352047731). --- .../sessions/FirebaseSessionsComponent.kt | 39 ++++++++++++++++++- .../google/firebase/sessions/SessionEvents.kt | 1 - 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt index 99de9e4a3fc..918782ef140 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt @@ -18,8 +18,11 @@ package com.google.firebase.sessions import android.content.Context import android.util.Log +import androidx.datastore.core.DataMigration import androidx.datastore.core.DataStore +import androidx.datastore.core.DataStoreFactory import androidx.datastore.core.MultiProcessDataStoreFactory +import androidx.datastore.core.Serializer import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler import androidx.datastore.dataStoreFile import com.google.android.datatransport.TransportFactory @@ -43,6 +46,7 @@ import dagger.BindsInstance import dagger.Component import dagger.Module import dagger.Provides +import java.io.File import javax.inject.Qualifier import javax.inject.Singleton import kotlin.coroutines.CoroutineContext @@ -137,7 +141,7 @@ internal interface FirebaseSessionsComponent { appContext: Context, @Blocking blockingDispatcher: CoroutineContext, ): DataStore = - MultiProcessDataStoreFactory.create( + createDataStore( serializer = SessionConfigsSerializer, corruptionHandler = ReplaceFileCorruptionHandler { ex -> @@ -154,7 +158,7 @@ internal interface FirebaseSessionsComponent { appContext: Context, @Blocking blockingDispatcher: CoroutineContext, ): DataStore = - MultiProcessDataStoreFactory.create( + createDataStore( serializer = SessionDataSerializer, corruptionHandler = ReplaceFileCorruptionHandler { ex -> @@ -164,6 +168,37 @@ internal interface FirebaseSessionsComponent { scope = CoroutineScope(blockingDispatcher), produceFile = { appContext.dataStoreFile("aqs/sessionDataStore.data") }, ) + + private fun createDataStore( + serializer: Serializer, + corruptionHandler: ReplaceFileCorruptionHandler, + migrations: List> = listOf(), + scope: CoroutineScope, + produceFile: () -> File, + ): DataStore = + if (loadDataStoreSharedCounter()) { + MultiProcessDataStoreFactory.create( + serializer, + corruptionHandler, + migrations, + scope, + produceFile, + ) + } else { + DataStoreFactory.create(serializer, corruptionHandler, migrations, scope, produceFile) + } + + /** This native library in unavailable in some conditions, for example, Robolectric tests */ + // TODO(mrober): Remove this when b/392626815 is resolved + private fun loadDataStoreSharedCounter(): Boolean = + try { + System.loadLibrary("datastore_shared_counter") + true + } catch (_: UnsatisfiedLinkError) { + false + } catch (_: SecurityException) { + false + } } } } diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt index 25b3cbeb15d..6a540fd0104 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt @@ -63,7 +63,6 @@ internal object SessionEvents { fun getApplicationInfo(firebaseApp: FirebaseApp): ApplicationInfo { val context = firebaseApp.applicationContext val packageName = context.packageName - @Suppress("DEPRECATION") // TODO(mrober): Use ApplicationInfoFlags when target sdk set to 33 val packageInfo = context.packageManager.getPackageInfo(packageName, 0) val buildVersion = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { From e0eb4c9dd504bb35e3e2befc5dc2acb8f279c79c Mon Sep 17 00:00:00 2001 From: Matthew Robertson Date: Wed, 2 Apr 2025 09:32:49 -0600 Subject: [PATCH 37/76] Fix unit tests showing warning about datastore_shared_counter (#6830) Fix Robolectric tests showing warning about datastore_shared_counter by falling back to the normal DataStore instance. This is a known issue, see [b/352047731](http://b/352047731). --- .../sessions/FirebaseSessionsComponent.kt | 39 ++++++++++++++++++- .../google/firebase/sessions/SessionEvents.kt | 1 - 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt index 99de9e4a3fc..918782ef140 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt @@ -18,8 +18,11 @@ package com.google.firebase.sessions import android.content.Context import android.util.Log +import androidx.datastore.core.DataMigration import androidx.datastore.core.DataStore +import androidx.datastore.core.DataStoreFactory import androidx.datastore.core.MultiProcessDataStoreFactory +import androidx.datastore.core.Serializer import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler import androidx.datastore.dataStoreFile import com.google.android.datatransport.TransportFactory @@ -43,6 +46,7 @@ import dagger.BindsInstance import dagger.Component import dagger.Module import dagger.Provides +import java.io.File import javax.inject.Qualifier import javax.inject.Singleton import kotlin.coroutines.CoroutineContext @@ -137,7 +141,7 @@ internal interface FirebaseSessionsComponent { appContext: Context, @Blocking blockingDispatcher: CoroutineContext, ): DataStore = - MultiProcessDataStoreFactory.create( + createDataStore( serializer = SessionConfigsSerializer, corruptionHandler = ReplaceFileCorruptionHandler { ex -> @@ -154,7 +158,7 @@ internal interface FirebaseSessionsComponent { appContext: Context, @Blocking blockingDispatcher: CoroutineContext, ): DataStore = - MultiProcessDataStoreFactory.create( + createDataStore( serializer = SessionDataSerializer, corruptionHandler = ReplaceFileCorruptionHandler { ex -> @@ -164,6 +168,37 @@ internal interface FirebaseSessionsComponent { scope = CoroutineScope(blockingDispatcher), produceFile = { appContext.dataStoreFile("aqs/sessionDataStore.data") }, ) + + private fun createDataStore( + serializer: Serializer, + corruptionHandler: ReplaceFileCorruptionHandler, + migrations: List> = listOf(), + scope: CoroutineScope, + produceFile: () -> File, + ): DataStore = + if (loadDataStoreSharedCounter()) { + MultiProcessDataStoreFactory.create( + serializer, + corruptionHandler, + migrations, + scope, + produceFile, + ) + } else { + DataStoreFactory.create(serializer, corruptionHandler, migrations, scope, produceFile) + } + + /** This native library in unavailable in some conditions, for example, Robolectric tests */ + // TODO(mrober): Remove this when b/392626815 is resolved + private fun loadDataStoreSharedCounter(): Boolean = + try { + System.loadLibrary("datastore_shared_counter") + true + } catch (_: UnsatisfiedLinkError) { + false + } catch (_: SecurityException) { + false + } } } } diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt index 25b3cbeb15d..6a540fd0104 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt @@ -63,7 +63,6 @@ internal object SessionEvents { fun getApplicationInfo(firebaseApp: FirebaseApp): ApplicationInfo { val context = firebaseApp.applicationContext val packageName = context.packageName - @Suppress("DEPRECATION") // TODO(mrober): Use ApplicationInfoFlags when target sdk set to 33 val packageInfo = context.packageManager.getPackageInfo(packageName, 0) val buildVersion = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { From b453f98d4c992ea0bf52be234f3e2500d66369a8 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 7 Feb 2025 14:58:37 -0500 Subject: [PATCH 38/76] Remove the logging of GaugeMetadata to allow using AQS (#6678) Based on the behaviour of AQS w/ Fireperf, an AQS session isn't available when (currently) logging gauge metadata. Changes: - Remove the current logging of gauge metadata - will be re-introduced in a future PR. - Switch Gauge collection from `scheduleAtFixedRate` to `scheduleWithFixedDelay`. As [documented](https://stackoverflow.com/a/78405653), this *should* prevent a potentially large amounts of gauge collection if a process is cached, and then restored during a verbose session - which *should* make it work better w/ AQS. - Remove API restricted behaviour which is no longer relevant. --- .../firebase/perf/session/SessionManager.java | 50 ++++++- .../perf/session/gauges/GaugeManager.java | 122 ++++++------------ .../perf/session/SessionManagerTest.java | 121 ++++++++++++++--- 3 files changed, 195 insertions(+), 98 deletions(-) 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 27f19ee704c..0e4a80757f6 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 @@ -27,9 +27,11 @@ 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. @@ -43,6 +45,7 @@ public class SessionManager extends AppStateUpdateHandler { private final Set> clients = new HashSet<>(); private PerfSession perfSession; + private Future syncInitFuture; /** Returns the singleton instance of SessionManager. */ public static SessionManager getInstance() { @@ -68,6 +71,7 @@ public SessionManager( this.gaugeManager = gaugeManager; this.perfSession = perfSession; this.appStateMonitor = appStateMonitor; + registerForAppState(); } @Override @@ -89,7 +93,42 @@ public void onUpdateAppState(ApplicationProcessState newAppState) { * (currently that is before onResume finishes) to ensure gauge collection starts on time. */ public void setApplicationContext(final Context appContext) { - gaugeManager.initializeGaugeMetadataManager(appContext); + // TODO(b/258263016): Migrate to go/firebase-android-executors + @SuppressLint("ThreadPoolCreation") + ExecutorService executorService = Executors.newSingleThreadExecutor(); + syncInitFuture = + executorService.submit( + () -> { + gaugeManager.initializeGaugeMetadataManager(appContext); + }); + } + + @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); + } + } } /** @@ -113,7 +152,7 @@ public void stopGaugeCollectionIfSessionRunningTooLong() { */ public void updatePerfSession(PerfSession perfSession) { // Do not update the perf session if it is the exact same sessionId. - if (Objects.equals(perfSession.sessionId(), this.perfSession.sessionId())) { + if (perfSession.sessionId() == this.perfSession.sessionId()) { return; } @@ -187,4 +226,9 @@ private void updateGaugeCollection(ApplicationProcessState applicationProcessSta public void setPerfSession(PerfSession perfSession) { this.perfSession = perfSession; } + + @VisibleForTesting + public Future getSyncInitFuture() { + return this.syncInitFuture; + } } 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 b22ab028b06..30da2f0160f 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 @@ -24,7 +24,6 @@ import com.google.firebase.perf.logging.AndroidLogger; import com.google.firebase.perf.session.PerfSession; import com.google.firebase.perf.transport.TransportManager; -import com.google.firebase.perf.util.Constants; import com.google.firebase.perf.util.Timer; import com.google.firebase.perf.v1.AndroidMemoryReading; import com.google.firebase.perf.v1.ApplicationProcessState; @@ -60,12 +59,10 @@ public class GaugeManager { private final TransportManager transportManager; @Nullable private GaugeMetadataManager gaugeMetadataManager; - @Nullable private ScheduledFuture gaugeManagerDataCollectionJob = null; - @Nullable private PerfSession session = null; - - // The default value for application process state is Foreground. This will be updated based on - // app state changes. - private ApplicationProcessState applicationProcessState = ApplicationProcessState.FOREGROUND; + @Nullable private ScheduledFuture gaugeManagerDataCollectionJob = null; + @Nullable private String sessionId = null; + private ApplicationProcessState applicationProcessState = + ApplicationProcessState.APPLICATION_PROCESS_STATE_UNKNOWN; // TODO(b/258263016): Migrate to go/firebase-android-executors @SuppressLint("ThreadPoolCreation") @@ -106,22 +103,38 @@ public static synchronized GaugeManager getInstance() { return instance; } - public void updateGaugeCollection(ApplicationProcessState applicationProcessState) { - if (gaugeManagerDataCollectionJob != null) { - gaugeManagerDataCollectionJob.cancel(false); + /** + * Starts the collection of available gauges for the given {@code sessionId} and {@code + * applicationProcessState}. The collected Gauge Metrics will be flushed at regular intervals. + * + *

GaugeManager can only collect gauges for one session at a time, and if this method is called + * again with the same or new sessionId while it's already collecting gauges, all future gauges + * will then be associated with the same or new sessionId and applicationProcessState. + * + * @param session The {@link PerfSession} to which the collected gauges will be associated with. + * @param applicationProcessState The {@link ApplicationProcessState} the collected GaugeMetrics + * will be associated with. + * @note: This method is NOT thread safe - {@link this.startCollectingGauges()} and {@link + * this.stopCollectingGauges()} should always be called from the same thread. + */ + public void startCollectingGauges( + PerfSession session, ApplicationProcessState applicationProcessState) { + if (this.sessionId != null) { + stopCollectingGauges(); } - if (shouldStopCollectingGauges()) { - logger.warn("Not starting gauge collection."); - stopCollectingGauges(); + long collectionFrequency = startCollectingGauges(applicationProcessState, session.getTimer()); + if (collectionFrequency == INVALID_GAUGE_COLLECTION_FREQUENCY) { + logger.warn("Invalid gauge collection frequency. Unable to start collecting Gauges."); return; } - long collectionFrequency = - Math.min( - getCpuGaugeCollectionFrequencyMs(applicationProcessState), - getMemoryGaugeCollectionFrequencyMs(applicationProcessState)); - String sessionIdForScheduledTask = session.aqsSessionId(); + this.sessionId = session.sessionId(); + this.applicationProcessState = applicationProcessState; + + // This is needed, otherwise the Runnable might use a stale value. + final String sessionIdForScheduledTask = sessionId; + final ApplicationProcessState applicationProcessStateForScheduledTask = applicationProcessState; try { gaugeManagerDataCollectionJob = @@ -129,7 +142,7 @@ public void updateGaugeCollection(ApplicationProcessState applicationProcessStat .get() .scheduleWithFixedDelay( () -> { - syncFlush(sessionIdForScheduledTask, applicationProcessState); + syncFlush(sessionIdForScheduledTask, applicationProcessStateForScheduledTask); }, /* initialDelay= */ collectionFrequency * APPROX_NUMBER_OF_DATA_POINTS_PER_GAUGE_METRIC, @@ -139,49 +152,10 @@ public void updateGaugeCollection(ApplicationProcessState applicationProcessStat } catch (RejectedExecutionException e) { logger.warn("Unable to start collecting Gauges: " + e.getMessage()); } - - if (this.applicationProcessState != applicationProcessState) { - this.applicationProcessState = applicationProcessState; - - // If there's a change in App State - update the gauge collection frequency as well. - stopCollectingGauges(); - startCollectingGauges(applicationProcessState, this.session.getTimer()); - } - } - - /** - * Starts the collection of available gauges for the given {@code session}. - * - *

GaugeManager can only collect gauges for one session at a time, and if this method is called - * again with the same or new sessionId while it's already collecting gauges, all future gauges - * will then be associated with the same or new sessionId and applicationProcessState. - * - * @param session The {@link PerfSession} to which the collected gauges will be associated with. - * @note: This method is NOT thread safe - {@link this.startCollectingGauges()} and {@link - * this.stopCollectingGauges()} should always be called from the same thread. - */ - public void startCollectingGauges(PerfSession session) { - if (this.session != null) { - stopCollectingGauges(); - } - - // Updates the session associated w/ Gauge Manager when starting collecting gauges. - this.session = session; - - if (!session.isGaugeAndEventCollectionEnabled()) { - return; - } - - long collectionFrequency = startCollectingGauges(applicationProcessState, session.getTimer()); - if (collectionFrequency == INVALID_GAUGE_COLLECTION_FREQUENCY) { - logger.warn("Invalid gauge collection frequency. Unable to start collecting Gauges."); - } } /** - * Starts the collection of available Gauges for the given {@code appState}. Do note that - * uploading the gauges *only* starts after calling - * {@link #updateGaugeCollection(ApplicationProcessState)} + * Starts the collection of available Gauges for the given {@code appState}. * * @param appState The app state to which the collected gauges are associated. * @param referenceTime The time off which the system time is calculated when collecting gauges. @@ -215,14 +189,12 @@ private long startCollectingGauges(ApplicationProcessState appState, Timer refer * this.stopCollectingGauges()} should always be called from the same thread. */ public void stopCollectingGauges() { - if (this.session == null) { + if (this.sessionId == null) { return; } // This is needed, otherwise the Runnable might use a stale value. - - // AQS is guaranteed to be available when stopping gauge collection. - final String sessionIdForScheduledTask = session.aqsSessionId(); + final String sessionIdForScheduledTask = sessionId; final ApplicationProcessState applicationProcessStateForScheduledTask = applicationProcessState; cpuGaugeCollector.get().stopCollecting(); @@ -232,10 +204,9 @@ public void stopCollectingGauges() { gaugeManagerDataCollectionJob.cancel(false); } - // TODO(b/394127311): Switch to using AQS. // Flush any data that was collected for this session one last time. @SuppressWarnings("FutureReturnValueIgnored") - ScheduledFuture unusedFuture = + ScheduledFuture unusedFuture = gaugeManagerExecutor .get() .schedule( @@ -245,7 +216,8 @@ public void stopCollectingGauges() { TIME_TO_WAIT_BEFORE_FLUSHING_GAUGES_QUEUE_MS, TimeUnit.MILLISECONDS); - this.session = null; + this.sessionId = null; + this.applicationProcessState = ApplicationProcessState.APPLICATION_PROCESS_STATE_UNKNOWN; } /** @@ -278,16 +250,17 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) { /** * Log the Gauge Metadata information to the transport. * - * @param aqsSessionId The {@link PerfSession#aqsSessionId()} ()} to which the collected Gauge Metrics + * @param sessionId The {@link PerfSession#sessionId()} 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 aqsSessionId, ApplicationProcessState appState) { + public boolean logGaugeMetadata(String sessionId, ApplicationProcessState appState) { + // TODO(b/394127311): Re-introduce logging of metadata for AQS. if (gaugeMetadataManager != null) { GaugeMetric gaugeMetric = GaugeMetric.newBuilder() - .setSessionId(aqsSessionId) + .setSessionId(sessionId) .setGaugeMetadata(getGaugeMetadata()) .build(); transportManager.log(gaugeMetric, appState); @@ -430,15 +403,4 @@ private long getMemoryGaugeCollectionFrequencyMs( return memoryGaugeCollectionFrequency; } } - - private boolean shouldStopCollectingGauges() { - return session == null - || !session.isGaugeAndEventCollectionEnabled() - || session.aqsSessionId().equals(Constants.UNDEFINED_AQS_ID); - } - - @VisibleForTesting - void setApplicationProcessState(ApplicationProcessState applicationProcessState) { - this.applicationProcessState = applicationProcessState; - } } 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 deff47ef7f4..37b9ff7215b 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 @@ -16,6 +16,9 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -37,6 +40,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.AdditionalMatchers; import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.Mock; @@ -78,15 +82,105 @@ public void setApplicationContext_initializeGaugeMetadataManager() new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); testSessionManager.setApplicationContext(mockApplicationContext); + testSessionManager.getSyncInitFuture().get(); inOrder.verify(mockGaugeManager).initializeGaugeMetadataManager(any()); } + @Test + public void testOnUpdateAppStateDoesNothingDuringAppStart() { + String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); + + assertThat(oldSessionId).isNotNull(); + assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); + + AppStateMonitor.getInstance().setIsColdStart(true); + + SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.FOREGROUND); + assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); + } + + @Test + public void testOnUpdateAppStateGeneratesNewSessionIdOnForegroundState() { + String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); + + assertThat(oldSessionId).isNotNull(); + assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); + + SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.FOREGROUND); + assertThat(oldSessionId).isNotEqualTo(SessionManager.getInstance().perfSession().sessionId()); + } + + @Test + public void testOnUpdateAppStateDoesntGenerateNewSessionIdOnBackgroundState() { + String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); + + assertThat(oldSessionId).isNotNull(); + assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); + + SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.BACKGROUND); + assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); + } + + @Test + public void testOnUpdateAppStateGeneratesNewSessionIdOnBackgroundStateIfPerfSessionExpires() { + when(mockPerfSession.isSessionRunningTooLong()).thenReturn(true); + SessionManager testSessionManager = + new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); + String oldSessionId = testSessionManager.perfSession().sessionId(); + + assertThat(oldSessionId).isNotNull(); + assertThat(oldSessionId).isEqualTo(testSessionManager.perfSession().sessionId()); + + testSessionManager.onUpdateAppState(ApplicationProcessState.BACKGROUND); + assertThat(oldSessionId).isNotEqualTo(testSessionManager.perfSession().sessionId()); + } + + @Test + public void + testOnUpdateAppStateDoesntMakeGaugeManagerLogGaugeMetadataOnForegroundStateIfSessionIsNonVerbose() { + forceNonVerboseSession(); + + SessionManager testSessionManager = + new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); + testSessionManager.onUpdateAppState(ApplicationProcessState.FOREGROUND); + + verify(mockGaugeManager, never()) + .logGaugeMetadata( + anyString(), nullable(com.google.firebase.perf.v1.ApplicationProcessState.class)); + } + + @Test + public void + testOnUpdateAppStateDoesntMakeGaugeManagerLogGaugeMetadataOnBackgroundStateEvenIfSessionIsVerbose() { + forceVerboseSession(); + + SessionManager testSessionManager = + new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); + testSessionManager.onUpdateAppState(ApplicationProcessState.BACKGROUND); + + verify(mockGaugeManager, never()) + .logGaugeMetadata( + anyString(), nullable(com.google.firebase.perf.v1.ApplicationProcessState.class)); + } + + @Test + public void testOnUpdateAppStateMakesGaugeManagerStartCollectingGaugesIfSessionIsVerbose() { + forceVerboseSession(); + + SessionManager testSessionManager = + new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); + testSessionManager.onUpdateAppState(ApplicationProcessState.FOREGROUND); + + verify(mockGaugeManager) + .startCollectingGauges(AdditionalMatchers.not(eq(mockPerfSession)), any()); + } + // LogGaugeData on new perf session when Verbose // NotLogGaugeData on new perf session when not Verbose // Mark Session as expired after time limit. @Test - public void testUpdatePerfSessionMakesGaugeManagerStopCollectingGaugesIfSessionIsNonVerbose() { + public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesIfSessionIsNonVerbose() { forceNonVerboseSession(); SessionManager testSessionManager = @@ -97,7 +191,7 @@ public void testUpdatePerfSessionMakesGaugeManagerStopCollectingGaugesIfSessionI } @Test - public void testUpdatePerfSessionMakesGaugeManagerStopCollectingGaugesWhenSessionsDisabled() { + public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesWhenSessionsDisabled() { forceSessionsFeatureDisabled(); SessionManager testSessionManager = @@ -127,25 +221,22 @@ public void testSessionIdDoesNotUpdateIfPerfSessionRunsTooLong() { } @Test - public void testUpdatePerfSessionStartsCollectingGaugesIfSessionIsVerbose() { + public void testPerfSessionExpiredMakesGaugeManagerStopsCollectingGaugesIfSessionIsVerbose() { + forceVerboseSession(); Timer mockTimer = mock(Timer.class); when(mockClock.getTime()).thenReturn(mockTimer); - when(mockAppStateMonitor.getAppState()).thenReturn(ApplicationProcessState.FOREGROUND); - PerfSession previousSession = new PerfSession("previousSession", mockClock); - previousSession.setGaugeAndEventCollectionEnabled(false); + PerfSession session = new PerfSession("sessionId", mockClock); + SessionManager testSessionManager = + new SessionManager(mockGaugeManager, session, mockAppStateMonitor); - PerfSession newSession = new PerfSession("newSession", mockClock); - newSession.setGaugeAndEventCollectionEnabled(true); + assertThat(session.isSessionRunningTooLong()).isFalse(); - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, previousSession, mockAppStateMonitor); - testSessionManager.updatePerfSession(newSession); - testSessionManager.setApplicationContext(mockApplicationContext); + when(mockTimer.getDurationMicros()) + .thenReturn(TimeUnit.HOURS.toMicros(5)); // Default Max Session Length is 4 hours - verify(mockGaugeManager, times(1)).initializeGaugeMetadataManager(mockApplicationContext); - verify(mockGaugeManager, times(1)).startCollectingGauges(newSession); - verify(mockGaugeManager, times(1)).updateGaugeCollection(ApplicationProcessState.FOREGROUND); + assertThat(session.isSessionRunningTooLong()).isTrue(); + verify(mockGaugeManager, times(0)).logGaugeMetadata(any(), any()); } @Test From 50f8ba09493840c48f7f36dfe19104894175b447 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 7 Feb 2025 15:20:31 -0500 Subject: [PATCH 39/76] Add a SessionSubscriber --- .../firebase/perf/FirebasePerfRegistrar.java | 1 + .../firebase/perf/FirebasePerformance.java | 20 ++----- .../firebase/perf/config/ConfigResolver.java | 6 +- .../FirebasePerformanceSessionSubscriber.kt | 60 +++++++------------ .../firebase/perf/session/PerfSession.java | 4 +- .../firebase/perf/session/SessionManager.java | 9 +-- 6 files changed, 32 insertions(+), 68 deletions(-) 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 daffc2de81a..af5a0af622b 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 @@ -32,6 +32,7 @@ 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; 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 2c41d402cd0..f825ab4610b 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 @@ -44,7 +44,7 @@ import com.google.firebase.perf.util.Timer; import com.google.firebase.remoteconfig.RemoteConfigComponent; import com.google.firebase.sessions.api.FirebaseSessionsDependencies; -import com.google.firebase.sessions.api.SessionSubscriber; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.URL; @@ -55,6 +55,7 @@ import javax.inject.Inject; import javax.inject.Singleton; + /** * The Firebase Performance Monitoring API. * @@ -95,9 +96,7 @@ 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 */ + /** Valid HttpMethods for manual network APIs */ @StringDef({ HttpMethod.GET, HttpMethod.PUT, @@ -141,7 +140,7 @@ 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; - /** + /** * Constructs the FirebasePerformance class and allows injecting dependencies. * *

TODO(b/172007278): Initialize SDK components in a background thread to avoid cases of cyclic @@ -170,7 +169,6 @@ public static FirebasePerformance getInstance() { this.mPerformanceCollectionForceEnabledState = false; this.configResolver = configResolver; this.mMetadataBundle = new ImmutableBundle(new Bundle()); - this.sessionSubscriber = new FirebasePerformanceSessionSubscriber(false); return; } @@ -187,9 +185,7 @@ public static FirebasePerformance getInstance() { sessionManager.setApplicationContext(appContext); mPerformanceCollectionForceEnabledState = configResolver.getIsPerformanceCollectionEnabled(); - this.sessionSubscriber = - new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled()); - FirebaseSessionsDependencies.register(this.sessionSubscriber); + FirebaseSessionsDependencies.register(new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled())); if (logger.isLogcatEnabled() && isPerformanceCollectionEnabled()) { logger.info( @@ -465,10 +461,4 @@ private static ImmutableBundle extractMetadata(Context appContext) { Boolean getPerformanceCollectionForceEnabledState() { return mPerformanceCollectionForceEnabledState; } - - @NonNull - @VisibleForTesting - protected SessionSubscriber getSessionSubscriber() { - return sessionSubscriber; - } } 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 index bd2ff2a32e7..203b2680873 100644 --- 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 @@ -1,51 +1,31 @@ -/* - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.google.firebase.perf.session +import com.google.firebase.perf.logging.AndroidLogger import com.google.firebase.perf.session.gauges.GaugeManager -import com.google.firebase.perf.util.Constants 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 { +class FirebasePerformanceSessionSubscriber(private val dataCollectionEnabled: Boolean) : SessionSubscriber { + override val isDataCollectionEnabled: Boolean + get() = dataCollectionEnabled - override val sessionSubscriberName: SessionSubscriber.Name = SessionSubscriber.Name.PERFORMANCE + override val sessionSubscriberName: SessionSubscriber.Name + get() = SessionSubscriber.Name.PERFORMANCE - override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { - val sessionManager = SessionManager.getInstance() - val currentPerfSession = sessionManager.perfSession() - val gaugeManager = GaugeManager.getInstance() + override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { + val currentPerfSession = SessionManager.getInstance().perfSession() - // A [PerfSession] was created before a session was started. - if (currentPerfSession.aqsSessionId().equals(Constants.UNDEFINED_AQS_ID)) { - currentPerfSession.setAQSId(sessionDetails) - gaugeManager.logGaugeMetadata( - currentPerfSession.aqsSessionId(), - ApplicationProcessState.FOREGROUND - ) - gaugeManager.updateGaugeCollection(ApplicationProcessState.FOREGROUND) - return - } + // A [PerfSession] was created before a session was started. + if (currentPerfSession.aqsSessionId() == null) { + currentPerfSession.setAQSId(sessionDetails) + GaugeManager.getInstance().logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + return + } - val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()) - updatedSession.setAQSId(sessionDetails) - sessionManager.updatePerfSession(updatedSession) - gaugeManager.logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) - } -} + val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()); + updatedSession.setAQSId(sessionDetails) + SessionManager.getInstance().updatePerfSession(updatedSession) + GaugeManager.getInstance().logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + } +} \ No newline at end of file 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 93985e67436..ee12fd07f02 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 @@ -25,6 +25,7 @@ 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.concurrent.TimeUnit; @@ -78,7 +79,7 @@ public String aqsSessionId() { return aqsSessionId; } - /** Sets the AQS sessionId for the given session. */ + /** Returns the AQS sessionId for the given session. */ public void setAQSId(SessionSubscriber.SessionDetails aqs) { if (aqsSessionId.equals(Constants.UNDEFINED_AQS_ID)) { aqsSessionId = aqs.getSessionId(); @@ -134,7 +135,6 @@ 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(aqsSessionId); 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 0e4a80757f6..0ebdbfb4d26 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 @@ -93,14 +93,7 @@ public void onUpdateAppState(ApplicationProcessState newAppState) { * (currently that is before onResume finishes) to ensure gauge collection starts on time. */ public void setApplicationContext(final Context appContext) { - // TODO(b/258263016): Migrate to go/firebase-android-executors - @SuppressLint("ThreadPoolCreation") - ExecutorService executorService = Executors.newSingleThreadExecutor(); - syncInitFuture = - executorService.submit( - () -> { - gaugeManager.initializeGaugeMetadataManager(appContext); - }); + gaugeManager.initializeGaugeMetadataManager(appContext); } @Override From d1588c94e6d54c23b755d1c5ff2883d10c61d759 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 7 Feb 2025 17:20:41 -0500 Subject: [PATCH 40/76] Remove app updates from session manager and add unit tests --- .../firebase/perf/session/SessionManager.java | 35 +---- .../perf/session/SessionManagerTest.java | 120 +++--------------- 2 files changed, 18 insertions(+), 137 deletions(-) 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 0ebdbfb4d26..a6b222e889b 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,6 @@ 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.session.gauges.GaugeManager; import com.google.firebase.perf.v1.ApplicationProcessState; import com.google.firebase.perf.v1.GaugeMetadata; @@ -27,6 +26,7 @@ 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; @@ -35,7 +35,7 @@ /** 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(); @@ -71,7 +71,6 @@ public SessionManager( this.gaugeManager = gaugeManager; this.perfSession = perfSession; this.appStateMonitor = appStateMonitor; - registerForAppState(); } @Override @@ -96,34 +95,6 @@ public void setApplicationContext(final Context appContext) { gaugeManager.initializeGaugeMetadataManager(appContext); } - @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); - } - } - } - /** * Checks if the current {@link PerfSession} is expired/timed out. If so, stop collecting gauges. * @@ -145,7 +116,7 @@ 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; } 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 37b9ff7215b..8c8ee679f99 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 @@ -16,9 +16,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -40,7 +37,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.AdditionalMatchers; import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.Mock; @@ -86,101 +82,12 @@ public void setApplicationContext_initializeGaugeMetadataManager() inOrder.verify(mockGaugeManager).initializeGaugeMetadataManager(any()); } - @Test - public void testOnUpdateAppStateDoesNothingDuringAppStart() { - String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - - AppStateMonitor.getInstance().setIsColdStart(true); - - SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.FOREGROUND); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - } - - @Test - public void testOnUpdateAppStateGeneratesNewSessionIdOnForegroundState() { - String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - - SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.FOREGROUND); - assertThat(oldSessionId).isNotEqualTo(SessionManager.getInstance().perfSession().sessionId()); - } - - @Test - public void testOnUpdateAppStateDoesntGenerateNewSessionIdOnBackgroundState() { - String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - - SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.BACKGROUND); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - } - - @Test - public void testOnUpdateAppStateGeneratesNewSessionIdOnBackgroundStateIfPerfSessionExpires() { - when(mockPerfSession.isSessionRunningTooLong()).thenReturn(true); - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - String oldSessionId = testSessionManager.perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(testSessionManager.perfSession().sessionId()); - - testSessionManager.onUpdateAppState(ApplicationProcessState.BACKGROUND); - assertThat(oldSessionId).isNotEqualTo(testSessionManager.perfSession().sessionId()); - } - - @Test - public void - testOnUpdateAppStateDoesntMakeGaugeManagerLogGaugeMetadataOnForegroundStateIfSessionIsNonVerbose() { - forceNonVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.FOREGROUND); - - verify(mockGaugeManager, never()) - .logGaugeMetadata( - anyString(), nullable(com.google.firebase.perf.v1.ApplicationProcessState.class)); - } - - @Test - public void - testOnUpdateAppStateDoesntMakeGaugeManagerLogGaugeMetadataOnBackgroundStateEvenIfSessionIsVerbose() { - forceVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.BACKGROUND); - - verify(mockGaugeManager, never()) - .logGaugeMetadata( - anyString(), nullable(com.google.firebase.perf.v1.ApplicationProcessState.class)); - } - - @Test - public void testOnUpdateAppStateMakesGaugeManagerStartCollectingGaugesIfSessionIsVerbose() { - forceVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.FOREGROUND); - - verify(mockGaugeManager) - .startCollectingGauges(AdditionalMatchers.not(eq(mockPerfSession)), any()); - } - // LogGaugeData on new perf session when Verbose // NotLogGaugeData on new perf session when not Verbose // Mark Session as expired after time limit. @Test - public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesIfSessionIsNonVerbose() { + public void testUpdatePerfSessionMakesGaugeManagerStopCollectingGaugesIfSessionIsNonVerbose() { forceNonVerboseSession(); SessionManager testSessionManager = @@ -191,7 +98,7 @@ public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesIfSessionIs } @Test - public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesWhenSessionsDisabled() { + public void testUpdatePerfSessionMakesGaugeManagerStopCollectingGaugesWhenSessionsDisabled() { forceSessionsFeatureDisabled(); SessionManager testSessionManager = @@ -221,22 +128,25 @@ public void testSessionIdDoesNotUpdateIfPerfSessionRunsTooLong() { } @Test - public void testPerfSessionExpiredMakesGaugeManagerStopsCollectingGaugesIfSessionIsVerbose() { - forceVerboseSession(); + public void testUpdatePerfSessionStartsCollectingGaugesIfSessionIsVerbose() { Timer mockTimer = mock(Timer.class); when(mockClock.getTime()).thenReturn(mockTimer); + when(mockAppStateMonitor.getAppState()).thenReturn(ApplicationProcessState.FOREGROUND); - PerfSession session = new PerfSession("sessionId", mockClock); - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, session, mockAppStateMonitor); + PerfSession previousSession = new PerfSession("previousSession", mockClock); + previousSession.setGaugeAndEventCollectionEnabled(false); - assertThat(session.isSessionRunningTooLong()).isFalse(); + PerfSession newSession = new PerfSession("newSession", mockClock); + newSession.setGaugeAndEventCollectionEnabled(true); - when(mockTimer.getDurationMicros()) - .thenReturn(TimeUnit.HOURS.toMicros(5)); // Default Max Session Length is 4 hours + SessionManager testSessionManager = + new SessionManager(mockGaugeManager, previousSession, mockAppStateMonitor); + testSessionManager.updatePerfSession(newSession); + testSessionManager.setApplicationContext(mockApplicationContext); - assertThat(session.isSessionRunningTooLong()).isTrue(); - verify(mockGaugeManager, times(0)).logGaugeMetadata(any(), any()); + verify(mockGaugeManager, times(1)).initializeGaugeMetadataManager(mockApplicationContext); + verify(mockGaugeManager, times(1)) + .startCollectingGauges(newSession, ApplicationProcessState.FOREGROUND); } @Test From 7718f958838732159213d979c0249238f7c15cc3 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 13:06:38 -0500 Subject: [PATCH 41/76] 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. --- .../firebase/perf/FirebasePerformance.java | 9 ++- .../FirebasePerformanceSessionSubscriber.kt | 55 ++++++++++++------- .../firebase/perf/session/PerfSession.java | 1 + .../perf/session/gauges/GaugeManager.java | 1 + 4 files changed, 41 insertions(+), 25 deletions(-) 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 f825ab4610b..e4ddfcd600c 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 @@ -44,7 +44,6 @@ 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; @@ -55,7 +54,6 @@ import javax.inject.Inject; import javax.inject.Singleton; - /** * The Firebase Performance Monitoring API. * @@ -96,7 +94,7 @@ public class FirebasePerformance implements FirebasePerformanceAttributable { // once during initialization and cache it. private final ImmutableBundle mMetadataBundle; - /** Valid HttpMethods for manual network APIs */ + /** Valid HttpMethods for manual network APIs */ @StringDef({ HttpMethod.GET, HttpMethod.PUT, @@ -140,7 +138,7 @@ 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; - /** + /** * Constructs the FirebasePerformance class and allows injecting dependencies. * *

TODO(b/172007278): Initialize SDK components in a background thread to avoid cases of cyclic @@ -185,7 +183,8 @@ public static FirebasePerformance getInstance() { sessionManager.setApplicationContext(appContext); mPerformanceCollectionForceEnabledState = configResolver.getIsPerformanceCollectionEnabled(); - FirebaseSessionsDependencies.register(new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled())); + FirebaseSessionsDependencies.register( + new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled())); if (logger.isLogcatEnabled() && isPerformanceCollectionEnabled()) { logger.info( 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 index 203b2680873..b6a3d30c139 100644 --- 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 @@ -1,31 +1,46 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.google.firebase.perf.session -import com.google.firebase.perf.logging.AndroidLogger 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(private val dataCollectionEnabled: Boolean) : SessionSubscriber { - override val isDataCollectionEnabled: Boolean - get() = dataCollectionEnabled - - override val sessionSubscriberName: SessionSubscriber.Name - get() = SessionSubscriber.Name.PERFORMANCE +class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: Boolean) : + SessionSubscriber { - override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { - val currentPerfSession = SessionManager.getInstance().perfSession() + override val sessionSubscriberName: SessionSubscriber.Name = SessionSubscriber.Name.PERFORMANCE - // A [PerfSession] was created before a session was started. - if (currentPerfSession.aqsSessionId() == null) { - currentPerfSession.setAQSId(sessionDetails) - GaugeManager.getInstance().logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) - return - } + override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { + val currentPerfSession = SessionManager.getInstance().perfSession() - val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()); - updatedSession.setAQSId(sessionDetails) - SessionManager.getInstance().updatePerfSession(updatedSession) - GaugeManager.getInstance().logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + // A [PerfSession] was created before a session was started. + if (currentPerfSession.aqsSessionId() == null) { + currentPerfSession.setAQSId(sessionDetails) + GaugeManager.getInstance() + .logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + return } -} \ No newline at end of file + + val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()) + updatedSession.setAQSId(sessionDetails) + SessionManager.getInstance().updatePerfSession(updatedSession) + GaugeManager.getInstance() + .logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + } +} 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 ee12fd07f02..08dde61bd30 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 @@ -135,6 +135,7 @@ 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(aqsSessionId); 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 30da2f0160f..803edc6f4a2 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 @@ -204,6 +204,7 @@ public void stopCollectingGauges() { gaugeManagerDataCollectionJob.cancel(false); } + // TODO(b/394127311): Switch to using AQS. // Flush any data that was collected for this session one last time. @SuppressWarnings("FutureReturnValueIgnored") ScheduledFuture unusedFuture = From e91e147a1465ef3d6f503a4fd11b16ab0a24af32 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 17:34:23 -0500 Subject: [PATCH 42/76] Revert initializing gauge collection in FirebasePerfEarly --- .../firebase/perf/FirebasePerfEarly.java | 2 +- .../FirebasePerformanceSessionSubscriber.kt | 4 +- .../perf/session/gauges/GaugeManager.java | 114 +++++++++++------- 3 files changed, 76 insertions(+), 44 deletions(-) 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 766fd7daa95..52ca3016df5 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 @@ -22,6 +22,7 @@ import com.google.firebase.perf.config.ConfigResolver; import com.google.firebase.perf.metrics.AppStartTrace; import com.google.firebase.perf.session.SessionManager; + import java.util.concurrent.Executor; /** @@ -54,7 +55,6 @@ public FirebasePerfEarly( // In the case of cold start, we create a session and start collecting gauges as early as // possible. // Uploading the gauges however only starts once AQS is initialized. - // TODO(b/394127311): Update this to use changes in AQS initialization, SessionManager.getInstance().initializeGaugeCollection(); } } 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 index b6a3d30c139..03178a7d92d 100644 --- 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 @@ -28,12 +28,14 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { val currentPerfSession = SessionManager.getInstance().perfSession() + val gaugeManager = GaugeManager.getInstance() // A [PerfSession] was created before a session was started. if (currentPerfSession.aqsSessionId() == null) { currentPerfSession.setAQSId(sessionDetails) - GaugeManager.getInstance() + gaugeManager .logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + gaugeManager.updateGaugeCollection(ApplicationProcessState.FOREGROUND) return } 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 803edc6f4a2..cde70a98e52 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 @@ -59,10 +59,12 @@ public class GaugeManager { private final TransportManager transportManager; @Nullable private GaugeMetadataManager gaugeMetadataManager; - @Nullable private ScheduledFuture gaugeManagerDataCollectionJob = null; - @Nullable private String sessionId = null; - private ApplicationProcessState applicationProcessState = - ApplicationProcessState.APPLICATION_PROCESS_STATE_UNKNOWN; + @Nullable private ScheduledFuture gaugeManagerDataCollectionJob = null; + @Nullable private PerfSession session = null; + + // The default value for application process state is Foreground. This will be updated based on + // app state changes. + private ApplicationProcessState applicationProcessState = ApplicationProcessState.FOREGROUND; // TODO(b/258263016): Migrate to go/firebase-android-executors @SuppressLint("ThreadPoolCreation") @@ -103,38 +105,24 @@ public static synchronized GaugeManager getInstance() { return instance; } - /** - * Starts the collection of available gauges for the given {@code sessionId} and {@code - * applicationProcessState}. The collected Gauge Metrics will be flushed at regular intervals. - * - *

GaugeManager can only collect gauges for one session at a time, and if this method is called - * again with the same or new sessionId while it's already collecting gauges, all future gauges - * will then be associated with the same or new sessionId and applicationProcessState. - * - * @param session The {@link PerfSession} to which the collected gauges will be associated with. - * @param applicationProcessState The {@link ApplicationProcessState} the collected GaugeMetrics - * will be associated with. - * @note: This method is NOT thread safe - {@link this.startCollectingGauges()} and {@link - * this.stopCollectingGauges()} should always be called from the same thread. - */ - public void startCollectingGauges( - PerfSession session, ApplicationProcessState applicationProcessState) { - if (this.sessionId != null) { - stopCollectingGauges(); + public void updateGaugeCollection(ApplicationProcessState applicationProcessState) { + if (gaugeManagerDataCollectionJob != null) { + gaugeManagerDataCollectionJob.cancel(false); } - long collectionFrequency = startCollectingGauges(applicationProcessState, session.getTimer()); - if (collectionFrequency == INVALID_GAUGE_COLLECTION_FREQUENCY) { - logger.warn("Invalid gauge collection frequency. Unable to start collecting Gauges."); + if (session == null + || !session.isGaugeAndEventCollectionEnabled() + || session.aqsSessionId() == null) { + logger.warn("Not starting gauge collection."); + stopCollectingGauges(); return; } - this.sessionId = session.sessionId(); - this.applicationProcessState = applicationProcessState; - - // This is needed, otherwise the Runnable might use a stale value. - final String sessionIdForScheduledTask = sessionId; - final ApplicationProcessState applicationProcessStateForScheduledTask = applicationProcessState; + long collectionFrequency = + Math.min( + getCpuGaugeCollectionFrequencyMs(applicationProcessState), + getMemoryGaugeCollectionFrequencyMs(applicationProcessState)); + String sessionIdForScheduledTask = session.aqsSessionId(); try { gaugeManagerDataCollectionJob = @@ -142,7 +130,7 @@ public void startCollectingGauges( .get() .scheduleWithFixedDelay( () -> { - syncFlush(sessionIdForScheduledTask, applicationProcessStateForScheduledTask); + syncFlush(sessionIdForScheduledTask, applicationProcessState); }, /* initialDelay= */ collectionFrequency * APPROX_NUMBER_OF_DATA_POINTS_PER_GAUGE_METRIC, @@ -152,6 +140,43 @@ public void startCollectingGauges( } catch (RejectedExecutionException e) { logger.warn("Unable to start collecting Gauges: " + e.getMessage()); } + + if (this.applicationProcessState != applicationProcessState) { + this.applicationProcessState = applicationProcessState; + + // If there's a change in App State - update the gauge collection frequency as well. + stopCollectingGauges(); + startCollectingGauges(applicationProcessState, this.session.getTimer()); + } + } + + /** + * Starts the collection of available gauges for the given {@code session}. + * + *

GaugeManager can only collect gauges for one session at a time, and if this method is called + * again with the same or new sessionId while it's already collecting gauges, all future gauges + * will then be associated with the same or new sessionId and applicationProcessState. + * + * @param session The {@link PerfSession} to which the collected gauges will be associated with. + * @note: This method is NOT thread safe - {@link this.startCollectingGauges()} and {@link + * this.stopCollectingGauges()} should always be called from the same thread. + */ + public void startCollectingGauges(PerfSession session) { + if (this.session != null) { + stopCollectingGauges(); + } + + // Updates the session associated w/ Gauge Manager when starting collecting gauges. + this.session = session; + + if (!session.isGaugeAndEventCollectionEnabled()) { + return; + } + + long collectionFrequency = startCollectingGauges(applicationProcessState, session.getTimer()); + if (collectionFrequency == INVALID_GAUGE_COLLECTION_FREQUENCY) { + logger.warn("Invalid gauge collection frequency. Unable to start collecting Gauges."); + } } /** @@ -189,12 +214,14 @@ private long startCollectingGauges(ApplicationProcessState appState, Timer refer * this.stopCollectingGauges()} should always be called from the same thread. */ public void stopCollectingGauges() { - if (this.sessionId == null) { + if (this.session == null) { return; } // This is needed, otherwise the Runnable might use a stale value. - final String sessionIdForScheduledTask = sessionId; + + // AQS is guaranteed to be available when stopping gauge collection. + final String sessionIdForScheduledTask = session.aqsSessionId(); final ApplicationProcessState applicationProcessStateForScheduledTask = applicationProcessState; cpuGaugeCollector.get().stopCollecting(); @@ -204,10 +231,9 @@ public void stopCollectingGauges() { gaugeManagerDataCollectionJob.cancel(false); } - // TODO(b/394127311): Switch to using AQS. // Flush any data that was collected for this session one last time. @SuppressWarnings("FutureReturnValueIgnored") - ScheduledFuture unusedFuture = + ScheduledFuture unusedFuture = gaugeManagerExecutor .get() .schedule( @@ -217,8 +243,7 @@ public void stopCollectingGauges() { TIME_TO_WAIT_BEFORE_FLUSHING_GAUGES_QUEUE_MS, TimeUnit.MILLISECONDS); - this.sessionId = null; - this.applicationProcessState = ApplicationProcessState.APPLICATION_PROCESS_STATE_UNKNOWN; + this.session = null; } /** @@ -243,6 +268,7 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) { } // Adding Session ID info. + // TODO(b/394127311): Switch to using AQS. gaugeMetricBuilder.setSessionId(sessionId); transportManager.log(gaugeMetricBuilder.build(), appState); @@ -251,17 +277,16 @@ 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 PerfSession#aqsSessionId()} ()} 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) { - // TODO(b/394127311): Re-introduce logging of metadata for AQS. + public boolean logGaugeMetadata(String aqsSessionId, ApplicationProcessState appState) { if (gaugeMetadataManager != null) { GaugeMetric gaugeMetric = GaugeMetric.newBuilder() - .setSessionId(sessionId) + .setSessionId(aqsSessionId) .setGaugeMetadata(getGaugeMetadata()) .build(); transportManager.log(gaugeMetric, appState); @@ -404,4 +429,9 @@ private long getMemoryGaugeCollectionFrequencyMs( return memoryGaugeCollectionFrequency; } } + + @VisibleForTesting + void setApplicationProcessState(ApplicationProcessState applicationProcessState) { + this.applicationProcessState = applicationProcessState; + } } From 29cffc64ef761bd909266edcdb335cf1020c8787 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 1 Apr 2025 12:45:22 -0400 Subject: [PATCH 43/76] Switch AQS ID to an always known ID --- .../google/firebase/perf/FirebasePerfEarly.java | 2 +- .../FirebasePerformanceSessionSubscriber.kt | 17 ++++++++++------- .../perf/session/gauges/GaugeManager.java | 4 ++-- 3 files changed, 13 insertions(+), 10 deletions(-) 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 52ca3016df5..766fd7daa95 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 @@ -22,7 +22,6 @@ import com.google.firebase.perf.config.ConfigResolver; import com.google.firebase.perf.metrics.AppStartTrace; import com.google.firebase.perf.session.SessionManager; - import java.util.concurrent.Executor; /** @@ -55,6 +54,7 @@ public FirebasePerfEarly( // In the case of cold start, we create a session and start collecting gauges as early as // possible. // Uploading the gauges however only starts once AQS is initialized. + // TODO(b/394127311): Update this to use changes in AQS initialization, SessionManager.getInstance().initializeGaugeCollection(); } } 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 index 03178a7d92d..bd2ff2a32e7 100644 --- 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 @@ -17,6 +17,7 @@ package com.google.firebase.perf.session import com.google.firebase.perf.session.gauges.GaugeManager +import com.google.firebase.perf.util.Constants import com.google.firebase.perf.v1.ApplicationProcessState import com.google.firebase.sessions.api.SessionSubscriber import java.util.UUID @@ -27,22 +28,24 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: override val sessionSubscriberName: SessionSubscriber.Name = SessionSubscriber.Name.PERFORMANCE override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { - val currentPerfSession = SessionManager.getInstance().perfSession() + val sessionManager = SessionManager.getInstance() + val currentPerfSession = sessionManager.perfSession() val gaugeManager = GaugeManager.getInstance() // A [PerfSession] was created before a session was started. - if (currentPerfSession.aqsSessionId() == null) { + if (currentPerfSession.aqsSessionId().equals(Constants.UNDEFINED_AQS_ID)) { currentPerfSession.setAQSId(sessionDetails) - gaugeManager - .logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + gaugeManager.logGaugeMetadata( + currentPerfSession.aqsSessionId(), + ApplicationProcessState.FOREGROUND + ) gaugeManager.updateGaugeCollection(ApplicationProcessState.FOREGROUND) return } val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()) updatedSession.setAQSId(sessionDetails) - SessionManager.getInstance().updatePerfSession(updatedSession) - GaugeManager.getInstance() - .logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + sessionManager.updatePerfSession(updatedSession) + gaugeManager.logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) } } 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 cde70a98e52..33566b5a563 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 @@ -24,6 +24,7 @@ import com.google.firebase.perf.logging.AndroidLogger; import com.google.firebase.perf.session.PerfSession; import com.google.firebase.perf.transport.TransportManager; +import com.google.firebase.perf.util.Constants; import com.google.firebase.perf.util.Timer; import com.google.firebase.perf.v1.AndroidMemoryReading; import com.google.firebase.perf.v1.ApplicationProcessState; @@ -112,7 +113,7 @@ public void updateGaugeCollection(ApplicationProcessState applicationProcessStat if (session == null || !session.isGaugeAndEventCollectionEnabled() - || session.aqsSessionId() == null) { + || session.aqsSessionId().equals(Constants.UNDEFINED_AQS_ID)) { logger.warn("Not starting gauge collection."); stopCollectingGauges(); return; @@ -268,7 +269,6 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) { } // Adding Session ID info. - // TODO(b/394127311): Switch to using AQS. gaugeMetricBuilder.setSessionId(sessionId); transportManager.log(gaugeMetricBuilder.build(), appState); From 7dfdc760b7302204ad3324b986fbdd71066a038c Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Thu, 3 Apr 2025 15:13:18 -0400 Subject: [PATCH 44/76] Update unit tests --- .../firebase/perf/FirebasePerfRegistrar.java | 1 - .../google/firebase/perf/FirebasePerformance.java | 15 +++++++++++++-- .../google/firebase/perf/session/PerfSession.java | 1 - .../firebase/perf/session/SessionManager.java | 12 ++---------- .../firebase/perf/session/SessionManagerTest.java | 4 +--- 5 files changed, 16 insertions(+), 17 deletions(-) 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 af5a0af622b..daffc2de81a 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 @@ -32,7 +32,6 @@ 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; 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 e4ddfcd600c..2c41d402cd0 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 @@ -44,6 +44,7 @@ import com.google.firebase.perf.util.Timer; import com.google.firebase.remoteconfig.RemoteConfigComponent; import com.google.firebase.sessions.api.FirebaseSessionsDependencies; +import com.google.firebase.sessions.api.SessionSubscriber; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.URL; @@ -94,6 +95,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, @@ -167,6 +170,7 @@ public static FirebasePerformance getInstance() { this.mPerformanceCollectionForceEnabledState = false; this.configResolver = configResolver; this.mMetadataBundle = new ImmutableBundle(new Bundle()); + this.sessionSubscriber = new FirebasePerformanceSessionSubscriber(false); return; } @@ -183,8 +187,9 @@ public static FirebasePerformance getInstance() { sessionManager.setApplicationContext(appContext); mPerformanceCollectionForceEnabledState = configResolver.getIsPerformanceCollectionEnabled(); - FirebaseSessionsDependencies.register( - new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled())); + this.sessionSubscriber = + new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled()); + FirebaseSessionsDependencies.register(this.sessionSubscriber); if (logger.isLogcatEnabled() && isPerformanceCollectionEnabled()) { logger.info( @@ -460,4 +465,10 @@ private static ImmutableBundle extractMetadata(Context appContext) { Boolean getPerformanceCollectionForceEnabledState() { return mPerformanceCollectionForceEnabledState; } + + @NonNull + @VisibleForTesting + protected SessionSubscriber getSessionSubscriber() { + return sessionSubscriber; + } } 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 08dde61bd30..af87cd1ac51 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 @@ -25,7 +25,6 @@ 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.concurrent.TimeUnit; 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 a6b222e889b..27f19ee704c 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,6 +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.session.gauges.GaugeManager; import com.google.firebase.perf.v1.ApplicationProcessState; import com.google.firebase.perf.v1.GaugeMetadata; @@ -29,13 +30,10 @@ 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 { +public class SessionManager extends AppStateUpdateHandler { @SuppressLint("StaticFieldLeak") private static final SessionManager instance = new SessionManager(); @@ -45,7 +43,6 @@ public class SessionManager { private final Set> clients = new HashSet<>(); private PerfSession perfSession; - private Future syncInitFuture; /** Returns the singleton instance of SessionManager. */ public static SessionManager getInstance() { @@ -190,9 +187,4 @@ private void updateGaugeCollection(ApplicationProcessState applicationProcessSta public void setPerfSession(PerfSession perfSession) { this.perfSession = perfSession; } - - @VisibleForTesting - public Future getSyncInitFuture() { - return this.syncInitFuture; - } } 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 8c8ee679f99..daf51be5559 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 @@ -78,7 +78,6 @@ public void setApplicationContext_initializeGaugeMetadataManager() new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); testSessionManager.setApplicationContext(mockApplicationContext); - testSessionManager.getSyncInitFuture().get(); inOrder.verify(mockGaugeManager).initializeGaugeMetadataManager(any()); } @@ -145,8 +144,7 @@ public void testUpdatePerfSessionStartsCollectingGaugesIfSessionIsVerbose() { testSessionManager.setApplicationContext(mockApplicationContext); verify(mockGaugeManager, times(1)).initializeGaugeMetadataManager(mockApplicationContext); - verify(mockGaugeManager, times(1)) - .startCollectingGauges(newSession, ApplicationProcessState.FOREGROUND); + verify(mockGaugeManager, times(1)).startCollectingGauges(newSession); } @Test From 1793cdaa2f6cae3424ec274a506d71c1c92e0c10 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Thu, 3 Apr 2025 15:24:52 -0400 Subject: [PATCH 45/76] Add a better explanation in SessionSubscriber --- .../perf/session/FirebasePerformanceSessionSubscriber.kt | 2 ++ 1 file changed, 2 insertions(+) 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 index bd2ff2a32e7..1e05a11121b 100644 --- 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 @@ -39,6 +39,8 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND ) + // Gauge collection is started in [FirebasePerfEarly] - but it's not scheduled to be + // uploaded. This starts uploading the gauges if it's verbose. gaugeManager.updateGaugeCollection(ApplicationProcessState.FOREGROUND) return } From c22a3ebb91da58d076fddd7efdfba47a72c177ec Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 4 Apr 2025 12:10:47 -0400 Subject: [PATCH 46/76] Attempt at updating the GaugeManagerTests --- .../firebase/perf/session/SessionManager.java | 2 +- .../perf/session/gauges/GaugeManager.java | 2 +- .../perf/session/SessionManagerTest.java | 2 +- .../perf/session/gauges/GaugeManagerTest.java | 275 +++++++++--------- 4 files changed, 144 insertions(+), 137 deletions(-) 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 27f19ee704c..c458e24e92d 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 @@ -173,7 +173,7 @@ public void unregisterForSessionUpdates(WeakReference client private void startOrStopCollectingGauges() { if (perfSession.isGaugeAndEventCollectionEnabled()) { - gaugeManager.startCollectingGauges(perfSession); + gaugeManager.startCollectingGaugeMetrics(perfSession); } else { gaugeManager.stopCollectingGauges(); } 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 33566b5a563..a46e59af25f 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 @@ -162,7 +162,7 @@ public void updateGaugeCollection(ApplicationProcessState applicationProcessStat * @note: This method is NOT thread safe - {@link this.startCollectingGauges()} and {@link * this.stopCollectingGauges()} should always be called from the same thread. */ - public void startCollectingGauges(PerfSession session) { + public void startCollectingGaugeMetrics(PerfSession session) { if (this.session != null) { stopCollectingGauges(); } 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 daf51be5559..56b9dd441c4 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 @@ -144,7 +144,7 @@ public void testUpdatePerfSessionStartsCollectingGaugesIfSessionIsVerbose() { testSessionManager.setApplicationContext(mockApplicationContext); verify(mockGaugeManager, times(1)).initializeGaugeMetadataManager(mockApplicationContext); - verify(mockGaugeManager, times(1)).startCollectingGauges(newSession); + verify(mockGaugeManager, times(1)).startCollectingGaugeMetrics(newSession); } @Test 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 26be1fde301..7dfa0df013e 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 @@ -15,6 +15,8 @@ package com.google.firebase.perf.session.gauges; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -39,7 +41,9 @@ import com.google.firebase.perf.v1.CpuMetricReading; import com.google.firebase.perf.v1.GaugeMetadata; import com.google.firebase.perf.v1.GaugeMetric; +import com.google.firebase.sessions.api.SessionSubscriber; import com.google.testing.timing.FakeScheduledExecutorService; +import java.util.UUID; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; @@ -83,12 +87,12 @@ public void setUp() { doNothing() .when(fakeCpuGaugeCollector) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.nullable(Timer.class)); + .startCollecting(anyLong(), ArgumentMatchers.nullable(Timer.class)); doNothing().when(fakeCpuGaugeCollector).stopCollecting(); doNothing() .when(fakeMemoryGaugeCollector) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.nullable(Timer.class)); + .startCollecting(anyLong(), ArgumentMatchers.nullable(Timer.class)); doNothing().when(fakeMemoryGaugeCollector).stopCollecting(); doNothing().when(fakeCpuGaugeCollector).collectOnce(ArgumentMatchers.nullable(Timer.class)); @@ -124,8 +128,8 @@ public void setUp() { @Test public void testStartCollectingGaugesStartsCollectingMetrics() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession); + PerfSession fakeSession = createTestPerfSession(); + testGaugeManager.startCollectingGaugeMetrics(fakeSession); verify(fakeCpuGaugeCollector) .startCollecting( eq(DEFAULT_CPU_GAUGE_COLLECTION_FREQUENCY_FG_MS), @@ -138,33 +142,33 @@ public void testStartCollectingGaugesStartsCollectingMetrics() { @Test public void - stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithValidFrequencyInBackground() { + stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithValidFrequencyInForeground() { // PASS 1: Test with 0 - doReturn(0L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyBackgroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); - testGaugeManager.startCollectingGauges(fakeSession1); + doReturn(0L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); + PerfSession fakeSession1 = createTestPerfSession(); + + testGaugeManager.startCollectingGaugeMetrics(fakeSession1); - // Verify that Cpu metric collection is not started verify(fakeCpuGaugeCollector, never()) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.nullable(Timer.class)); + .startCollecting(anyLong(), ArgumentMatchers.nullable(Timer.class)); // Verify that Memory metric collection is started verify(fakeMemoryGaugeCollector) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.nullable(Timer.class)); + .startCollecting(anyLong(), ArgumentMatchers.nullable(Timer.class)); // PASS 2: Test with -ve value - doReturn(-25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyBackgroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); - testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); - testGaugeManager.startCollectingGauges(fakeSession2); + doReturn(-25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); + PerfSession fakeSession2 = createTestPerfSession(); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); + testGaugeManager.startCollectingGaugeMetrics(fakeSession2); // Verify that Cpu metric collection is not started verify(fakeCpuGaugeCollector, never()) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.nullable(Timer.class)); + .startCollecting(anyLong(), ArgumentMatchers.nullable(Timer.class)); // Verify that Memory metric collection is started verify(fakeMemoryGaugeCollector, times(2)) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.any(Timer.class)); + .startCollecting(anyLong(), ArgumentMatchers.any(Timer.class)); } @Test @@ -172,62 +176,61 @@ public void testStartCollectingGaugesStartsCollectingMetrics() { startCollectingGaugesOnBackground_invalidMemoryCaptureMs_onlyDisableMemoryCollection() { // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyBackgroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = createTestPerfSession(); testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); - testGaugeManager.startCollectingGauges(fakeSession1); + testGaugeManager.startCollectingGaugeMetrics(fakeSession1); // Verify that Memory metric collection is not started verify(fakeMemoryGaugeCollector, never()) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.nullable(Timer.class)); + .startCollecting(anyLong(), ArgumentMatchers.nullable(Timer.class)); // Verify that Cpu metric collection is started verify(fakeCpuGaugeCollector) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.nullable(Timer.class)); + .startCollecting(anyLong(), ArgumentMatchers.nullable(Timer.class)); // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyBackgroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = createTestPerfSession(); testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); - testGaugeManager.startCollectingGauges(fakeSession2); + testGaugeManager.startCollectingGaugeMetrics(fakeSession2); // Verify that Memory metric collection is not started verify(fakeMemoryGaugeCollector, never()) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.nullable(Timer.class)); + .startCollecting(anyLong(), ArgumentMatchers.nullable(Timer.class)); // Verify that Cpu metric collection is started verify(fakeCpuGaugeCollector, times(2)) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.any(Timer.class)); + .startCollecting(anyLong(), ArgumentMatchers.any(Timer.class)); } @Test public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithValidFrequency() { // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = createTestPerfSession(); testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); - testGaugeManager.startCollectingGauges(fakeSession1); + testGaugeManager.startCollectingGaugeMetrics(fakeSession1); // Verify that Cpu metric collection is not started verify(fakeCpuGaugeCollector, never()) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.any(Timer.class)); + .startCollecting(anyLong(), ArgumentMatchers.any(Timer.class)); // Verify that Memory metric collection is started - verify(fakeMemoryGaugeCollector) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.any(Timer.class)); + verify(fakeMemoryGaugeCollector).startCollecting(anyLong(), ArgumentMatchers.any(Timer.class)); // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = createTestPerfSession(); testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); - testGaugeManager.startCollectingGauges(fakeSession2); + testGaugeManager.startCollectingGaugeMetrics(fakeSession2); // Verify that Cpu metric collection is not started verify(fakeCpuGaugeCollector, never()) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.nullable(Timer.class)); + .startCollecting(anyLong(), ArgumentMatchers.nullable(Timer.class)); // Verify that Memory metric collection is started verify(fakeMemoryGaugeCollector, times(2)) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.any(Timer.class)); + .startCollecting(anyLong(), ArgumentMatchers.any(Timer.class)); } @Test @@ -235,59 +238,60 @@ public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithV startCollectingGaugesOnForeground_invalidMemoryCaptureMs_onlyDisableMemoryCollection() { // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = createTestPerfSession(); testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); - testGaugeManager.startCollectingGauges(fakeSession1); + testGaugeManager.startCollectingGaugeMetrics(fakeSession1); // Verify that Memory metric collection is not started verify(fakeMemoryGaugeCollector, never()) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.any(Timer.class)); + .startCollecting(anyLong(), ArgumentMatchers.any(Timer.class)); // Verify that Cpu metric collection is started - verify(fakeCpuGaugeCollector) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.any(Timer.class)); + verify(fakeCpuGaugeCollector).startCollecting(anyLong(), ArgumentMatchers.any(Timer.class)); // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = createTestPerfSession(); testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); - testGaugeManager.startCollectingGauges(fakeSession2); + testGaugeManager.startCollectingGaugeMetrics(fakeSession2); // Verify that Memory metric collection is not started verify(fakeMemoryGaugeCollector, never()) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.nullable(Timer.class)); + .startCollecting(anyLong(), ArgumentMatchers.nullable(Timer.class)); // Verify that Cpu metric collection is started verify(fakeCpuGaugeCollector, times(2)) - .startCollecting(ArgumentMatchers.anyLong(), ArgumentMatchers.any(Timer.class)); + .startCollecting(anyLong(), ArgumentMatchers.any(Timer.class)); } @Test public void testStartCollectingGaugesDoesNotStartAJobToConsumeMetricsWithUnknownAppState() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = createTestPerfSession(); testGaugeManager.setApplicationProcessState( ApplicationProcessState.APPLICATION_PROCESS_STATE_UNKNOWN); - testGaugeManager.startCollectingGauges(fakeSession); + testGaugeManager.startCollectingGaugeMetrics(fakeSession); assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); } @Test - public void stopCollectingCPUMetrics_invalidCPUCaptureFrequency_appInForegrounf() { + public void stopCollectingCPUMetrics_invalidCPUCaptureFrequency_appInForeground() { // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = createTestPerfSession(); testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); - testGaugeManager.startCollectingGauges(fakeSession1); - assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); + testGaugeManager.startCollectingGaugeMetrics(fakeSession1); + assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = createTestPerfSession(); testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); - testGaugeManager.startCollectingGauges(fakeSession2); - assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); + testGaugeManager.startCollectingGaugeMetrics(fakeSession2); + + fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); + assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); } @Test @@ -295,18 +299,19 @@ public void stopCollectingGauges_invalidMemoryCollectionFrequency_appInForegroun // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = createTestPerfSession(); testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); - testGaugeManager.startCollectingGauges(fakeSession1); - assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); + testGaugeManager.startCollectingGaugeMetrics(fakeSession1); + assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = createTestPerfSession(); testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); - testGaugeManager.startCollectingGauges(fakeSession2); - assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); + testGaugeManager.startCollectingGaugeMetrics(fakeSession2); + fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); + assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); } @Test @@ -315,18 +320,21 @@ public void stopCollectingGauges_invalidGaugeCollectionFrequency_appInForeground doReturn(0L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); doReturn(0L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = createTestPerfSession(); testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); - testGaugeManager.startCollectingGauges(fakeSession1); + testGaugeManager.startCollectingGaugeMetrics(fakeSession1); assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); doReturn(-25L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = createTestPerfSession(); testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); - testGaugeManager.startCollectingGauges(fakeSession2); + testGaugeManager.startCollectingGaugeMetrics(fakeSession2); + // Since the prior session exists, it collects a single metric before stopping. + assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); + fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); } @@ -335,29 +343,22 @@ public void startCollectingGauges_validGaugeCollectionFrequency_appInForeground( doReturn(25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); doReturn(15L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = createTestPerfSession(); testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); - testGaugeManager.startCollectingGauges(fakeSession); + testGaugeManager.startCollectingGaugeMetrics(fakeSession); assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); - verify(fakeCpuGaugeCollector) - .startCollecting(eq(DEFAULT_CPU_GAUGE_COLLECTION_FREQUENCY_FG_MS), ArgumentMatchers.any()); - verify(fakeMemoryGaugeCollector) - .startCollecting( - eq(DEFAULT_MEMORY_GAUGE_COLLECTION_FREQUENCY_FG_MS), ArgumentMatchers.any()); + verify(fakeCpuGaugeCollector).startCollecting(eq(25L), ArgumentMatchers.any()); + verify(fakeMemoryGaugeCollector).startCollecting(eq(15L), ArgumentMatchers.any()); } @Test - public void testStartCollectingGaugesStartsAJobToConsumeTheGeneratedMetrics() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + public void testStartCollectingGaugesDoesNotStartAJobToConsumeTheGeneratedMetrics() { + PerfSession fakeSession = createTestPerfSession(); testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); - testGaugeManager.startCollectingGauges(fakeSession); + testGaugeManager.startCollectingGaugeMetrics(fakeSession); - assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); - assertThat(fakeScheduledExecutorService.getDelayToNextTask(TimeUnit.MILLISECONDS)) - .isEqualTo( - getMinimumBackgroundCollectionFrequency() - * APPROX_NUMBER_OF_DATA_POINTS_PER_GAUGE_METRIC); + assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); CpuMetricReading fakeCpuMetricReading1 = createFakeCpuMetricReading(200, 100); CpuMetricReading fakeCpuMetricReading2 = createFakeCpuMetricReading(300, 200); @@ -372,22 +373,15 @@ public void testStartCollectingGaugesStartsAJobToConsumeTheGeneratedMetrics() { fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading2); fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); - GaugeMetric recordedGaugeMetric = - getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); - - assertThatCpuGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric, fakeCpuMetricReading1, fakeCpuMetricReading2); - - assertThatMemoryGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric, fakeMemoryMetricReading1, fakeMemoryMetricReading2); + assertThatNothingWasLoggedToTransport(ApplicationProcessState.BACKGROUND); } @Test public void testStopCollectingGaugesStopsCollectingAllGaugeMetrics() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = createTestPerfSession(); testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); - testGaugeManager.startCollectingGauges(fakeSession); + testGaugeManager.startCollectingGaugeMetrics(fakeSession); verify(fakeCpuGaugeCollector) .startCollecting(eq(DEFAULT_CPU_GAUGE_COLLECTION_FREQUENCY_BG_MS), ArgumentMatchers.any()); @@ -399,11 +393,15 @@ public void testStopCollectingGaugesStopsCollectingAllGaugeMetrics() { @Test public void testStopCollectingGaugesCreatesOneLastJobToConsumeAnyPendingMetrics() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = createTestPerfSession(); testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); - testGaugeManager.startCollectingGauges(fakeSession); - assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); + testGaugeManager.startCollectingGaugeMetrics(fakeSession); + + // It's not currently logging to transport. + assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); + // An AQS session ID is expected when stopping Gauge collection. + fakeSession.setAQSId(new SessionSubscriber.SessionDetails("aqsSessionId")); testGaugeManager.stopCollectingGauges(); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); @@ -421,17 +419,17 @@ public void testStopCollectingGaugesCreatesOneLastJobToConsumeAnyPendingMetrics( GaugeMetric recordedGaugeMetric = getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); assertThatCpuGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric, fakeCpuMetricReading); + fakeSession.aqsSessionId(), recordedGaugeMetric, fakeCpuMetricReading); assertThatMemoryGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric, fakeMemoryMetricReading); + fakeSession.aqsSessionId(), recordedGaugeMetric, fakeMemoryMetricReading); } @Test - public void testGaugeManagerClearsTheQueueEachRun() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + public void testGaugeManagerDoesNotClearTheQueueUnlessUpdated() { + PerfSession fakeSession = createTestPerfSession(); - testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); - testGaugeManager.startCollectingGauges(fakeSession); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); + testGaugeManager.startCollectingGaugeMetrics(fakeSession); fakeCpuGaugeCollector.cpuMetricReadings.add(createFakeCpuMetricReading(200, 100)); fakeCpuGaugeCollector.cpuMetricReadings.add(createFakeCpuMetricReading(300, 400)); @@ -442,18 +440,11 @@ public void testGaugeManagerClearsTheQueueEachRun() { assertThat(fakeMemoryGaugeCollector.memoryMetricReadings).isNotEmpty(); fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); - assertThat(fakeCpuGaugeCollector.cpuMetricReadings).isEmpty(); - assertThat(fakeMemoryGaugeCollector.memoryMetricReadings).isEmpty(); - - fakeCpuGaugeCollector.cpuMetricReadings.add(createFakeCpuMetricReading(200, 100)); - fakeMemoryGaugeCollector.memoryMetricReadings.add( - createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 1234)); - fakeMemoryGaugeCollector.memoryMetricReadings.add( - createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 2345)); - assertThat(fakeCpuGaugeCollector.cpuMetricReadings).isNotEmpty(); assertThat(fakeMemoryGaugeCollector.memoryMetricReadings).isNotEmpty(); + testGaugeManager.updateGaugeCollection(ApplicationProcessState.FOREGROUND); + fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); assertThat(fakeCpuGaugeCollector.cpuMetricReadings).isEmpty(); assertThat(fakeMemoryGaugeCollector.memoryMetricReadings).isEmpty(); @@ -461,11 +452,11 @@ public void testGaugeManagerClearsTheQueueEachRun() { @Test public void testStartingGaugeManagerWithNewSessionIdButSameAppState() { - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = createTestPerfSession(); // Start collecting Gauges. testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); - testGaugeManager.startCollectingGauges(fakeSession1); + testGaugeManager.startCollectingGaugeMetrics(fakeSession1); CpuMetricReading fakeCpuMetricReading1 = createFakeCpuMetricReading(200, 100); fakeCpuGaugeCollector.cpuMetricReadings.add(fakeCpuMetricReading1); AndroidMemoryReading fakeMemoryMetricReading1 = @@ -476,9 +467,9 @@ public void testStartingGaugeManagerWithNewSessionIdButSameAppState() { GaugeMetric recordedGaugeMetric1 = getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); assertThatCpuGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric1, fakeCpuMetricReading1); + fakeSession1.sessionId(), recordedGaugeMetric1, fakeCpuMetricReading1); assertThatMemoryGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric1, fakeMemoryMetricReading1); + fakeSession1.sessionId(), recordedGaugeMetric1, fakeMemoryMetricReading1); // One Cpu and Memory metric was added when the gauge was collecting for the previous sessionId. CpuMetricReading fakeCpuMetricReading2 = createFakeCpuMetricReading(400, 500); @@ -487,20 +478,20 @@ public void testStartingGaugeManagerWithNewSessionIdButSameAppState() { createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 2345); fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading2); - PerfSession fakeSession2 = new PerfSession("sessionId2", new Clock()); + PerfSession fakeSession2 = createTestPerfSession(); // Start collecting gauges for new session, but same app state. testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); - testGaugeManager.startCollectingGauges(fakeSession2); + testGaugeManager.startCollectingGaugeMetrics(fakeSession2); // The next sweep conducted by GaugeManager still associates metrics to old sessionId and state. fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); GaugeMetric recordedGaugeMetric2 = getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); assertThatCpuGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric2, fakeCpuMetricReading2); + fakeSession1.sessionId(), recordedGaugeMetric2, fakeCpuMetricReading2); assertThatMemoryGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric2, fakeMemoryMetricReading2); + fakeSession1.sessionId(), recordedGaugeMetric2, fakeMemoryMetricReading2); // Collect some more Cpu and Memory metrics and verify that they're associated with new // sessionId and state. @@ -514,18 +505,18 @@ public void testStartingGaugeManagerWithNewSessionIdButSameAppState() { GaugeMetric recordedGaugeMetric3 = getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); assertThatCpuGaugeMetricWasSentToTransport( - "sessionId2", recordedGaugeMetric3, fakeCpuMetricReading3); + fakeSession2.sessionId(), recordedGaugeMetric3, fakeCpuMetricReading3); assertThatMemoryGaugeMetricWasSentToTransport( - "sessionId2", recordedGaugeMetric3, fakeMemoryMetricReading3); + fakeSession2.sessionId(), recordedGaugeMetric3, fakeMemoryMetricReading3); } @Test public void testStartGaugeManagerWithSameSessionIdButDifferentAppState() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = createTestPerfSession(); // Start collecting Gauges. testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); - testGaugeManager.startCollectingGauges(fakeSession); + testGaugeManager.startCollectingGaugeMetrics(fakeSession); CpuMetricReading fakeCpuMetricReading1 = createFakeCpuMetricReading(200, 100); fakeCpuGaugeCollector.cpuMetricReadings.add(fakeCpuMetricReading1); AndroidMemoryReading fakeMemoryMetricReading1 = @@ -536,9 +527,9 @@ public void testStartGaugeManagerWithSameSessionIdButDifferentAppState() { GaugeMetric recordedGaugeMetric1 = getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); assertThatCpuGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric1, fakeCpuMetricReading1); + fakeSession.sessionId(), recordedGaugeMetric1, fakeCpuMetricReading1); assertThatMemoryGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric1, fakeMemoryMetricReading1); + fakeSession.sessionId(), recordedGaugeMetric1, fakeMemoryMetricReading1); // One Cpu and Memory metric was added when the gauge was collecting for the previous sessionId. CpuMetricReading fakeCpuMetricReading2 = createFakeCpuMetricReading(400, 500); @@ -549,16 +540,16 @@ public void testStartGaugeManagerWithSameSessionIdButDifferentAppState() { // Start collecting gauges for same session, but new app state testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); - testGaugeManager.startCollectingGauges(fakeSession); + testGaugeManager.startCollectingGaugeMetrics(fakeSession); // The next sweep conducted by GaugeManager still associates metrics to old sessionId and state. fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); GaugeMetric recordedGaugeMetric2 = getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); assertThatCpuGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric2, fakeCpuMetricReading2); + fakeSession.sessionId(), recordedGaugeMetric2, fakeCpuMetricReading2); assertThatMemoryGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric2, fakeMemoryMetricReading2); + fakeSession.sessionId(), recordedGaugeMetric2, fakeMemoryMetricReading2); // Collect some more Cpu and Memory metrics and verify that they're associated with new // sessionId and state. @@ -572,17 +563,17 @@ public void testStartGaugeManagerWithSameSessionIdButDifferentAppState() { GaugeMetric recordedGaugeMetric3 = getLastRecordedGaugeMetric(ApplicationProcessState.FOREGROUND, 1); assertThatCpuGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric3, fakeCpuMetricReading3); + fakeSession.sessionId(), recordedGaugeMetric3, fakeCpuMetricReading3); assertThatMemoryGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric3, fakeMemoryMetricReading3); + fakeSession.sessionId(), recordedGaugeMetric3, fakeMemoryMetricReading3); } @Test public void testStartGaugeManagerWithNewSessionIdAndNewAppState() { - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = createTestPerfSession(); // Start collecting Gauges. - testGaugeManager.startCollectingGauges(fakeSession1); + testGaugeManager.startCollectingGaugeMetrics(fakeSession1); testGaugeManager.updateGaugeCollection(ApplicationProcessState.BACKGROUND); CpuMetricReading fakeCpuMetricReading1 = createFakeCpuMetricReading(200, 100); fakeCpuGaugeCollector.cpuMetricReadings.add(fakeCpuMetricReading1); @@ -594,9 +585,9 @@ public void testStartGaugeManagerWithNewSessionIdAndNewAppState() { GaugeMetric recordedGaugeMetric1 = getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); assertThatCpuGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric1, fakeCpuMetricReading1); + fakeSession1.sessionId(), recordedGaugeMetric1, fakeCpuMetricReading1); assertThatMemoryGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric1, fakeMemoryMetricReading1); + fakeSession1.sessionId(), recordedGaugeMetric1, fakeMemoryMetricReading1); // One Cpu and Memory metric was added when the gauge was collecting for the previous sessionId. CpuMetricReading fakeCpuMetricReading2 = createFakeCpuMetricReading(400, 500); @@ -605,10 +596,10 @@ public void testStartGaugeManagerWithNewSessionIdAndNewAppState() { createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 2345); fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading2); - PerfSession fakeSession2 = new PerfSession("sessionId2", new Clock()); + PerfSession fakeSession2 = createTestPerfSession(); // Start collecting gauges for new session and new app state - testGaugeManager.startCollectingGauges(fakeSession2); + testGaugeManager.startCollectingGaugeMetrics(fakeSession2); testGaugeManager.updateGaugeCollection(ApplicationProcessState.FOREGROUND); // The next sweep conducted by GaugeManager still associates metrics to old sessionId and state. @@ -616,9 +607,9 @@ public void testStartGaugeManagerWithNewSessionIdAndNewAppState() { GaugeMetric recordedGaugeMetric2 = getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); assertThatCpuGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric2, fakeCpuMetricReading2); + fakeSession1.sessionId(), recordedGaugeMetric2, fakeCpuMetricReading2); assertThatMemoryGaugeMetricWasSentToTransport( - "sessionId", recordedGaugeMetric2, fakeMemoryMetricReading2); + fakeSession1.sessionId(), recordedGaugeMetric2, fakeMemoryMetricReading2); // Collect some more Cpu and Memory metrics and verify that they're associated with new // sessionId and state. @@ -632,9 +623,9 @@ public void testStartGaugeManagerWithNewSessionIdAndNewAppState() { GaugeMetric recordedGaugeMetric3 = getLastRecordedGaugeMetric(ApplicationProcessState.FOREGROUND, 1); assertThatCpuGaugeMetricWasSentToTransport( - "sessionId2", recordedGaugeMetric3, fakeCpuMetricReading3); + fakeSession2.sessionId(), recordedGaugeMetric3, fakeCpuMetricReading3); assertThatMemoryGaugeMetricWasSentToTransport( - "sessionId2", recordedGaugeMetric3, fakeMemoryMetricReading3); + fakeSession2.sessionId(), recordedGaugeMetric3, fakeMemoryMetricReading3); } @Test @@ -713,6 +704,12 @@ public void testCollectGaugeMetricOnceCollectAllMetricsWhenCollectionIsEnabled() verify(fakeMemoryGaugeCollector).collectOnce(ArgumentMatchers.any()); } + private PerfSession createTestPerfSession() { + PerfSession testSession = new PerfSession(UUID.randomUUID().toString(), new Clock()); + testSession.setGaugeAndEventCollectionEnabled(true); + return testSession; + } + /** @return The minimum background collection frequency of all the Gauges. */ private long getMinimumBackgroundCollectionFrequency() { return DEFAULT_CPU_GAUGE_COLLECTION_FREQUENCY_BG_MS; @@ -753,6 +750,16 @@ private GaugeMetric getLastRecordedGaugeMetric( return argMetric.getValue(); } + /** + * Asserts that no metric was logged to transport. + * + * @param applicationProcessState The {@link ApplicationProcessState} that it was collected for. + */ + private void assertThatNothingWasLoggedToTransport( + ApplicationProcessState applicationProcessState) { + verify(mockTransportManager, never()).log((GaugeMetric) any(), eq(applicationProcessState)); + } + private void assertThatCpuGaugeMetricWasSentToTransport( String sessionId, GaugeMetric recordedGaugeMetric, CpuMetricReading... cpuMetricReadings) { assertThat(recordedGaugeMetric.getSessionId()).isEqualTo(sessionId); From fa39f77775f21e1ca1a049440f8dd80b539fbdf8 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 4 Apr 2025 13:33:52 -0400 Subject: [PATCH 47/76] More tests --- .../FirebasePerformanceSessionSubscriber.kt | 2 +- .../perf/session/gauges/GaugeManagerTest.java | 191 +----------------- 2 files changed, 11 insertions(+), 182 deletions(-) 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 index 1e05a11121b..8ed21c5a383 100644 --- 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 @@ -47,7 +47,7 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()) updatedSession.setAQSId(sessionDetails) - sessionManager.updatePerfSession(updatedSession) gaugeManager.logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + sessionManager.updatePerfSession(updatedSession) } } 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 7dfa0df013e..88d0a9ea853 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 @@ -140,6 +140,13 @@ public void testStartCollectingGaugesStartsCollectingMetrics() { ArgumentMatchers.nullable(Timer.class)); } + @Test + public void testStartCollectingGaugesDoesNotLogMetrics() { + PerfSession fakeSession = createTestPerfSession(); + testGaugeManager.startCollectingGaugeMetrics(fakeSession); + assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); + } + @Test public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithValidFrequencyInForeground() { @@ -355,7 +362,7 @@ public void startCollectingGauges_validGaugeCollectionFrequency_appInForeground( @Test public void testStartCollectingGaugesDoesNotStartAJobToConsumeTheGeneratedMetrics() { PerfSession fakeSession = createTestPerfSession(); - testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); + testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); testGaugeManager.startCollectingGaugeMetrics(fakeSession); assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); @@ -373,7 +380,7 @@ public void testStartCollectingGaugesDoesNotStartAJobToConsumeTheGeneratedMetric fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading2); fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); - assertThatNothingWasLoggedToTransport(ApplicationProcessState.BACKGROUND); + assertThatNothingWasLoggedToTransport(ApplicationProcessState.FOREGROUND); } @Test @@ -450,184 +457,6 @@ public void testGaugeManagerDoesNotClearTheQueueUnlessUpdated() { assertThat(fakeMemoryGaugeCollector.memoryMetricReadings).isEmpty(); } - @Test - public void testStartingGaugeManagerWithNewSessionIdButSameAppState() { - PerfSession fakeSession1 = createTestPerfSession(); - - // Start collecting Gauges. - testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); - testGaugeManager.startCollectingGaugeMetrics(fakeSession1); - CpuMetricReading fakeCpuMetricReading1 = createFakeCpuMetricReading(200, 100); - fakeCpuGaugeCollector.cpuMetricReadings.add(fakeCpuMetricReading1); - AndroidMemoryReading fakeMemoryMetricReading1 = - createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 1234); - fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading1); - - fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); - GaugeMetric recordedGaugeMetric1 = - getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); - assertThatCpuGaugeMetricWasSentToTransport( - fakeSession1.sessionId(), recordedGaugeMetric1, fakeCpuMetricReading1); - assertThatMemoryGaugeMetricWasSentToTransport( - fakeSession1.sessionId(), recordedGaugeMetric1, fakeMemoryMetricReading1); - - // One Cpu and Memory metric was added when the gauge was collecting for the previous sessionId. - CpuMetricReading fakeCpuMetricReading2 = createFakeCpuMetricReading(400, 500); - fakeCpuGaugeCollector.cpuMetricReadings.add(fakeCpuMetricReading2); - AndroidMemoryReading fakeMemoryMetricReading2 = - createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 2345); - fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading2); - - PerfSession fakeSession2 = createTestPerfSession(); - - // Start collecting gauges for new session, but same app state. - testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); - testGaugeManager.startCollectingGaugeMetrics(fakeSession2); - - // The next sweep conducted by GaugeManager still associates metrics to old sessionId and state. - fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); - GaugeMetric recordedGaugeMetric2 = - getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); - assertThatCpuGaugeMetricWasSentToTransport( - fakeSession1.sessionId(), recordedGaugeMetric2, fakeCpuMetricReading2); - assertThatMemoryGaugeMetricWasSentToTransport( - fakeSession1.sessionId(), recordedGaugeMetric2, fakeMemoryMetricReading2); - - // Collect some more Cpu and Memory metrics and verify that they're associated with new - // sessionId and state. - CpuMetricReading fakeCpuMetricReading3 = createFakeCpuMetricReading(500, 600); - fakeCpuGaugeCollector.cpuMetricReadings.add(fakeCpuMetricReading3); - AndroidMemoryReading fakeMemoryMetricReading3 = - createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 3456); - fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading3); - - fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); - GaugeMetric recordedGaugeMetric3 = - getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); - assertThatCpuGaugeMetricWasSentToTransport( - fakeSession2.sessionId(), recordedGaugeMetric3, fakeCpuMetricReading3); - assertThatMemoryGaugeMetricWasSentToTransport( - fakeSession2.sessionId(), recordedGaugeMetric3, fakeMemoryMetricReading3); - } - - @Test - public void testStartGaugeManagerWithSameSessionIdButDifferentAppState() { - PerfSession fakeSession = createTestPerfSession(); - - // Start collecting Gauges. - testGaugeManager.setApplicationProcessState(ApplicationProcessState.BACKGROUND); - testGaugeManager.startCollectingGaugeMetrics(fakeSession); - CpuMetricReading fakeCpuMetricReading1 = createFakeCpuMetricReading(200, 100); - fakeCpuGaugeCollector.cpuMetricReadings.add(fakeCpuMetricReading1); - AndroidMemoryReading fakeMemoryMetricReading1 = - createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 1234); - fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading1); - - fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); - GaugeMetric recordedGaugeMetric1 = - getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); - assertThatCpuGaugeMetricWasSentToTransport( - fakeSession.sessionId(), recordedGaugeMetric1, fakeCpuMetricReading1); - assertThatMemoryGaugeMetricWasSentToTransport( - fakeSession.sessionId(), recordedGaugeMetric1, fakeMemoryMetricReading1); - - // One Cpu and Memory metric was added when the gauge was collecting for the previous sessionId. - CpuMetricReading fakeCpuMetricReading2 = createFakeCpuMetricReading(400, 500); - fakeCpuGaugeCollector.cpuMetricReadings.add(fakeCpuMetricReading2); - AndroidMemoryReading fakeMemoryMetricReading2 = - createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 2345); - fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading2); - - // Start collecting gauges for same session, but new app state - testGaugeManager.setApplicationProcessState(ApplicationProcessState.FOREGROUND); - testGaugeManager.startCollectingGaugeMetrics(fakeSession); - - // The next sweep conducted by GaugeManager still associates metrics to old sessionId and state. - fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); - GaugeMetric recordedGaugeMetric2 = - getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); - assertThatCpuGaugeMetricWasSentToTransport( - fakeSession.sessionId(), recordedGaugeMetric2, fakeCpuMetricReading2); - assertThatMemoryGaugeMetricWasSentToTransport( - fakeSession.sessionId(), recordedGaugeMetric2, fakeMemoryMetricReading2); - - // Collect some more Cpu and Memory metrics and verify that they're associated with new - // sessionId and state. - CpuMetricReading fakeCpuMetricReading3 = createFakeCpuMetricReading(500, 600); - fakeCpuGaugeCollector.cpuMetricReadings.add(fakeCpuMetricReading3); - AndroidMemoryReading fakeMemoryMetricReading3 = - createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 3456); - fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading3); - - fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); - GaugeMetric recordedGaugeMetric3 = - getLastRecordedGaugeMetric(ApplicationProcessState.FOREGROUND, 1); - assertThatCpuGaugeMetricWasSentToTransport( - fakeSession.sessionId(), recordedGaugeMetric3, fakeCpuMetricReading3); - assertThatMemoryGaugeMetricWasSentToTransport( - fakeSession.sessionId(), recordedGaugeMetric3, fakeMemoryMetricReading3); - } - - @Test - public void testStartGaugeManagerWithNewSessionIdAndNewAppState() { - PerfSession fakeSession1 = createTestPerfSession(); - - // Start collecting Gauges. - testGaugeManager.startCollectingGaugeMetrics(fakeSession1); - testGaugeManager.updateGaugeCollection(ApplicationProcessState.BACKGROUND); - CpuMetricReading fakeCpuMetricReading1 = createFakeCpuMetricReading(200, 100); - fakeCpuGaugeCollector.cpuMetricReadings.add(fakeCpuMetricReading1); - AndroidMemoryReading fakeMemoryMetricReading1 = - createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 1234); - fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading1); - - fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); - GaugeMetric recordedGaugeMetric1 = - getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); - assertThatCpuGaugeMetricWasSentToTransport( - fakeSession1.sessionId(), recordedGaugeMetric1, fakeCpuMetricReading1); - assertThatMemoryGaugeMetricWasSentToTransport( - fakeSession1.sessionId(), recordedGaugeMetric1, fakeMemoryMetricReading1); - - // One Cpu and Memory metric was added when the gauge was collecting for the previous sessionId. - CpuMetricReading fakeCpuMetricReading2 = createFakeCpuMetricReading(400, 500); - fakeCpuGaugeCollector.cpuMetricReadings.add(fakeCpuMetricReading2); - AndroidMemoryReading fakeMemoryMetricReading2 = - createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 2345); - fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading2); - - PerfSession fakeSession2 = createTestPerfSession(); - - // Start collecting gauges for new session and new app state - testGaugeManager.startCollectingGaugeMetrics(fakeSession2); - testGaugeManager.updateGaugeCollection(ApplicationProcessState.FOREGROUND); - - // The next sweep conducted by GaugeManager still associates metrics to old sessionId and state. - fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); - GaugeMetric recordedGaugeMetric2 = - getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); - assertThatCpuGaugeMetricWasSentToTransport( - fakeSession1.sessionId(), recordedGaugeMetric2, fakeCpuMetricReading2); - assertThatMemoryGaugeMetricWasSentToTransport( - fakeSession1.sessionId(), recordedGaugeMetric2, fakeMemoryMetricReading2); - - // Collect some more Cpu and Memory metrics and verify that they're associated with new - // sessionId and state. - CpuMetricReading fakeCpuMetricReading3 = createFakeCpuMetricReading(500, 600); - fakeCpuGaugeCollector.cpuMetricReadings.add(fakeCpuMetricReading3); - AndroidMemoryReading fakeMemoryMetricReading3 = - createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 3456); - fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading3); - - fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); - GaugeMetric recordedGaugeMetric3 = - getLastRecordedGaugeMetric(ApplicationProcessState.FOREGROUND, 1); - assertThatCpuGaugeMetricWasSentToTransport( - fakeSession2.sessionId(), recordedGaugeMetric3, fakeCpuMetricReading3); - assertThatMemoryGaugeMetricWasSentToTransport( - fakeSession2.sessionId(), recordedGaugeMetric3, fakeMemoryMetricReading3); - } - @Test public void testLogGaugeMetadataSendDataToTransport() { when(fakeGaugeMetadataManager.getDeviceRamSizeKb()).thenReturn(2000); @@ -651,7 +480,7 @@ public void testLogGaugeMetadataSendDataToTransport() { } @Test - public void testLogGaugeMetadataDoesntLogWhenGaugeMetadataManagerNotAvailable() { + public void testLogGaugeMetadataDoesNotLogWhenGaugeMetadataManagerNotAvailable() { testGaugeManager = new GaugeManager( From 639c85eef0947d2c050ac3c3bf2d24104546dddd Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 4 Apr 2025 13:36:40 -0400 Subject: [PATCH 48/76] Revert no-op change --- .../com/google/firebase/perf/config/ConfigResolver.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 7e321515141..1ee9d395e03 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) + return (isPerformanceCollectionEnabled == null || isPerformanceCollectionEnabled == true) && 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 (Boolean.TRUE.equals(getIsPerformanceCollectionDeactivated())) { + if (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 (Boolean.TRUE.equals(getIsPerformanceCollectionDeactivated())) { + if (getIsPerformanceCollectionDeactivated()) { return; } From 63d79592653effcbe1088c5b5d9ee4dc1e8bd104 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 4 Apr 2025 13:38:46 -0400 Subject: [PATCH 49/76] API change --- firebase-perf/api.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/firebase-perf/api.txt b/firebase-perf/api.txt index 2352c0de69f..0cbad75160a 100644 --- a/firebase-perf/api.txt +++ b/firebase-perf/api.txt @@ -3,6 +3,7 @@ package com.google.firebase.perf { @javax.inject.Singleton public class FirebasePerformance { method public static com.google.firebase.perf.FirebasePerformance getInstance(); + method @VisibleForTesting protected com.google.firebase.sessions.api.SessionSubscriber getSessionSubscriber(); method public boolean isPerformanceCollectionEnabled(); method public com.google.firebase.perf.metrics.HttpMetric newHttpMetric(String, @com.google.firebase.perf.FirebasePerformance.HttpMethod String); method public com.google.firebase.perf.metrics.HttpMetric newHttpMetric(java.net.URL, @com.google.firebase.perf.FirebasePerformance.HttpMethod String); From 4574165f7b9f3ac757b5b26af7f7b5ffd0dcdcfa Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 4 Apr 2025 13:41:50 -0400 Subject: [PATCH 50/76] Fix API change --- firebase-perf/api.txt | 1 - .../main/java/com/google/firebase/perf/FirebasePerformance.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/firebase-perf/api.txt b/firebase-perf/api.txt index 0cbad75160a..2352c0de69f 100644 --- a/firebase-perf/api.txt +++ b/firebase-perf/api.txt @@ -3,7 +3,6 @@ package com.google.firebase.perf { @javax.inject.Singleton public class FirebasePerformance { method public static com.google.firebase.perf.FirebasePerformance getInstance(); - method @VisibleForTesting protected com.google.firebase.sessions.api.SessionSubscriber getSessionSubscriber(); method public boolean isPerformanceCollectionEnabled(); method public com.google.firebase.perf.metrics.HttpMetric newHttpMetric(String, @com.google.firebase.perf.FirebasePerformance.HttpMethod String); method public com.google.firebase.perf.metrics.HttpMetric newHttpMetric(java.net.URL, @com.google.firebase.perf.FirebasePerformance.HttpMethod String); 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 2c41d402cd0..44efa6c8fdc 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 @@ -468,7 +468,7 @@ Boolean getPerformanceCollectionForceEnabledState() { @NonNull @VisibleForTesting - protected SessionSubscriber getSessionSubscriber() { + SessionSubscriber getSessionSubscriber() { return sessionSubscriber; } } From 955c5f9b791c199a4deceebc1b445aaf9433ad02 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 4 Apr 2025 15:27:18 -0400 Subject: [PATCH 51/76] Add test rule which might fix github action unit test failure --- firebase-perf/firebase-perf.gradle | 1 + .../firebase/perf/transport/TransportManagerTest.java | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/firebase-perf/firebase-perf.gradle b/firebase-perf/firebase-perf.gradle index 49c921edeb0..3877763d4bd 100644 --- a/firebase-perf/firebase-perf.gradle +++ b/firebase-perf/firebase-perf.gradle @@ -138,4 +138,5 @@ dependencies { testImplementation libs.mockito.core testImplementation libs.mockito.mockito.inline testImplementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.6' + testImplementation "androidx.arch.core:core-testing:2.1.0" } diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java index a965f732f1b..3f93ddc08a5 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java @@ -28,6 +28,8 @@ import android.content.Context; import android.content.pm.PackageInfo; + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule; import androidx.test.core.app.ApplicationProvider; import com.google.android.datatransport.TransportFactory; import com.google.android.gms.tasks.Tasks; @@ -59,6 +61,7 @@ import java.util.Map; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -88,6 +91,9 @@ public class TransportManagerTest extends FirebasePerformanceTestBase { @Mock private FlgTransport mockFlgTransport; @Captor private ArgumentCaptor perfMetricArgumentCaptor; + @Rule + public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule(); + @Before public void setUp() { initMocks(this); From 2026636b48fb7a3e54167e8b58cd2cea56d16da2 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 4 Apr 2025 15:28:21 -0400 Subject: [PATCH 52/76] style --- .../google/firebase/perf/transport/TransportManagerTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java index 3f93ddc08a5..6b9b5cfa3a3 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java @@ -28,7 +28,6 @@ import android.content.Context; import android.content.pm.PackageInfo; - import androidx.arch.core.executor.testing.InstantTaskExecutorRule; import androidx.test.core.app.ApplicationProvider; import com.google.android.datatransport.TransportFactory; @@ -91,8 +90,7 @@ public class TransportManagerTest extends FirebasePerformanceTestBase { @Mock private FlgTransport mockFlgTransport; @Captor private ArgumentCaptor perfMetricArgumentCaptor; - @Rule - public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule(); + @Rule public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule(); @Before public void setUp() { From 3986c22a22e9139382c05fb90d03fbf1237d658f Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 4 Apr 2025 15:44:13 -0400 Subject: [PATCH 53/76] update comment --- .../com/google/firebase/perf/session/gauges/GaugeManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 a46e59af25f..35136d0365f 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 @@ -63,8 +63,8 @@ public class GaugeManager { @Nullable private ScheduledFuture gaugeManagerDataCollectionJob = null; @Nullable private PerfSession session = null; - // The default value for application process state is Foreground. This will be updated based on - // app state changes. + // The default value for application process state is Foreground. This is used to start collecting + // gauge metrics by default. private ApplicationProcessState applicationProcessState = ApplicationProcessState.FOREGROUND; // TODO(b/258263016): Migrate to go/firebase-android-executors From 9385461666898cb1d48e1ff8c15eb34ba5860bec Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 4 Apr 2025 16:01:19 -0400 Subject: [PATCH 54/76] nit --- .../perf/session/gauges/GaugeManagerTest.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) 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 88d0a9ea853..6fcd4bd5581 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 @@ -145,6 +145,7 @@ public void testStartCollectingGaugesDoesNotLogMetrics() { PerfSession fakeSession = createTestPerfSession(); testGaugeManager.startCollectingGaugeMetrics(fakeSession); assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); + assertThatNothingWasLoggedToTransport(); } @Test @@ -380,7 +381,7 @@ public void testStartCollectingGaugesDoesNotStartAJobToConsumeTheGeneratedMetric fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading2); fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); - assertThatNothingWasLoggedToTransport(ApplicationProcessState.FOREGROUND); + assertThatNothingWasLoggedToTransport(); } @Test @@ -581,12 +582,10 @@ private GaugeMetric getLastRecordedGaugeMetric( /** * Asserts that no metric was logged to transport. - * - * @param applicationProcessState The {@link ApplicationProcessState} that it was collected for. */ - private void assertThatNothingWasLoggedToTransport( - ApplicationProcessState applicationProcessState) { - verify(mockTransportManager, never()).log((GaugeMetric) any(), eq(applicationProcessState)); + private void assertThatNothingWasLoggedToTransport() { + verify(mockTransportManager, never()) + .log((GaugeMetric) any(), any(ApplicationProcessState.class)); } private void assertThatCpuGaugeMetricWasSentToTransport( From c01019d7c648af6f9368f947e9f094e5b0913ba8 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 4 Apr 2025 16:35:07 -0400 Subject: [PATCH 55/76] Update test and add a TODO --- .../firebase/perf/session/gauges/GaugeManager.java | 12 ++++++++---- .../perf/session/gauges/GaugeManagerTest.java | 6 ++++++ 2 files changed, 14 insertions(+), 4 deletions(-) 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 35136d0365f..765703cfb50 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 @@ -221,8 +221,6 @@ public void stopCollectingGauges() { // This is needed, otherwise the Runnable might use a stale value. - // AQS is guaranteed to be available when stopping gauge collection. - final String sessionIdForScheduledTask = session.aqsSessionId(); final ApplicationProcessState applicationProcessStateForScheduledTask = applicationProcessState; cpuGaugeCollector.get().stopCollecting(); @@ -232,6 +230,14 @@ public void stopCollectingGauges() { gaugeManagerDataCollectionJob.cancel(false); } + + final String sessionIdForScheduledTask = session.aqsSessionId(); + this.session = null; + if (sessionIdForScheduledTask.equals(Constants.UNDEFINED_AQS_ID)) { + // TODO(b/394127311): Use DebugEnforcementCheck. + return; + } + // Flush any data that was collected for this session one last time. @SuppressWarnings("FutureReturnValueIgnored") ScheduledFuture unusedFuture = @@ -244,7 +250,6 @@ public void stopCollectingGauges() { TIME_TO_WAIT_BEFORE_FLUSHING_GAUGES_QUEUE_MS, TimeUnit.MILLISECONDS); - this.session = null; } /** @@ -270,7 +275,6 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) { // Adding Session ID info. gaugeMetricBuilder.setSessionId(sessionId); - transportManager.log(gaugeMetricBuilder.build(), appState); } 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 6fcd4bd5581..9d959593ce9 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 @@ -451,13 +451,19 @@ public void testGaugeManagerDoesNotClearTheQueueUnlessUpdated() { assertThat(fakeCpuGaugeCollector.cpuMetricReadings).isNotEmpty(); assertThat(fakeMemoryGaugeCollector.memoryMetricReadings).isNotEmpty(); + + fakeSession.setAQSId(new SessionSubscriber.SessionDetails("aqsSessionId")); testGaugeManager.updateGaugeCollection(ApplicationProcessState.FOREGROUND); fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); assertThat(fakeCpuGaugeCollector.cpuMetricReadings).isEmpty(); assertThat(fakeMemoryGaugeCollector.memoryMetricReadings).isEmpty(); + + assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); } + + @Test public void testLogGaugeMetadataSendDataToTransport() { when(fakeGaugeMetadataManager.getDeviceRamSizeKb()).thenReturn(2000); From 775682c2f92254949b3d6e1653a2ff432c061638 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 4 Apr 2025 16:45:47 -0400 Subject: [PATCH 56/76] Add logging in syncFlush --- .../firebase/perf/session/gauges/GaugeManager.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) 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 765703cfb50..e0a7d6c2162 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 @@ -230,13 +230,8 @@ public void stopCollectingGauges() { gaugeManagerDataCollectionJob.cancel(false); } - final String sessionIdForScheduledTask = session.aqsSessionId(); this.session = null; - if (sessionIdForScheduledTask.equals(Constants.UNDEFINED_AQS_ID)) { - // TODO(b/394127311): Use DebugEnforcementCheck. - return; - } // Flush any data that was collected for this session one last time. @SuppressWarnings("FutureReturnValueIgnored") @@ -249,7 +244,6 @@ public void stopCollectingGauges() { }, TIME_TO_WAIT_BEFORE_FLUSHING_GAUGES_QUEUE_MS, TimeUnit.MILLISECONDS); - } /** @@ -260,6 +254,11 @@ public void stopCollectingGauges() { * @param appState The app state for which these gauges are collected. */ private void syncFlush(String sessionId, ApplicationProcessState appState) { + if (sessionId.equals(Constants.UNDEFINED_AQS_ID)) { + // TODO(b/394127311): Use DebugEnforcementCheck. + // This will currently log those sessions to Constants.UNDEFINED_AQS_ID) + logger.debug("Flushing app start gauge metrics to an undefined session ID."); + } GaugeMetric.Builder gaugeMetricBuilder = GaugeMetric.newBuilder(); // Adding CPU metric readings. From 13ce733304f8722f19106fecd4fc1a048ec38ff7 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 4 Apr 2025 16:49:15 -0400 Subject: [PATCH 57/76] style + logs change --- .../com/google/firebase/perf/session/gauges/GaugeManager.java | 3 +-- .../google/firebase/perf/session/gauges/GaugeManagerTest.java | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) 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 e0a7d6c2162..f1defd1903b 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 @@ -256,8 +256,7 @@ public void stopCollectingGauges() { private void syncFlush(String sessionId, ApplicationProcessState appState) { if (sessionId.equals(Constants.UNDEFINED_AQS_ID)) { // TODO(b/394127311): Use DebugEnforcementCheck. - // This will currently log those sessions to Constants.UNDEFINED_AQS_ID) - logger.debug("Flushing app start gauge metrics to an undefined session ID."); + logger.debug("Flushing gauge metrics to an undefined session ID."); } GaugeMetric.Builder gaugeMetricBuilder = GaugeMetric.newBuilder(); 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 9d959593ce9..526e119a271 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 @@ -451,7 +451,6 @@ public void testGaugeManagerDoesNotClearTheQueueUnlessUpdated() { assertThat(fakeCpuGaugeCollector.cpuMetricReadings).isNotEmpty(); assertThat(fakeMemoryGaugeCollector.memoryMetricReadings).isNotEmpty(); - fakeSession.setAQSId(new SessionSubscriber.SessionDetails("aqsSessionId")); testGaugeManager.updateGaugeCollection(ApplicationProcessState.FOREGROUND); @@ -462,8 +461,6 @@ public void testGaugeManagerDoesNotClearTheQueueUnlessUpdated() { assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); } - - @Test public void testLogGaugeMetadataSendDataToTransport() { when(fakeGaugeMetadataManager.getDeviceRamSizeKb()).thenReturn(2000); From da8acfabf5955446d6ac04f9e6595aed6faef80e Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Mon, 7 Apr 2025 10:53:17 -0400 Subject: [PATCH 58/76] Update comment in updateAppState --- .../java/com/google/firebase/perf/session/SessionManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 c458e24e92d..ac08aeb6c9d 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 @@ -74,8 +74,8 @@ public SessionManager( 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(). + // Ignore the app state change if it's a cold start. [FirebasePerformanceSessionSubscriber] + // handles any change that's needed. return; } From 1e8b62982c4e043d34d1bee094bdeb2ac1834fd3 Mon Sep 17 00:00:00 2001 From: themiswang Date: Mon, 7 Apr 2025 11:48:25 -0400 Subject: [PATCH 59/76] convert perf session to use aqs support session id (#6832) - Override PerfSession session id to aqs session id when possible - Add assertion for debug mode to double check if aqs session id available by the time of use --- .../firebase/perf/FirebasePerformance.java | 3 + .../perf/logging/DebugEnforcementCheck.kt | 32 ++++++++ .../FirebasePerformanceSessionSubscriber.kt | 8 +- .../firebase/perf/session/PerfSession.java | 75 +++++++++---------- .../firebase/perf/session/SessionManager.java | 20 +++-- .../perf/application/AppStateMonitorTest.java | 3 + .../perf/session/PerfSessionTest.java | 30 ++++---- .../perf/session/SessionManagerTest.java | 7 +- .../perf/session/gauges/GaugeManagerTest.java | 56 +++++++------- .../perf/transport/TransportManagerTest.java | 6 +- 10 files changed, 138 insertions(+), 102 deletions(-) create mode 100644 firebase-perf/src/main/java/com/google/firebase/perf/logging/DebugEnforcementCheck.kt 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 e4ddfcd600c..587bff395de 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 @@ -34,6 +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.metrics.HttpMetric; import com.google.firebase.perf.metrics.Trace; import com.google.firebase.perf.session.FirebasePerformanceSessionSubscriber; @@ -43,6 +44,7 @@ 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 java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -169,6 +171,7 @@ public static FirebasePerformance getInstance() { this.mMetadataBundle = new ImmutableBundle(new Bundle()); return; } + DebugEnforcementCheck.setEnforcement(BuildConfig.DEBUG); TransportManager.getInstance() .initialize(firebaseApp, firebaseInstallationsApi, transportFactoryProvider); diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/logging/DebugEnforcementCheck.kt b/firebase-perf/src/main/java/com/google/firebase/perf/logging/DebugEnforcementCheck.kt new file mode 100644 index 00000000000..a2f3b186f9b --- /dev/null +++ b/firebase-perf/src/main/java/com/google/firebase/perf/logging/DebugEnforcementCheck.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.perf.logging + +class DebugEnforcementCheck { + 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) + assert(!enforcement) { failureMessage } + } + } + } +} 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 index b6a3d30c139..08175baf1df 100644 --- 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 @@ -30,17 +30,15 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: val currentPerfSession = SessionManager.getInstance().perfSession() // A [PerfSession] was created before a session was started. - if (currentPerfSession.aqsSessionId() == null) { - currentPerfSession.setAQSId(sessionDetails) + if (!currentPerfSession.isAqsReady) { GaugeManager.getInstance() - .logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + .logGaugeMetadata(currentPerfSession.sessionId(), ApplicationProcessState.FOREGROUND) return } val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()) - updatedSession.setAQSId(sessionDetails) SessionManager.getInstance().updatePerfSession(updatedSession) GaugeManager.getInstance() - .logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + .logGaugeMetadata(updatedSession.sessionId(), ApplicationProcessState.FOREGROUND) } } 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 075848ab747..e4260034107 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,34 +23,40 @@ 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 final String sessionId; private final Timer creationTime; - @Nullable private String aqsSessionId; - + 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(@NonNull String sessionId) { - String prunedSessionId = sessionId.replace("-", ""); - PerfSession session = new PerfSession(prunedSessionId, new Clock()); - session.setGaugeAndEventCollectionEnabled(shouldCollectGaugesAndEvents()); - + public static PerfSession createWithId(@Nullable String aqsSessionId) { + String sessionId; + Boolean isAqsReady; + if (aqsSessionId != null) { + sessionId = aqsSessionId; + isAqsReady = true; + } else { + sessionId = UUID.randomUUID().toString().replace("-", ""); + isAqsReady = false; + } + PerfSession session = new PerfSession(sessionId, new Clock(), isAqsReady); + session.setGaugeAndEventCollectionEnabled(shouldCollectGaugesAndEvents(sessionId)); return session; } /** Creates a PerfSession with the provided {@code sessionId} and {@code clock}. */ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public PerfSession(String sessionId, Clock clock) { + public PerfSession(String sessionId, Clock clock, boolean isAqsReady) { this.sessionId = sessionId; + this.isAqsReady = isAqsReady; creationTime = clock.getTime(); } @@ -58,27 +64,15 @@ 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 of the session. */ + /** Returns the sessionId for the given session. */ public String sessionId() { return 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(); - } - } - /** * Returns a timer object that has been seeded with the system time at which the session began. */ @@ -105,18 +99,6 @@ public boolean isVerbose() { return isGaugeAndEventCollectionEnabled; } - /** Checks if the current {@link com.google.firebase.perf.v1.PerfSession} is verbose or not. */ - @VisibleForTesting - static boolean isVerbose(@NonNull com.google.firebase.perf.v1.PerfSession perfSession) { - for (SessionVerbosity sessionVerbosity : perfSession.getSessionVerbosityList()) { - if (sessionVerbosity == SessionVerbosity.GAUGES_AND_SYSTEM_EVENTS) { - return true; - } - } - - return false; - } - /** * Checks if it has been more than {@link ConfigResolver#getSessionsMaxDurationMinutes()} time * since the creation time of the current session. @@ -128,7 +110,6 @@ 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); @@ -179,11 +160,10 @@ public static com.google.firebase.perf.v1.PerfSession[] buildAndSort( } /** If true, Session Gauge collection is enabled. */ - public static boolean shouldCollectGaugesAndEvents() { + public static boolean shouldCollectGaugesAndEvents(String sessionId) { ConfigResolver configResolver = ConfigResolver.getInstance(); - return configResolver.isPerformanceMonitoringEnabled() - && Math.random() < configResolver.getSessionsSamplingRate(); + && (Math.abs(sessionId.hashCode() % 100) < configResolver.getSessionsSamplingRate() * 100); } /** @@ -207,6 +187,7 @@ 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); } @@ -224,4 +205,16 @@ public PerfSession[] newArray(int size) { return new PerfSession[size]; } }; + + /** Checks if the current {@link com.google.firebase.perf.v1.PerfSession} is verbose or not. */ + @VisibleForTesting + static boolean isVerbose(@NonNull com.google.firebase.perf.v1.PerfSession perfSession) { + for (SessionVerbosity sessionVerbosity : perfSession.getSessionVerbosityList()) { + if (sessionVerbosity == SessionVerbosity.GAUGES_AND_SYSTEM_EVENTS) { + return true; + } + } + + return false; + } } 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 cf99c1e52ea..f7f17cd4588 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,6 +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.session.gauges.GaugeManager; import com.google.firebase.perf.v1.ApplicationProcessState; import com.google.firebase.perf.v1.GaugeMetadata; @@ -28,12 +29,10 @@ import java.util.Iterator; import java.util.Objects; import java.util.Set; -import java.util.UUID; /** Session manager to generate sessionIDs and broadcast to the application. */ @Keep // Needed because of b/117526359. public class SessionManager { - @SuppressLint("StaticFieldLeak") private static final SessionManager instance = new SessionManager(); @@ -50,15 +49,15 @@ 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"); + return perfSession; } private SessionManager() { - // Generate a new sessionID for every cold start. - this( - GaugeManager.getInstance(), - PerfSession.createWithId(UUID.randomUUID().toString()), - AppStateMonitor.getInstance()); + // session should quickly updated by session subscriber. + this(GaugeManager.getInstance(), PerfSession.createWithId(null), AppStateMonitor.getInstance()); } @VisibleForTesting @@ -83,6 +82,10 @@ public void setApplicationContext(final Context appContext) { * @see PerfSession#isSessionRunningTooLong() */ public void stopGaugeCollectionIfSessionRunningTooLong() { + DebugEnforcementCheck.Companion.checkSession( + perfSession.isAqsReady, + "Session is not ready while trying to stopGaugeCollectionIfSessionRunningTooLong"); + if (perfSession.isSessionRunningTooLong()) { gaugeManager.stopCollectingGauges(); } @@ -156,6 +159,9 @@ public void unregisterForSessionUpdates(WeakReference client } private void startOrStopCollectingGauges(ApplicationProcessState appState) { + DebugEnforcementCheck.Companion.checkSession( + perfSession.isAqsReady, "Session is not ready while trying to startOrStopCollectingGauges"); + if (perfSession.isGaugeAndEventCollectionEnabled()) { gaugeManager.startCollectingGauges(perfSession, appState); } else { diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/application/AppStateMonitorTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/application/AppStateMonitorTest.java index 0b7d4bbfc17..f30ee5d73a0 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/application/AppStateMonitorTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/application/AppStateMonitorTest.java @@ -39,6 +39,8 @@ import com.google.firebase.perf.config.DeviceCacheManager; import com.google.firebase.perf.metrics.NetworkRequestMetricBuilder; import com.google.firebase.perf.metrics.Trace; +import com.google.firebase.perf.session.PerfSession; +import com.google.firebase.perf.session.SessionManager; import com.google.firebase.perf.session.gauges.GaugeManager; import com.google.firebase.perf.transport.TransportManager; import com.google.firebase.perf.util.Clock; @@ -80,6 +82,7 @@ public class AppStateMonitorTest extends FirebasePerformanceTestBase { @Before public void setUp() { currentTime = 0; + SessionManager.getInstance().updatePerfSession(PerfSession.createWithId("sessionId")); initMocks(this); doAnswer((Answer) invocationOnMock -> new Timer(currentTime)).when(clock).getTime(); 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..19d8aadf76b 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 @@ -62,7 +62,7 @@ public void setUp() { @Test public void instanceCreation() { - PerfSession session = new PerfSession("sessionId", mockClock); + PerfSession session = new PerfSession("sessionId", mockClock, true); assertThat(session).isNotNull(); session.setGaugeAndEventCollectionEnabled(true); Assert.assertTrue(session.isGaugeAndEventCollectionEnabled()); @@ -78,17 +78,17 @@ public void shouldCollectGaugesAndEvents_perfMonDisabledAtRuntime_sessionNotVerb configResolver.setMetadataBundle(new ImmutableBundle(bundle)); // By default, session is verbose if developer has set 100% of session verbosity. - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isTrue(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isTrue(); // Case #1: developer has disabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(false); - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isFalse(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); // Case #2: developer has enabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(true); - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isTrue(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isTrue(); } @Test @@ -102,17 +102,17 @@ public void shouldCollectGaugesAndEvents_perfMonDisabledAtBuildtime_verbosityDep // By default, session is not verbose if developer disabled performance monitoring at build // time. - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isFalse(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); // Case #1: developer has enabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(true); - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isTrue(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isTrue(); // Case #2: developer has disabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(false); - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isFalse(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); } @Test @@ -124,22 +124,22 @@ public void shouldCollectGaugesAndEvents_perfMonDeactivated_sessionNotVerbose() configResolver.setMetadataBundle(new ImmutableBundle(bundle)); // Session will never be verbose if developer deactivated performance monitoring at build time. - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isFalse(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); // Case #1: developer has enabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(true); - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isFalse(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); // Case #2: developer has disabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(false); - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isFalse(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); } @Test public void testPerfSessionConversion() { - PerfSession session1 = new PerfSession("sessionId", mockClock); + PerfSession session1 = new PerfSession("sessionId", mockClock, true); session1.setGaugeAndEventCollectionEnabled(true); com.google.firebase.perf.v1.PerfSession perfSession = session1.build(); @@ -150,7 +150,7 @@ public void testPerfSessionConversion() { @Test public void testPerfSessionConversionWithoutVerbosity() { - PerfSession session1 = new PerfSession("sessionId", mockClock); + PerfSession session1 = new PerfSession("sessionId", mockClock, true); com.google.firebase.perf.v1.PerfSession perfSession = session1.build(); Assert.assertEquals(session1.sessionId(), perfSession.getSessionId()); @@ -216,7 +216,7 @@ public void testIsExpiredReturnsFalseWhenCurrentSessionLengthIsLessThanMaxSessio - TimeUnit.MINUTES.toMicros(1)); // Default Max Session Length is 4 hours when(mockClock.getTime()).thenReturn(mockTimer); - PerfSession session = new PerfSession("sessionId", mockClock); + PerfSession session = new PerfSession("sessionId", mockClock, true); assertThat(session.isSessionRunningTooLong()).isFalse(); } @@ -227,7 +227,7 @@ public void testIsExpiredReturnsFalseWhenCurrentSessionLengthIsEqualToMaxSession .thenReturn(TimeUnit.HOURS.toMicros(4)); // Default Max Session Length is 4 hours when(mockClock.getTime()).thenReturn(mockTimer); - PerfSession session = new PerfSession("sessionId", mockClock); + PerfSession session = new PerfSession("sessionId", mockClock, true); assertThat(session.isSessionRunningTooLong()).isFalse(); } @@ -238,7 +238,7 @@ public void testIsExpiredReturnsTrueWhenCurrentSessionLengthIsGreaterThanMaxSess .thenReturn(TimeUnit.HOURS.toMicros(5)); // Default Max Session Length is 4 hours when(mockClock.getTime()).thenReturn(mockTimer); - PerfSession session = new PerfSession("sessionId", mockClock); + PerfSession session = new PerfSession("sessionId", mockClock, true); assertThat(session.isSessionRunningTooLong()).isTrue(); } } 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 954b0ae88d3..2e1e080a98c 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 @@ -66,7 +66,6 @@ public void setUp() { public void testInstanceCreation() { assertThat(SessionManager.getInstance()).isNotNull(); assertThat(SessionManager.getInstance()).isEqualTo(SessionManager.getInstance()); - assertThat(SessionManager.getInstance().perfSession().sessionId()).isNotNull(); } @Test @@ -113,7 +112,7 @@ public void testSessionIdDoesNotUpdateIfPerfSessionRunsTooLong() { Timer mockTimer = mock(Timer.class); when(mockClock.getTime()).thenReturn(mockTimer); - PerfSession session = new PerfSession("sessionId", mockClock); + PerfSession session = new PerfSession("sessionId", mockClock, true); SessionManager testSessionManager = new SessionManager(mockGaugeManager, session, mockAppStateMonitor); @@ -132,10 +131,10 @@ public void testUpdatePerfSessionStartsCollectingGaugesIfSessionIsVerbose() { when(mockClock.getTime()).thenReturn(mockTimer); when(mockAppStateMonitor.getAppState()).thenReturn(ApplicationProcessState.FOREGROUND); - PerfSession previousSession = new PerfSession("previousSession", mockClock); + PerfSession previousSession = new PerfSession("previousSession", mockClock, true); previousSession.setGaugeAndEventCollectionEnabled(false); - PerfSession newSession = new PerfSession("newSession", mockClock); + PerfSession newSession = new PerfSession("newSession", mockClock, true); newSession.setGaugeAndEventCollectionEnabled(true); SessionManager testSessionManager = 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..7ba04852890 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 @@ -124,7 +124,7 @@ public void setUp() { @Test public void testStartCollectingGaugesStartsCollectingMetricsInBackgroundState() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); verify(fakeCpuGaugeCollector) .startCollecting( @@ -138,7 +138,7 @@ public void testStartCollectingGaugesStartsCollectingMetricsInBackgroundState() @Test public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.FOREGROUND); verify(fakeCpuGaugeCollector) .startCollecting( @@ -153,7 +153,7 @@ public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() @Test public void testStartCollectingGaugesDoesNotStartCollectingMetricsWithUnknownApplicationProcessState() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges( fakeSession, ApplicationProcessState.APPLICATION_PROCESS_STATE_UNKNOWN); verify(fakeCpuGaugeCollector, never()) @@ -167,7 +167,7 @@ public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithValidFrequencyInBackground() { // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyBackgroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.BACKGROUND); // Verify that Cpu metric collection is not started @@ -180,7 +180,7 @@ public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyBackgroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.BACKGROUND); // Verify that Cpu metric collection is not started @@ -197,7 +197,7 @@ public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() startCollectingGaugesOnBackground_invalidMemoryCaptureMs_onlyDisableMemoryCollection() { // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyBackgroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.BACKGROUND); // Verify that Memory metric collection is not started @@ -210,7 +210,7 @@ public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyBackgroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.BACKGROUND); // Verify that Memory metric collection is not started @@ -226,7 +226,7 @@ public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithValidFrequency() { // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.FOREGROUND); // Verify that Cpu metric collection is not started @@ -239,7 +239,7 @@ public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithV // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); // Verify that Cpu metric collection is not started @@ -256,7 +256,7 @@ public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithV startCollectingGaugesOnForeground_invalidMemoryCaptureMs_onlyDisableMemoryCollection() { // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.FOREGROUND); // Verify that Memory metric collection is not started @@ -269,7 +269,7 @@ public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithV // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); // Verify that Memory metric collection is not started @@ -283,7 +283,7 @@ public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithV @Test public void testStartCollectingGaugesDoesNotStartAJobToConsumeMetricsWithUnknownAppState() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges( fakeSession, ApplicationProcessState.APPLICATION_PROCESS_STATE_UNKNOWN); assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); @@ -294,14 +294,14 @@ public void stopCollectingCPUMetrics_invalidCPUCaptureFrequency_appInForegrounf( // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.FOREGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); } @@ -311,14 +311,14 @@ public void stopCollectingGauges_invalidMemoryCollectionFrequency_appInForegroun // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.FOREGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); } @@ -329,7 +329,7 @@ public void stopCollectingGauges_invalidGaugeCollectionFrequency_appInForeground doReturn(0L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); doReturn(0L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.FOREGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); @@ -337,7 +337,7 @@ public void stopCollectingGauges_invalidGaugeCollectionFrequency_appInForeground doReturn(-25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); doReturn(-25L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); } @@ -347,7 +347,7 @@ public void startCollectingGauges_validGaugeCollectionFrequency_appInForeground( doReturn(25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); doReturn(15L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.FOREGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); @@ -357,7 +357,7 @@ public void startCollectingGauges_validGaugeCollectionFrequency_appInForeground( @Test public void testStartCollectingGaugesStartsAJobToConsumeTheGeneratedMetrics() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); @@ -391,7 +391,7 @@ public void testStartCollectingGaugesStartsAJobToConsumeTheGeneratedMetrics() { @Test public void testStopCollectingGaugesStopsCollectingAllGaugeMetrics() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); verify(fakeCpuGaugeCollector) @@ -405,7 +405,7 @@ public void testStopCollectingGaugesStopsCollectingAllGaugeMetrics() { @Test public void testStopCollectingGaugesCreatesOneLastJobToConsumeAnyPendingMetrics() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); @@ -433,7 +433,7 @@ public void testStopCollectingGaugesCreatesOneLastJobToConsumeAnyPendingMetrics( @Test public void testGaugeManagerClearsTheQueueEachRun() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); @@ -465,7 +465,7 @@ public void testGaugeManagerClearsTheQueueEachRun() { @Test public void testStartingGaugeManagerWithNewSessionIdButSameAppState() { - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); // Start collecting Gauges. testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.BACKGROUND); @@ -490,7 +490,7 @@ public void testStartingGaugeManagerWithNewSessionIdButSameAppState() { createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 2345); fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading2); - PerfSession fakeSession2 = new PerfSession("sessionId2", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId2", new Clock(), true); // Start collecting gauges for new session, but same app state. testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.BACKGROUND); @@ -523,7 +523,7 @@ public void testStartingGaugeManagerWithNewSessionIdButSameAppState() { @Test public void testStartGaugeManagerWithSameSessionIdButDifferentAppState() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); // Start collecting Gauges. testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); @@ -579,7 +579,7 @@ public void testStartGaugeManagerWithSameSessionIdButDifferentAppState() { @Test public void testStartGaugeManagerWithNewSessionIdAndNewAppState() { - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); // Start collecting Gauges. testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.BACKGROUND); @@ -604,7 +604,7 @@ public void testStartGaugeManagerWithNewSessionIdAndNewAppState() { createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 2345); fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading2); - PerfSession fakeSession2 = new PerfSession("sessionId2", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId2", new Clock(), true); // Start collecting gauges for new session and new app state testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java index 5376265aa0e..d1a5e1a7cc2 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java @@ -1169,7 +1169,8 @@ public void logTraceMetric_sessionEnabled_doesNotStripOffSessionId() { TraceMetric.Builder validTrace = createValidTraceMetric().toBuilder(); List perfSessions = new ArrayList<>(); perfSessions.add( - new com.google.firebase.perf.session.PerfSession("fakeSessionId", new Clock()).build()); + new com.google.firebase.perf.session.PerfSession("fakeSessionId", new Clock(), true) + .build()); validTrace.addAllPerfSessions(perfSessions); testTransportManager.log(validTrace.build()); @@ -1187,7 +1188,8 @@ public void logNetworkMetric_sessionEnabled_doesNotStripOffSessionId() { createValidNetworkRequestMetric().toBuilder(); List perfSessions = new ArrayList<>(); perfSessions.add( - new com.google.firebase.perf.session.PerfSession("fakeSessionId", new Clock()).build()); + new com.google.firebase.perf.session.PerfSession("fakeSessionId", new Clock(), true) + .build()); validNetworkRequest.clearPerfSessions().addAllPerfSessions(perfSessions); testTransportManager.log(validNetworkRequest.build()); From 10369d015ff89f0c72f338d0459c7d39809c6689 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Fri, 7 Feb 2025 14:58:37 -0500 Subject: [PATCH 60/76] Remove the logging of GaugeMetadata to allow using AQS (#6678) Based on the behaviour of AQS w/ Fireperf, an AQS session isn't available when (currently) logging gauge metadata. Changes: - Remove the current logging of gauge metadata - will be re-introduced in a future PR. - Switch Gauge collection from `scheduleAtFixedRate` to `scheduleWithFixedDelay`. As [documented](https://stackoverflow.com/a/78405653), this *should* prevent a potentially large amounts of gauge collection if a process is cached, and then restored during a verbose session - which *should* make it work better w/ AQS. - Remove API restricted behaviour which is no longer relevant. --- .../firebase/perf/FirebasePerformance.java | 1 - .../firebase/perf/session/SessionManager.java | 17 ----- .../session/gauges/CpuGaugeCollector.java | 13 +--- .../perf/session/gauges/GaugeManager.java | 9 +-- .../session/gauges/GaugeMetadataManager.java | 34 +--------- .../session/gauges/MemoryGaugeCollector.java | 6 +- .../perf/session/SessionManagerTest.java | 58 +--------------- .../gauges/GaugeMetadataManagerTest.java | 66 +------------------ 8 files changed, 14 insertions(+), 190 deletions(-) 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..3cc49896ce0 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 @@ -182,7 +182,6 @@ public static FirebasePerformance getInstance() { .initialize(firebaseApp, firebaseInstallationsApi, transportFactoryProvider); Context appContext = firebaseApp.getApplicationContext(); - // TODO(b/110178816): Explore moving off of main thread. mMetadataBundle = extractMetadata(appContext); remoteConfigManager.setFirebaseRemoteConfigProvider(firebaseRemoteConfigProvider); 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..29ffb988ba0 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 @@ -79,9 +79,6 @@ 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(); @@ -89,10 +86,6 @@ public void setApplicationContext(final Context appContext) { executorService.submit( () -> { gaugeManager.initializeGaugeMetadataManager(appContext); - if (appStartSession.isGaugeAndEventCollectionEnabled()) { - gaugeManager.logGaugeMetadata( - appStartSession.sessionId(), ApplicationProcessState.FOREGROUND); - } }); } @@ -164,9 +157,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 +168,6 @@ public void updatePerfSession(PerfSession perfSession) { * this does not reset the perfSession. */ public void initializeGaugeCollection() { - logGaugeMetadataIfCollectionEnabled(ApplicationProcessState.FOREGROUND); startOrStopCollectingGauges(ApplicationProcessState.FOREGROUND); } @@ -206,12 +195,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..30da2f0160f 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 @@ -72,8 +72,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 +81,7 @@ private GaugeManager() { Lazy gaugeManagerExecutor, TransportManager transportManager, ConfigResolver configResolver, - GaugeMetadataManager gaugeMetadataManager, + @Nullable GaugeMetadataManager gaugeMetadataManager, Lazy cpuGaugeCollector, Lazy memoryGaugeCollector) { @@ -140,7 +140,7 @@ public void startCollectingGauges( gaugeManagerDataCollectionJob = gaugeManagerExecutor .get() - .scheduleAtFixedRate( + .scheduleWithFixedDelay( () -> { syncFlush(sessionIdForScheduledTask, applicationProcessStateForScheduledTask); }, @@ -256,6 +256,7 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) { * @return true if GaugeMetadata was logged, false otherwise. */ public boolean logGaugeMetadata(String sessionId, ApplicationProcessState appState) { + // TODO(b/394127311): Re-introduce logging of metadata for AQS. if (gaugeMetadataManager != null) { GaugeMetric gaugeMetric = GaugeMetric.newBuilder() 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/test/java/com/google/firebase/perf/session/SessionManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/session/SessionManagerTest.java index f3e3795f3f8..37b9ff7215b 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 @@ -74,7 +74,7 @@ public void testInstanceCreation() { } @Test - public void setApplicationContext_logGaugeMetadata_afterGaugeMetadataManagerIsInitialized() + public void setApplicationContext_initializeGaugeMetadataManager() throws ExecutionException, InterruptedException { when(mockPerfSession.isGaugeAndEventCollectionEnabled()).thenReturn(true); InOrder inOrder = Mockito.inOrder(mockGaugeManager); @@ -84,7 +84,6 @@ public void setApplicationContext_logGaugeMetadata_afterGaugeMetadataManagerIsIn testSessionManager.getSyncInitFuture().get(); inOrder.verify(mockGaugeManager).initializeGaugeMetadataManager(any()); - inOrder.verify(mockGaugeManager).logGaugeMetadata(any(), any()); } @Test @@ -136,20 +135,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 +163,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 +202,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/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"; } From a9f47b49de997311a8255351f06033ba72ffcf0d Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 11 Feb 2025 13:06:38 -0500 Subject: [PATCH 61/76] 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. --- firebase-perf/firebase-perf.gradle | 2 +- .../firebase/perf/FirebasePerfRegistrar.java | 7 + .../firebase/perf/FirebasePerformance.java | 17 +-- .../FirebasePerformanceSessionSubscriber.kt | 46 +++++++ .../firebase/perf/session/PerfSession.java | 18 ++- .../firebase/perf/session/SessionManager.java | 53 +------- .../perf/session/gauges/GaugeManager.java | 10 +- .../perf/transport/TransportManager.java | 1 + .../perf/session/SessionManagerTest.java | 121 +++--------------- .../api/FirebaseSessionsDependencies.kt | 13 -- 10 files changed, 103 insertions(+), 185 deletions(-) create mode 100644 firebase-perf/src/main/java/com/google/firebase/perf/session/FirebasePerformanceSessionSubscriber.kt 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/FirebasePerfRegistrar.java b/firebase-perf/src/main/java/com/google/firebase/perf/FirebasePerfRegistrar.java index c01f035af1f..daffc2de81a 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,11 @@ public class FirebasePerfRegistrar implements ComponentRegistrar { private static final String LIBRARY_NAME = "fire-perf"; private static final String EARLY_LIBRARY_NAME = "fire-perf-early"; + static { + // Add Firebase Performance as a dependency of Sessions when this class is loaded into memory. + FirebaseSessionsDependencies.addDependency(SessionSubscriber.Name.PERFORMANCE); + } + @Override @Keep public List> 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 3cc49896ce0..e4ddfcd600c 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; @@ -191,6 +183,9 @@ public static FirebasePerformance getInstance() { sessionManager.setApplicationContext(appContext); mPerformanceCollectionForceEnabledState = configResolver.getIsPerformanceCollectionEnabled(); + FirebaseSessionsDependencies.register( + new FirebasePerformanceSessionSubscriber(isPerformanceCollectionEnabled())); + if (logger.isLogcatEnabled() && isPerformanceCollectionEnabled()) { logger.info( String.format( @@ -281,7 +276,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/session/FirebasePerformanceSessionSubscriber.kt b/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebasePerformanceSessionSubscriber.kt new file mode 100644 index 00000000000..b6a3d30c139 --- /dev/null +++ b/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebasePerformanceSessionSubscriber.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.perf.session + +import com.google.firebase.perf.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 { + + override val sessionSubscriberName: SessionSubscriber.Name = SessionSubscriber.Name.PERFORMANCE + + override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { + val currentPerfSession = SessionManager.getInstance().perfSession() + + // A [PerfSession] was created before a session was started. + if (currentPerfSession.aqsSessionId() == null) { + currentPerfSession.setAQSId(sessionDetails) + GaugeManager.getInstance() + .logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + return + } + + val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()) + updatedSession.setAQSId(sessionDetails) + SessionManager.getInstance().updatePerfSession(updatedSession) + GaugeManager.getInstance() + .logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + } +} 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..075848ab747 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,6 +23,7 @@ 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.concurrent.TimeUnit; @@ -31,6 +32,7 @@ public class PerfSession implements Parcelable { private final String sessionId; private final Timer creationTime; + @Nullable private String aqsSessionId; private boolean isGaugeAndEventCollectionEnabled = false; @@ -59,11 +61,24 @@ 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; } + /** 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(); + } + } + /** * Returns a timer object that has been seeded with the system time at which the session began. */ @@ -113,6 +128,7 @@ 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); 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 29ffb988ba0..cf99c1e52ea 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,6 @@ 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.session.gauges.GaugeManager; import com.google.firebase.perf.v1.ApplicationProcessState; import com.google.firebase.perf.v1.GaugeMetadata; @@ -27,15 +26,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(); @@ -45,7 +42,6 @@ public class SessionManager extends AppStateUpdateHandler { private final Set> clients = new HashSet<>(); private PerfSession perfSession; - private Future syncInitFuture; /** Returns the singleton instance of SessionManager. */ public static SessionManager getInstance() { @@ -71,7 +67,6 @@ public SessionManager( this.gaugeManager = gaugeManager; this.perfSession = perfSession; this.appStateMonitor = appStateMonitor; - registerForAppState(); } /** @@ -79,42 +74,7 @@ public SessionManager( * (currently that is before onResume finishes) to ensure gauge collection starts on time. */ public void setApplicationContext(final Context appContext) { - // TODO(b/258263016): Migrate to go/firebase-android-executors - @SuppressLint("ThreadPoolCreation") - ExecutorService executorService = Executors.newSingleThreadExecutor(); - syncInitFuture = - executorService.submit( - () -> { - gaugeManager.initializeGaugeMetadataManager(appContext); - }); - } - - @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); } /** @@ -138,7 +98,7 @@ 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; } @@ -207,9 +167,4 @@ private void startOrStopCollectingGauges(ApplicationProcessState appState) { public void setPerfSession(PerfSession perfSession) { this.perfSession = perfSession; } - - @VisibleForTesting - public Future getSyncInitFuture() { - return this.syncInitFuture; - } } 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 30da2f0160f..1c06ceac9dd 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 @@ -136,6 +136,7 @@ public void startCollectingGauges( final String sessionIdForScheduledTask = sessionId; final ApplicationProcessState applicationProcessStateForScheduledTask = applicationProcessState; + // TODO(b/394127311): Switch to using AQS. try { gaugeManagerDataCollectionJob = gaugeManagerExecutor @@ -204,6 +205,7 @@ public void stopCollectingGauges() { gaugeManagerDataCollectionJob.cancel(false); } + // TODO(b/394127311): Switch to using AQS. // Flush any data that was collected for this session one last time. @SuppressWarnings("FutureReturnValueIgnored") ScheduledFuture unusedFuture = @@ -242,6 +244,7 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) { } // Adding Session ID info. + // TODO(b/394127311): Switch to using AQS. gaugeMetricBuilder.setSessionId(sessionId); transportManager.log(gaugeMetricBuilder.build(), appState); @@ -250,17 +253,16 @@ 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 PerfSession#aqsSessionId()} ()} 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) { - // TODO(b/394127311): Re-introduce logging of metadata for AQS. + public boolean logGaugeMetadata(String aqsSessionId, ApplicationProcessState appState) { if (gaugeMetadataManager != null) { GaugeMetric gaugeMetric = GaugeMetric.newBuilder() - .setSessionId(sessionId) + .setSessionId(aqsSessionId) .setGaugeMetadata(getGaugeMetadata()) .build(); transportManager.log(gaugeMetric, appState); 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/session/SessionManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/session/SessionManagerTest.java index 37b9ff7215b..954b0ae88d3 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 @@ -16,9 +16,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -40,7 +37,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.AdditionalMatchers; import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.Mock; @@ -82,105 +78,15 @@ public void setApplicationContext_initializeGaugeMetadataManager() new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); testSessionManager.setApplicationContext(mockApplicationContext); - testSessionManager.getSyncInitFuture().get(); inOrder.verify(mockGaugeManager).initializeGaugeMetadataManager(any()); } - @Test - public void testOnUpdateAppStateDoesNothingDuringAppStart() { - String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - - AppStateMonitor.getInstance().setIsColdStart(true); - - SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.FOREGROUND); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - } - - @Test - public void testOnUpdateAppStateGeneratesNewSessionIdOnForegroundState() { - String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - - SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.FOREGROUND); - assertThat(oldSessionId).isNotEqualTo(SessionManager.getInstance().perfSession().sessionId()); - } - - @Test - public void testOnUpdateAppStateDoesntGenerateNewSessionIdOnBackgroundState() { - String oldSessionId = SessionManager.getInstance().perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - - SessionManager.getInstance().onUpdateAppState(ApplicationProcessState.BACKGROUND); - assertThat(oldSessionId).isEqualTo(SessionManager.getInstance().perfSession().sessionId()); - } - - @Test - public void testOnUpdateAppStateGeneratesNewSessionIdOnBackgroundStateIfPerfSessionExpires() { - when(mockPerfSession.isSessionRunningTooLong()).thenReturn(true); - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - String oldSessionId = testSessionManager.perfSession().sessionId(); - - assertThat(oldSessionId).isNotNull(); - assertThat(oldSessionId).isEqualTo(testSessionManager.perfSession().sessionId()); - - testSessionManager.onUpdateAppState(ApplicationProcessState.BACKGROUND); - assertThat(oldSessionId).isNotEqualTo(testSessionManager.perfSession().sessionId()); - } - - @Test - public void - testOnUpdateAppStateDoesntMakeGaugeManagerLogGaugeMetadataOnForegroundStateIfSessionIsNonVerbose() { - forceNonVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.FOREGROUND); - - verify(mockGaugeManager, never()) - .logGaugeMetadata( - anyString(), nullable(com.google.firebase.perf.v1.ApplicationProcessState.class)); - } - - @Test - public void - testOnUpdateAppStateDoesntMakeGaugeManagerLogGaugeMetadataOnBackgroundStateEvenIfSessionIsVerbose() { - forceVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.BACKGROUND); - - verify(mockGaugeManager, never()) - .logGaugeMetadata( - anyString(), nullable(com.google.firebase.perf.v1.ApplicationProcessState.class)); - } - - @Test - public void testOnUpdateAppStateMakesGaugeManagerStartCollectingGaugesIfSessionIsVerbose() { - forceVerboseSession(); - - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, mockPerfSession, mockAppStateMonitor); - testSessionManager.onUpdateAppState(ApplicationProcessState.FOREGROUND); - - verify(mockGaugeManager) - .startCollectingGauges(AdditionalMatchers.not(eq(mockPerfSession)), any()); - } - // LogGaugeData on new perf session when Verbose // NotLogGaugeData on new perf session when not Verbose // Mark Session as expired after time limit. @Test - public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesIfSessionIsNonVerbose() { + public void testUpdatePerfSessionMakesGaugeManagerStopCollectingGaugesIfSessionIsNonVerbose() { forceNonVerboseSession(); SessionManager testSessionManager = @@ -191,7 +97,7 @@ public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesIfSessionIs } @Test - public void testOnUpdateAppStateMakesGaugeManagerStopCollectingGaugesWhenSessionsDisabled() { + public void testUpdatePerfSessionMakesGaugeManagerStopCollectingGaugesWhenSessionsDisabled() { forceSessionsFeatureDisabled(); SessionManager testSessionManager = @@ -221,22 +127,25 @@ public void testSessionIdDoesNotUpdateIfPerfSessionRunsTooLong() { } @Test - public void testPerfSessionExpiredMakesGaugeManagerStopsCollectingGaugesIfSessionIsVerbose() { - forceVerboseSession(); + public void testUpdatePerfSessionStartsCollectingGaugesIfSessionIsVerbose() { Timer mockTimer = mock(Timer.class); when(mockClock.getTime()).thenReturn(mockTimer); + when(mockAppStateMonitor.getAppState()).thenReturn(ApplicationProcessState.FOREGROUND); - PerfSession session = new PerfSession("sessionId", mockClock); - SessionManager testSessionManager = - new SessionManager(mockGaugeManager, session, mockAppStateMonitor); + PerfSession previousSession = new PerfSession("previousSession", mockClock); + previousSession.setGaugeAndEventCollectionEnabled(false); - assertThat(session.isSessionRunningTooLong()).isFalse(); + PerfSession newSession = new PerfSession("newSession", mockClock); + newSession.setGaugeAndEventCollectionEnabled(true); - when(mockTimer.getDurationMicros()) - .thenReturn(TimeUnit.HOURS.toMicros(5)); // Default Max Session Length is 4 hours + SessionManager testSessionManager = + new SessionManager(mockGaugeManager, previousSession, mockAppStateMonitor); + testSessionManager.updatePerfSession(newSession); + testSessionManager.setApplicationContext(mockApplicationContext); - assertThat(session.isSessionRunningTooLong()).isTrue(); - verify(mockGaugeManager, times(0)).logGaugeMetadata(any(), any()); + verify(mockGaugeManager, times(1)).initializeGaugeMetadataManager(mockApplicationContext); + verify(mockGaugeManager, times(1)) + .startCollectingGauges(newSession, ApplicationProcessState.FOREGROUND); } @Test 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 From 7d9c0bf382abce4b3726d0aeb5c728b6662318bd Mon Sep 17 00:00:00 2001 From: themiswang Date: Mon, 7 Apr 2025 11:48:25 -0400 Subject: [PATCH 62/76] convert perf session to use aqs support session id (#6832) - Override PerfSession session id to aqs session id when possible - Add assertion for debug mode to double check if aqs session id available by the time of use --- .../firebase/perf/FirebasePerformance.java | 3 + .../perf/logging/DebugEnforcementCheck.kt | 32 ++++++++ .../FirebasePerformanceSessionSubscriber.kt | 8 +- .../firebase/perf/session/PerfSession.java | 75 +++++++++---------- .../firebase/perf/session/SessionManager.java | 20 +++-- .../perf/application/AppStateMonitorTest.java | 3 + .../perf/session/PerfSessionTest.java | 30 ++++---- .../perf/session/SessionManagerTest.java | 7 +- .../perf/session/gauges/GaugeManagerTest.java | 56 +++++++------- .../perf/transport/TransportManagerTest.java | 6 +- 10 files changed, 138 insertions(+), 102 deletions(-) create mode 100644 firebase-perf/src/main/java/com/google/firebase/perf/logging/DebugEnforcementCheck.kt 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 e4ddfcd600c..587bff395de 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 @@ -34,6 +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.metrics.HttpMetric; import com.google.firebase.perf.metrics.Trace; import com.google.firebase.perf.session.FirebasePerformanceSessionSubscriber; @@ -43,6 +44,7 @@ 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 java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -169,6 +171,7 @@ public static FirebasePerformance getInstance() { this.mMetadataBundle = new ImmutableBundle(new Bundle()); return; } + DebugEnforcementCheck.setEnforcement(BuildConfig.DEBUG); TransportManager.getInstance() .initialize(firebaseApp, firebaseInstallationsApi, transportFactoryProvider); diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/logging/DebugEnforcementCheck.kt b/firebase-perf/src/main/java/com/google/firebase/perf/logging/DebugEnforcementCheck.kt new file mode 100644 index 00000000000..a2f3b186f9b --- /dev/null +++ b/firebase-perf/src/main/java/com/google/firebase/perf/logging/DebugEnforcementCheck.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.perf.logging + +class DebugEnforcementCheck { + 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) + assert(!enforcement) { failureMessage } + } + } + } +} 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 index b6a3d30c139..08175baf1df 100644 --- 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 @@ -30,17 +30,15 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: val currentPerfSession = SessionManager.getInstance().perfSession() // A [PerfSession] was created before a session was started. - if (currentPerfSession.aqsSessionId() == null) { - currentPerfSession.setAQSId(sessionDetails) + if (!currentPerfSession.isAqsReady) { GaugeManager.getInstance() - .logGaugeMetadata(currentPerfSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + .logGaugeMetadata(currentPerfSession.sessionId(), ApplicationProcessState.FOREGROUND) return } val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()) - updatedSession.setAQSId(sessionDetails) SessionManager.getInstance().updatePerfSession(updatedSession) GaugeManager.getInstance() - .logGaugeMetadata(updatedSession.aqsSessionId(), ApplicationProcessState.FOREGROUND) + .logGaugeMetadata(updatedSession.sessionId(), ApplicationProcessState.FOREGROUND) } } 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 075848ab747..e4260034107 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,34 +23,40 @@ 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 final String sessionId; private final Timer creationTime; - @Nullable private String aqsSessionId; - + 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(@NonNull String sessionId) { - String prunedSessionId = sessionId.replace("-", ""); - PerfSession session = new PerfSession(prunedSessionId, new Clock()); - session.setGaugeAndEventCollectionEnabled(shouldCollectGaugesAndEvents()); - + public static PerfSession createWithId(@Nullable String aqsSessionId) { + String sessionId; + Boolean isAqsReady; + if (aqsSessionId != null) { + sessionId = aqsSessionId; + isAqsReady = true; + } else { + sessionId = UUID.randomUUID().toString().replace("-", ""); + isAqsReady = false; + } + PerfSession session = new PerfSession(sessionId, new Clock(), isAqsReady); + session.setGaugeAndEventCollectionEnabled(shouldCollectGaugesAndEvents(sessionId)); return session; } /** Creates a PerfSession with the provided {@code sessionId} and {@code clock}. */ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public PerfSession(String sessionId, Clock clock) { + public PerfSession(String sessionId, Clock clock, boolean isAqsReady) { this.sessionId = sessionId; + this.isAqsReady = isAqsReady; creationTime = clock.getTime(); } @@ -58,27 +64,15 @@ 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 of the session. */ + /** Returns the sessionId for the given session. */ public String sessionId() { return 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(); - } - } - /** * Returns a timer object that has been seeded with the system time at which the session began. */ @@ -105,18 +99,6 @@ public boolean isVerbose() { return isGaugeAndEventCollectionEnabled; } - /** Checks if the current {@link com.google.firebase.perf.v1.PerfSession} is verbose or not. */ - @VisibleForTesting - static boolean isVerbose(@NonNull com.google.firebase.perf.v1.PerfSession perfSession) { - for (SessionVerbosity sessionVerbosity : perfSession.getSessionVerbosityList()) { - if (sessionVerbosity == SessionVerbosity.GAUGES_AND_SYSTEM_EVENTS) { - return true; - } - } - - return false; - } - /** * Checks if it has been more than {@link ConfigResolver#getSessionsMaxDurationMinutes()} time * since the creation time of the current session. @@ -128,7 +110,6 @@ 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); @@ -179,11 +160,10 @@ public static com.google.firebase.perf.v1.PerfSession[] buildAndSort( } /** If true, Session Gauge collection is enabled. */ - public static boolean shouldCollectGaugesAndEvents() { + public static boolean shouldCollectGaugesAndEvents(String sessionId) { ConfigResolver configResolver = ConfigResolver.getInstance(); - return configResolver.isPerformanceMonitoringEnabled() - && Math.random() < configResolver.getSessionsSamplingRate(); + && (Math.abs(sessionId.hashCode() % 100) < configResolver.getSessionsSamplingRate() * 100); } /** @@ -207,6 +187,7 @@ 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); } @@ -224,4 +205,16 @@ public PerfSession[] newArray(int size) { return new PerfSession[size]; } }; + + /** Checks if the current {@link com.google.firebase.perf.v1.PerfSession} is verbose or not. */ + @VisibleForTesting + static boolean isVerbose(@NonNull com.google.firebase.perf.v1.PerfSession perfSession) { + for (SessionVerbosity sessionVerbosity : perfSession.getSessionVerbosityList()) { + if (sessionVerbosity == SessionVerbosity.GAUGES_AND_SYSTEM_EVENTS) { + return true; + } + } + + return false; + } } 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 cf99c1e52ea..f7f17cd4588 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,6 +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.session.gauges.GaugeManager; import com.google.firebase.perf.v1.ApplicationProcessState; import com.google.firebase.perf.v1.GaugeMetadata; @@ -28,12 +29,10 @@ import java.util.Iterator; import java.util.Objects; import java.util.Set; -import java.util.UUID; /** Session manager to generate sessionIDs and broadcast to the application. */ @Keep // Needed because of b/117526359. public class SessionManager { - @SuppressLint("StaticFieldLeak") private static final SessionManager instance = new SessionManager(); @@ -50,15 +49,15 @@ 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"); + return perfSession; } private SessionManager() { - // Generate a new sessionID for every cold start. - this( - GaugeManager.getInstance(), - PerfSession.createWithId(UUID.randomUUID().toString()), - AppStateMonitor.getInstance()); + // session should quickly updated by session subscriber. + this(GaugeManager.getInstance(), PerfSession.createWithId(null), AppStateMonitor.getInstance()); } @VisibleForTesting @@ -83,6 +82,10 @@ public void setApplicationContext(final Context appContext) { * @see PerfSession#isSessionRunningTooLong() */ public void stopGaugeCollectionIfSessionRunningTooLong() { + DebugEnforcementCheck.Companion.checkSession( + perfSession.isAqsReady, + "Session is not ready while trying to stopGaugeCollectionIfSessionRunningTooLong"); + if (perfSession.isSessionRunningTooLong()) { gaugeManager.stopCollectingGauges(); } @@ -156,6 +159,9 @@ public void unregisterForSessionUpdates(WeakReference client } private void startOrStopCollectingGauges(ApplicationProcessState appState) { + DebugEnforcementCheck.Companion.checkSession( + perfSession.isAqsReady, "Session is not ready while trying to startOrStopCollectingGauges"); + if (perfSession.isGaugeAndEventCollectionEnabled()) { gaugeManager.startCollectingGauges(perfSession, appState); } else { diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/application/AppStateMonitorTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/application/AppStateMonitorTest.java index 0b7d4bbfc17..f30ee5d73a0 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/application/AppStateMonitorTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/application/AppStateMonitorTest.java @@ -39,6 +39,8 @@ import com.google.firebase.perf.config.DeviceCacheManager; import com.google.firebase.perf.metrics.NetworkRequestMetricBuilder; import com.google.firebase.perf.metrics.Trace; +import com.google.firebase.perf.session.PerfSession; +import com.google.firebase.perf.session.SessionManager; import com.google.firebase.perf.session.gauges.GaugeManager; import com.google.firebase.perf.transport.TransportManager; import com.google.firebase.perf.util.Clock; @@ -80,6 +82,7 @@ public class AppStateMonitorTest extends FirebasePerformanceTestBase { @Before public void setUp() { currentTime = 0; + SessionManager.getInstance().updatePerfSession(PerfSession.createWithId("sessionId")); initMocks(this); doAnswer((Answer) invocationOnMock -> new Timer(currentTime)).when(clock).getTime(); 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..19d8aadf76b 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 @@ -62,7 +62,7 @@ public void setUp() { @Test public void instanceCreation() { - PerfSession session = new PerfSession("sessionId", mockClock); + PerfSession session = new PerfSession("sessionId", mockClock, true); assertThat(session).isNotNull(); session.setGaugeAndEventCollectionEnabled(true); Assert.assertTrue(session.isGaugeAndEventCollectionEnabled()); @@ -78,17 +78,17 @@ public void shouldCollectGaugesAndEvents_perfMonDisabledAtRuntime_sessionNotVerb configResolver.setMetadataBundle(new ImmutableBundle(bundle)); // By default, session is verbose if developer has set 100% of session verbosity. - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isTrue(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isTrue(); // Case #1: developer has disabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(false); - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isFalse(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); // Case #2: developer has enabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(true); - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isTrue(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isTrue(); } @Test @@ -102,17 +102,17 @@ public void shouldCollectGaugesAndEvents_perfMonDisabledAtBuildtime_verbosityDep // By default, session is not verbose if developer disabled performance monitoring at build // time. - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isFalse(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); // Case #1: developer has enabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(true); - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isTrue(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isTrue(); // Case #2: developer has disabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(false); - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isFalse(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); } @Test @@ -124,22 +124,22 @@ public void shouldCollectGaugesAndEvents_perfMonDeactivated_sessionNotVerbose() configResolver.setMetadataBundle(new ImmutableBundle(bundle)); // Session will never be verbose if developer deactivated performance monitoring at build time. - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isFalse(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); // Case #1: developer has enabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(true); - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isFalse(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); // Case #2: developer has disabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(false); - assertThat(PerfSession.shouldCollectGaugesAndEvents()).isFalse(); + assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); } @Test public void testPerfSessionConversion() { - PerfSession session1 = new PerfSession("sessionId", mockClock); + PerfSession session1 = new PerfSession("sessionId", mockClock, true); session1.setGaugeAndEventCollectionEnabled(true); com.google.firebase.perf.v1.PerfSession perfSession = session1.build(); @@ -150,7 +150,7 @@ public void testPerfSessionConversion() { @Test public void testPerfSessionConversionWithoutVerbosity() { - PerfSession session1 = new PerfSession("sessionId", mockClock); + PerfSession session1 = new PerfSession("sessionId", mockClock, true); com.google.firebase.perf.v1.PerfSession perfSession = session1.build(); Assert.assertEquals(session1.sessionId(), perfSession.getSessionId()); @@ -216,7 +216,7 @@ public void testIsExpiredReturnsFalseWhenCurrentSessionLengthIsLessThanMaxSessio - TimeUnit.MINUTES.toMicros(1)); // Default Max Session Length is 4 hours when(mockClock.getTime()).thenReturn(mockTimer); - PerfSession session = new PerfSession("sessionId", mockClock); + PerfSession session = new PerfSession("sessionId", mockClock, true); assertThat(session.isSessionRunningTooLong()).isFalse(); } @@ -227,7 +227,7 @@ public void testIsExpiredReturnsFalseWhenCurrentSessionLengthIsEqualToMaxSession .thenReturn(TimeUnit.HOURS.toMicros(4)); // Default Max Session Length is 4 hours when(mockClock.getTime()).thenReturn(mockTimer); - PerfSession session = new PerfSession("sessionId", mockClock); + PerfSession session = new PerfSession("sessionId", mockClock, true); assertThat(session.isSessionRunningTooLong()).isFalse(); } @@ -238,7 +238,7 @@ public void testIsExpiredReturnsTrueWhenCurrentSessionLengthIsGreaterThanMaxSess .thenReturn(TimeUnit.HOURS.toMicros(5)); // Default Max Session Length is 4 hours when(mockClock.getTime()).thenReturn(mockTimer); - PerfSession session = new PerfSession("sessionId", mockClock); + PerfSession session = new PerfSession("sessionId", mockClock, true); assertThat(session.isSessionRunningTooLong()).isTrue(); } } 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 954b0ae88d3..2e1e080a98c 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 @@ -66,7 +66,6 @@ public void setUp() { public void testInstanceCreation() { assertThat(SessionManager.getInstance()).isNotNull(); assertThat(SessionManager.getInstance()).isEqualTo(SessionManager.getInstance()); - assertThat(SessionManager.getInstance().perfSession().sessionId()).isNotNull(); } @Test @@ -113,7 +112,7 @@ public void testSessionIdDoesNotUpdateIfPerfSessionRunsTooLong() { Timer mockTimer = mock(Timer.class); when(mockClock.getTime()).thenReturn(mockTimer); - PerfSession session = new PerfSession("sessionId", mockClock); + PerfSession session = new PerfSession("sessionId", mockClock, true); SessionManager testSessionManager = new SessionManager(mockGaugeManager, session, mockAppStateMonitor); @@ -132,10 +131,10 @@ public void testUpdatePerfSessionStartsCollectingGaugesIfSessionIsVerbose() { when(mockClock.getTime()).thenReturn(mockTimer); when(mockAppStateMonitor.getAppState()).thenReturn(ApplicationProcessState.FOREGROUND); - PerfSession previousSession = new PerfSession("previousSession", mockClock); + PerfSession previousSession = new PerfSession("previousSession", mockClock, true); previousSession.setGaugeAndEventCollectionEnabled(false); - PerfSession newSession = new PerfSession("newSession", mockClock); + PerfSession newSession = new PerfSession("newSession", mockClock, true); newSession.setGaugeAndEventCollectionEnabled(true); SessionManager testSessionManager = 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..7ba04852890 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 @@ -124,7 +124,7 @@ public void setUp() { @Test public void testStartCollectingGaugesStartsCollectingMetricsInBackgroundState() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); verify(fakeCpuGaugeCollector) .startCollecting( @@ -138,7 +138,7 @@ public void testStartCollectingGaugesStartsCollectingMetricsInBackgroundState() @Test public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.FOREGROUND); verify(fakeCpuGaugeCollector) .startCollecting( @@ -153,7 +153,7 @@ public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() @Test public void testStartCollectingGaugesDoesNotStartCollectingMetricsWithUnknownApplicationProcessState() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges( fakeSession, ApplicationProcessState.APPLICATION_PROCESS_STATE_UNKNOWN); verify(fakeCpuGaugeCollector, never()) @@ -167,7 +167,7 @@ public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithValidFrequencyInBackground() { // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyBackgroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.BACKGROUND); // Verify that Cpu metric collection is not started @@ -180,7 +180,7 @@ public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyBackgroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.BACKGROUND); // Verify that Cpu metric collection is not started @@ -197,7 +197,7 @@ public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() startCollectingGaugesOnBackground_invalidMemoryCaptureMs_onlyDisableMemoryCollection() { // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyBackgroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.BACKGROUND); // Verify that Memory metric collection is not started @@ -210,7 +210,7 @@ public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyBackgroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.BACKGROUND); // Verify that Memory metric collection is not started @@ -226,7 +226,7 @@ public void testStartCollectingGaugesStartsCollectingMetricsInForegroundState() public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithValidFrequency() { // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.FOREGROUND); // Verify that Cpu metric collection is not started @@ -239,7 +239,7 @@ public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithV // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); // Verify that Cpu metric collection is not started @@ -256,7 +256,7 @@ public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithV startCollectingGaugesOnForeground_invalidMemoryCaptureMs_onlyDisableMemoryCollection() { // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.FOREGROUND); // Verify that Memory metric collection is not started @@ -269,7 +269,7 @@ public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithV // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); // Verify that Memory metric collection is not started @@ -283,7 +283,7 @@ public void stopCollectingCPUMetric_invalidCPUCaptureFrequency_OtherMetricsWithV @Test public void testStartCollectingGaugesDoesNotStartAJobToConsumeMetricsWithUnknownAppState() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges( fakeSession, ApplicationProcessState.APPLICATION_PROCESS_STATE_UNKNOWN); assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); @@ -294,14 +294,14 @@ public void stopCollectingCPUMetrics_invalidCPUCaptureFrequency_appInForegrounf( // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.FOREGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); } @@ -311,14 +311,14 @@ public void stopCollectingGauges_invalidMemoryCollectionFrequency_appInForegroun // PASS 1: Test with 0 doReturn(0L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.FOREGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); // PASS 2: Test with -ve value doReturn(-25L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); } @@ -329,7 +329,7 @@ public void stopCollectingGauges_invalidGaugeCollectionFrequency_appInForeground doReturn(0L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); doReturn(0L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.FOREGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); @@ -337,7 +337,7 @@ public void stopCollectingGauges_invalidGaugeCollectionFrequency_appInForeground doReturn(-25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); doReturn(-25L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession2 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); } @@ -347,7 +347,7 @@ public void startCollectingGauges_validGaugeCollectionFrequency_appInForeground( doReturn(25L).when(mockConfigResolver).getSessionsCpuCaptureFrequencyForegroundMs(); doReturn(15L).when(mockConfigResolver).getSessionsMemoryCaptureFrequencyForegroundMs(); - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.FOREGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); @@ -357,7 +357,7 @@ public void startCollectingGauges_validGaugeCollectionFrequency_appInForeground( @Test public void testStartCollectingGaugesStartsAJobToConsumeTheGeneratedMetrics() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); @@ -391,7 +391,7 @@ public void testStartCollectingGaugesStartsAJobToConsumeTheGeneratedMetrics() { @Test public void testStopCollectingGaugesStopsCollectingAllGaugeMetrics() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); verify(fakeCpuGaugeCollector) @@ -405,7 +405,7 @@ public void testStopCollectingGaugesStopsCollectingAllGaugeMetrics() { @Test public void testStopCollectingGaugesCreatesOneLastJobToConsumeAnyPendingMetrics() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); @@ -433,7 +433,7 @@ public void testStopCollectingGaugesCreatesOneLastJobToConsumeAnyPendingMetrics( @Test public void testGaugeManagerClearsTheQueueEachRun() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); @@ -465,7 +465,7 @@ public void testGaugeManagerClearsTheQueueEachRun() { @Test public void testStartingGaugeManagerWithNewSessionIdButSameAppState() { - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); // Start collecting Gauges. testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.BACKGROUND); @@ -490,7 +490,7 @@ public void testStartingGaugeManagerWithNewSessionIdButSameAppState() { createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 2345); fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading2); - PerfSession fakeSession2 = new PerfSession("sessionId2", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId2", new Clock(), true); // Start collecting gauges for new session, but same app state. testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.BACKGROUND); @@ -523,7 +523,7 @@ public void testStartingGaugeManagerWithNewSessionIdButSameAppState() { @Test public void testStartGaugeManagerWithSameSessionIdButDifferentAppState() { - PerfSession fakeSession = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession = new PerfSession("sessionId", new Clock(), true); // Start collecting Gauges. testGaugeManager.startCollectingGauges(fakeSession, ApplicationProcessState.BACKGROUND); @@ -579,7 +579,7 @@ public void testStartGaugeManagerWithSameSessionIdButDifferentAppState() { @Test public void testStartGaugeManagerWithNewSessionIdAndNewAppState() { - PerfSession fakeSession1 = new PerfSession("sessionId", new Clock()); + PerfSession fakeSession1 = new PerfSession("sessionId", new Clock(), true); // Start collecting Gauges. testGaugeManager.startCollectingGauges(fakeSession1, ApplicationProcessState.BACKGROUND); @@ -604,7 +604,7 @@ public void testStartGaugeManagerWithNewSessionIdAndNewAppState() { createFakeAndroidMetricReading(/* currentUsedAppJavaHeapMemoryKb= */ 2345); fakeMemoryGaugeCollector.memoryMetricReadings.add(fakeMemoryMetricReading2); - PerfSession fakeSession2 = new PerfSession("sessionId2", new Clock()); + PerfSession fakeSession2 = new PerfSession("sessionId2", new Clock(), true); // Start collecting gauges for new session and new app state testGaugeManager.startCollectingGauges(fakeSession2, ApplicationProcessState.FOREGROUND); diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java index 5376265aa0e..d1a5e1a7cc2 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java @@ -1169,7 +1169,8 @@ public void logTraceMetric_sessionEnabled_doesNotStripOffSessionId() { TraceMetric.Builder validTrace = createValidTraceMetric().toBuilder(); List perfSessions = new ArrayList<>(); perfSessions.add( - new com.google.firebase.perf.session.PerfSession("fakeSessionId", new Clock()).build()); + new com.google.firebase.perf.session.PerfSession("fakeSessionId", new Clock(), true) + .build()); validTrace.addAllPerfSessions(perfSessions); testTransportManager.log(validTrace.build()); @@ -1187,7 +1188,8 @@ public void logNetworkMetric_sessionEnabled_doesNotStripOffSessionId() { createValidNetworkRequestMetric().toBuilder(); List perfSessions = new ArrayList<>(); perfSessions.add( - new com.google.firebase.perf.session.PerfSession("fakeSessionId", new Clock()).build()); + new com.google.firebase.perf.session.PerfSession("fakeSessionId", new Clock(), true) + .build()); validNetworkRequest.clearPerfSessions().addAllPerfSessions(perfSessions); testTransportManager.log(validNetworkRequest.build()); From c933078882ef28e183636a9058bac20f58923beb Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Mon, 7 Apr 2025 15:38:29 -0400 Subject: [PATCH 63/76] Revert changes in sessions to fireperf-aqs --- firebase-sessions/CHANGELOG.md | 14 +- .../firebase-sessions.gradle.kts | 4 +- .../sessions/FirebaseSessionsTests.kt | 34 +- .../sessions/FirebaseSessionsComponent.kt | 93 +--- .../sessions/FirebaseSessionsRegistrar.kt | 4 +- .../sessions/SessionDataStoreConfigs.kt | 40 ++ .../firebase/sessions/SessionDatastore.kt | 65 ++- .../google/firebase/sessions/SessionEvents.kt | 1 + .../firebase/sessions/SessionGenerator.kt | 2 +- .../google/firebase/sessions/TimeProvider.kt | 15 +- .../sessions/settings/RemoteSettings.kt | 57 ++- .../settings/RemoteSettingsFetcher.kt | 4 +- .../sessions/settings/SettingsCache.kt | 146 +++--- .../firebase/sessions/SessionGeneratorTest.kt | 13 +- .../sessions/settings/RemoteSettingsTest.kt | 450 +++++++++++------- .../sessions/settings/SessionsSettingsTest.kt | 220 +++++---- .../sessions/settings/SettingsCacheTest.kt | 252 +++------- .../sessions/testing/FakeTimeProvider.kt | 10 +- .../sessions/testing/TestSessionEventData.kt | 16 +- 19 files changed, 752 insertions(+), 688 deletions(-) create mode 100644 firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDataStoreConfigs.kt diff --git a/firebase-sessions/CHANGELOG.md b/firebase-sessions/CHANGELOG.md index f73a860ee60..8353fbf9029 100644 --- a/firebase-sessions/CHANGELOG.md +++ b/firebase-sessions/CHANGELOG.md @@ -1,5 +1,5 @@ # Unreleased -* [changed] Use multi-process DataStore instead of Preferences DataStore + # 2.1.0 * [changed] Add warning for known issue b/328687152 @@ -7,9 +7,21 @@ * [changed] Updated datastore dependency to v1.1.3 to fix [CVE-2024-7254](https://github.com/advisories/GHSA-735f-pc8j-v9w8). + +## Kotlin +The Kotlin extensions library transitively includes the updated +`firebase-sessions` library. The Kotlin extensions library has no additional +updates. + # 2.0.9 * [fixed] Make AQS resilient to background init in multi-process apps. + +## Kotlin +The Kotlin extensions library transitively includes the updated +`firebase-sessions` library. The Kotlin extensions library has no additional +updates. + # 2.0.7 * [fixed] Removed extraneous logs that risk leaking internal identifiers. diff --git a/firebase-sessions/firebase-sessions.gradle.kts b/firebase-sessions/firebase-sessions.gradle.kts index 23edc952d5e..b136a281660 100644 --- a/firebase-sessions/firebase-sessions.gradle.kts +++ b/firebase-sessions/firebase-sessions.gradle.kts @@ -21,7 +21,6 @@ plugins { id("firebase-vendor") id("kotlin-android") id("kotlin-kapt") - id("kotlinx-serialization") } firebaseLibrary { @@ -77,8 +76,7 @@ dependencies { implementation("com.google.android.datatransport:transport-api:3.2.0") implementation(libs.javax.inject) implementation(libs.androidx.annotation) - implementation(libs.androidx.datastore) - implementation(libs.kotlinx.serialization.json) + implementation(libs.androidx.datastore.preferences) vendor(libs.dagger.dagger) { exclude(group = "javax.inject", module = "javax.inject") } diff --git a/firebase-sessions/src/androidTest/kotlin/com/google/firebase/sessions/FirebaseSessionsTests.kt b/firebase-sessions/src/androidTest/kotlin/com/google/firebase/sessions/FirebaseSessionsTests.kt index c74b6e4e329..1cf67e0c5e1 100644 --- a/firebase-sessions/src/androidTest/kotlin/com/google/firebase/sessions/FirebaseSessionsTests.kt +++ b/firebase-sessions/src/androidTest/kotlin/com/google/firebase/sessions/FirebaseSessionsTests.kt @@ -20,10 +20,12 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import com.google.firebase.Firebase +import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseOptions import com.google.firebase.initialize import com.google.firebase.sessions.settings.SessionsSettings -import org.junit.BeforeClass +import org.junit.After +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -34,6 +36,23 @@ import org.junit.runner.RunWith */ @RunWith(AndroidJUnit4::class) class FirebaseSessionsTests { + @Before + fun setUp() { + Firebase.initialize( + ApplicationProvider.getApplicationContext(), + FirebaseOptions.Builder() + .setApplicationId(APP_ID) + .setApiKey(API_KEY) + .setProjectId(PROJECT_ID) + .build() + ) + } + + @After + fun cleanUp() { + FirebaseApp.clearInstancesForTest() + } + @Test fun firebaseSessionsDoesInitialize() { assertThat(FirebaseSessions.instance).isNotNull() @@ -50,18 +69,5 @@ class FirebaseSessionsTests { private const val APP_ID = "1:1:android:1a" private const val API_KEY = "API-KEY-API-KEY-API-KEY-API-KEY-API-KEY" private const val PROJECT_ID = "PROJECT-ID" - - @BeforeClass - @JvmStatic - fun setUp() { - Firebase.initialize( - ApplicationProvider.getApplicationContext(), - FirebaseOptions.Builder() - .setApplicationId(APP_ID) - .setApiKey(API_KEY) - .setProjectId(PROJECT_ID) - .build(), - ) - } } } diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt index 918782ef140..5680c9cc0ec 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt @@ -18,39 +18,37 @@ package com.google.firebase.sessions import android.content.Context import android.util.Log -import androidx.datastore.core.DataMigration import androidx.datastore.core.DataStore -import androidx.datastore.core.DataStoreFactory -import androidx.datastore.core.MultiProcessDataStoreFactory -import androidx.datastore.core.Serializer import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler -import androidx.datastore.dataStoreFile +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.emptyPreferences +import androidx.datastore.preferences.preferencesDataStoreFile import com.google.android.datatransport.TransportFactory import com.google.firebase.FirebaseApp import com.google.firebase.annotations.concurrent.Background import com.google.firebase.annotations.concurrent.Blocking import com.google.firebase.inject.Provider import com.google.firebase.installations.FirebaseInstallationsApi +import com.google.firebase.sessions.ProcessDetailsProvider.getProcessName import com.google.firebase.sessions.settings.CrashlyticsSettingsFetcher import com.google.firebase.sessions.settings.LocalOverrideSettings import com.google.firebase.sessions.settings.RemoteSettings import com.google.firebase.sessions.settings.RemoteSettingsFetcher -import com.google.firebase.sessions.settings.SessionConfigs -import com.google.firebase.sessions.settings.SessionConfigsSerializer import com.google.firebase.sessions.settings.SessionsSettings -import com.google.firebase.sessions.settings.SettingsCache -import com.google.firebase.sessions.settings.SettingsCacheImpl import com.google.firebase.sessions.settings.SettingsProvider import dagger.Binds import dagger.BindsInstance import dagger.Component import dagger.Module import dagger.Provides -import java.io.File import javax.inject.Qualifier import javax.inject.Singleton import kotlin.coroutines.CoroutineContext -import kotlinx.coroutines.CoroutineScope + +@Qualifier internal annotation class SessionConfigsDataStore + +@Qualifier internal annotation class SessionDetailsDataStore @Qualifier internal annotation class LocalOverrideSettingsProvider @@ -121,8 +119,6 @@ internal interface FirebaseSessionsComponent { @RemoteSettingsProvider fun remoteSettings(impl: RemoteSettings): SettingsProvider - @Binds @Singleton fun settingsCache(impl: SettingsCacheImpl): SettingsCache - companion object { private const val TAG = "FirebaseSessions" @@ -137,67 +133,30 @@ internal interface FirebaseSessionsComponent { @Provides @Singleton - fun sessionConfigsDataStore( - appContext: Context, - @Blocking blockingDispatcher: CoroutineContext, - ): DataStore = - createDataStore( - serializer = SessionConfigsSerializer, + @SessionConfigsDataStore + fun sessionConfigsDataStore(appContext: Context): DataStore = + PreferenceDataStoreFactory.create( corruptionHandler = ReplaceFileCorruptionHandler { ex -> - Log.w(TAG, "CorruptionException in session configs DataStore", ex) - SessionConfigsSerializer.defaultValue - }, - scope = CoroutineScope(blockingDispatcher), - produceFile = { appContext.dataStoreFile("aqs/sessionConfigsDataStore.data") }, - ) + Log.w(TAG, "CorruptionException in settings DataStore in ${getProcessName()}.", ex) + emptyPreferences() + } + ) { + appContext.preferencesDataStoreFile(SessionDataStoreConfigs.SETTINGS_CONFIG_NAME) + } @Provides @Singleton - fun sessionDataStore( - appContext: Context, - @Blocking blockingDispatcher: CoroutineContext, - ): DataStore = - createDataStore( - serializer = SessionDataSerializer, + @SessionDetailsDataStore + fun sessionDetailsDataStore(appContext: Context): DataStore = + PreferenceDataStoreFactory.create( corruptionHandler = ReplaceFileCorruptionHandler { ex -> - Log.w(TAG, "CorruptionException in session data DataStore", ex) - SessionDataSerializer.defaultValue - }, - scope = CoroutineScope(blockingDispatcher), - produceFile = { appContext.dataStoreFile("aqs/sessionDataStore.data") }, - ) - - private fun createDataStore( - serializer: Serializer, - corruptionHandler: ReplaceFileCorruptionHandler, - migrations: List> = listOf(), - scope: CoroutineScope, - produceFile: () -> File, - ): DataStore = - if (loadDataStoreSharedCounter()) { - MultiProcessDataStoreFactory.create( - serializer, - corruptionHandler, - migrations, - scope, - produceFile, - ) - } else { - DataStoreFactory.create(serializer, corruptionHandler, migrations, scope, produceFile) - } - - /** This native library in unavailable in some conditions, for example, Robolectric tests */ - // TODO(mrober): Remove this when b/392626815 is resolved - private fun loadDataStoreSharedCounter(): Boolean = - try { - System.loadLibrary("datastore_shared_counter") - true - } catch (_: UnsatisfiedLinkError) { - false - } catch (_: SecurityException) { - false + Log.w(TAG, "CorruptionException in sessions DataStore in ${getProcessName()}.", ex) + emptyPreferences() + } + ) { + appContext.preferencesDataStoreFile(SessionDataStoreConfigs.SESSIONS_CONFIG_NAME) } } } diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsRegistrar.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsRegistrar.kt index 76c0c6330f4..5cb8de7a182 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsRegistrar.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsRegistrar.kt @@ -19,7 +19,7 @@ package com.google.firebase.sessions import android.content.Context import android.util.Log import androidx.annotation.Keep -import androidx.datastore.core.MultiProcessDataStoreFactory +import androidx.datastore.preferences.preferencesDataStore import com.google.android.datatransport.TransportFactory import com.google.firebase.FirebaseApp import com.google.firebase.annotations.concurrent.Background @@ -84,7 +84,7 @@ internal class FirebaseSessionsRegistrar : ComponentRegistrar { init { try { - MultiProcessDataStoreFactory.javaClass + ::preferencesDataStore.javaClass } catch (ex: NoClassDefFoundError) { Log.w( TAG, diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDataStoreConfigs.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDataStoreConfigs.kt new file mode 100644 index 00000000000..109e980e666 --- /dev/null +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDataStoreConfigs.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2023 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.util.Base64 +import com.google.firebase.sessions.settings.SessionsSettings + +/** + * Util object for handling DataStore configs in multi-process apps safely. + * + * This can be removed when datastore-preferences:1.1.0 becomes stable. + */ +internal object SessionDataStoreConfigs { + /** Sanitized process name to use in config filenames. */ + private val PROCESS_NAME = + Base64.encodeToString( + ProcessDetailsProvider.getProcessName().encodeToByteArray(), + Base64.NO_WRAP or Base64.URL_SAFE, // URL safe is also filename safe. + ) + + /** Config name for [SessionDatastore] */ + val SESSIONS_CONFIG_NAME = "firebase_session_${PROCESS_NAME}_data" + + /** Config name for [SessionsSettings] */ + val SETTINGS_CONFIG_NAME = "firebase_session_${PROCESS_NAME}_settings" +} diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDatastore.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDatastore.kt index b3b72b4d4d7..2c4f243f942 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDatastore.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionDatastore.kt @@ -17,15 +17,15 @@ package com.google.firebase.sessions import android.util.Log -import androidx.datastore.core.CorruptionException import androidx.datastore.core.DataStore -import androidx.datastore.core.Serializer +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.emptyPreferences +import androidx.datastore.preferences.core.stringPreferencesKey import com.google.firebase.Firebase import com.google.firebase.annotations.concurrent.Background import com.google.firebase.app import java.io.IOException -import java.io.InputStream -import java.io.OutputStream import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject import javax.inject.Singleton @@ -33,29 +33,11 @@ import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -/** Data for sessions information */ -@Serializable internal data class SessionData(val sessionId: String?) - -/** DataStore json [Serializer] for [SessionData]. */ -internal object SessionDataSerializer : Serializer { - override val defaultValue = SessionData(sessionId = null) - - override suspend fun readFrom(input: InputStream): SessionData = - try { - Json.decodeFromString(input.readBytes().decodeToString()) - } catch (ex: Exception) { - throw CorruptionException("Cannot parse session data", ex) - } - - override suspend fun writeTo(t: SessionData, output: OutputStream) { - @Suppress("BlockingMethodInNonBlockingContext") // blockingDispatcher is safe for blocking calls - output.write(Json.encodeToString(SessionData.serializer(), t).encodeToByteArray()) - } -} +/** Datastore for sessions information */ +internal data class FirebaseSessionsData(val sessionId: String?) /** Handles reading to and writing from the [DataStore]. */ internal interface SessionDatastore { @@ -79,17 +61,23 @@ internal class SessionDatastoreImpl @Inject constructor( @Background private val backgroundDispatcher: CoroutineContext, - private val sessionDataStore: DataStore, + @SessionDetailsDataStore private val dataStore: DataStore, ) : SessionDatastore { /** Most recent session from datastore is updated asynchronously whenever it changes */ - private val currentSessionFromDatastore = AtomicReference() + private val currentSessionFromDatastore = AtomicReference() - private val firebaseSessionDataFlow: Flow = - sessionDataStore.data.catch { ex -> - Log.e(TAG, "Error reading stored session data.", ex) - emit(SessionDataSerializer.defaultValue) - } + private object FirebaseSessionDataKeys { + val SESSION_ID = stringPreferencesKey("session_id") + } + + private val firebaseSessionDataFlow: Flow = + dataStore.data + .catch { exception -> + Log.e(TAG, "Error reading stored session data.", exception) + emit(emptyPreferences()) + } + .map { preferences -> mapSessionsData(preferences) } init { CoroutineScope(backgroundDispatcher).launch { @@ -100,14 +88,19 @@ constructor( override fun updateSessionId(sessionId: String) { CoroutineScope(backgroundDispatcher).launch { try { - sessionDataStore.updateData { SessionData(sessionId) } - } catch (ex: IOException) { - Log.w(TAG, "Failed to update session Id", ex) + dataStore.edit { preferences -> + preferences[FirebaseSessionDataKeys.SESSION_ID] = sessionId + } + } catch (e: IOException) { + Log.w(TAG, "Failed to update session Id: $e") } } } - override fun getCurrentSessionId(): String? = currentSessionFromDatastore.get()?.sessionId + override fun getCurrentSessionId() = currentSessionFromDatastore.get()?.sessionId + + private fun mapSessionsData(preferences: Preferences): FirebaseSessionsData = + FirebaseSessionsData(preferences[FirebaseSessionDataKeys.SESSION_ID]) private companion object { private const val TAG = "FirebaseSessionsRepo" diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt index 6a540fd0104..25b3cbeb15d 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt @@ -63,6 +63,7 @@ internal object SessionEvents { fun getApplicationInfo(firebaseApp: FirebaseApp): ApplicationInfo { val context = firebaseApp.applicationContext val packageName = context.packageName + @Suppress("DEPRECATION") // TODO(mrober): Use ApplicationInfoFlags when target sdk set to 33 val packageInfo = context.packageManager.getPackageInfo(packageName, 0) val buildVersion = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionGenerator.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionGenerator.kt index 409f9989348..4c4775e8b24 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionGenerator.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionGenerator.kt @@ -60,7 +60,7 @@ constructor(private val timeProvider: TimeProvider, private val uuidGenerator: U sessionId = if (sessionIndex == 0) firstSessionId else generateSessionId(), firstSessionId, sessionIndex, - sessionStartTimestampUs = timeProvider.currentTime().us, + sessionStartTimestampUs = timeProvider.currentTimeUs(), ) return currentSession } diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/TimeProvider.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/TimeProvider.kt index 869b64b2ff2..b66b09af19f 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/TimeProvider.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/TimeProvider.kt @@ -20,17 +20,11 @@ import android.os.SystemClock import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds -/** Time with accessors for microseconds, milliseconds, and seconds. */ -internal data class Time(val ms: Long) { - val us = ms * 1_000 - val seconds = ms / 1_000 -} - /** Time provider interface, for testing purposes. */ internal interface TimeProvider { fun elapsedRealtime(): Duration - fun currentTime(): Time + fun currentTimeUs(): Long } /** "Wall clock" time provider implementation. */ @@ -44,11 +38,14 @@ internal object TimeProviderImpl : TimeProvider { override fun elapsedRealtime(): Duration = SystemClock.elapsedRealtime().milliseconds /** - * Gets the current "wall clock" time. + * Gets the current "wall clock" time in microseconds. * * This clock can be set by the user or the phone network, so the time may jump backwards or * forwards unpredictably. This clock should only be used when correspondence with real-world * dates and times is important, such as in a calendar or alarm clock application. */ - override fun currentTime(): Time = Time(ms = System.currentTimeMillis()) + override fun currentTimeUs(): Long = System.currentTimeMillis() * US_PER_MILLIS + + /** Microseconds per millisecond. */ + private const val US_PER_MILLIS = 1000L } diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettings.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettings.kt index 1079577e03c..67a48bc7924 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettings.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettings.kt @@ -19,15 +19,18 @@ package com.google.firebase.sessions.settings import android.os.Build import android.util.Log import androidx.annotation.VisibleForTesting +import com.google.firebase.annotations.concurrent.Background import com.google.firebase.installations.FirebaseInstallationsApi import com.google.firebase.sessions.ApplicationInfo import com.google.firebase.sessions.InstallationId -import com.google.firebase.sessions.TimeProvider +import dagger.Lazy import javax.inject.Inject import javax.inject.Singleton +import kotlin.coroutines.CoroutineContext import kotlin.time.Duration -import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.json.JSONException @@ -37,12 +40,15 @@ import org.json.JSONObject internal class RemoteSettings @Inject constructor( - private val timeProvider: TimeProvider, + @Background private val backgroundDispatcher: CoroutineContext, private val firebaseInstallationsApi: FirebaseInstallationsApi, private val appInfo: ApplicationInfo, private val configsFetcher: CrashlyticsSettingsFetcher, - private val settingsCache: SettingsCache, + private val lazySettingsCache: Lazy, ) : SettingsProvider { + private val settingsCache: SettingsCache + get() = lazySettingsCache.get() + private val fetchInProgress = Mutex() override val sessionEnabled: Boolean? @@ -84,9 +90,10 @@ constructor( val options = mapOf( "X-Crashlytics-Installation-ID" to installationId, - "X-Crashlytics-Device-Model" to sanitize("${Build.MANUFACTURER}${Build.MODEL}"), - "X-Crashlytics-OS-Build-Version" to sanitize(Build.VERSION.INCREMENTAL), - "X-Crashlytics-OS-Display-Version" to sanitize(Build.VERSION.RELEASE), + "X-Crashlytics-Device-Model" to + removeForwardSlashesIn(String.format("%s/%s", Build.MANUFACTURER, Build.MODEL)), + "X-Crashlytics-OS-Build-Version" to removeForwardSlashesIn(Build.VERSION.INCREMENTAL), + "X-Crashlytics-OS-Display-Version" to removeForwardSlashesIn(Build.VERSION.RELEASE), "X-Crashlytics-API-Client-Version" to appInfo.sessionSdkVersion, ) @@ -122,19 +129,22 @@ constructor( } } - settingsCache.updateConfigs( - SessionConfigs( - sessionsEnabled = sessionsEnabled, - sessionTimeoutSeconds = sessionTimeoutSeconds, - sessionSamplingRate = sessionSamplingRate, - cacheDurationSeconds = cacheDuration ?: defaultCacheDuration, - cacheUpdatedTimeSeconds = timeProvider.currentTime().seconds, - ) - ) + sessionsEnabled?.let { settingsCache.updateSettingsEnabled(sessionsEnabled) } + + sessionTimeoutSeconds?.let { + settingsCache.updateSessionRestartTimeout(sessionTimeoutSeconds) + } + + sessionSamplingRate?.let { settingsCache.updateSamplingRate(sessionSamplingRate) } + + cacheDuration?.let { settingsCache.updateSessionCacheDuration(cacheDuration) } + ?: let { settingsCache.updateSessionCacheDuration(86400) } + + settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) }, onFailure = { msg -> // Network request failed here. - Log.e(TAG, "Error failed to fetch the remote configs: $msg") + Log.e(TAG, "Error failing to fetch the remote configs: $msg") }, ) } @@ -143,17 +153,18 @@ constructor( override fun isSettingsStale(): Boolean = settingsCache.hasCacheExpired() @VisibleForTesting - internal suspend fun clearCachedSettings() { - settingsCache.updateConfigs(SessionConfigsSerializer.defaultValue) + internal fun clearCachedSettings() { + val scope = CoroutineScope(backgroundDispatcher) + scope.launch { settingsCache.removeConfigs() } } - private fun sanitize(s: String) = s.replace(sanitizeRegex, "") + private fun removeForwardSlashesIn(s: String): String { + return s.replace(FORWARD_SLASH_STRING.toRegex(), "") + } private companion object { const val TAG = "SessionConfigFetcher" - val defaultCacheDuration = 24.hours.inWholeSeconds.toInt() - - val sanitizeRegex = "/".toRegex() + const val FORWARD_SLASH_STRING: String = "/" } } diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettingsFetcher.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettingsFetcher.kt index bd45ec8fb24..92d530f2fa1 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettingsFetcher.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettingsFetcher.kt @@ -17,7 +17,7 @@ package com.google.firebase.sessions.settings import android.net.Uri -import com.google.firebase.annotations.concurrent.Blocking +import com.google.firebase.annotations.concurrent.Background import com.google.firebase.sessions.ApplicationInfo import java.io.BufferedReader import java.io.InputStreamReader @@ -42,7 +42,7 @@ internal class RemoteSettingsFetcher @Inject constructor( private val appInfo: ApplicationInfo, - @Blocking private val blockingDispatcher: CoroutineContext, + @Background private val blockingDispatcher: CoroutineContext, ) : CrashlyticsSettingsFetcher { @Suppress("BlockingMethodInNonBlockingContext") // blockingDispatcher is safe for blocking calls. override suspend fun doConfigFetch( diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SettingsCache.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SettingsCache.kt index 1640a5c7b7a..2e60e51650a 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SettingsCache.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SettingsCache.kt @@ -19,96 +19,126 @@ package com.google.firebase.sessions.settings import android.util.Log import androidx.annotation.VisibleForTesting import androidx.datastore.core.DataStore -import com.google.firebase.annotations.concurrent.Background -import com.google.firebase.sessions.TimeProvider +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.doublePreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.core.longPreferencesKey +import com.google.firebase.sessions.SessionConfigsDataStore import java.io.IOException -import java.util.concurrent.atomic.AtomicReference import javax.inject.Inject import javax.inject.Singleton -import kotlin.coroutines.CoroutineContext -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -internal interface SettingsCache { - fun hasCacheExpired(): Boolean - - fun sessionsEnabled(): Boolean? - - fun sessionSamplingRate(): Double? - - fun sessionRestartTimeout(): Int? - - suspend fun updateConfigs(sessionConfigs: SessionConfigs) -} +internal data class SessionConfigs( + val sessionEnabled: Boolean?, + val sessionSamplingRate: Double?, + val sessionRestartTimeout: Int?, + val cacheDuration: Int?, + val cacheUpdatedTime: Long?, +) @Singleton -internal class SettingsCacheImpl +internal class SettingsCache @Inject -constructor( - @Background private val backgroundDispatcher: CoroutineContext, - private val timeProvider: TimeProvider, - private val sessionConfigsDataStore: DataStore, -) : SettingsCache { - private val sessionConfigsAtomicReference = AtomicReference() - - private val sessionConfigs: SessionConfigs - get() { - // Ensure configs are loaded from disk before the first access - if (sessionConfigsAtomicReference.get() == null) { - // Double check to avoid the `runBlocking` unless necessary - sessionConfigsAtomicReference.compareAndSet( - null, - runBlocking { sessionConfigsDataStore.data.first() }, - ) - } - - return sessionConfigsAtomicReference.get() - } +constructor(@SessionConfigsDataStore private val dataStore: DataStore) { + private lateinit var sessionConfigs: SessionConfigs init { - CoroutineScope(backgroundDispatcher).launch { - sessionConfigsDataStore.data.collect(sessionConfigsAtomicReference::set) - } + // Block until the cache is loaded from disk to ensure cache + // values are valid and readable from the main thread on init. + runBlocking { updateSessionConfigs(dataStore.data.first().toPreferences()) } + } + + /** Update session configs from the given [preferences]. */ + private fun updateSessionConfigs(preferences: Preferences) { + sessionConfigs = + SessionConfigs( + sessionEnabled = preferences[SESSIONS_ENABLED], + sessionSamplingRate = preferences[SAMPLING_RATE], + sessionRestartTimeout = preferences[RESTART_TIMEOUT_SECONDS], + cacheDuration = preferences[CACHE_DURATION_SECONDS], + cacheUpdatedTime = preferences[CACHE_UPDATED_TIME], + ) } - override fun hasCacheExpired(): Boolean { - val cacheUpdatedTimeSeconds = sessionConfigs.cacheUpdatedTimeSeconds - val cacheDurationSeconds = sessionConfigs.cacheDurationSeconds + internal fun hasCacheExpired(): Boolean { + val cacheUpdatedTime = sessionConfigs.cacheUpdatedTime + val cacheDuration = sessionConfigs.cacheDuration - if (cacheUpdatedTimeSeconds != null && cacheDurationSeconds != null) { - val timeDifferenceSeconds = timeProvider.currentTime().seconds - cacheUpdatedTimeSeconds - if (timeDifferenceSeconds < cacheDurationSeconds) { + if (cacheUpdatedTime != null && cacheDuration != null) { + val timeDifferenceSeconds = (System.currentTimeMillis() - cacheUpdatedTime) / 1000 + if (timeDifferenceSeconds < cacheDuration) { return false } } return true } - override fun sessionsEnabled(): Boolean? = sessionConfigs.sessionsEnabled + fun sessionsEnabled(): Boolean? = sessionConfigs.sessionEnabled + + fun sessionSamplingRate(): Double? = sessionConfigs.sessionSamplingRate + + fun sessionRestartTimeout(): Int? = sessionConfigs.sessionRestartTimeout + + suspend fun updateSettingsEnabled(enabled: Boolean?) { + updateConfigValue(SESSIONS_ENABLED, enabled) + } + + suspend fun updateSamplingRate(rate: Double?) { + updateConfigValue(SAMPLING_RATE, rate) + } - override fun sessionSamplingRate(): Double? = sessionConfigs.sessionSamplingRate + suspend fun updateSessionRestartTimeout(timeoutInSeconds: Int?) { + updateConfigValue(RESTART_TIMEOUT_SECONDS, timeoutInSeconds) + } - override fun sessionRestartTimeout(): Int? = sessionConfigs.sessionTimeoutSeconds + suspend fun updateSessionCacheDuration(cacheDurationInSeconds: Int?) { + updateConfigValue(CACHE_DURATION_SECONDS, cacheDurationInSeconds) + } + + suspend fun updateSessionCacheUpdatedTime(cacheUpdatedTime: Long?) { + updateConfigValue(CACHE_UPDATED_TIME, cacheUpdatedTime) + } - override suspend fun updateConfigs(sessionConfigs: SessionConfigs) { + @VisibleForTesting + internal suspend fun removeConfigs() { try { - sessionConfigsDataStore.updateData { sessionConfigs } - } catch (ex: IOException) { - Log.w(TAG, "Failed to update config values: $ex") + dataStore.edit { preferences -> + preferences.clear() + updateSessionConfigs(preferences) + } + } catch (e: IOException) { + Log.w(TAG, "Failed to remove config values: $e") } } - @VisibleForTesting - internal suspend fun removeConfigs() = + /** Updated the config value, or remove the key if the value is null. */ + private suspend fun updateConfigValue(key: Preferences.Key, value: T?) { + // TODO(mrober): Refactor these to update all the values in one transaction. try { - sessionConfigsDataStore.updateData { SessionConfigsSerializer.defaultValue } + dataStore.edit { preferences -> + if (value != null) { + preferences[key] = value + } else { + preferences.remove(key) + } + updateSessionConfigs(preferences) + } } catch (ex: IOException) { - Log.w(TAG, "Failed to remove config values: $ex") + Log.w(TAG, "Failed to update cache config value: $ex") } + } private companion object { const val TAG = "SettingsCache" + + val SESSIONS_ENABLED = booleanPreferencesKey("firebase_sessions_enabled") + val SAMPLING_RATE = doublePreferencesKey("firebase_sessions_sampling_rate") + val RESTART_TIMEOUT_SECONDS = intPreferencesKey("firebase_sessions_restart_timeout") + val CACHE_DURATION_SECONDS = intPreferencesKey("firebase_sessions_cache_duration") + val CACHE_UPDATED_TIME = longPreferencesKey("firebase_sessions_cache_updated_time") } } diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionGeneratorTest.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionGeneratorTest.kt index bf260e73a4f..7126bae4dbf 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionGeneratorTest.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionGeneratorTest.kt @@ -16,15 +16,12 @@ package com.google.firebase.sessions -import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import com.google.firebase.sessions.testing.FakeTimeProvider import com.google.firebase.sessions.testing.FakeUuidGenerator -import com.google.firebase.sessions.testing.TestSessionEventData.TEST_SESSION_TIMESTAMP +import com.google.firebase.sessions.testing.TestSessionEventData.TEST_SESSION_TIMESTAMP_US import org.junit.Test -import org.junit.runner.RunWith -@RunWith(AndroidJUnit4::class) class SessionGeneratorTest { private fun isValidSessionId(sessionId: String): Boolean { if (sessionId.length != 32) { @@ -99,7 +96,7 @@ class SessionGeneratorTest { sessionId = SESSION_ID_1, firstSessionId = SESSION_ID_1, sessionIndex = 0, - sessionStartTimestampUs = TEST_SESSION_TIMESTAMP.us, + sessionStartTimestampUs = TEST_SESSION_TIMESTAMP_US, ) ) } @@ -122,7 +119,7 @@ class SessionGeneratorTest { sessionId = SESSION_ID_1, firstSessionId = SESSION_ID_1, sessionIndex = 0, - sessionStartTimestampUs = TEST_SESSION_TIMESTAMP.us, + sessionStartTimestampUs = TEST_SESSION_TIMESTAMP_US, ) ) @@ -138,7 +135,7 @@ class SessionGeneratorTest { sessionId = SESSION_ID_2, firstSessionId = SESSION_ID_1, sessionIndex = 1, - sessionStartTimestampUs = TEST_SESSION_TIMESTAMP.us, + sessionStartTimestampUs = TEST_SESSION_TIMESTAMP_US, ) ) @@ -154,7 +151,7 @@ class SessionGeneratorTest { sessionId = SESSION_ID_3, firstSessionId = SESSION_ID_1, sessionIndex = 2, - sessionStartTimestampUs = TEST_SESSION_TIMESTAMP.us, + sessionStartTimestampUs = TEST_SESSION_TIMESTAMP_US, ) ) } diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/RemoteSettingsTest.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/RemoteSettingsTest.kt index 74df328ae57..e4fb0b00148 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/RemoteSettingsTest.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/RemoteSettingsTest.kt @@ -16,20 +16,28 @@ package com.google.firebase.sessions.settings +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import androidx.datastore.preferences.preferencesDataStoreFile import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import com.google.firebase.FirebaseApp +import com.google.firebase.concurrent.TestOnlyExecutors +import com.google.firebase.installations.FirebaseInstallationsApi +import com.google.firebase.sessions.ApplicationInfo import com.google.firebase.sessions.SessionEvents import com.google.firebase.sessions.testing.FakeFirebaseApp import com.google.firebase.sessions.testing.FakeFirebaseInstallations import com.google.firebase.sessions.testing.FakeRemoteConfigFetcher -import com.google.firebase.sessions.testing.FakeSettingsCache -import com.google.firebase.sessions.testing.FakeTimeProvider +import kotlin.coroutines.CoroutineContext import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withTimeout import org.json.JSONObject @@ -37,204 +45,261 @@ import org.junit.After import org.junit.Test import org.junit.runner.RunWith +@OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) class RemoteSettingsTest { @Test - fun remoteSettings_successfulFetchCachesValues() = runTest { - val firebaseApp = FakeFirebaseApp().firebaseApp - val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") - val fakeFetcher = FakeRemoteConfigFetcher() - - val remoteSettings = - RemoteSettings( - FakeTimeProvider(), - firebaseInstallations, - SessionEvents.getApplicationInfo(firebaseApp), - fakeFetcher, - FakeSettingsCache(), - ) - - assertThat(remoteSettings.sessionEnabled).isNull() - assertThat(remoteSettings.samplingRate).isNull() - assertThat(remoteSettings.sessionRestartTimeout).isNull() - - fakeFetcher.responseJSONObject = JSONObject(VALID_RESPONSE) - remoteSettings.updateSettings() - - assertThat(remoteSettings.sessionEnabled).isFalse() - assertThat(remoteSettings.samplingRate).isEqualTo(0.75) - assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) - - remoteSettings.clearCachedSettings() - } + fun remoteSettings_successfulFetchCachesValues() = + runTest(UnconfinedTestDispatcher()) { + val firebaseApp = FakeFirebaseApp().firebaseApp + val context = firebaseApp.applicationContext + val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") + val fakeFetcher = FakeRemoteConfigFetcher() + + val remoteSettings = + buildRemoteSettings( + TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext, + firebaseInstallations, + SessionEvents.getApplicationInfo(firebaseApp), + fakeFetcher, + SettingsCache( + PreferenceDataStoreFactory.create( + scope = this, + produceFile = { context.preferencesDataStoreFile(SESSION_TEST_CONFIGS_NAME) }, + ) + ), + ) + + runCurrent() + + assertThat(remoteSettings.sessionEnabled).isNull() + assertThat(remoteSettings.samplingRate).isNull() + assertThat(remoteSettings.sessionRestartTimeout).isNull() + + fakeFetcher.responseJSONObject = JSONObject(VALID_RESPONSE) + remoteSettings.updateSettings() + + runCurrent() + + assertThat(remoteSettings.sessionEnabled).isFalse() + assertThat(remoteSettings.samplingRate).isEqualTo(0.75) + assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) + + remoteSettings.clearCachedSettings() + } @Test - fun remoteSettings_successfulFetchWithLessConfigsCachesOnlyReceivedValues() = runTest { - val firebaseApp = FakeFirebaseApp().firebaseApp - val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") - val fakeFetcher = FakeRemoteConfigFetcher() - - val remoteSettings = - RemoteSettings( - FakeTimeProvider(), - firebaseInstallations, - SessionEvents.getApplicationInfo(firebaseApp), - fakeFetcher, - FakeSettingsCache(), - ) - - assertThat(remoteSettings.sessionEnabled).isNull() - assertThat(remoteSettings.samplingRate).isNull() - assertThat(remoteSettings.sessionRestartTimeout).isNull() - - val fetchedResponse = JSONObject(VALID_RESPONSE) - fetchedResponse.getJSONObject("app_quality").remove("sessions_enabled") - fakeFetcher.responseJSONObject = fetchedResponse - remoteSettings.updateSettings() - - assertThat(remoteSettings.sessionEnabled).isNull() - assertThat(remoteSettings.samplingRate).isEqualTo(0.75) - assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) - - remoteSettings.clearCachedSettings() - } + fun remoteSettings_successfulFetchWithLessConfigsCachesOnlyReceivedValues() = + runTest(UnconfinedTestDispatcher()) { + val firebaseApp = FakeFirebaseApp().firebaseApp + val context = firebaseApp.applicationContext + val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") + val fakeFetcher = FakeRemoteConfigFetcher() + + val remoteSettings = + buildRemoteSettings( + TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext, + firebaseInstallations, + SessionEvents.getApplicationInfo(firebaseApp), + fakeFetcher, + SettingsCache( + PreferenceDataStoreFactory.create( + scope = this, + produceFile = { context.preferencesDataStoreFile(SESSION_TEST_CONFIGS_NAME) }, + ) + ), + ) + + runCurrent() + + assertThat(remoteSettings.sessionEnabled).isNull() + assertThat(remoteSettings.samplingRate).isNull() + assertThat(remoteSettings.sessionRestartTimeout).isNull() + + val fetchedResponse = JSONObject(VALID_RESPONSE) + fetchedResponse.getJSONObject("app_quality").remove("sessions_enabled") + fakeFetcher.responseJSONObject = fetchedResponse + remoteSettings.updateSettings() + + runCurrent() + + assertThat(remoteSettings.sessionEnabled).isNull() + assertThat(remoteSettings.samplingRate).isEqualTo(0.75) + assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) + + remoteSettings.clearCachedSettings() + } @Test - fun remoteSettings_successfulReFetchUpdatesCache() = runTest { - val firebaseApp = FakeFirebaseApp().firebaseApp - val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") - val fakeFetcher = FakeRemoteConfigFetcher() - val fakeTimeProvider = FakeTimeProvider() - - val remoteSettings = - RemoteSettings( - fakeTimeProvider, - firebaseInstallations, - SessionEvents.getApplicationInfo(firebaseApp), - fakeFetcher, - FakeSettingsCache(fakeTimeProvider), - ) - - val fetchedResponse = JSONObject(VALID_RESPONSE) - fetchedResponse.getJSONObject("app_quality").put("cache_duration", 1) - fakeFetcher.responseJSONObject = fetchedResponse - remoteSettings.updateSettings() - - assertThat(remoteSettings.sessionEnabled).isFalse() - assertThat(remoteSettings.samplingRate).isEqualTo(0.75) - assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) - - fetchedResponse.getJSONObject("app_quality").put("sessions_enabled", true) - fetchedResponse.getJSONObject("app_quality").put("sampling_rate", 0.25) - fetchedResponse.getJSONObject("app_quality").put("session_timeout_seconds", 1200) - - fakeTimeProvider.addInterval(31.minutes) - - fakeFetcher.responseJSONObject = fetchedResponse - remoteSettings.updateSettings() - - assertThat(remoteSettings.sessionEnabled).isTrue() - assertThat(remoteSettings.samplingRate).isEqualTo(0.25) - assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(20.minutes) - - remoteSettings.clearCachedSettings() - } + fun remoteSettings_successfulReFetchUpdatesCache() = + runTest(UnconfinedTestDispatcher()) { + val firebaseApp = FakeFirebaseApp().firebaseApp + val context = firebaseApp.applicationContext + val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") + val fakeFetcher = FakeRemoteConfigFetcher() + + val remoteSettings = + buildRemoteSettings( + TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext, + firebaseInstallations, + SessionEvents.getApplicationInfo(firebaseApp), + fakeFetcher, + SettingsCache( + PreferenceDataStoreFactory.create( + scope = this, + produceFile = { context.preferencesDataStoreFile(SESSION_TEST_CONFIGS_NAME) }, + ) + ), + ) + + val fetchedResponse = JSONObject(VALID_RESPONSE) + fetchedResponse.getJSONObject("app_quality").put("cache_duration", 1) + fakeFetcher.responseJSONObject = fetchedResponse + remoteSettings.updateSettings() + + runCurrent() + + assertThat(remoteSettings.sessionEnabled).isFalse() + assertThat(remoteSettings.samplingRate).isEqualTo(0.75) + assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) + + fetchedResponse.getJSONObject("app_quality").put("sessions_enabled", true) + fetchedResponse.getJSONObject("app_quality").put("sampling_rate", 0.25) + fetchedResponse.getJSONObject("app_quality").put("session_timeout_seconds", 1200) + + // TODO(mrober): Fix these so we don't need to sleep. Maybe use FakeTime? + // Sleep for a second before updating configs + Thread.sleep(2000) + + fakeFetcher.responseJSONObject = fetchedResponse + remoteSettings.updateSettings() + + runCurrent() + + assertThat(remoteSettings.sessionEnabled).isTrue() + assertThat(remoteSettings.samplingRate).isEqualTo(0.25) + assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(20.minutes) + + remoteSettings.clearCachedSettings() + } @Test - fun remoteSettings_successfulFetchWithEmptyConfigRetainsOldConfigs() = runTest { - val firebaseApp = FakeFirebaseApp().firebaseApp - val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") - val fakeFetcher = FakeRemoteConfigFetcher() - val fakeTimeProvider = FakeTimeProvider() - - val remoteSettings = - RemoteSettings( - fakeTimeProvider, - firebaseInstallations, - SessionEvents.getApplicationInfo(firebaseApp), - fakeFetcher, - FakeSettingsCache(), - ) - - val fetchedResponse = JSONObject(VALID_RESPONSE) - fetchedResponse.getJSONObject("app_quality").put("cache_duration", 1) - fakeFetcher.responseJSONObject = fetchedResponse - remoteSettings.updateSettings() - - fakeTimeProvider.addInterval(31.seconds) - - assertThat(remoteSettings.sessionEnabled).isFalse() - assertThat(remoteSettings.samplingRate).isEqualTo(0.75) - assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) - - fetchedResponse.remove("app_quality") - - fakeFetcher.responseJSONObject = fetchedResponse - remoteSettings.updateSettings() - - assertThat(remoteSettings.sessionEnabled).isFalse() - assertThat(remoteSettings.samplingRate).isEqualTo(0.75) - assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) - - remoteSettings.clearCachedSettings() - } + fun remoteSettings_successfulFetchWithEmptyConfigRetainsOldConfigs() = + runTest(UnconfinedTestDispatcher()) { + val firebaseApp = FakeFirebaseApp().firebaseApp + val context = firebaseApp.applicationContext + val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") + val fakeFetcher = FakeRemoteConfigFetcher() + + val remoteSettings = + buildRemoteSettings( + TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext, + firebaseInstallations, + SessionEvents.getApplicationInfo(firebaseApp), + fakeFetcher, + SettingsCache( + PreferenceDataStoreFactory.create( + scope = this, + produceFile = { context.preferencesDataStoreFile(SESSION_TEST_CONFIGS_NAME) }, + ) + ), + ) + + val fetchedResponse = JSONObject(VALID_RESPONSE) + fetchedResponse.getJSONObject("app_quality").put("cache_duration", 1) + fakeFetcher.responseJSONObject = fetchedResponse + remoteSettings.updateSettings() + + runCurrent() + + assertThat(remoteSettings.sessionEnabled).isFalse() + assertThat(remoteSettings.samplingRate).isEqualTo(0.75) + assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) + + fetchedResponse.remove("app_quality") + + // Sleep for a second before updating configs + Thread.sleep(2000) + + fakeFetcher.responseJSONObject = fetchedResponse + remoteSettings.updateSettings() + + runCurrent() + + assertThat(remoteSettings.sessionEnabled).isFalse() + assertThat(remoteSettings.samplingRate).isEqualTo(0.75) + assertThat(remoteSettings.sessionRestartTimeout).isEqualTo(40.minutes) + + remoteSettings.clearCachedSettings() + } @Test - fun remoteSettings_fetchWhileFetchInProgress() = runTest { - // This test does: - // 1. Do a fetch with a fake fetcher that will block for 3 seconds. - // 2. While that is happening, do a second fetch. - // - First fetch is still fetching, so second fetch should fall through to the mutex. - // - Second fetch will be blocked until first completes. - // - First fetch returns, should unblock the second fetch. - // - Second fetch should go into mutex, sees cache is valid in "double check," exist early. - // 3. After a fetch completes, do a third fetch. - // - First fetch should have have updated the cache. - // - Third fetch should exit even earlier, never having gone into the mutex. - - val firebaseApp = FakeFirebaseApp().firebaseApp - val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") - val fakeFetcherWithDelay = - FakeRemoteConfigFetcher(JSONObject(VALID_RESPONSE), networkDelay = 3.seconds) - - fakeFetcherWithDelay.responseJSONObject.getJSONObject("app_quality").put("sampling_rate", 0.125) - - val remoteSettingsWithDelay = - RemoteSettings( - FakeTimeProvider(), - firebaseInstallations, - SessionEvents.getApplicationInfo(firebaseApp), - configsFetcher = fakeFetcherWithDelay, - FakeSettingsCache(), - ) - - // Do the first fetch. This one should fetched the configsFetcher. - val firstFetch = launch(Dispatchers.Default) { remoteSettingsWithDelay.updateSettings() } - - // Wait a second, and then do the second fetch while first is still running. - // This one should block until the first fetch completes, but then exit early. - launch(Dispatchers.Default) { - delay(1.seconds) - remoteSettingsWithDelay.updateSettings() - } + fun remoteSettings_fetchWhileFetchInProgress() = + runTest(UnconfinedTestDispatcher()) { + // This test does: + // 1. Do a fetch with a fake fetcher that will block for 3 seconds. + // 2. While that is happening, do a second fetch. + // - First fetch is still fetching, so second fetch should fall through to the mutex. + // - Second fetch will be blocked until first completes. + // - First fetch returns, should unblock the second fetch. + // - Second fetch should go into mutex, sees cache is valid in "double check," exist early. + // 3. After a fetch completes, do a third fetch. + // - First fetch should have have updated the cache. + // - Third fetch should exit even earlier, never having gone into the mutex. + + val firebaseApp = FakeFirebaseApp().firebaseApp + val context = firebaseApp.applicationContext + val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") + val fakeFetcherWithDelay = + FakeRemoteConfigFetcher(JSONObject(VALID_RESPONSE), networkDelay = 3.seconds) + + fakeFetcherWithDelay.responseJSONObject + .getJSONObject("app_quality") + .put("sampling_rate", 0.125) + + val remoteSettingsWithDelay = + buildRemoteSettings( + TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext, + firebaseInstallations, + SessionEvents.getApplicationInfo(firebaseApp), + fakeFetcherWithDelay, + SettingsCache( + PreferenceDataStoreFactory.create( + scope = this, + produceFile = { context.preferencesDataStoreFile(SESSION_TEST_CONFIGS_NAME) }, + ) + ), + ) + + // Do the first fetch. This one should fetched the configsFetcher. + val firstFetch = launch(Dispatchers.Default) { remoteSettingsWithDelay.updateSettings() } + + // Wait a second, and then do the second fetch while first is still running. + // This one should block until the first fetch completes, but then exit early. + launch(Dispatchers.Default) { + delay(1.seconds) + remoteSettingsWithDelay.updateSettings() + } - // Wait until the first fetch is done, then do a third fetch. - // This one should not even block, and exit early. - firstFetch.join() - withTimeout(1.seconds) { remoteSettingsWithDelay.updateSettings() } + // Wait until the first fetch is done, then do a third fetch. + // This one should not even block, and exit early. + firstFetch.join() + withTimeout(1.seconds) { remoteSettingsWithDelay.updateSettings() } - // Assert that the configsFetcher was fetched exactly once. - assertThat(fakeFetcherWithDelay.timesCalled).isEqualTo(1) - assertThat(remoteSettingsWithDelay.samplingRate).isEqualTo(0.125) - } + // Assert that the configsFetcher was fetched exactly once. + assertThat(fakeFetcherWithDelay.timesCalled).isEqualTo(1) + assertThat(remoteSettingsWithDelay.samplingRate).isEqualTo(0.125) + } @After fun cleanUp() { FirebaseApp.clearInstancesForTest() } - private companion object { + internal companion object { + const val SESSION_TEST_CONFIGS_NAME = "firebase_session_settings_test" + const val VALID_RESPONSE = """ { @@ -253,5 +318,30 @@ class RemoteSettingsTest { } } """ + + /** + * Build an instance of [RemoteSettings] using the Dagger factory. + * + * This is needed because the SDK vendors Dagger to a difference namespace, but it does not for + * these unit tests. The [RemoteSettings.lazySettingsCache] has type [dagger.Lazy] in these + * tests, but type `com.google.firebase.sessions.dagger.Lazy` in the SDK. This method to build + * the instance is the easiest I could find that does not need any reference to [dagger.Lazy] in + * the test code. + */ + fun buildRemoteSettings( + backgroundDispatcher: CoroutineContext, + firebaseInstallationsApi: FirebaseInstallationsApi, + appInfo: ApplicationInfo, + configsFetcher: CrashlyticsSettingsFetcher, + settingsCache: SettingsCache, + ): RemoteSettings = + RemoteSettings_Factory.create( + { backgroundDispatcher }, + { firebaseInstallationsApi }, + { appInfo }, + { configsFetcher }, + { settingsCache }, + ) + .get() } } diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SessionsSettingsTest.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SessionsSettingsTest.kt index 146857ae7f4..12f40e7cca8 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SessionsSettingsTest.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SessionsSettingsTest.kt @@ -17,16 +17,22 @@ package com.google.firebase.sessions.settings import android.os.Bundle +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import androidx.datastore.preferences.preferencesDataStoreFile import com.google.common.truth.Truth.assertThat import com.google.firebase.FirebaseApp +import com.google.firebase.concurrent.TestOnlyExecutors +import com.google.firebase.sessions.SessionDataStoreConfigs import com.google.firebase.sessions.SessionEvents import com.google.firebase.sessions.testing.FakeFirebaseApp import com.google.firebase.sessions.testing.FakeFirebaseInstallations import com.google.firebase.sessions.testing.FakeRemoteConfigFetcher -import com.google.firebase.sessions.testing.FakeSettingsCache import com.google.firebase.sessions.testing.FakeSettingsProvider -import com.google.firebase.sessions.testing.FakeTimeProvider import kotlin.time.Duration.Companion.minutes +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.json.JSONObject import org.junit.After @@ -34,6 +40,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner +@OptIn(ExperimentalCoroutinesApi::class) @RunWith(RobolectricTestRunner::class) class SessionsSettingsTest { @@ -84,116 +91,147 @@ class SessionsSettingsTest { remoteSettings = FakeSettingsProvider(), ) + runCurrent() + assertThat(sessionsSettings.sessionsEnabled).isFalse() assertThat(sessionsSettings.samplingRate).isEqualTo(0.5) assertThat(sessionsSettings.sessionRestartTimeout).isEqualTo(30.minutes) } @Test - fun sessionSettings_remoteSettingsOverrideDefaultsWhenPresent() = runTest { - val firebaseApp = FakeFirebaseApp().firebaseApp - val context = firebaseApp.applicationContext - val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") - val fakeFetcher = FakeRemoteConfigFetcher(JSONObject(VALID_RESPONSE)) - - val remoteSettings = - RemoteSettings( - FakeTimeProvider(), - firebaseInstallations, - SessionEvents.getApplicationInfo(firebaseApp), - fakeFetcher, - FakeSettingsCache(), - ) + fun sessionSettings_remoteSettingsOverrideDefaultsWhenPresent() = + runTest(UnconfinedTestDispatcher()) { + val firebaseApp = FakeFirebaseApp().firebaseApp + val context = firebaseApp.applicationContext + val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") + val fakeFetcher = FakeRemoteConfigFetcher(JSONObject(VALID_RESPONSE)) - val sessionsSettings = - SessionsSettings( - localOverrideSettings = LocalOverrideSettings(context), - remoteSettings = remoteSettings, - ) + val remoteSettings = + RemoteSettingsTest.buildRemoteSettings( + TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext, + firebaseInstallations, + SessionEvents.getApplicationInfo(firebaseApp), + fakeFetcher, + SettingsCache( + PreferenceDataStoreFactory.create( + scope = this, + produceFile = { context.preferencesDataStoreFile(SESSION_TEST_CONFIGS_NAME) }, + ) + ), + ) - sessionsSettings.updateSettings() + val sessionsSettings = + SessionsSettings( + localOverrideSettings = LocalOverrideSettings(context), + remoteSettings = remoteSettings, + ) - assertThat(sessionsSettings.sessionsEnabled).isFalse() - assertThat(sessionsSettings.samplingRate).isEqualTo(0.75) - assertThat(sessionsSettings.sessionRestartTimeout).isEqualTo(40.minutes) + sessionsSettings.updateSettings() - remoteSettings.clearCachedSettings() - } + runCurrent() + + assertThat(sessionsSettings.sessionsEnabled).isFalse() + assertThat(sessionsSettings.samplingRate).isEqualTo(0.75) + assertThat(sessionsSettings.sessionRestartTimeout).isEqualTo(40.minutes) + + remoteSettings.clearCachedSettings() + } @Test - fun sessionSettings_manifestOverridesRemoteSettingsAndDefaultsWhenPresent() = runTest { - val metadata = Bundle() - metadata.putBoolean("firebase_sessions_enabled", true) - metadata.putDouble("firebase_sessions_sampling_rate", 0.5) - metadata.putInt("firebase_sessions_sessions_restart_timeout", 180) - val firebaseApp = FakeFirebaseApp(metadata).firebaseApp - val context = firebaseApp.applicationContext - val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") - val fakeFetcher = FakeRemoteConfigFetcher(JSONObject(VALID_RESPONSE)) - - val remoteSettings = - RemoteSettings( - FakeTimeProvider(), - firebaseInstallations, - SessionEvents.getApplicationInfo(firebaseApp), - fakeFetcher, - FakeSettingsCache(), - ) + fun sessionSettings_manifestOverridesRemoteSettingsAndDefaultsWhenPresent() = + runTest(UnconfinedTestDispatcher()) { + val metadata = Bundle() + metadata.putBoolean("firebase_sessions_enabled", true) + metadata.putDouble("firebase_sessions_sampling_rate", 0.5) + metadata.putInt("firebase_sessions_sessions_restart_timeout", 180) + val firebaseApp = FakeFirebaseApp(metadata).firebaseApp + val context = firebaseApp.applicationContext + val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") + val fakeFetcher = FakeRemoteConfigFetcher(JSONObject(VALID_RESPONSE)) - val sessionsSettings = - SessionsSettings( - localOverrideSettings = LocalOverrideSettings(context), - remoteSettings = remoteSettings, - ) + val remoteSettings = + RemoteSettingsTest.buildRemoteSettings( + TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext, + firebaseInstallations, + SessionEvents.getApplicationInfo(firebaseApp), + fakeFetcher, + SettingsCache( + PreferenceDataStoreFactory.create( + scope = this, + produceFile = { context.preferencesDataStoreFile(SESSION_TEST_CONFIGS_NAME) }, + ) + ), + ) - sessionsSettings.updateSettings() + val sessionsSettings = + SessionsSettings( + localOverrideSettings = LocalOverrideSettings(context), + remoteSettings = remoteSettings, + ) - assertThat(sessionsSettings.sessionsEnabled).isTrue() - assertThat(sessionsSettings.samplingRate).isEqualTo(0.5) - assertThat(sessionsSettings.sessionRestartTimeout).isEqualTo(3.minutes) + sessionsSettings.updateSettings() - remoteSettings.clearCachedSettings() - } + runCurrent() + + assertThat(sessionsSettings.sessionsEnabled).isTrue() + assertThat(sessionsSettings.samplingRate).isEqualTo(0.5) + assertThat(sessionsSettings.sessionRestartTimeout).isEqualTo(3.minutes) + + remoteSettings.clearCachedSettings() + } @Test - fun sessionSettings_invalidManifestConfigsDoNotOverride() = runTest { - val metadata = Bundle() - metadata.putBoolean("firebase_sessions_enabled", false) - metadata.putDouble("firebase_sessions_sampling_rate", -0.2) // Invalid - metadata.putInt("firebase_sessions_sessions_restart_timeout", -2) // Invalid - val firebaseApp = FakeFirebaseApp(metadata).firebaseApp - val context = firebaseApp.applicationContext - val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") - val fakeFetcher = FakeRemoteConfigFetcher() - val invalidResponse = - VALID_RESPONSE.replace( - "\"sampling_rate\":0.75,", - "\"sampling_rate\":1.2,", // Invalid - ) - fakeFetcher.responseJSONObject = JSONObject(invalidResponse) - - val remoteSettings = - RemoteSettings( - FakeTimeProvider(), - firebaseInstallations, - SessionEvents.getApplicationInfo(firebaseApp), - fakeFetcher, - FakeSettingsCache(), - ) + fun sessionSettings_invalidManifestConfigsDoNotOverride() = + runTest(UnconfinedTestDispatcher()) { + val metadata = Bundle() + metadata.putBoolean("firebase_sessions_enabled", false) + metadata.putDouble("firebase_sessions_sampling_rate", -0.2) // Invalid + metadata.putInt("firebase_sessions_sessions_restart_timeout", -2) // Invalid + val firebaseApp = FakeFirebaseApp(metadata).firebaseApp + val context = firebaseApp.applicationContext + val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD") + val fakeFetcher = FakeRemoteConfigFetcher() + val invalidResponse = + VALID_RESPONSE.replace( + "\"sampling_rate\":0.75,", + "\"sampling_rate\":1.2,", // Invalid + ) + fakeFetcher.responseJSONObject = JSONObject(invalidResponse) - val sessionsSettings = - SessionsSettings( - localOverrideSettings = LocalOverrideSettings(context), - remoteSettings = remoteSettings, - ) + val remoteSettings = + RemoteSettingsTest.buildRemoteSettings( + TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext, + firebaseInstallations, + SessionEvents.getApplicationInfo(firebaseApp), + fakeFetcher, + SettingsCache( + PreferenceDataStoreFactory.create( + scope = this, + produceFile = { context.preferencesDataStoreFile(SESSION_TEST_CONFIGS_NAME) }, + ) + ), + ) - sessionsSettings.updateSettings() + val sessionsSettings = + SessionsSettings( + localOverrideSettings = LocalOverrideSettings(context), + remoteSettings = remoteSettings, + ) - assertThat(sessionsSettings.sessionsEnabled).isFalse() // Manifest - assertThat(sessionsSettings.samplingRate).isEqualTo(1.0) // SDK default - assertThat(sessionsSettings.sessionRestartTimeout).isEqualTo(40.minutes) // Remote + sessionsSettings.updateSettings() - remoteSettings.clearCachedSettings() + runCurrent() + + assertThat(sessionsSettings.sessionsEnabled).isFalse() // Manifest + assertThat(sessionsSettings.samplingRate).isEqualTo(1.0) // SDK default + assertThat(sessionsSettings.sessionRestartTimeout).isEqualTo(40.minutes) // Remote + + remoteSettings.clearCachedSettings() + } + + @Test + fun sessionSettings_dataStorePreferencesNameIsFilenameSafe() { + assertThat(SessionDataStoreConfigs.SESSIONS_CONFIG_NAME).matches("^[a-zA-Z0-9_=]+\$") } @After @@ -202,6 +240,8 @@ class SessionsSettingsTest { } private companion object { + const val SESSION_TEST_CONFIGS_NAME = "firebase_session_settings_test" + const val VALID_RESPONSE = """ { diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SettingsCacheTest.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SettingsCacheTest.kt index a8d8429b5a8..c4d35c86456 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SettingsCacheTest.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/settings/SettingsCacheTest.kt @@ -17,16 +17,13 @@ package com.google.firebase.sessions.settings import android.content.Context -import androidx.datastore.core.DataStoreFactory -import androidx.datastore.dataStoreFile -import androidx.test.core.app.ApplicationProvider +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.preferencesDataStore import com.google.common.truth.Truth.assertThat import com.google.firebase.FirebaseApp -import com.google.firebase.sessions.testing.FakeTimeProvider -import kotlinx.coroutines.CoroutineScope +import com.google.firebase.sessions.testing.FakeFirebaseApp import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Test @@ -36,24 +33,13 @@ import org.robolectric.RobolectricTestRunner @OptIn(ExperimentalCoroutinesApi::class) @RunWith(RobolectricTestRunner::class) class SettingsCacheTest { - private val appContext: Context = ApplicationProvider.getApplicationContext() + private val Context.dataStore: DataStore by + preferencesDataStore(name = SESSION_TEST_CONFIGS_NAME) @Test fun sessionCache_returnsEmptyCache() = runTest { - val fakeTimeProvider = FakeTimeProvider() - val settingsCache = - SettingsCacheImpl( - backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), - timeProvider = fakeTimeProvider, - sessionConfigsDataStore = - DataStoreFactory.create( - serializer = SessionConfigsSerializer, - scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")), - produceFile = { appContext.dataStoreFile("sessionConfigsDataStore.data") }, - ), - ) - - runCurrent() + val context = FakeFirebaseApp().firebaseApp.applicationContext + val settingsCache = SettingsCache(context.dataStore) assertThat(settingsCache.sessionSamplingRate()).isNull() assertThat(settingsCache.sessionsEnabled()).isNull() @@ -63,28 +49,14 @@ class SettingsCacheTest { @Test fun settingConfigsReturnsCachedValue() = runTest { - val fakeTimeProvider = FakeTimeProvider() - val settingsCache = - SettingsCacheImpl( - backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), - timeProvider = fakeTimeProvider, - sessionConfigsDataStore = - DataStoreFactory.create( - serializer = SessionConfigsSerializer, - scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")), - produceFile = { appContext.dataStoreFile("sessionConfigsDataStore.data") }, - ), - ) - - settingsCache.updateConfigs( - SessionConfigs( - sessionsEnabled = false, - sessionSamplingRate = 0.25, - sessionTimeoutSeconds = 600, - cacheUpdatedTimeSeconds = fakeTimeProvider.currentTime().seconds, - cacheDurationSeconds = 1000, - ) - ) + val context = FakeFirebaseApp().firebaseApp.applicationContext + val settingsCache = SettingsCache(context.dataStore) + + settingsCache.updateSettingsEnabled(false) + settingsCache.updateSamplingRate(0.25) + settingsCache.updateSessionRestartTimeout(600) + settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) + settingsCache.updateSessionCacheDuration(1000) assertThat(settingsCache.sessionsEnabled()).isFalse() assertThat(settingsCache.sessionSamplingRate()).isEqualTo(0.25) @@ -97,40 +69,17 @@ class SettingsCacheTest { @Test fun settingConfigsReturnsPreviouslyStoredValue() = runTest { - val sessionConfigsDataStore = - DataStoreFactory.create( - serializer = SessionConfigsSerializer, - scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")), - produceFile = { appContext.dataStoreFile("sessionConfigsDataStore.data") }, - ) - - val fakeTimeProvider = FakeTimeProvider() - val settingsCache = - SettingsCacheImpl( - backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), - timeProvider = fakeTimeProvider, - sessionConfigsDataStore = sessionConfigsDataStore, - ) - - settingsCache.updateConfigs( - SessionConfigs( - sessionsEnabled = false, - sessionSamplingRate = 0.25, - sessionTimeoutSeconds = 600, - cacheUpdatedTimeSeconds = fakeTimeProvider.currentTime().seconds, - cacheDurationSeconds = 1000, - ) - ) + val context = FakeFirebaseApp().firebaseApp.applicationContext + val settingsCache = SettingsCache(context.dataStore) - // Create a new instance to imitate a second app launch. - val newSettingsCache = - SettingsCacheImpl( - backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), - timeProvider = fakeTimeProvider, - sessionConfigsDataStore = sessionConfigsDataStore, - ) + settingsCache.updateSettingsEnabled(false) + settingsCache.updateSamplingRate(0.25) + settingsCache.updateSessionRestartTimeout(600) + settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) + settingsCache.updateSessionCacheDuration(1000) - runCurrent() + // Create a new instance to imitate a second app launch. + val newSettingsCache = SettingsCache(context.dataStore) assertThat(newSettingsCache.sessionsEnabled()).isFalse() assertThat(newSettingsCache.sessionSamplingRate()).isEqualTo(0.25) @@ -144,28 +93,14 @@ class SettingsCacheTest { @Test fun settingConfigsReturnsCacheExpiredWithShortCacheDuration() = runTest { - val fakeTimeProvider = FakeTimeProvider() - val settingsCache = - SettingsCacheImpl( - backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), - timeProvider = fakeTimeProvider, - sessionConfigsDataStore = - DataStoreFactory.create( - serializer = SessionConfigsSerializer, - scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")), - produceFile = { appContext.dataStoreFile("sessionConfigsDataStore.data") }, - ), - ) - - settingsCache.updateConfigs( - SessionConfigs( - sessionsEnabled = false, - sessionSamplingRate = 0.25, - sessionTimeoutSeconds = 600, - cacheUpdatedTimeSeconds = fakeTimeProvider.currentTime().seconds, - cacheDurationSeconds = 0, - ) - ) + val context = FakeFirebaseApp().firebaseApp.applicationContext + val settingsCache = SettingsCache(context.dataStore) + + settingsCache.updateSettingsEnabled(false) + settingsCache.updateSamplingRate(0.25) + settingsCache.updateSessionRestartTimeout(600) + settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) + settingsCache.updateSessionCacheDuration(0) assertThat(settingsCache.sessionSamplingRate()).isEqualTo(0.25) assertThat(settingsCache.sessionsEnabled()).isFalse() @@ -177,28 +112,13 @@ class SettingsCacheTest { @Test fun settingConfigsReturnsCachedValueWithPartialConfigs() = runTest { - val fakeTimeProvider = FakeTimeProvider() - val settingsCache = - SettingsCacheImpl( - backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), - timeProvider = fakeTimeProvider, - sessionConfigsDataStore = - DataStoreFactory.create( - serializer = SessionConfigsSerializer, - scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")), - produceFile = { appContext.dataStoreFile("sessionConfigsDataStore.data") }, - ), - ) - - settingsCache.updateConfigs( - SessionConfigs( - sessionsEnabled = false, - sessionSamplingRate = 0.25, - sessionTimeoutSeconds = null, - cacheUpdatedTimeSeconds = fakeTimeProvider.currentTime().seconds, - cacheDurationSeconds = 1000, - ) - ) + val context = FakeFirebaseApp().firebaseApp.applicationContext + val settingsCache = SettingsCache(context.dataStore) + + settingsCache.updateSettingsEnabled(false) + settingsCache.updateSamplingRate(0.25) + settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) + settingsCache.updateSessionCacheDuration(1000) assertThat(settingsCache.sessionSamplingRate()).isEqualTo(0.25) assertThat(settingsCache.sessionsEnabled()).isFalse() @@ -210,43 +130,25 @@ class SettingsCacheTest { @Test fun settingConfigsAllowsUpdateConfigsAndCachesValues() = runTest { - val fakeTimeProvider = FakeTimeProvider() - val settingsCache = - SettingsCacheImpl( - backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), - timeProvider = fakeTimeProvider, - sessionConfigsDataStore = - DataStoreFactory.create( - serializer = SessionConfigsSerializer, - scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")), - produceFile = { appContext.dataStoreFile("sessionConfigsDataStore.data") }, - ), - ) - - settingsCache.updateConfigs( - SessionConfigs( - sessionsEnabled = false, - sessionSamplingRate = 0.25, - sessionTimeoutSeconds = 600, - cacheUpdatedTimeSeconds = fakeTimeProvider.currentTime().seconds, - cacheDurationSeconds = 1000, - ) - ) + val context = FakeFirebaseApp().firebaseApp.applicationContext + val settingsCache = SettingsCache(context.dataStore) + + settingsCache.updateSettingsEnabled(false) + settingsCache.updateSamplingRate(0.25) + settingsCache.updateSessionRestartTimeout(600) + settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) + settingsCache.updateSessionCacheDuration(1000) assertThat(settingsCache.sessionSamplingRate()).isEqualTo(0.25) assertThat(settingsCache.sessionsEnabled()).isFalse() assertThat(settingsCache.sessionRestartTimeout()).isEqualTo(600) assertThat(settingsCache.hasCacheExpired()).isFalse() - settingsCache.updateConfigs( - SessionConfigs( - sessionsEnabled = true, - sessionSamplingRate = 0.33, - sessionTimeoutSeconds = 100, - cacheUpdatedTimeSeconds = fakeTimeProvider.currentTime().seconds, - cacheDurationSeconds = 0, - ) - ) + settingsCache.updateSettingsEnabled(true) + settingsCache.updateSamplingRate(0.33) + settingsCache.updateSessionRestartTimeout(100) + settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) + settingsCache.updateSessionCacheDuration(0) assertThat(settingsCache.sessionSamplingRate()).isEqualTo(0.33) assertThat(settingsCache.sessionsEnabled()).isTrue() @@ -258,43 +160,25 @@ class SettingsCacheTest { @Test fun settingConfigsCleansCacheForNullValues() = runTest { - val fakeTimeProvider = FakeTimeProvider() - val settingsCache = - SettingsCacheImpl( - backgroundDispatcher = StandardTestDispatcher(testScheduler, "background"), - timeProvider = fakeTimeProvider, - sessionConfigsDataStore = - DataStoreFactory.create( - serializer = SessionConfigsSerializer, - scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")), - produceFile = { appContext.dataStoreFile("sessionConfigsDataStore.data") }, - ), - ) - - settingsCache.updateConfigs( - SessionConfigs( - sessionsEnabled = false, - sessionSamplingRate = 0.25, - sessionTimeoutSeconds = 600, - cacheUpdatedTimeSeconds = fakeTimeProvider.currentTime().seconds, - cacheDurationSeconds = 1000, - ) - ) + val context = FakeFirebaseApp().firebaseApp.applicationContext + val settingsCache = SettingsCache(context.dataStore) + + settingsCache.updateSettingsEnabled(false) + settingsCache.updateSamplingRate(0.25) + settingsCache.updateSessionRestartTimeout(600) + settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) + settingsCache.updateSessionCacheDuration(1000) assertThat(settingsCache.sessionSamplingRate()).isEqualTo(0.25) assertThat(settingsCache.sessionsEnabled()).isFalse() assertThat(settingsCache.sessionRestartTimeout()).isEqualTo(600) assertThat(settingsCache.hasCacheExpired()).isFalse() - settingsCache.updateConfigs( - SessionConfigs( - sessionsEnabled = null, - sessionSamplingRate = 0.33, - sessionTimeoutSeconds = null, - cacheUpdatedTimeSeconds = fakeTimeProvider.currentTime().seconds, - cacheDurationSeconds = 1000, - ) - ) + settingsCache.updateSettingsEnabled(null) + settingsCache.updateSamplingRate(0.33) + settingsCache.updateSessionRestartTimeout(null) + settingsCache.updateSessionCacheUpdatedTime(System.currentTimeMillis()) + settingsCache.updateSessionCacheDuration(1000) assertThat(settingsCache.sessionSamplingRate()).isEqualTo(0.33) assertThat(settingsCache.sessionsEnabled()).isNull() @@ -308,4 +192,8 @@ class SettingsCacheTest { fun cleanUp() { FirebaseApp.clearInstancesForTest() } + + private companion object { + const val SESSION_TEST_CONFIGS_NAME = "firebase_test_session_settings" + } } diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/FakeTimeProvider.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/FakeTimeProvider.kt index 295600cf48e..35010de415a 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/FakeTimeProvider.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/FakeTimeProvider.kt @@ -16,19 +16,17 @@ package com.google.firebase.sessions.testing -import com.google.firebase.sessions.Time import com.google.firebase.sessions.TimeProvider -import com.google.firebase.sessions.testing.TestSessionEventData.TEST_SESSION_TIMESTAMP +import com.google.firebase.sessions.testing.TestSessionEventData.TEST_SESSION_TIMESTAMP_US import kotlin.time.Duration -import kotlin.time.DurationUnit.MILLISECONDS +import kotlin.time.DurationUnit /** * Fake [TimeProvider] that allows programmatically elapsing time forward. * * Default [elapsedRealtime] is [Duration.ZERO] until the time is moved using [addInterval]. */ -internal class FakeTimeProvider(private val initialTime: Time = TEST_SESSION_TIMESTAMP) : - TimeProvider { +class FakeTimeProvider(private val initialTimeUs: Long = TEST_SESSION_TIMESTAMP_US) : TimeProvider { private var elapsed = Duration.ZERO fun addInterval(interval: Duration) { @@ -40,5 +38,5 @@ internal class FakeTimeProvider(private val initialTime: Time = TEST_SESSION_TIM override fun elapsedRealtime(): Duration = elapsed - override fun currentTime(): Time = Time(ms = initialTime.ms + elapsed.toLong(MILLISECONDS)) + override fun currentTimeUs(): Long = initialTimeUs + elapsed.toLong(DurationUnit.MICROSECONDS) } diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/TestSessionEventData.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/TestSessionEventData.kt index 105950a37f4..7619bc12588 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/TestSessionEventData.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/TestSessionEventData.kt @@ -30,24 +30,23 @@ import com.google.firebase.sessions.ProcessDetails import com.google.firebase.sessions.SessionDetails import com.google.firebase.sessions.SessionEvent import com.google.firebase.sessions.SessionInfo -import com.google.firebase.sessions.Time internal object TestSessionEventData { - val TEST_SESSION_TIMESTAMP: Time = Time(ms = 12340) + const val TEST_SESSION_TIMESTAMP_US: Long = 12340000 val TEST_SESSION_DETAILS = SessionDetails( sessionId = "a1b2c3", firstSessionId = "a1a1a1", sessionIndex = 3, - sessionStartTimestampUs = TEST_SESSION_TIMESTAMP.us, + sessionStartTimestampUs = TEST_SESSION_TIMESTAMP_US ) val TEST_DATA_COLLECTION_STATUS = DataCollectionStatus( performance = DataCollectionState.COLLECTION_SDK_NOT_INSTALLED, crashlytics = DataCollectionState.COLLECTION_SDK_NOT_INSTALLED, - sessionSamplingRate = 1.0, + sessionSamplingRate = 1.0 ) val TEST_SESSION_DATA = @@ -55,14 +54,19 @@ internal object TestSessionEventData { sessionId = "a1b2c3", firstSessionId = "a1a1a1", sessionIndex = 3, - eventTimestampUs = TEST_SESSION_TIMESTAMP.us, + eventTimestampUs = TEST_SESSION_TIMESTAMP_US, dataCollectionStatus = TEST_DATA_COLLECTION_STATUS, firebaseInstallationId = "", firebaseAuthenticationToken = "", ) val TEST_PROCESS_DETAILS = - ProcessDetails(processName = "com.google.firebase.sessions.test", 0, 100, false) + ProcessDetails( + processName = "com.google.firebase.sessions.test", + 0, + 100, + false, + ) val TEST_APP_PROCESS_DETAILS = listOf(TEST_PROCESS_DETAILS) From 7fb534d49d23fbcb49ff3499b50dfd7730034bf7 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Mon, 7 Apr 2025 15:41:14 -0400 Subject: [PATCH 64/76] Revert additional changes from sessions --- .../sessions/settings/SessionConfigs.kt | 58 ------------------ .../firebase/sessions/SessionDatastoreTest.kt | 59 ------------------- .../sessions/testing/FakeSettingsCache.kt | 52 ---------------- 3 files changed, 169 deletions(-) delete mode 100644 firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SessionConfigs.kt delete mode 100644 firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionDatastoreTest.kt delete mode 100644 firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/FakeSettingsCache.kt 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 deleted file mode 100644 index ab310ebed8a..00000000000 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SessionConfigs.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 deleted file mode 100644 index efe7bb27a97..00000000000 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionDatastoreTest.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 deleted file mode 100644 index 2c58ef22d7d..00000000000 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/FakeSettingsCache.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 - } -} From 73a04e7d5f70c9ddd4c1ba3b665f21c4cd43351f Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Mon, 7 Apr 2025 15:42:15 -0400 Subject: [PATCH 65/76] Revert libs change --- gradle/libs.versions.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5fa68f6cc03..44079b349e8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -91,7 +91,6 @@ 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" } From a0ee2b287ce3b9cf3ef4edcc37881222de8c879d Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 8 Apr 2025 12:00:10 -0400 Subject: [PATCH 66/76] More changes --- .../google/firebase/perf/session/AqsUtils.kt | 20 ++++++++++++++++ .../FirebasePerformanceSessionSubscriber.kt | 2 +- .../firebase/perf/session/PerfSession.java | 23 +++++++------------ .../perf/session/gauges/GaugeManager.java | 6 ++--- .../google/firebase/perf/util/Constants.java | 4 +++- .../perf/session/PerfSessionTest.java | 21 +++++++++-------- 6 files changed, 47 insertions(+), 29 deletions(-) create mode 100644 firebase-perf/src/main/java/com/google/firebase/perf/session/AqsUtils.kt diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/session/AqsUtils.kt b/firebase-perf/src/main/java/com/google/firebase/perf/session/AqsUtils.kt new file mode 100644 index 00000000000..29b6936aafd --- /dev/null +++ b/firebase-perf/src/main/java/com/google/firebase/perf/session/AqsUtils.kt @@ -0,0 +1,20 @@ +package com.google.firebase.perf.session + +import com.google.firebase.perf.util.Constants +import java.util.UUID + +/** + * Identifies whether the [PerfSession] is an AQS or not. + */ +fun PerfSession.isAQS(): Boolean { + return !this.sessionId().startsWith(Constants.UNDEFINED_AQS_ID_PREFIX) +} + +@JvmOverloads fun createSessionId(aqsId: String = Constants.UNDEFINED_AQS_ID_PREFIX): String { +if (aqsId == Constants.UNDEFINED_AQS_ID_PREFIX) { + val uuid = UUID.randomUUID().toString().replace("-", ""); + return uuid.replaceRange(0, aqsId.length, aqsId) +} + +return aqsId; +} 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 index 08175baf1df..737f01227fb 100644 --- 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 @@ -30,7 +30,7 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: val currentPerfSession = SessionManager.getInstance().perfSession() // A [PerfSession] was created before a session was started. - if (!currentPerfSession.isAqsReady) { + if (!currentPerfSession.isAQS()) { GaugeManager.getInstance() .logGaugeMetadata(currentPerfSession.sessionId(), ApplicationProcessState.FOREGROUND) return 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 e4260034107..2b1e9b6d318 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 @@ -32,31 +32,26 @@ 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; + if (aqsSessionId == null) { + sessionId = AqsUtilsKt.createSessionId(); } else { - sessionId = UUID.randomUUID().toString().replace("-", ""); - isAqsReady = false; + sessionId = AqsUtilsKt.createSessionId(aqsSessionId); } - 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(); } @@ -64,7 +59,6 @@ private PerfSession(@NonNull Parcel in) { super(); sessionId = in.readString(); isGaugeAndEventCollectionEnabled = in.readByte() != 0; - isAqsReady = in.readByte() != 0; creationTime = in.readParcelable(Timer.class.getClassLoader()); } @@ -160,10 +154,10 @@ 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); } /** @@ -187,7 +181,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); } 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 f1defd1903b..84d65b8918f 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 @@ -113,7 +113,7 @@ public void updateGaugeCollection(ApplicationProcessState applicationProcessStat if (session == null || !session.isGaugeAndEventCollectionEnabled() - || session.aqsSessionId().equals(Constants.UNDEFINED_AQS_ID)) { + || session.sessionId().equals(Constants.UNDEFINED_AQS_ID_PREFIX)) { logger.warn("Not starting gauge collection."); stopCollectingGauges(); return; @@ -230,7 +230,7 @@ public void stopCollectingGauges() { gaugeManagerDataCollectionJob.cancel(false); } - final String sessionIdForScheduledTask = session.aqsSessionId(); + final String sessionIdForScheduledTask = session.sessionId(); this.session = null; // Flush any data that was collected for this session one last time. @@ -254,7 +254,7 @@ public void stopCollectingGauges() { * @param appState The app state for which these gauges are collected. */ private void syncFlush(String sessionId, ApplicationProcessState appState) { - if (sessionId.equals(Constants.UNDEFINED_AQS_ID)) { + if (sessionId.equals(Constants.UNDEFINED_AQS_ID_PREFIX)) { // TODO(b/394127311): Use DebugEnforcementCheck. logger.debug("Flushing gauge metrics to an undefined session ID."); } diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/util/Constants.java b/firebase-perf/src/main/java/com/google/firebase/perf/util/Constants.java index 95d95a9ccd6..a8ef753f0bf 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/util/Constants.java +++ b/firebase-perf/src/main/java/com/google/firebase/perf/util/Constants.java @@ -22,7 +22,9 @@ public class Constants { public static final String PREFS_NAME = "FirebasePerfSharedPrefs"; public static final String ENABLE_DISABLE = "isEnabled"; - public static final String UNDEFINED_AQS_ID = "_uaqsid"; + // Contains non-hex characters and so guarantees it isn't an AQS. + // https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.uuid/-uuid/ + public static final String UNDEFINED_AQS_ID_PREFIX = "noaqsid"; public static final double MIN_SAMPLING_RATE = 0.0; public static final double MAX_SAMPLING_RATE = 1.0; 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 19d8aadf76b..b1a7cf24e09 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 @@ -76,19 +76,20 @@ public void shouldCollectGaugesAndEvents_perfMonDisabledAtRuntime_sessionNotVerb Bundle bundle = new Bundle(); bundle.putFloat("sessions_sampling_percentage", 100); configResolver.setMetadataBundle(new ImmutableBundle(bundle)); + PerfSession testSession = PerfSession.createWithId("aqsSessionId"); // By default, session is verbose if developer has set 100% of session verbosity. - assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isTrue(); + assertThat(testSession.shouldCollectGaugesAndEvents()).isTrue(); // Case #1: developer has disabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(false); - assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); + assertThat(testSession.shouldCollectGaugesAndEvents()).isFalse(); // Case #2: developer has enabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(true); - assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isTrue(); + assertThat(testSession.shouldCollectGaugesAndEvents()).isTrue(); } @Test @@ -99,20 +100,21 @@ public void shouldCollectGaugesAndEvents_perfMonDisabledAtBuildtime_verbosityDep bundle.putFloat("sessions_sampling_percentage", 100); bundle.putBoolean("firebase_performance_collection_enabled", false); configResolver.setMetadataBundle(new ImmutableBundle(bundle)); + PerfSession testSession = PerfSession.createWithId("aqsSessionId"); // By default, session is not verbose if developer disabled performance monitoring at build // time. - assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); + assertThat(testSession.shouldCollectGaugesAndEvents()).isFalse(); // Case #1: developer has enabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(true); - assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isTrue(); + assertThat(testSession.shouldCollectGaugesAndEvents()).isTrue(); // Case #2: developer has disabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(false); - assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); + assertThat(testSession.shouldCollectGaugesAndEvents()).isFalse(); } @Test @@ -122,19 +124,20 @@ public void shouldCollectGaugesAndEvents_perfMonDeactivated_sessionNotVerbose() bundle.putFloat("sessions_sampling_percentage", 100); bundle.putBoolean("firebase_performance_collection_deactivated", true); configResolver.setMetadataBundle(new ImmutableBundle(bundle)); + PerfSession testSession = PerfSession.createWithId("aqsSessionId"); // Session will never be verbose if developer deactivated performance monitoring at build time. - assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); + assertThat(testSession.shouldCollectGaugesAndEvents()).isFalse(); // Case #1: developer has enabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(true); - assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); + assertThat(testSession.shouldCollectGaugesAndEvents()).isFalse(); // Case #2: developer has disabled Performance Monitoring during runtime. configResolver.setIsPerformanceCollectionEnabled(false); - assertThat(PerfSession.shouldCollectGaugesAndEvents("sessionId")).isFalse(); + assertThat(testSession.shouldCollectGaugesAndEvents()).isFalse(); } @Test From 97bc643b75c429b7642b477b2c2d0cf8ab334e0c Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 8 Apr 2025 15:11:08 -0400 Subject: [PATCH 67/76] Fix tests --- .../firebase/perf/FirebasePerformance.java | 13 +++++++-- .../google/firebase/perf/session/AqsUtils.kt | 20 ------------- .../perf/session/FirebaseSessionsHelper.kt | 18 ++++++++++++ .../firebase/perf/session/PerfSession.java | 13 ++++----- .../perf/session/gauges/GaugeManager.java | 22 +++++++++------ .../perf/FirebasePerformanceTestBase.java | 12 ++++++-- .../perf/session/PerfSessionTest.java | 28 +++++++++++++------ .../perf/transport/TransportManagerTest.java | 7 ++--- 8 files changed, 80 insertions(+), 53 deletions(-) delete mode 100644 firebase-perf/src/main/java/com/google/firebase/perf/session/AqsUtils.kt create mode 100644 firebase-perf/src/main/java/com/google/firebase/perf/session/FirebaseSessionsHelper.kt 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 587bff395de..fa8317af030 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 @@ -46,6 +46,7 @@ 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; @@ -96,6 +97,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, @@ -169,6 +172,7 @@ 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); @@ -186,8 +190,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( @@ -463,4 +467,9 @@ private static ImmutableBundle extractMetadata(Context appContext) { Boolean getPerformanceCollectionForceEnabledState() { return mPerformanceCollectionForceEnabledState; } + + @VisibleForTesting + SessionSubscriber getSessionSubscriber() { + return sessionSubscriber; + } } diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/session/AqsUtils.kt b/firebase-perf/src/main/java/com/google/firebase/perf/session/AqsUtils.kt deleted file mode 100644 index 29b6936aafd..00000000000 --- a/firebase-perf/src/main/java/com/google/firebase/perf/session/AqsUtils.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.google.firebase.perf.session - -import com.google.firebase.perf.util.Constants -import java.util.UUID - -/** - * Identifies whether the [PerfSession] is an AQS or not. - */ -fun PerfSession.isAQS(): Boolean { - return !this.sessionId().startsWith(Constants.UNDEFINED_AQS_ID_PREFIX) -} - -@JvmOverloads fun createSessionId(aqsId: String = Constants.UNDEFINED_AQS_ID_PREFIX): String { -if (aqsId == Constants.UNDEFINED_AQS_ID_PREFIX) { - val uuid = UUID.randomUUID().toString().replace("-", ""); - return uuid.replaceRange(0, aqsId.length, aqsId) -} - -return aqsId; -} diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebaseSessionsHelper.kt b/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebaseSessionsHelper.kt new file mode 100644 index 00000000000..3ecd4711ecb --- /dev/null +++ b/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebaseSessionsHelper.kt @@ -0,0 +1,18 @@ +package com.google.firebase.perf.session + +import com.google.firebase.perf.util.Constants +import java.util.UUID + +/** Identifies whether the [PerfSession] is an AQS or not. */ +fun PerfSession.isAQS(): Boolean { + return !this.sessionId().startsWith(Constants.UNDEFINED_AQS_ID_PREFIX) +} + +fun createLegacySessionId(): String { + val uuid = UUID.randomUUID().toString().replace("-", "") + return uuid.replaceRange( + 0, + Constants.UNDEFINED_AQS_ID_PREFIX.length, + Constants.UNDEFINED_AQS_ID_PREFIX + ) +} 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 2b1e9b6d318..a89c8987896 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 @@ -24,7 +24,6 @@ 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. */ @@ -37,11 +36,9 @@ public class PerfSession implements Parcelable { * Creates a PerfSession object and decides what metrics to collect. */ public static PerfSession createWithId(@Nullable String aqsSessionId) { - String sessionId; - if (aqsSessionId == null) { - sessionId = AqsUtilsKt.createSessionId(); - } else { - sessionId = AqsUtilsKt.createSessionId(aqsSessionId); + String sessionId = aqsSessionId; + if (sessionId == null) { + sessionId = FirebaseSessionsHelperKt.createLegacySessionId(); } PerfSession session = new PerfSession(sessionId, new Clock()); session.setGaugeAndEventCollectionEnabled(session.shouldCollectGaugesAndEvents()); @@ -63,6 +60,7 @@ private PerfSession(@NonNull Parcel in) { } /** Returns the sessionId for the given session. */ + @NonNull public String sessionId() { return sessionId; } @@ -157,7 +155,8 @@ public static com.google.firebase.perf.v1.PerfSession[] buildAndSort( public boolean shouldCollectGaugesAndEvents() { ConfigResolver configResolver = ConfigResolver.getInstance(); return configResolver.isPerformanceMonitoringEnabled() - && (Math.abs(this.sessionId.hashCode() % 100) < configResolver.getSessionsSamplingRate() * 100); + && (Math.abs(this.sessionId.hashCode() % 100) + < configResolver.getSessionsSamplingRate() * 100); } /** 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 84d65b8918f..d4080bc36f4 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.FirebaseSessionsHelperKt; import com.google.firebase.perf.session.PerfSession; import com.google.firebase.perf.transport.TransportManager; import com.google.firebase.perf.util.Constants; @@ -111,9 +112,7 @@ public void updateGaugeCollection(ApplicationProcessState applicationProcessStat gaugeManagerDataCollectionJob.cancel(false); } - if (session == null - || !session.isGaugeAndEventCollectionEnabled() - || session.sessionId().equals(Constants.UNDEFINED_AQS_ID_PREFIX)) { + if (!isValidSessionForLogging()) { logger.warn("Not starting gauge collection."); stopCollectingGauges(); return; @@ -123,7 +122,7 @@ public void updateGaugeCollection(ApplicationProcessState applicationProcessStat Math.min( getCpuGaugeCollectionFrequencyMs(applicationProcessState), getMemoryGaugeCollectionFrequencyMs(applicationProcessState)); - String sessionIdForScheduledTask = session.aqsSessionId(); + String sessionIdForScheduledTask = session.sessionId(); try { gaugeManagerDataCollectionJob = @@ -254,9 +253,9 @@ public void stopCollectingGauges() { * @param appState The app state for which these gauges are collected. */ private void syncFlush(String sessionId, ApplicationProcessState appState) { - if (sessionId.equals(Constants.UNDEFINED_AQS_ID_PREFIX)) { + if (sessionId.contains(Constants.UNDEFINED_AQS_ID_PREFIX)) { // TODO(b/394127311): Use DebugEnforcementCheck. - logger.debug("Flushing gauge metrics to an undefined session ID."); + logger.debug("Flushing gauge metrics to a legacy session ID."); } GaugeMetric.Builder gaugeMetricBuilder = GaugeMetric.newBuilder(); @@ -279,16 +278,16 @@ private void syncFlush(String sessionId, ApplicationProcessState appState) { /** * Log the Gauge Metadata information to the transport. * - * @param aqsSessionId The {@link PerfSession#aqsSessionId()} ()} to which the collected Gauge Metrics + * @param sessionId The {@link PerfSession#sessionId()} ()} 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 aqsSessionId, ApplicationProcessState appState) { + public boolean logGaugeMetadata(String sessionId, ApplicationProcessState appState) { if (gaugeMetadataManager != null) { GaugeMetric gaugeMetric = GaugeMetric.newBuilder() - .setSessionId(aqsSessionId) + .setSessionId(sessionId) .setGaugeMetadata(getGaugeMetadata()) .build(); transportManager.log(gaugeMetric, appState); @@ -432,6 +431,11 @@ private long getMemoryGaugeCollectionFrequencyMs( } } + private boolean isValidSessionForLogging() { + if (session == null) return false; + return session.isGaugeAndEventCollectionEnabled() && FirebaseSessionsHelperKt.isAQS(session); + } + @VisibleForTesting void setApplicationProcessState(ApplicationProcessState applicationProcessState) { this.applicationProcessState = applicationProcessState; 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 e8b5386ae98..46813471478 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 @@ -96,8 +96,7 @@ protected static void forceNonVerboseSession() { } protected static void forceAppQualitySession() { - PerfSession existingPerfSession = PerfSession.createWithId(UUID.randomUUID().toString()); - existingPerfSession.setAQSId(new SessionSubscriber.SessionDetails("notnull")); + PerfSession existingPerfSession = createPerfSession(false); SessionManager.getInstance().setPerfSession(existingPerfSession); SessionSubscriber sessionSubscriber = FirebasePerformance.getInstance().getSessionSubscriber(); sessionSubscriber.onSessionChanged(new SessionSubscriber.SessionDetails("fakeAQS")); @@ -109,4 +108,13 @@ private static void forceVerboseSessionWithSamplingPercentage(long samplingPerce ConfigResolver.getInstance().setMetadataBundle(new ImmutableBundle(bundle)); forceAppQualitySession(); } + + private static PerfSession createPerfSession(boolean isLegacy) { + if (isLegacy) { + return PerfSession.createWithId(null); + } + + String aqsId = UUID.randomUUID().toString().replace("-", ""); + return PerfSession.createWithId(aqsId); + } } 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 b1a7cf24e09..afd778aca3b 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 @@ -62,12 +62,24 @@ public void setUp() { @Test public void instanceCreation() { - PerfSession session = new PerfSession("sessionId", mockClock, true); + PerfSession session = PerfSession.createWithId("sessionId"); assertThat(session).isNotNull(); session.setGaugeAndEventCollectionEnabled(true); - Assert.assertTrue(session.isGaugeAndEventCollectionEnabled()); + assertThat(session.isVerbose()).isTrue(); session.setGaugeAndEventCollectionEnabled(false); - Assert.assertFalse(session.isGaugeAndEventCollectionEnabled()); + assertThat(session.isVerbose()).isFalse(); + assertThat(FirebaseSessionsHelperKt.isAQS(session)).isTrue(); + } + + @Test + public void legacyInstanceCreation() { + PerfSession perfSession = PerfSession.createWithId(null); + assertThat(perfSession).isNotNull(); + perfSession.setGaugeAndEventCollectionEnabled(true); + assertThat(perfSession.isVerbose()).isTrue(); + perfSession.setGaugeAndEventCollectionEnabled(false); + assertThat(perfSession.isVerbose()).isFalse(); + assertThat(FirebaseSessionsHelperKt.isAQS(perfSession)).isFalse(); } @Test @@ -142,7 +154,7 @@ public void shouldCollectGaugesAndEvents_perfMonDeactivated_sessionNotVerbose() @Test public void testPerfSessionConversion() { - PerfSession session1 = new PerfSession("sessionId", mockClock, true); + PerfSession session1 = PerfSession.createWithId("aqsSessionId"); session1.setGaugeAndEventCollectionEnabled(true); com.google.firebase.perf.v1.PerfSession perfSession = session1.build(); @@ -153,7 +165,7 @@ public void testPerfSessionConversion() { @Test public void testPerfSessionConversionWithoutVerbosity() { - PerfSession session1 = new PerfSession("sessionId", mockClock, true); + PerfSession session1 = PerfSession.createWithId("sessionId"); com.google.firebase.perf.v1.PerfSession perfSession = session1.build(); Assert.assertEquals(session1.sessionId(), perfSession.getSessionId()); @@ -219,7 +231,7 @@ public void testIsExpiredReturnsFalseWhenCurrentSessionLengthIsLessThanMaxSessio - TimeUnit.MINUTES.toMicros(1)); // Default Max Session Length is 4 hours when(mockClock.getTime()).thenReturn(mockTimer); - PerfSession session = new PerfSession("sessionId", mockClock, true); + PerfSession session = PerfSession.createWithId("sessionId"); assertThat(session.isSessionRunningTooLong()).isFalse(); } @@ -230,7 +242,7 @@ public void testIsExpiredReturnsFalseWhenCurrentSessionLengthIsEqualToMaxSession .thenReturn(TimeUnit.HOURS.toMicros(4)); // Default Max Session Length is 4 hours when(mockClock.getTime()).thenReturn(mockTimer); - PerfSession session = new PerfSession("sessionId", mockClock, true); + PerfSession session = PerfSession.createWithId("sessionId"); assertThat(session.isSessionRunningTooLong()).isFalse(); } @@ -241,7 +253,7 @@ public void testIsExpiredReturnsTrueWhenCurrentSessionLengthIsGreaterThanMaxSess .thenReturn(TimeUnit.HOURS.toMicros(5)); // Default Max Session Length is 4 hours when(mockClock.getTime()).thenReturn(mockTimer); - PerfSession session = new PerfSession("sessionId", mockClock, true); + PerfSession session = PerfSession.createWithId("sessionId"); assertThat(session.isSessionRunningTooLong()).isTrue(); } } diff --git a/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java b/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java index 6b9b5cfa3a3..2096fd10ab9 100644 --- a/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java +++ b/firebase-perf/src/test/java/com/google/firebase/perf/transport/TransportManagerTest.java @@ -53,7 +53,6 @@ import com.google.firebase.perf.v1.PerfMetric; import com.google.firebase.perf.v1.PerfSession; import com.google.firebase.perf.v1.TraceMetric; -import com.google.firebase.sessions.api.SessionSubscriber; import com.google.testing.timing.FakeScheduledExecutorService; import java.util.ArrayList; import java.util.List; @@ -1175,7 +1174,6 @@ public void logTraceMetric_sessionEnabled_doesNotStripOffSessionId() { List perfSessions = new ArrayList<>(); com.google.firebase.perf.session.PerfSession testSession = com.google.firebase.perf.session.PerfSession.createWithId("fakeSessionId"); - testSession.setAQSId(new SessionSubscriber.SessionDetails("fakeAqsSessionId")); perfSessions.add(testSession.build()); validTrace.addAllPerfSessions(perfSessions); @@ -1185,7 +1183,7 @@ public void logTraceMetric_sessionEnabled_doesNotStripOffSessionId() { PerfMetric loggedPerfMetric = getLastLoggedEvent(times(1)); assertThat(loggedPerfMetric.getTraceMetric().getPerfSessionsCount()).isEqualTo(1); assertThat(loggedPerfMetric.getTraceMetric().getPerfSessions(0).getSessionId()) - .isEqualTo("fakeAqsSessionId"); + .isEqualTo("fakeSessionId"); } @Test @@ -1195,7 +1193,6 @@ public void logNetworkMetric_sessionEnabled_doesNotStripOffSessionId() { List perfSessions = new ArrayList<>(); com.google.firebase.perf.session.PerfSession testSession = com.google.firebase.perf.session.PerfSession.createWithId("fakeSessionId"); - testSession.setAQSId(new SessionSubscriber.SessionDetails("fakeAqsSessionId")); perfSessions.add(testSession.build()); validNetworkRequest.clearPerfSessions().addAllPerfSessions(perfSessions); @@ -1205,7 +1202,7 @@ public void logNetworkMetric_sessionEnabled_doesNotStripOffSessionId() { PerfMetric loggedPerfMetric = getLastLoggedEvent(times(1)); assertThat(loggedPerfMetric.getNetworkRequestMetric().getPerfSessionsCount()).isEqualTo(1); assertThat(loggedPerfMetric.getNetworkRequestMetric().getPerfSessions(0).getSessionId()) - .isEqualTo("fakeAqsSessionId"); + .isEqualTo("fakeSessionId"); } @Test From 8377e8ca86857fceeddc3915f01fabf9f579d7ce Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 8 Apr 2025 15:24:43 -0400 Subject: [PATCH 68/76] Revert usage of mockClock --- .../com/google/firebase/perf/session/PerfSessionTest.java | 6 +++--- .../google/firebase/perf/session/SessionManagerTest.java | 6 +++--- .../firebase/perf/session/gauges/GaugeManagerTest.java | 8 ++------ 3 files changed, 8 insertions(+), 12 deletions(-) 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 afd778aca3b..f53c11fd35b 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 @@ -231,7 +231,7 @@ public void testIsExpiredReturnsFalseWhenCurrentSessionLengthIsLessThanMaxSessio - TimeUnit.MINUTES.toMicros(1)); // Default Max Session Length is 4 hours when(mockClock.getTime()).thenReturn(mockTimer); - PerfSession session = PerfSession.createWithId("sessionId"); + PerfSession session = new PerfSession("sessionId", mockClock); assertThat(session.isSessionRunningTooLong()).isFalse(); } @@ -242,7 +242,7 @@ public void testIsExpiredReturnsFalseWhenCurrentSessionLengthIsEqualToMaxSession .thenReturn(TimeUnit.HOURS.toMicros(4)); // Default Max Session Length is 4 hours when(mockClock.getTime()).thenReturn(mockTimer); - PerfSession session = PerfSession.createWithId("sessionId"); + PerfSession session = new PerfSession("sessionId", mockClock); assertThat(session.isSessionRunningTooLong()).isFalse(); } @@ -253,7 +253,7 @@ public void testIsExpiredReturnsTrueWhenCurrentSessionLengthIsGreaterThanMaxSess .thenReturn(TimeUnit.HOURS.toMicros(5)); // Default Max Session Length is 4 hours when(mockClock.getTime()).thenReturn(mockTimer); - PerfSession session = PerfSession.createWithId("sessionId"); + PerfSession session = new PerfSession("sessionId", mockClock); assertThat(session.isSessionRunningTooLong()).isTrue(); } } 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 5660fefadf5..be819a32f26 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 @@ -112,7 +112,7 @@ public void testSessionIdDoesNotUpdateIfPerfSessionRunsTooLong() { Timer mockTimer = mock(Timer.class); when(mockClock.getTime()).thenReturn(mockTimer); - PerfSession session = new PerfSession("sessionId", mockClock, true); + PerfSession session = new PerfSession("sessionId", mockClock); SessionManager testSessionManager = new SessionManager(mockGaugeManager, session, mockAppStateMonitor); @@ -131,10 +131,10 @@ public void testUpdatePerfSessionStartsCollectingGaugesIfSessionIsVerbose() { when(mockClock.getTime()).thenReturn(mockTimer); when(mockAppStateMonitor.getAppState()).thenReturn(ApplicationProcessState.FOREGROUND); - PerfSession previousSession = new PerfSession("previousSession", mockClock, true); + PerfSession previousSession = new PerfSession("previousSession", mockClock); previousSession.setGaugeAndEventCollectionEnabled(false); - PerfSession newSession = new PerfSession("newSession", mockClock, true); + PerfSession newSession = new PerfSession("newSession", mockClock); newSession.setGaugeAndEventCollectionEnabled(true); SessionManager testSessionManager = 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 526e119a271..1133a0cfe0c 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 @@ -41,7 +41,6 @@ import com.google.firebase.perf.v1.CpuMetricReading; import com.google.firebase.perf.v1.GaugeMetadata; import com.google.firebase.perf.v1.GaugeMetric; -import com.google.firebase.sessions.api.SessionSubscriber; import com.google.testing.timing.FakeScheduledExecutorService; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -408,8 +407,6 @@ public void testStopCollectingGaugesCreatesOneLastJobToConsumeAnyPendingMetrics( // It's not currently logging to transport. assertThat(fakeScheduledExecutorService.isEmpty()).isTrue(); - // An AQS session ID is expected when stopping Gauge collection. - fakeSession.setAQSId(new SessionSubscriber.SessionDetails("aqsSessionId")); testGaugeManager.stopCollectingGauges(); assertThat(fakeScheduledExecutorService.isEmpty()).isFalse(); @@ -427,9 +424,9 @@ public void testStopCollectingGaugesCreatesOneLastJobToConsumeAnyPendingMetrics( GaugeMetric recordedGaugeMetric = getLastRecordedGaugeMetric(ApplicationProcessState.BACKGROUND, 1); assertThatCpuGaugeMetricWasSentToTransport( - fakeSession.aqsSessionId(), recordedGaugeMetric, fakeCpuMetricReading); + fakeSession.sessionId(), recordedGaugeMetric, fakeCpuMetricReading); assertThatMemoryGaugeMetricWasSentToTransport( - fakeSession.aqsSessionId(), recordedGaugeMetric, fakeMemoryMetricReading); + fakeSession.sessionId(), recordedGaugeMetric, fakeMemoryMetricReading); } @Test @@ -451,7 +448,6 @@ public void testGaugeManagerDoesNotClearTheQueueUnlessUpdated() { assertThat(fakeCpuGaugeCollector.cpuMetricReadings).isNotEmpty(); assertThat(fakeMemoryGaugeCollector.memoryMetricReadings).isNotEmpty(); - fakeSession.setAQSId(new SessionSubscriber.SessionDetails("aqsSessionId")); testGaugeManager.updateGaugeCollection(ApplicationProcessState.FOREGROUND); fakeScheduledExecutorService.simulateSleepExecutingAtMostOneTask(); From f2a4728f8b3f93609dc432f0e1c560c1bb218280 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 8 Apr 2025 17:06:43 -0400 Subject: [PATCH 69/76] Re-introduce gauge collection for app start --- .../perf/logging/DebugEnforcementCheck.kt | 9 +++-- .../FirebasePerformanceSessionSubscriber.kt | 16 ++++++--- .../perf/session/FirebaseSessionsHelper.kt | 7 ++-- .../firebase/perf/session/SessionManager.java | 8 ++--- .../perf/session/gauges/GaugeManager.java | 36 ++++++++++++++++++- .../perf/session/PerfSessionTest.java | 4 +-- 6 files changed, 61 insertions(+), 19 deletions(-) diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/logging/DebugEnforcementCheck.kt b/firebase-perf/src/main/java/com/google/firebase/perf/logging/DebugEnforcementCheck.kt index a2f3b186f9b..68880860643 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/logging/DebugEnforcementCheck.kt +++ b/firebase-perf/src/main/java/com/google/firebase/perf/logging/DebugEnforcementCheck.kt @@ -16,15 +16,18 @@ package com.google.firebase.perf.logging +import com.google.firebase.perf.session.PerfSession +import com.google.firebase.perf.session.isLegacy + class DebugEnforcementCheck { 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) + public fun checkSession(session: PerfSession, failureMessage: String) { + if (session.isLegacy()) { + logger.debug("legacy session ${session.sessionId()}: $failureMessage") assert(!enforcement) { failureMessage } } } 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 index 737f01227fb..19133f7555e 100644 --- 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 @@ -16,10 +16,10 @@ package com.google.firebase.perf.session +import com.google.firebase.perf.logging.DebugEnforcementCheck 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 { @@ -28,15 +28,21 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: override fun onSessionChanged(sessionDetails: SessionSubscriber.SessionDetails) { val currentPerfSession = SessionManager.getInstance().perfSession() + DebugEnforcementCheck.checkSession(currentPerfSession, "onSessionChanged") // A [PerfSession] was created before a session was started. - if (!currentPerfSession.isAQS()) { + // Since these was gauge collection with app startup, it logs them to the updated session ID. + if (currentPerfSession.isLegacy()) { GaugeManager.getInstance() - .logGaugeMetadata(currentPerfSession.sessionId(), ApplicationProcessState.FOREGROUND) - return + .logGaugeMetadata(sessionDetails.sessionId, ApplicationProcessState.FOREGROUND) + GaugeManager.getInstance() + .stopCollectingGaugesForLegacySession( + sessionDetails.sessionId, + ApplicationProcessState.FOREGROUND + ) } - val updatedSession = PerfSession.createWithId(UUID.randomUUID().toString()) + val updatedSession = PerfSession.createWithId(sessionDetails.sessionId) SessionManager.getInstance().updatePerfSession(updatedSession) GaugeManager.getInstance() .logGaugeMetadata(updatedSession.sessionId(), ApplicationProcessState.FOREGROUND) diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebaseSessionsHelper.kt b/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebaseSessionsHelper.kt index 3ecd4711ecb..a8aee80d129 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebaseSessionsHelper.kt +++ b/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebaseSessionsHelper.kt @@ -3,11 +3,12 @@ package com.google.firebase.perf.session import com.google.firebase.perf.util.Constants import java.util.UUID -/** Identifies whether the [PerfSession] is an AQS or not. */ -fun PerfSession.isAQS(): Boolean { - return !this.sessionId().startsWith(Constants.UNDEFINED_AQS_ID_PREFIX) +/** Identifies whether the [PerfSession] is legacy or not. */ +fun PerfSession.isLegacy(): Boolean { + return this.sessionId().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( 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 ac08aeb6c9d..9ce86249795 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 @@ -20,6 +20,7 @@ import androidx.annotation.VisibleForTesting; import com.google.firebase.perf.application.AppStateMonitor; import com.google.firebase.perf.application.AppStateUpdateHandler; +import com.google.firebase.perf.logging.DebugEnforcementCheck; import com.google.firebase.perf.session.gauges.GaugeManager; import com.google.firebase.perf.v1.ApplicationProcessState; import com.google.firebase.perf.v1.GaugeMetadata; @@ -29,7 +30,6 @@ import java.util.Iterator; import java.util.Objects; import java.util.Set; -import java.util.UUID; /** Session manager to generate sessionIDs and broadcast to the application. */ @Keep // Needed because of b/117526359. @@ -51,15 +51,13 @@ public static SessionManager getInstance() { /** Returns the currently active PerfSession. */ public final PerfSession perfSession() { + DebugEnforcementCheck.Companion.checkSession(perfSession, "SessionManager.perfSession()"); return 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.createWithId(null), AppStateMonitor.getInstance()); } @VisibleForTesting 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 d4080bc36f4..afcf85fc41b 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 @@ -245,6 +245,40 @@ public void stopCollectingGauges() { TimeUnit.MILLISECONDS); } + /** + * Stops the collection of gauges if it was currently collecting them for a legacy session. + * + * @note: This method is NOT thread safe - {@link this.startCollectingGauges()} and {@link + * this.stopCollectingGauges()} should always be called from the same thread. + */ + public void stopCollectingGaugesForLegacySession( + String updatedSessionId, ApplicationProcessState flushToApplicationProcessState) { + final ApplicationProcessState applicationProcessStateForScheduledTask = + flushToApplicationProcessState; + + cpuGaugeCollector.get().stopCollecting(); + memoryGaugeCollector.get().stopCollecting(); + + if (gaugeManagerDataCollectionJob != null) { + gaugeManagerDataCollectionJob.cancel(false); + } + + final String sessionIdForScheduledTask = updatedSessionId; + this.session = null; + + // Flush any data that was collected for this session one last time. + @SuppressWarnings("FutureReturnValueIgnored") + ScheduledFuture unusedFuture = + gaugeManagerExecutor + .get() + .schedule( + () -> { + syncFlush(sessionIdForScheduledTask, applicationProcessStateForScheduledTask); + }, + TIME_TO_WAIT_BEFORE_FLUSHING_GAUGES_QUEUE_MS, + TimeUnit.MILLISECONDS); + } + /** * This method reads any pending data points from all the Gauge's queues, assembles a GaugeMetric * proto and logs it to transport. @@ -433,7 +467,7 @@ private long getMemoryGaugeCollectionFrequencyMs( private boolean isValidSessionForLogging() { if (session == null) return false; - return session.isGaugeAndEventCollectionEnabled() && FirebaseSessionsHelperKt.isAQS(session); + return session.isGaugeAndEventCollectionEnabled() && FirebaseSessionsHelperKt.isLegacy(session); } @VisibleForTesting 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 f53c11fd35b..174060be881 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 @@ -68,7 +68,7 @@ public void instanceCreation() { assertThat(session.isVerbose()).isTrue(); session.setGaugeAndEventCollectionEnabled(false); assertThat(session.isVerbose()).isFalse(); - assertThat(FirebaseSessionsHelperKt.isAQS(session)).isTrue(); + assertThat(FirebaseSessionsHelperKt.isLegacy(session)).isTrue(); } @Test @@ -79,7 +79,7 @@ public void legacyInstanceCreation() { assertThat(perfSession.isVerbose()).isTrue(); perfSession.setGaugeAndEventCollectionEnabled(false); assertThat(perfSession.isVerbose()).isFalse(); - assertThat(FirebaseSessionsHelperKt.isAQS(perfSession)).isFalse(); + assertThat(FirebaseSessionsHelperKt.isLegacy(perfSession)).isFalse(); } @Test From 78d4524bf05ec0994f57449d1a2441dbb0903e40 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 8 Apr 2025 17:09:18 -0400 Subject: [PATCH 70/76] Add verbose check --- .../perf/session/FirebasePerformanceSessionSubscriber.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 19133f7555e..485e52ba3bf 100644 --- 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 @@ -31,8 +31,8 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: DebugEnforcementCheck.checkSession(currentPerfSession, "onSessionChanged") // A [PerfSession] was created before a session was started. - // Since these was gauge collection with app startup, it logs them to the updated session ID. - if (currentPerfSession.isLegacy()) { + // Since these were verbose gauge collection at app startup, it logs them to the updated session ID. + if (currentPerfSession.isLegacy() && currentPerfSession.isVerbose) { GaugeManager.getInstance() .logGaugeMetadata(sessionDetails.sessionId, ApplicationProcessState.FOREGROUND) GaugeManager.getInstance() From ebb53259ffeb130749058a79bd257ae5578756aa Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 8 Apr 2025 17:13:12 -0400 Subject: [PATCH 71/76] style --- .../perf/session/FirebasePerformanceSessionSubscriber.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 index 485e52ba3bf..750a82a2008 100644 --- 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 @@ -31,7 +31,8 @@ class FirebasePerformanceSessionSubscriber(override val isDataCollectionEnabled: DebugEnforcementCheck.checkSession(currentPerfSession, "onSessionChanged") // A [PerfSession] was created before a session was started. - // Since these were verbose gauge collection at app startup, it logs them to the updated session ID. + // Since these were verbose gauge collection at app startup, it logs them to the updated session + // ID. if (currentPerfSession.isLegacy() && currentPerfSession.isVerbose) { GaugeManager.getInstance() .logGaugeMetadata(sessionDetails.sessionId, ApplicationProcessState.FOREGROUND) From 281e7b1d785b8119cbbd76bc0c0c1e6ff57f32ee Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 8 Apr 2025 17:27:16 -0400 Subject: [PATCH 72/76] Try simplifying logging --- .../firebase/perf/logging/DebugEnforcementCheck.kt | 9 ++++++++- .../firebase/perf/session/FirebaseSessionsHelper.kt | 7 ++++++- .../firebase/perf/session/gauges/GaugeManager.java | 6 ++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/logging/DebugEnforcementCheck.kt b/firebase-perf/src/main/java/com/google/firebase/perf/logging/DebugEnforcementCheck.kt index 68880860643..ad22eadd59a 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/logging/DebugEnforcementCheck.kt +++ b/firebase-perf/src/main/java/com/google/firebase/perf/logging/DebugEnforcementCheck.kt @@ -25,11 +25,18 @@ class DebugEnforcementCheck { @JvmStatic var enforcement: Boolean = false private var logger: AndroidLogger = AndroidLogger.getInstance() - public fun checkSession(session: PerfSession, failureMessage: String) { + fun checkSession(session: PerfSession, failureMessage: String) { if (session.isLegacy()) { logger.debug("legacy session ${session.sessionId()}: $failureMessage") assert(!enforcement) { failureMessage } } } + + fun checkSession(sessionId: String, failureMessage: String) { + if (sessionId.isLegacy()) { + logger.debug("legacy session ${sessionId}: $failureMessage") + assert(!enforcement) { failureMessage } + } + } } } diff --git a/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebaseSessionsHelper.kt b/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebaseSessionsHelper.kt index a8aee80d129..71e251119f4 100644 --- a/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebaseSessionsHelper.kt +++ b/firebase-perf/src/main/java/com/google/firebase/perf/session/FirebaseSessionsHelper.kt @@ -5,7 +5,12 @@ import java.util.UUID /** Identifies whether the [PerfSession] is legacy or not. */ fun PerfSession.isLegacy(): Boolean { - return this.sessionId().startsWith(Constants.UNDEFINED_AQS_ID_PREFIX) + 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. */ 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 afcf85fc41b..90080e41819 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.logging.DebugEnforcementCheck; import com.google.firebase.perf.session.FirebaseSessionsHelperKt; import com.google.firebase.perf.session.PerfSession; import com.google.firebase.perf.transport.TransportManager; @@ -287,10 +288,7 @@ public void stopCollectingGaugesForLegacySession( * @param appState The app state for which these gauges are collected. */ private void syncFlush(String sessionId, ApplicationProcessState appState) { - if (sessionId.contains(Constants.UNDEFINED_AQS_ID_PREFIX)) { - // TODO(b/394127311): Use DebugEnforcementCheck. - logger.debug("Flushing gauge metrics to a legacy session ID."); - } + DebugEnforcementCheck.Companion.checkSession(sessionId, "syncFlush"); GaugeMetric.Builder gaugeMetricBuilder = GaugeMetric.newBuilder(); // Adding CPU metric readings. From f5a494ed185c0eea2bfcc4543f29ad57a3d959b8 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 8 Apr 2025 17:27:37 -0400 Subject: [PATCH 73/76] style --- .../com/google/firebase/perf/session/gauges/GaugeManager.java | 1 - 1 file changed, 1 deletion(-) 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 90080e41819..2a1669cfd8a 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 @@ -26,7 +26,6 @@ import com.google.firebase.perf.session.FirebaseSessionsHelperKt; import com.google.firebase.perf.session.PerfSession; import com.google.firebase.perf.transport.TransportManager; -import com.google.firebase.perf.util.Constants; import com.google.firebase.perf.util.Timer; import com.google.firebase.perf.v1.AndroidMemoryReading; import com.google.firebase.perf.v1.ApplicationProcessState; From a2d6ed2f8534bcc70380afb54d576b7043579181 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Tue, 8 Apr 2025 17:52:58 -0400 Subject: [PATCH 74/76] Add a legacy sessions build config --- firebase-perf/firebase-perf.gradle | 2 ++ .../java/com/google/firebase/perf/FirebasePerformance.java | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/firebase-perf/firebase-perf.gradle b/firebase-perf/firebase-perf.gradle index 3877763d4bd..0e6c0f47029 100644 --- a/firebase-perf/firebase-perf.gradle +++ b/firebase-perf/firebase-perf.gradle @@ -70,6 +70,7 @@ 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 @@ -77,6 +78,7 @@ android { // 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 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 fa8317af030..5ff288ebb40 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 @@ -44,7 +44,6 @@ 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; @@ -175,7 +174,7 @@ public static FirebasePerformance getInstance() { this.sessionSubscriber = new FirebasePerformanceSessionSubscriber(false); return; } - DebugEnforcementCheck.setEnforcement(BuildConfig.DEBUG); + DebugEnforcementCheck.setEnforcement(BuildConfig.ENFORCE_LEGACY_SESSIONS); TransportManager.getInstance() .initialize(firebaseApp, firebaseInstallationsApi, transportFactoryProvider); From 3f8c550d80fc1b26c691e19fc8e58a5e19251d21 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Wed, 9 Apr 2025 11:25:57 -0400 Subject: [PATCH 75/76] Fix unit tests --- .../java/com/google/firebase/perf/FirebasePerformance.java | 1 + .../com/google/firebase/perf/session/gauges/GaugeManager.java | 3 ++- .../com/google/firebase/perf/session/PerfSessionTest.java | 4 ++-- .../google/firebase/perf/session/gauges/GaugeManagerTest.java | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) 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 5ff288ebb40..3f588443fd8 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 @@ -175,6 +175,7 @@ public static FirebasePerformance getInstance() { return; } DebugEnforcementCheck.setEnforcement(BuildConfig.ENFORCE_LEGACY_SESSIONS); + logger.debug(""); TransportManager.getInstance() .initialize(firebaseApp, firebaseInstallationsApi, transportFactoryProvider); 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 2a1669cfd8a..71b04524ddd 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 @@ -464,7 +464,8 @@ private long getMemoryGaugeCollectionFrequencyMs( private boolean isValidSessionForLogging() { if (session == null) return false; - return session.isGaugeAndEventCollectionEnabled() && FirebaseSessionsHelperKt.isLegacy(session); + return session.isGaugeAndEventCollectionEnabled() + && !FirebaseSessionsHelperKt.isLegacy(session); } @VisibleForTesting 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 174060be881..9e4e3149e62 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 @@ -68,7 +68,7 @@ public void instanceCreation() { assertThat(session.isVerbose()).isTrue(); session.setGaugeAndEventCollectionEnabled(false); assertThat(session.isVerbose()).isFalse(); - assertThat(FirebaseSessionsHelperKt.isLegacy(session)).isTrue(); + assertThat(FirebaseSessionsHelperKt.isLegacy(session)).isFalse(); } @Test @@ -79,7 +79,7 @@ public void legacyInstanceCreation() { assertThat(perfSession.isVerbose()).isTrue(); perfSession.setGaugeAndEventCollectionEnabled(false); assertThat(perfSession.isVerbose()).isFalse(); - assertThat(FirebaseSessionsHelperKt.isLegacy(perfSession)).isFalse(); + assertThat(FirebaseSessionsHelperKt.isLegacy(perfSession)).isTrue(); } @Test 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 1133a0cfe0c..f5731d9182c 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 @@ -34,7 +34,6 @@ import com.google.firebase.perf.config.ConfigResolver; import com.google.firebase.perf.session.PerfSession; import com.google.firebase.perf.transport.TransportManager; -import com.google.firebase.perf.util.Clock; import com.google.firebase.perf.util.Timer; import com.google.firebase.perf.v1.AndroidMemoryReading; import com.google.firebase.perf.v1.ApplicationProcessState; @@ -534,7 +533,8 @@ public void testCollectGaugeMetricOnceCollectAllMetricsWhenCollectionIsEnabled() } private PerfSession createTestPerfSession() { - PerfSession testSession = new PerfSession(UUID.randomUUID().toString(), new Clock()); + PerfSession testSession = + PerfSession.createWithId(UUID.randomUUID().toString().replace("-", "")); testSession.setGaugeAndEventCollectionEnabled(true); return testSession; } From c9c0441b37a86f8a0797bfe81ac55d2a46b535f2 Mon Sep 17 00:00:00 2001 From: Tejas Deshpande Date: Wed, 9 Apr 2025 11:31:13 -0400 Subject: [PATCH 76/76] unused log --- .../main/java/com/google/firebase/perf/FirebasePerformance.java | 1 - 1 file changed, 1 deletion(-) 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 3f588443fd8..5ff288ebb40 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 @@ -175,7 +175,6 @@ public static FirebasePerformance getInstance() { return; } DebugEnforcementCheck.setEnforcement(BuildConfig.ENFORCE_LEGACY_SESSIONS); - logger.debug(""); TransportManager.getInstance() .initialize(firebaseApp, firebaseInstallationsApi, transportFactoryProvider);