Skip to content

Commit 316735f

Browse files
Fireperf: use the new class FrameMetricsRecorder in AppStateMonitor and FragmentStateMonitor (#3683)
1 parent c76d022 commit 316735f

File tree

5 files changed

+195
-233
lines changed

5 files changed

+195
-233
lines changed

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

Lines changed: 64 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,18 @@
2020
import android.content.Context;
2121
import android.os.Bundle;
2222
import androidx.annotation.NonNull;
23-
import androidx.core.app.FrameMetricsAggregator;
2423
import androidx.fragment.app.FragmentActivity;
2524
import com.google.android.gms.common.util.VisibleForTesting;
2625
import com.google.firebase.perf.config.ConfigResolver;
2726
import com.google.firebase.perf.logging.AndroidLogger;
28-
import com.google.firebase.perf.metrics.FrameMetricsCalculator;
27+
import com.google.firebase.perf.metrics.FrameMetricsCalculator.PerfFrameMetrics;
2928
import com.google.firebase.perf.metrics.Trace;
3029
import com.google.firebase.perf.session.SessionManager;
3130
import com.google.firebase.perf.transport.TransportManager;
3231
import com.google.firebase.perf.util.Clock;
3332
import com.google.firebase.perf.util.Constants;
3433
import com.google.firebase.perf.util.Constants.CounterNames;
34+
import com.google.firebase.perf.util.Optional;
3535
import com.google.firebase.perf.util.ScreenTraceUtil;
3636
import com.google.firebase.perf.util.Timer;
3737
import com.google.firebase.perf.v1.ApplicationProcessState;
@@ -49,13 +49,16 @@
4949
public class AppStateMonitor implements ActivityLifecycleCallbacks {
5050

5151
private static final AndroidLogger logger = AndroidLogger.getInstance();
52-
private static final String FRAME_METRICS_AGGREGATOR_CLASSNAME =
53-
"androidx.core.app.FrameMetricsAggregator";
5452

5553
private static volatile AppStateMonitor instance;
56-
private static boolean hasFrameMetricsAggregator = false;
5754

5855
private final WeakHashMap<Activity, Boolean> activityToResumedMap = new WeakHashMap<>();
56+
private final WeakHashMap<Activity, FrameMetricsRecorder> activityToRecorderMap =
57+
new WeakHashMap<>();
58+
59+
// Map for holding the fragment state monitor to remove receiving the fragment state callbacks
60+
private final WeakHashMap<Activity, FragmentStateMonitor> activityToFragmentStateMonitorMap =
61+
new WeakHashMap<>();
5962
private final WeakHashMap<Activity, Trace> activityToScreenTraceMap = new WeakHashMap<>();
6063
private final Map<String, Long> metricToCountMap = new HashMap<>();
6164
private final Set<WeakReference<AppStateCallback>> appStateSubscribers = new HashSet<>();
@@ -67,8 +70,7 @@ public class AppStateMonitor implements ActivityLifecycleCallbacks {
6770
private final TransportManager transportManager;
6871
private final ConfigResolver configResolver;
6972
private final Clock clock;
70-
71-
private FrameMetricsAggregator frameMetricsAggregator;
73+
private final boolean screenPerformanceRecordingSupported;
7274

7375
private Timer resumeTime; // The time app comes to foreground
7476
private Timer stopTime; // The time app goes to background
@@ -94,18 +96,19 @@ public static AppStateMonitor getInstance() {
9496
transportManager,
9597
clock,
9698
ConfigResolver.getInstance(),
97-
hasFrameMetricsAggregatorClass() ? new FrameMetricsAggregator() : null);
99+
isScreenPerformanceRecordingSupported());
98100
}
99101

102+
@VisibleForTesting
100103
AppStateMonitor(
101104
TransportManager transportManager,
102105
Clock clock,
103106
ConfigResolver configResolver,
104-
FrameMetricsAggregator frameMetricsAggregator) {
107+
boolean screenPerformanceRecordingSupported) {
105108
this.transportManager = transportManager;
106109
this.clock = clock;
107110
this.configResolver = configResolver;
108-
this.frameMetricsAggregator = frameMetricsAggregator;
111+
this.screenPerformanceRecordingSupported = screenPerformanceRecordingSupported;
109112
}
110113

111114
public synchronized void registerActivityLifecycleCallbacks(Context context) {
@@ -149,28 +152,64 @@ public void incrementTsnsCount(int value) {
149152
tsnsCount.addAndGet(value);
150153
}
151154

152-
@Override
153-
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
155+
// Starts tracking the frame metrics for an activity.
156+
private void startFrameMonitoring(Activity activity) {
154157
if (isScreenTraceSupported() && configResolver.isPerformanceMonitoringEnabled()) {
158+
FrameMetricsRecorder recorder = new FrameMetricsRecorder(activity);
159+
activityToRecorderMap.put(activity, recorder);
155160
if (activity instanceof FragmentActivity) {
161+
FragmentStateMonitor fragmentStateMonitor =
162+
new FragmentStateMonitor(clock, transportManager, this, recorder);
163+
activityToFragmentStateMonitorMap.put(activity, fragmentStateMonitor);
156164
FragmentActivity fragmentActivity = (FragmentActivity) activity;
157165
fragmentActivity
158166
.getSupportFragmentManager()
159-
.registerFragmentLifecycleCallbacks(
160-
new FragmentStateMonitor(clock, transportManager, this, frameMetricsAggregator),
161-
true);
167+
.registerFragmentLifecycleCallbacks(fragmentStateMonitor, true);
162168
}
163169
}
164170
}
165171

166172
@Override
167-
public void onActivityDestroyed(Activity activity) {}
173+
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
174+
startFrameMonitoring(activity);
175+
}
168176

169177
@Override
170-
public synchronized void onActivityStarted(Activity activity) {}
178+
public void onActivityDestroyed(Activity activity) {
179+
// Dereference FrameMetricsRecorder from the map because it holds an Activity reference
180+
activityToRecorderMap.remove(activity);
181+
// Dereference FragmentStateMonitor because it holds a FrameMetricsRecorder reference
182+
if (activityToFragmentStateMonitorMap.containsKey(activity)) {
183+
FragmentActivity fragmentActivity = (FragmentActivity) activity;
184+
fragmentActivity
185+
.getSupportFragmentManager()
186+
.unregisterFragmentLifecycleCallbacks(activityToFragmentStateMonitorMap.remove(activity));
187+
}
188+
}
189+
190+
@Override
191+
public synchronized void onActivityStarted(Activity activity) {
192+
if (isScreenTraceSupported() && configResolver.isPerformanceMonitoringEnabled()) {
193+
if (!activityToRecorderMap.containsKey(activity)) {
194+
// If performance monitoring is disabled at start and enabled at runtime, start monitoring
195+
// the activity as the app comes to foreground.
196+
startFrameMonitoring(activity);
197+
}
198+
// Starts recording frame metrics for this activity.
199+
activityToRecorderMap.get(activity).start();
200+
// Start the Trace
201+
Trace screenTrace = new Trace(getScreenTraceName(activity), transportManager, clock, this);
202+
screenTrace.start();
203+
activityToScreenTraceMap.put(activity, screenTrace);
204+
}
205+
}
171206

172207
@Override
173208
public synchronized void onActivityStopped(Activity activity) {
209+
if (isScreenTraceSupported()) {
210+
sendScreenTrace(activity);
211+
}
212+
174213
// Last activity has its onActivityStopped called, the app goes to background.
175214
if (activityToResumedMap.containsKey(activity)) {
176215
activityToResumedMap.remove(activity);
@@ -211,20 +250,6 @@ public synchronized void onActivityResumed(Activity activity) {
211250
// current activity was paused then resumed without onStop, for example by an AlertDialog
212251
activityToResumedMap.put(activity, true);
213252
}
214-
215-
// Screen trace is after session update so the sessionId is not added twice to the Trace
216-
if (isScreenTraceSupported() && configResolver.isPerformanceMonitoringEnabled()) {
217-
// Starts recording frame metrics for this activity.
218-
/**
219-
* TODO: Only add activities that are hardware acceleration enabled so that calling {@link
220-
* FrameMetricsAggregator#remove(Activity)} will not throw exceptions.
221-
*/
222-
frameMetricsAggregator.add(activity);
223-
// Start the Trace
224-
Trace screenTrace = new Trace(getScreenTraceName(activity), transportManager, clock, this);
225-
screenTrace.start();
226-
activityToScreenTraceMap.put(activity, screenTrace);
227-
}
228253
}
229254

230255
/** Returns if this is the cold start of the app. */
@@ -315,54 +340,24 @@ public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
315340
@Override
316341
public void onActivityPaused(Activity activity) {}
317342

318-
/** Stops screen trace right after onPause because of b/210055697 */
319-
@Override
320-
public void onActivityPostPaused(@NonNull Activity activity) {
321-
if (isScreenTraceSupported()) {
322-
sendScreenTrace(activity);
323-
}
324-
}
325-
326343
/**
327-
* Send screen trace. If hardware acceleration is not enabled, all frame metrics will be zero and
328-
* the trace will not be sent.
344+
* Sends the screen trace for the provided activity.
329345
*
330346
* @param activity activity object.
331347
*/
332348
private void sendScreenTrace(Activity activity) {
333-
if (!activityToScreenTraceMap.containsKey(activity)) {
334-
return;
335-
}
336349
Trace screenTrace = activityToScreenTraceMap.get(activity);
337350
if (screenTrace == null) {
338351
return;
339352
}
340353
activityToScreenTraceMap.remove(activity);
341354

342-
int totalFrames = 0;
343-
int slowFrames = 0;
344-
int frozenFrames = 0;
345-
/**
346-
* Resets the metrics data and returns the currently-collected metrics. Note that {@link
347-
* FrameMetricsAggregator#reset()} will not stop recording for the activity. The reason of using
348-
* {@link FrameMetricsAggregator#reset()} is that {@link
349-
* FrameMetricsAggregator#remove(Activity)} will throw exceptions for hardware acceleration
350-
* disabled activities.
351-
*/
352-
try {
353-
frameMetricsAggregator.remove(activity);
354-
} catch (IllegalArgumentException ignored) {
355-
logger.debug("View not hardware accelerated. Unable to collect screen trace.");
356-
}
357-
FrameMetricsCalculator.PerfFrameMetrics perfFrameMetrics =
358-
FrameMetricsCalculator.calculateFrameMetrics(frameMetricsAggregator.reset());
359-
if (perfFrameMetrics.getTotalFrames() == 0
360-
&& perfFrameMetrics.getSlowFrames() == 0
361-
&& perfFrameMetrics.getFrozenFrames() == 0) {
362-
// All metrics are zero, no need to send screen trace.
355+
Optional<PerfFrameMetrics> perfFrameMetrics = activityToRecorderMap.get(activity).stop();
356+
if (!perfFrameMetrics.isAvailable()) {
357+
logger.warn("Failed to record frame data for %s.", activity.getClass().getSimpleName());
363358
return;
364359
}
365-
ScreenTraceUtil.addFrameCounters(screenTrace, perfFrameMetrics);
360+
ScreenTraceUtil.addFrameCounters(screenTrace, perfFrameMetrics.get());
366361
// Stop and record trace
367362
screenTrace.stop();
368363
}
@@ -407,23 +402,16 @@ private void sendSessionLog(String name, Timer startTime, Timer endTime) {
407402
* @return true if supported, false if not.
408403
*/
409404
protected boolean isScreenTraceSupported() {
410-
return hasFrameMetricsAggregator;
405+
return screenPerformanceRecordingSupported;
411406
}
412407

413408
/**
414409
* FrameMetricsAggregator first appears in Android Support Library 26.1.0. Before GMSCore SDK is
415410
* updated to 26.1.0 (b/69954793), there will be ClassNotFoundException. This method is to check
416411
* if FrameMetricsAggregator exists to avoid ClassNotFoundException.
417412
*/
418-
private static boolean hasFrameMetricsAggregatorClass() {
419-
try {
420-
Class<?> initializerClass = Class.forName(FRAME_METRICS_AGGREGATOR_CLASSNAME);
421-
hasFrameMetricsAggregator = true;
422-
return true;
423-
} catch (ClassNotFoundException e) {
424-
hasFrameMetricsAggregator = false;
425-
return false;
426-
}
413+
private static boolean isScreenPerformanceRecordingSupported() {
414+
return FrameMetricsRecorder.isFrameMetricsRecordingSupported();
427415
}
428416

429417
/** An interface to be implemented by subscribers which needs to receive app state update. */

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

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,38 +15,36 @@
1515
package com.google.firebase.perf.application;
1616

1717
import androidx.annotation.NonNull;
18-
import androidx.core.app.FrameMetricsAggregator;
1918
import androidx.fragment.app.Fragment;
2019
import androidx.fragment.app.FragmentManager;
2120
import com.google.android.gms.common.util.VisibleForTesting;
2221
import com.google.firebase.perf.logging.AndroidLogger;
23-
import com.google.firebase.perf.metrics.FrameMetricsCalculator;
2422
import com.google.firebase.perf.metrics.FrameMetricsCalculator.PerfFrameMetrics;
2523
import com.google.firebase.perf.metrics.Trace;
2624
import com.google.firebase.perf.transport.TransportManager;
2725
import com.google.firebase.perf.util.Clock;
2826
import com.google.firebase.perf.util.Constants;
27+
import com.google.firebase.perf.util.Optional;
2928
import com.google.firebase.perf.util.ScreenTraceUtil;
3029
import java.util.WeakHashMap;
3130

3231
public class FragmentStateMonitor extends FragmentManager.FragmentLifecycleCallbacks {
3332
private static final AndroidLogger logger = AndroidLogger.getInstance();
3433
private final WeakHashMap<Fragment, Trace> fragmentToTraceMap = new WeakHashMap<>();
35-
private final WeakHashMap<Fragment, PerfFrameMetrics> fragmentToMetricsMap = new WeakHashMap<>();
3634
private final Clock clock;
3735
private final TransportManager transportManager;
3836
private final AppStateMonitor appStateMonitor;
39-
private final FrameMetricsAggregator frameMetricsAggregator;
37+
private final FrameMetricsRecorder activityFramesRecorder;
4038

4139
public FragmentStateMonitor(
4240
Clock clock,
4341
TransportManager transportManager,
4442
AppStateMonitor appStateMonitor,
45-
FrameMetricsAggregator fma) {
43+
FrameMetricsRecorder recorder) {
4644
this.clock = clock;
4745
this.transportManager = transportManager;
4846
this.appStateMonitor = appStateMonitor;
49-
this.frameMetricsAggregator = fma;
47+
this.activityFramesRecorder = recorder;
5048
}
5149

5250
/**
@@ -78,10 +76,7 @@ public void onFragmentResumed(@NonNull FragmentManager fm, @NonNull Fragment f)
7876
Constants.ACTIVITY_ATTRIBUTE_KEY, f.getActivity().getClass().getSimpleName());
7977
}
8078
fragmentToTraceMap.put(f, fragmentTrace);
81-
82-
PerfFrameMetrics perfFrameMetrics =
83-
FrameMetricsCalculator.calculateFrameMetrics(this.frameMetricsAggregator.getMetrics());
84-
fragmentToMetricsMap.put(f, perfFrameMetrics);
79+
activityFramesRecorder.startFragment(f);
8580
}
8681

8782
@Override
@@ -96,33 +91,18 @@ public void onFragmentPaused(@NonNull FragmentManager fm, @NonNull Fragment f) {
9691

9792
Trace fragmentTrace = fragmentToTraceMap.get(f);
9893
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();
10994

110-
if (totalFrames == 0 && slowFrames == 0 && frozenFrames == 0) {
111-
// All metrics are zero, no need to send screen trace.
95+
Optional<PerfFrameMetrics> frameMetricsData = activityFramesRecorder.stopFragment(f);
96+
if (!frameMetricsData.isAvailable()) {
97+
logger.warn("onFragmentPaused: recorder failed to trace %s", f.getClass().getSimpleName());
11298
return;
11399
}
114-
ScreenTraceUtil.addFrameCounters(
115-
fragmentTrace, new PerfFrameMetrics(totalFrames, slowFrames, frozenFrames));
100+
ScreenTraceUtil.addFrameCounters(fragmentTrace, frameMetricsData.get());
116101
fragmentTrace.stop();
117102
}
118103

119104
@VisibleForTesting
120105
WeakHashMap<Fragment, Trace> getFragmentToTraceMap() {
121106
return fragmentToTraceMap;
122107
}
123-
124-
@VisibleForTesting
125-
WeakHashMap<Fragment, PerfFrameMetrics> getFragmentToMetricsMap() {
126-
return fragmentToMetricsMap;
127-
}
128108
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,24 @@
4141
*/
4242
public class FrameMetricsRecorder {
4343
private static final AndroidLogger logger = AndroidLogger.getInstance();
44+
private static final String FRAME_METRICS_AGGREGATOR_CLASSNAME =
45+
"androidx.core.app.FrameMetricsAggregator";
4446

4547
private final Activity activity;
4648
private final FrameMetricsAggregator frameMetricsAggregator;
4749
private final Map<Fragment, PerfFrameMetrics> fragmentSnapshotMap;
4850

4951
private boolean isRecording = false;
5052

53+
static boolean isFrameMetricsRecordingSupported() {
54+
try {
55+
Class<?> initializerClass = Class.forName(FRAME_METRICS_AGGREGATOR_CLASSNAME);
56+
return true;
57+
} catch (ClassNotFoundException e) {
58+
return false;
59+
}
60+
}
61+
5162
/**
5263
* Creates a recorder for a specific activity.
5364
*

0 commit comments

Comments
 (0)