Skip to content

Commit 23d1a5e

Browse files
authored
Add fragment trace sampling rate config flag (#3546)
1 parent f73d432 commit 23d1a5e

File tree

4 files changed

+326
-0
lines changed

4 files changed

+326
-0
lines changed

firebase-perf/src/main/java/com/google/firebase/perf/config/ConfigResolver.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.google.firebase.perf.BuildConfig;
2424
import com.google.firebase.perf.config.ConfigurationConstants.CollectionDeactivated;
2525
import com.google.firebase.perf.config.ConfigurationConstants.CollectionEnabled;
26+
import com.google.firebase.perf.config.ConfigurationConstants.FragmentSamplingRate;
2627
import com.google.firebase.perf.config.ConfigurationConstants.LogSourceName;
2728
import com.google.firebase.perf.config.ConfigurationConstants.NetworkEventCountBackground;
2829
import com.google.firebase.perf.config.ConfigurationConstants.NetworkEventCountForeground;
@@ -729,6 +730,43 @@ public String getAndCacheLogSourceName() {
729730
return configFlag.getDefault();
730731
}
731732

733+
/** Returns what percentage of fragment traces should be collected, range is [0.00f, 1.00f]. */
734+
public float getFragmentSamplingRate() {
735+
// Order of precedence is:
736+
// 1. If the value exists in Android Manifest, convert from [0.00f, 100.00f] to [0.00f, 1.00f]
737+
// and return this value.
738+
// 2. If the value exists through Firebase Remote Config, cache and return this value.
739+
// 3. If the value exists in device cache, return this value.
740+
// 4. Otherwise, return default value.
741+
FragmentSamplingRate config = FragmentSamplingRate.getInstance();
742+
743+
// 1. Reads value in Android Manifest (it is set by developers during build time).
744+
Optional<Float> metadataValue = getMetadataFloat(config);
745+
if (metadataValue.isAvailable()) {
746+
// Sampling percentage from metadata needs to convert from [0.00f, 100.00f] to [0.00f, 1.00f].
747+
float samplingRate = metadataValue.get() / 100.0f;
748+
if (isSamplingRateValid(samplingRate)) {
749+
return samplingRate;
750+
}
751+
}
752+
753+
// 2. Reads value from Firebase Remote Config, saves this value in cache layer if valid.
754+
Optional<Float> rcValue = getRemoteConfigFloat(config);
755+
if (rcValue.isAvailable() && isSamplingRateValid(rcValue.get())) {
756+
deviceCacheManager.setValue(config.getDeviceCacheFlag(), rcValue.get());
757+
return rcValue.get();
758+
}
759+
760+
// 3. Reads value from cache layer.
761+
Optional<Float> deviceCacheValue = getDeviceCacheFloat(config);
762+
if (deviceCacheValue.isAvailable() && isSamplingRateValid(deviceCacheValue.get())) {
763+
return deviceCacheValue.get();
764+
}
765+
766+
// 4. Returns default value if there is no valid value from above approaches.
767+
return config.getDefault();
768+
}
769+
732770
// endregion
733771

734772
// Helper functions for interaction with Metadata layer.

firebase-perf/src/main/java/com/google/firebase/perf/config/ConfigurationConstants.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,4 +623,42 @@ protected String getDeviceCacheFlag() {
623623
return "com.google.firebase.perf.LogSourceName";
624624
}
625625
}
626+
627+
protected static final class FragmentSamplingRate extends ConfigurationFlag<Float> {
628+
private static FragmentSamplingRate instance;
629+
630+
private FragmentSamplingRate() {
631+
super();
632+
}
633+
634+
protected static synchronized FragmentSamplingRate getInstance() {
635+
if (instance == null) {
636+
instance = new FragmentSamplingRate();
637+
}
638+
return instance;
639+
}
640+
641+
@Override
642+
protected Float getDefault() {
643+
// Sampling rate range is [0.00f, 1.00f]. By default, sampling rate is 1.00f, which is 100%.
644+
// 0.00f means 0%, Fireperf will not capture any event for fragment trace from the device,
645+
// 1.00f means 100%, Fireperf will capture all events for fragment trace from the device.
646+
return 1.00f;
647+
}
648+
649+
@Override
650+
protected String getRemoteConfigFlag() {
651+
return "fpr_vc_fragment_sampling_rate";
652+
}
653+
654+
@Override
655+
protected String getDeviceCacheFlag() {
656+
return "com.google.firebase.perf.FragmentSamplingRate";
657+
}
658+
659+
@Override
660+
protected String getMetadataFlag() {
661+
return "fragment_sampling_percentage";
662+
}
663+
}
626664
}

