Skip to content

Commit 26c905b

Browse files
jeremyjiang-devvisumickeyleotianlizhan
authored
Fireperf: Add Fragment Screen Performance Support (#3591)
* Add fragment trace sampling rate config flag (#3546) * Fireperf: fragment lifecycle callbacks (#3565) * onResume and onPause * copyright * rename to * more specific language * Fireperf fragments: trace creation and adding custom attributes (#3575) * implementation * test * gjf * ebugfix * copyright * add tests * long name test * fix test * change error to warn message * Fix hasFrameMetricsAggregator's value not being set. * Fix googleJavaFormat error * Add frame metrics to fragment traces (#3592) * Add frame metrics to fragment traces. * Fix AppStateMonitor.java * Rename FrameMetrics to PerfFrameMetrics * Fix screen trace logging by printing the trace name. (#3599) * Fireperf fragments: sampling (#3588) * ssample fragment after trace already sampled * gjf * tests and qol for immutable bundle * test names and revert getFloat * fragment-sampling * fix test * separate bucketId checkArguments * Fireperf fragments eap: change version number (#3604) * change version number * changelog * changelog edit * Update the gradle properties to match the EAP release branch (#3606) * Update the gradle properties to match the release branch * Revert ktx gradle properties * default no0 sampling rate * gradle.properties revert * gradle.properties revert 2 * fix unit tests * review Co-authored-by: Visu <[email protected]> Co-authored-by: Leo Zhan <[email protected]>
1 parent a751091 commit 26c905b

File tree

17 files changed

+1309
-76
lines changed

17 files changed

+1309
-76
lines changed

firebase-perf/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Refer [GMaven](https://maven.google.com/web/index.html?q=firebase-perf#com.googl
2222
## Unreleased
2323

2424
* {{fixed}} Fixed a bug where screen traces were not capturing frame metrics for multi-activity apps.
25+
* {{feature}} Added support for measuring screen performance metrics for "Activity Fragments" out-of-the-box.
2526

2627
## Released
2728

firebase-perf/dev-app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
<meta-data
3636
android:name="sessions_sampling_percentage"
3737
android:value="100.0" />
38+
<meta-data
39+
android:name="fragment_sampling_percentage"
40+
android:value="100.0" />
3841

3942
<receiver
4043
android:name=".FirebasePerfTestReceiver"

firebase-perf/e2e-app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
<meta-data
3434
android:name="sessions_sampling_percentage"
3535
android:value="100.0" />
36+
<meta-data
37+
android:name="fragment_sampling_percentage"
38+
android:value="100.0" />
3639
</application>
3740

3841
</manifest>

firebase-perf/src/main/java/com/google/firebase/perf/application/AppStateMonitor.java

Lines changed: 43 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,21 @@
1919
import android.app.Application.ActivityLifecycleCallbacks;
2020
import android.content.Context;
2121
import android.os.Bundle;
22-
import android.util.SparseIntArray;
2322
import androidx.annotation.NonNull;
2423
import androidx.core.app.FrameMetricsAggregator;
24+
import androidx.fragment.app.FragmentActivity;
2525
import com.google.android.gms.common.util.VisibleForTesting;
2626
import com.google.firebase.perf.config.ConfigResolver;
2727
import com.google.firebase.perf.logging.AndroidLogger;
28+
import com.google.firebase.perf.metrics.FrameMetricsCalculator;
2829
import com.google.firebase.perf.metrics.Trace;
2930
import com.google.firebase.perf.session.SessionManager;
3031
import com.google.firebase.perf.transport.TransportManager;
3132
import com.google.firebase.perf.util.Clock;
3233
import com.google.firebase.perf.util.Constants;
3334
import com.google.firebase.perf.util.Constants.CounterNames;
35+
import com.google.firebase.perf.util.ScreenTraceUtil;
3436
import com.google.firebase.perf.util.Timer;
35-
import com.google.firebase.perf.util.Utils;
3637
import com.google.firebase.perf.v1.ApplicationProcessState;
3738
import com.google.firebase.perf.v1.TraceMetric;
3839
import java.lang.ref.WeakReference;
@@ -52,6 +53,7 @@ public class AppStateMonitor implements ActivityLifecycleCallbacks {
5253
"androidx.core.app.FrameMetricsAggregator";
5354

5455
private static volatile AppStateMonitor instance;
56+
private static boolean hasFrameMetricsAggregator = false;
5557

5658
private final WeakHashMap<Activity, Boolean> activityToResumedMap = new WeakHashMap<>();
5759
private final WeakHashMap<Activity, Trace> activityToScreenTraceMap = new WeakHashMap<>();
@@ -75,7 +77,6 @@ public class AppStateMonitor implements ActivityLifecycleCallbacks {
7577

7678
private boolean isRegisteredForLifecycleCallbacks = false;
7779
private boolean isColdStart = true;
78-
private boolean hasFrameMetricsAggregator = false;
7980

8081
public static AppStateMonitor getInstance() {
8182
if (instance == null) {
@@ -89,13 +90,22 @@ public static AppStateMonitor getInstance() {
8990
}
9091

9192
AppStateMonitor(TransportManager transportManager, Clock clock) {
93+
this(
94+
transportManager,
95+
clock,
96+
ConfigResolver.getInstance(),
97+
hasFrameMetricsAggregatorClass() ? new FrameMetricsAggregator() : null);
98+
}
99+
100+
AppStateMonitor(
101+
TransportManager transportManager,
102+
Clock clock,
103+
ConfigResolver configResolver,
104+
FrameMetricsAggregator frameMetricsAggregator) {
92105
this.transportManager = transportManager;
93106
this.clock = clock;
94-
configResolver = ConfigResolver.getInstance();
95-
hasFrameMetricsAggregator = hasFrameMetricsAggregatorClass();
96-
if (hasFrameMetricsAggregator) {
97-
frameMetricsAggregator = new FrameMetricsAggregator();
98-
}
107+
this.configResolver = configResolver;
108+
this.frameMetricsAggregator = frameMetricsAggregator;
99109
}
100110

101111
public synchronized void registerActivityLifecycleCallbacks(Context context) {
@@ -140,7 +150,18 @@ public void incrementTsnsCount(int value) {
140150
}
141151

142152
@Override
143-
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
153+
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
154+
if (isScreenTraceSupported() && configResolver.isPerformanceMonitoringEnabled()) {
155+
if (activity instanceof FragmentActivity) {
156+
FragmentActivity fragmentActivity = (FragmentActivity) activity;
157+
fragmentActivity
158+
.getSupportFragmentManager()
159+
.registerFragmentLifecycleCallbacks(
160+
new FragmentStateMonitor(clock, transportManager, this, frameMetricsAggregator),
161+
true);
162+
}
163+
}
164+
}
144165

145166
@Override
146167
public void onActivityDestroyed(Activity activity) {}
@@ -192,7 +213,7 @@ public synchronized void onActivityResumed(Activity activity) {
192213
}
193214

194215
// Screen trace is after session update so the sessionId is not added twice to the Trace
195-
if (isScreenTraceSupported(activity) && configResolver.isPerformanceMonitoringEnabled()) {
216+
if (isScreenTraceSupported() && configResolver.isPerformanceMonitoringEnabled()) {
196217
// Starts recording frame metrics for this activity.
197218
/**
198219
* TODO: Only add activities that are hardware acceleration enabled so that calling {@link
@@ -297,7 +318,7 @@ public void onActivityPaused(Activity activity) {}
297318
/** Stops screen trace right after onPause because of b/210055697 */
298319
@Override
299320
public void onActivityPostPaused(@NonNull Activity activity) {
300-
if (isScreenTraceSupported(activity)) {
321+
if (isScreenTraceSupported()) {
301322
sendScreenTrace(activity);
302323
}
303324
}
@@ -333,50 +354,15 @@ private void sendScreenTrace(Activity activity) {
333354
} catch (IllegalArgumentException ignored) {
334355
logger.debug("View not hardware accelerated. Unable to collect screen trace.");
335356
}
336-
SparseIntArray[] arr = frameMetricsAggregator.reset();
337-
if (arr != null) {
338-
SparseIntArray frameTimes = arr[FrameMetricsAggregator.TOTAL_INDEX];
339-
if (frameTimes != null) {
340-
for (int i = 0; i < frameTimes.size(); i++) {
341-
int frameTime = frameTimes.keyAt(i);
342-
int numFrames = frameTimes.valueAt(i);
343-
totalFrames += numFrames;
344-
if (frameTime > Constants.FROZEN_FRAME_TIME) {
345-
// Frozen frames mean the app appear frozen. The recommended thresholds is 700ms
346-
frozenFrames += numFrames;
347-
}
348-
if (frameTime > Constants.SLOW_FRAME_TIME) {
349-
// Slow frames are anything above 16ms (i.e. 60 frames/second)
350-
slowFrames += numFrames;
351-
}
352-
}
353-
}
354-
}
355-
if (totalFrames == 0 && slowFrames == 0 && frozenFrames == 0) {
357+
FrameMetricsCalculator.PerfFrameMetrics perfFrameMetrics =
358+
FrameMetricsCalculator.calculateFrameMetrics(frameMetricsAggregator.reset());
359+
if (perfFrameMetrics.getTotalFrames() == 0
360+
&& perfFrameMetrics.getSlowFrames() == 0
361+
&& perfFrameMetrics.getFrozenFrames() == 0) {
356362
// All metrics are zero, no need to send screen trace.
357-
// return;
358-
}
359-
// Only incrementMetric if corresponding metric is non-zero.
360-
if (totalFrames > 0) {
361-
screenTrace.putMetric(Constants.CounterNames.FRAMES_TOTAL.toString(), totalFrames);
362-
}
363-
if (slowFrames > 0) {
364-
screenTrace.putMetric(Constants.CounterNames.FRAMES_SLOW.toString(), slowFrames);
365-
}
366-
if (frozenFrames > 0) {
367-
screenTrace.putMetric(Constants.CounterNames.FRAMES_FROZEN.toString(), frozenFrames);
368-
}
369-
if (Utils.isDebugLoggingEnabled(activity.getApplicationContext())) {
370-
logger.debug(
371-
"sendScreenTrace name:"
372-
+ getScreenTraceName(activity)
373-
+ " _fr_tot:"
374-
+ totalFrames
375-
+ " _fr_slo:"
376-
+ slowFrames
377-
+ " _fr_fzn:"
378-
+ frozenFrames);
363+
return;
379364
}
365+
ScreenTraceUtil.addFrameCounters(screenTrace, perfFrameMetrics);
380366
// Stop and record trace
381367
screenTrace.stop();
382368
}
@@ -418,10 +404,9 @@ private void sendSessionLog(String name, Timer startTime, Timer endTime) {
418404
/**
419405
* Only send screen trace if FrameMetricsAggregator exists.
420406
*
421-
* @param activity The Activity for which we're monitoring the screen rendering performance.
422407
* @return true if supported, false if not.
423408
*/
424-
private boolean isScreenTraceSupported(Activity activity) {
409+
protected boolean isScreenTraceSupported() {
425410
return hasFrameMetricsAggregator;
426411
}
427412

@@ -430,11 +415,13 @@ private boolean isScreenTraceSupported(Activity activity) {
430415
* updated to 26.1.0 (b/69954793), there will be ClassNotFoundException. This method is to check
431416
* if FrameMetricsAggregator exists to avoid ClassNotFoundException.
432417
*/
433-
private boolean hasFrameMetricsAggregatorClass() {
418+
private static boolean hasFrameMetricsAggregatorClass() {
434419
try {
435420
Class<?> initializerClass = Class.forName(FRAME_METRICS_AGGREGATOR_CLASSNAME);
421+
hasFrameMetricsAggregator = true;
436422
return true;
437423
} catch (ClassNotFoundException e) {
424+
hasFrameMetricsAggregator = false;
438425
return false;
439426
}
440427
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.perf.application;
16+
17+
import androidx.annotation.NonNull;
18+
import androidx.core.app.FrameMetricsAggregator;
19+
import androidx.fragment.app.Fragment;
20+
import androidx.fragment.app.FragmentManager;
21+
import com.google.android.gms.common.util.VisibleForTesting;
22+
import com.google.firebase.perf.logging.AndroidLogger;
23+
import com.google.firebase.perf.metrics.FrameMetricsCalculator;
24+
import com.google.firebase.perf.metrics.FrameMetricsCalculator.PerfFrameMetrics;
25+
import com.google.firebase.perf.metrics.Trace;
26+
import com.google.firebase.perf.transport.TransportManager;
27+
import com.google.firebase.perf.util.Clock;
28+
import com.google.firebase.perf.util.Constants;
29+
import com.google.firebase.perf.util.ScreenTraceUtil;
30+
import java.util.WeakHashMap;
31+
32+
public class FragmentStateMonitor extends FragmentManager.FragmentLifecycleCallbacks {
33+
private static final AndroidLogger logger = AndroidLogger.getInstance();
34+
private final WeakHashMap<Fragment, Trace> fragmentToTraceMap = new WeakHashMap<>();
35+
private final WeakHashMap<Fragment, PerfFrameMetrics> fragmentToMetricsMap = new WeakHashMap<>();
36+
private final Clock clock;
37+
private final TransportManager transportManager;
38+
private final AppStateMonitor appStateMonitor;
39+
private final FrameMetricsAggregator frameMetricsAggregator;
40+
41+
public FragmentStateMonitor(
42+
Clock clock,
43+
TransportManager transportManager,
44+
AppStateMonitor appStateMonitor,
45+
FrameMetricsAggregator fma) {
46+
this.clock = clock;
47+
this.transportManager = transportManager;
48+
this.appStateMonitor = appStateMonitor;
49+
this.frameMetricsAggregator = fma;
50+
}
51+
52+
/**
53+
* Fragment screen trace name is prefix "_st_" concatenates with Fragment's class name.
54+
*
55+
* @param fragment fragment object.
56+
* @return Fragment screen trace name.
57+
*/
58+
public String getFragmentScreenTraceName(Fragment fragment) {
59+
return Constants.SCREEN_TRACE_PREFIX + fragment.getClass().getSimpleName();
60+
}
61+
62+
@Override
63+
public void onFragmentResumed(@NonNull FragmentManager fm, @NonNull Fragment f) {
64+
super.onFragmentResumed(fm, f);
65+
// Start Fragment screen trace
66+
logger.debug("FragmentMonitor %s.onFragmentResumed", f.getClass().getSimpleName());
67+
Trace fragmentTrace =
68+
new Trace(getFragmentScreenTraceName(f), transportManager, clock, appStateMonitor);
69+
fragmentTrace.start();
70+
71+
if (f.getParentFragment() != null) {
72+
fragmentTrace.putAttribute(
73+
Constants.PARENT_FRAGMENT_ATTRIBUTE_KEY,
74+
f.getParentFragment().getClass().getSimpleName());
75+
}
76+
if (f.getActivity() != null) {
77+
fragmentTrace.putAttribute(
78+
Constants.ACTIVITY_ATTRIBUTE_KEY, f.getActivity().getClass().getSimpleName());
79+
}
80+
fragmentToTraceMap.put(f, fragmentTrace);
81+
82+
PerfFrameMetrics perfFrameMetrics =
83+
FrameMetricsCalculator.calculateFrameMetrics(this.frameMetricsAggregator.getMetrics());
84+
fragmentToMetricsMap.put(f, perfFrameMetrics);
85+
}
86+
87+
@Override
88+
public void onFragmentPaused(@NonNull FragmentManager fm, @NonNull Fragment f) {
89+
super.onFragmentPaused(fm, f);
90+
// Stop Fragment screen trace
91+
logger.debug("FragmentMonitor %s.onFragmentPaused ", f.getClass().getSimpleName());
92+
if (!fragmentToTraceMap.containsKey(f)) {
93+
logger.warn("FragmentMonitor: missed a fragment trace from %s", f.getClass().getSimpleName());
94+
return;
95+
}
96+
97+
Trace fragmentTrace = fragmentToTraceMap.get(f);
98+
fragmentToTraceMap.remove(f);
99+
PerfFrameMetrics prePerfFrameMetrics = fragmentToMetricsMap.get(f);
100+
fragmentToMetricsMap.remove(f);
101+
102+
PerfFrameMetrics curPerfFrameMetrics =
103+
FrameMetricsCalculator.calculateFrameMetrics(this.frameMetricsAggregator.getMetrics());
104+
105+
int totalFrames = curPerfFrameMetrics.getTotalFrames() - prePerfFrameMetrics.getTotalFrames();
106+
int slowFrames = curPerfFrameMetrics.getSlowFrames() - prePerfFrameMetrics.getSlowFrames();
107+
int frozenFrames =
108+
curPerfFrameMetrics.getFrozenFrames() - prePerfFrameMetrics.getFrozenFrames();
109+
110+
if (totalFrames == 0 && slowFrames == 0 && frozenFrames == 0) {
111+
// All metrics are zero, no need to send screen trace.
112+
return;
113+
}
114+
ScreenTraceUtil.addFrameCounters(
115+
fragmentTrace, new PerfFrameMetrics(totalFrames, slowFrames, frozenFrames));
116+
fragmentTrace.stop();
117+
}
118+
119+
@VisibleForTesting
120+
WeakHashMap<Fragment, Trace> getFragmentToTraceMap() {
121+
return fragmentToTraceMap;
122+
}
123+
124+
@VisibleForTesting
125+
WeakHashMap<Fragment, PerfFrameMetrics> getFragmentToMetricsMap() {
126+
return fragmentToMetricsMap;
127+
}
128+
}

0 commit comments

Comments
 (0)