firebase-perf/src/test/java/com/google/firebase/perf/config/ConfigResolverTest.java

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@ public class ConfigResolverTest extends FirebasePerformanceTestBase {
126126
private static final String SESSIONS_MAX_DURATION_MIN_CACHE_KEY =
127127
"com.google.firebase.perf.SessionsMaxDurationMinutes";
128128

129+
// Fragment trace sampling rate flags
130+
private static final String FRAGMENT_SAMPLING_RATE_FRC_KEY = "fpr_vc_fragment_sampling_rate";
131+
private static final String FRAGMENT_SAMPLING_RATE_CACHE_KEY =
132+
"com.google.firebase.perf.FragmentSamplingRate";
133+
129134
private ConfigResolver testConfigResolver;
130135

131136
@Mock private RemoteConfigManager mockRemoteConfigManager;
@@ -2482,4 +2487,237 @@ public void getAndCacheLogSourceName_invalidRemoteConfigData_returnsCache() {
24822487
verify(mockDeviceCacheManager, times(1))
24832488
.setValue(eq("com.google.firebase.perf.LogSourceName"), eq("FIREPERF_INTERNAL_LOW"));
24842489
}
2490+
2491+
@Test
2492+
public void getFragmentSamplingRate_validMetadata_returnsMetadata() {
2493+
// #1 pass: Validate that method returns Remote Config Value when there is no metadata value.
2494+
when(mockRemoteConfigManager.getFloat(FRAGMENT_SAMPLING_RATE_FRC_KEY))
2495+
.thenReturn(Optional.of(0.01f));
2496+
2497+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(0.01f);
2498+
2499+
// #2 pass: Validate that method returns Metadata value which takes higher precedence.
2500+
Bundle bundle = new Bundle();
2501+
bundle.putFloat("fragment_sampling_percentage", 20.0f);
2502+
testConfigResolver.setMetadataBundle(new ImmutableBundle(bundle));
2503+
2504+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(0.2f);
2505+
}
2506+
2507+
@Test
2508+
public void getFragmentSamplingRate_validMetadata_notSaveMetadataInCache() {
2509+
Bundle bundle = new Bundle();
2510+
bundle.putFloat("fragment_sampling_percentage", 20.0f);
2511+
testConfigResolver.setMetadataBundle(new ImmutableBundle(bundle));
2512+
2513+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(0.2f);
2514+
2515+
verify(mockDeviceCacheManager, never()).setValue(any(), any());
2516+
}
2517+
2518+
@Test
2519+
public void getFragmentSamplingRate_invalidAndroidMetadataBundle_returnDefaultValue() {
2520+
when(mockRemoteConfigManager.getFloat(FRAGMENT_SAMPLING_RATE_FRC_KEY))
2521+
.thenReturn(Optional.absent());
2522+
when(mockDeviceCacheManager.getFloat(FRAGMENT_SAMPLING_RATE_CACHE_KEY))
2523+
.thenReturn(Optional.absent());
2524+
2525+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(1.0f);
2526+
2527+
// Case #1: Android Metadata bundle value is too high.
2528+
Bundle bundle = new Bundle();
2529+
bundle.putFloat("fragment_sampling_percentage", 200.00f);
2530+
testConfigResolver.setMetadataBundle(new ImmutableBundle(bundle));
2531+
2532+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(1.0f);
2533+
2534+
// Case #2: Android Metadata bundle value is too low.
2535+
bundle = new Bundle();
2536+
bundle.putFloat("fragment_sampling_percentage", -1.00f);
2537+
testConfigResolver.setMetadataBundle(new ImmutableBundle(bundle));
2538+
2539+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(1.0f);
2540+
}
2541+
2542+
@Test
2543+
public void getFragmentSamplingRate_invalidAndroidMetadataBundle_returnRemoteConfigValue() {
2544+
when(mockRemoteConfigManager.getFloat(FRAGMENT_SAMPLING_RATE_FRC_KEY))
2545+
.thenReturn(Optional.of(0.25f));
2546+
2547+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(0.25f);
2548+
2549+
// Case #1: Android Metadata bundle value is too high.
2550+
Bundle bundle = new Bundle();
2551+
bundle.putFloat("fragment_sampling_percentage", 200.00f);
2552+
testConfigResolver.setMetadataBundle(new ImmutableBundle(bundle));
2553+
2554+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(0.25f);
2555+
2556+
// Case #2: Android Metadata bundle value is too low.
2557+
bundle = new Bundle();
2558+
bundle.putFloat("fragment_sampling_percentage", -1.00f);
2559+
testConfigResolver.setMetadataBundle(new ImmutableBundle(bundle));
2560+
2561+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(0.25f);
2562+
}
2563+
2564+
@Test
2565+
public void getFragmentSamplingRate_invalidMetadataBundle_returnCacheValue() {
2566+
when(mockRemoteConfigManager.getFloat(FRAGMENT_SAMPLING_RATE_FRC_KEY))
2567+
.thenReturn(Optional.absent());
2568+
when(mockDeviceCacheManager.getFloat(FRAGMENT_SAMPLING_RATE_CACHE_KEY))
2569+
.thenReturn(Optional.of(1.0f));
2570+
2571+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(1.0f);
2572+
2573+
// Case #1: Android Metadata bundle value is too high.
2574+
Bundle bundle = new Bundle();
2575+
bundle.putFloat("fragment_sampling_percentage", 200.00f);
2576+
testConfigResolver.setMetadataBundle(new ImmutableBundle(bundle));
2577+
2578+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(1.0f);
2579+
2580+
// Case #2: Android Metadata bundle value is too low.
2581+
bundle = new Bundle();
2582+
bundle.putFloat("fragment_sampling_percentage", -1.00f);
2583+
testConfigResolver.setMetadataBundle(new ImmutableBundle(bundle));
2584+
2585+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(1.0f);
2586+
}
2587+
2588+
@Test
2589+
public void getFragmentSamplingRate_validRemoteConfig_returnRemoteConfigValue() {
2590+
when(mockRemoteConfigManager.getFloat(FRAGMENT_SAMPLING_RATE_FRC_KEY))
2591+
.thenReturn(Optional.of(0.25f));
2592+
2593+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(0.25f);
2594+
verify(mockDeviceCacheManager, times(1))
2595+
.setValue(eq(FRAGMENT_SAMPLING_RATE_CACHE_KEY), eq(0.25f));
2596+
2597+
when(mockRemoteConfigManager.getFloat(FRAGMENT_SAMPLING_RATE_FRC_KEY))
2598+
.thenReturn(Optional.of(0.0f));
2599+
2600+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(0.0f);
2601+
verify(mockDeviceCacheManager, times(1))
2602+
.setValue(eq(FRAGMENT_SAMPLING_RATE_CACHE_KEY), eq(0.0f));
2603+
2604+
when(mockRemoteConfigManager.getFloat(FRAGMENT_SAMPLING_RATE_FRC_KEY))
2605+
.thenReturn(Optional.of(0.00005f));
2606+
2607+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(0.00005f);
2608+
verify(mockDeviceCacheManager, times(1))
2609+
.setValue(eq(FRAGMENT_SAMPLING_RATE_CACHE_KEY), eq(0.00005f));
2610+
2611+
when(mockRemoteConfigManager.getFloat(FRAGMENT_SAMPLING_RATE_FRC_KEY))
2612+
.thenReturn(Optional.of(0.0000000001f));
2613+
2614+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(0.0000000001f);
2615+
verify(mockDeviceCacheManager, times(1))
2616+
.setValue(eq(FRAGMENT_SAMPLING_RATE_CACHE_KEY), eq(0.0000000001f));
2617+
}
2618+
2619+
@Test
2620+
public void getFragmentSamplingRate_invalidRemoteConfig_returnDefaultValue() {
2621+
// Mock behavior that device cache doesn't have session sampling rate value.
2622+
when(mockDeviceCacheManager.getFloat(FRAGMENT_SAMPLING_RATE_CACHE_KEY))
2623+
.thenReturn(Optional.absent());
2624+
2625+
// Case #1: Firebase Remote Config value is too high.
2626+
when(mockRemoteConfigManager.getFloat(FRAGMENT_SAMPLING_RATE_FRC_KEY))
2627+
.thenReturn(Optional.of(1.01f));
2628+
2629+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(1.0f);
2630+
verify(mockDeviceCacheManager, never()).setValue(any(), any());
2631+
2632+
// Case #2: Firebase Remote Config value is too low.
2633+
when(mockRemoteConfigManager.getFloat(FRAGMENT_SAMPLING_RATE_FRC_KEY))
2634+
.thenReturn(Optional.of(-0.1f));
2635+
2636+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(1.0f);
2637+
verify(mockDeviceCacheManager, never()).setValue(any(), any());
2638+
}
2639+
2640+
@Test
2641+
public void getFragmentSamplingRate_invalidRemoteConfig_returnCacheValue() {
2642+
// Mock behavior that device cache doesn't have session sampling rate value.
2643+
when(mockDeviceCacheManager.getFloat(FRAGMENT_SAMPLING_RATE_CACHE_KEY))
2644+
.thenReturn(Optional.of(0.25f));
2645+
2646+
// Case #1: Firebase Remote Config value is too high.
2647+
when(mockRemoteConfigManager.getFloat(FRAGMENT_SAMPLING_RATE_FRC_KEY))
2648+
.thenReturn(Optional.of(1.01f));
2649+
2650+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(0.25f);
2651+
verify(mockDeviceCacheManager, never()).setValue(any(), any());
2652+
2653+
// Case #2: Firebase Remote Config value is too low.
2654+
when(mockRemoteConfigManager.getFloat(FRAGMENT_SAMPLING_RATE_FRC_KEY))
2655+
.thenReturn(Optional.of(-0.1f));
2656+
2657+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(0.25f);
2658+
verify(mockDeviceCacheManager, never()).setValue(any(), any());
2659+
}
2660+
2661+
@Test
2662+
public void getFragmentSamplingRate_validCache_returnCacheValue() {
2663+
when(mockDeviceCacheManager.getFloat(FRAGMENT_SAMPLING_RATE_CACHE_KEY))
2664+
.thenReturn(Optional.of(1.0f));
2665+
2666+
when(mockRemoteConfigManager.getFloat(FRAGMENT_SAMPLING_RATE_FRC_KEY))
2667+
.thenReturn(Optional.absent());
2668+
2669+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(1.0f);
2670+
}
2671+
2672+
@Test
2673+
public void getFragmentSamplingRate_invalidCache_returnDefaultValue() {
2674+
// Mock behavior that remote config doesn't have session sampling rate value.
2675+
when(mockRemoteConfigManager.getFloat(FRAGMENT_SAMPLING_RATE_FRC_KEY))
2676+
.thenReturn(Optional.absent());
2677+
2678+
// Case #1: Device Cache value is too high.
2679+
when(mockDeviceCacheManager.getFloat(FRAGMENT_SAMPLING_RATE_CACHE_KEY))
2680+
.thenReturn(Optional.of(10.0f));
2681+
2682+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(1.0f);
2683+
2684+
// Case #2: Device Cache value is too low.
2685+
when(mockDeviceCacheManager.getFloat(FRAGMENT_SAMPLING_RATE_CACHE_KEY))
2686+
.thenReturn(Optional.of(-1.0f));
2687+
2688+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(1.0f);
2689+
}
2690+
2691+
@Test
2692+
public void
2693+
getFragmentSamplingRate_metadataAndRemoteConfigAndCacheAreSet_metadataHasHighestConfigPrecedence() {
2694+
// Set cache value.
2695+
when(mockDeviceCacheManager.getFloat(FRAGMENT_SAMPLING_RATE_CACHE_KEY))
2696+
.thenReturn(Optional.of(0.2f));
2697+
2698+
// Set remote config value.
2699+
when(mockRemoteConfigManager.getFloat(FRAGMENT_SAMPLING_RATE_FRC_KEY))
2700+
.thenReturn(Optional.of(0.3f));
2701+
2702+
// Set Android Manifest value.
2703+
Bundle bundle = new Bundle();
2704+
bundle.putFloat("fragment_sampling_percentage", 4.0f);
2705+
testConfigResolver.setMetadataBundle(new ImmutableBundle(bundle));
2706+
2707+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(0.04f);
2708+
}
2709+
2710+
@Test
2711+
public void
2712+
getFragmentSamplingRate_remoteConfigAndCacheAreSet_remoteConfigHasHighestConfigPrecedence() {
2713+
// Set cache value.
2714+
when(mockDeviceCacheManager.getFloat(FRAGMENT_SAMPLING_RATE_CACHE_KEY))
2715+
.thenReturn(Optional.of(0.2f));
2716+
2717+
// Set remote config value.
2718+
when(mockRemoteConfigManager.getFloat(FRAGMENT_SAMPLING_RATE_FRC_KEY))
2719+
.thenReturn(Optional.of(0.3f));
2720+
2721+
assertThat(testConfigResolver.getFragmentSamplingRate()).isEqualTo(0.3f);
2722+
}
24852723
}

firebase-perf/src/test/java/com/google/firebase/perf/config/ConfigurationConstantsTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.google.firebase.perf.config.ConfigurationConstants.CollectionDeactivated;
2020
import com.google.firebase.perf.config.ConfigurationConstants.CollectionEnabled;
21+
import com.google.firebase.perf.config.ConfigurationConstants.FragmentSamplingRate;
2122
import com.google.firebase.perf.config.ConfigurationConstants.LogSourceName;
2223
import com.google.firebase.perf.config.ConfigurationConstants.NetworkEventCountBackground;
2324
import com.google.firebase.perf.config.ConfigurationConstants.NetworkEventCountForeground;
@@ -253,4 +254,15 @@ public void getInstance_LogSourceName_validateConstants() {
253254
assertThat(LogSourceName.getLogSourceName(675L)).isEqualTo("FIREPERF_INTERNAL_LOW");
254255
assertThat(LogSourceName.getLogSourceName(676L)).isEqualTo("FIREPERF_INTERNAL_HIGH");
255256
}
257+
258+
@Test
259+
public void getInstance_FragmentSamplingRate_validateConstants() {
260+
FragmentSamplingRate configFlag = FragmentSamplingRate.getInstance();
261+
262+
assertThat(configFlag.getDefault()).isEqualTo(1.0f);
263+
assertThat(configFlag.getDeviceCacheFlag())
264+
.isEqualTo("com.google.firebase.perf.FragmentSamplingRate");
265+
assertThat(configFlag.getRemoteConfigFlag()).isEqualTo("fpr_vc_fragment_sampling_rate");
266+
assertThat(configFlag.getMetadataFlag()).isEqualTo("fragment_sampling_percentage");
267+
}
256268
}

0 commit comments

Comments
 (0)