Skip to content

Commit f226e41

Browse files
Add frame metrics to fragment traces (#3592)
* Add frame metrics to fragment traces. * Fix AppStateMonitor.java * Rename FrameMetrics to PerfFrameMetrics
1 parent 987db30 commit f226e41

File tree

6 files changed

+414
-55
lines changed

6 files changed

+414
-55
lines changed

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

Lines changed: 10 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +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;
2524
import androidx.fragment.app.FragmentActivity;
2625
import com.google.android.gms.common.util.VisibleForTesting;
2726
import com.google.firebase.perf.config.ConfigResolver;
2827
import com.google.firebase.perf.logging.AndroidLogger;
28+
import com.google.firebase.perf.metrics.FrameMetricsCalculator;
2929
import com.google.firebase.perf.metrics.Trace;
3030
import com.google.firebase.perf.session.SessionManager;
3131
import com.google.firebase.perf.transport.TransportManager;
3232
import com.google.firebase.perf.util.Clock;
3333
import com.google.firebase.perf.util.Constants;
3434
import com.google.firebase.perf.util.Constants.CounterNames;
35+
import com.google.firebase.perf.util.ScreenTraceUtil;
3536
import com.google.firebase.perf.util.Timer;
36-
import com.google.firebase.perf.util.Utils;
3737
import com.google.firebase.perf.v1.ApplicationProcessState;
3838
import com.google.firebase.perf.v1.TraceMetric;
3939
import java.lang.ref.WeakReference;
@@ -213,7 +213,7 @@ public synchronized void onActivityResumed(Activity activity) {
213213
}
214214

215215
// Screen trace is after session update so the sessionId is not added twice to the Trace
216-
if (isScreenTraceSupported(activity) && configResolver.isPerformanceMonitoringEnabled()) {
216+
if (isScreenTraceSupported() && configResolver.isPerformanceMonitoringEnabled()) {
217217
// Starts recording frame metrics for this activity.
218218
/**
219219
* TODO: Only add activities that are hardware acceleration enabled so that calling {@link
@@ -354,50 +354,15 @@ private void sendScreenTrace(Activity activity) {
354354
} catch (IllegalArgumentException ignored) {
355355
logger.debug("View not hardware accelerated. Unable to collect screen trace.");
356356
}
357-
SparseIntArray[] arr = frameMetricsAggregator.reset();
358-
if (arr != null) {
359-
SparseIntArray frameTimes = arr[FrameMetricsAggregator.TOTAL_INDEX];
360-
if (frameTimes != null) {
361-
for (int i = 0; i < frameTimes.size(); i++) {
362-
int frameTime = frameTimes.keyAt(i);
363-
int numFrames = frameTimes.valueAt(i);
364-
totalFrames += numFrames;
365-
if (frameTime > Constants.FROZEN_FRAME_TIME) {
366-
// Frozen frames mean the app appear frozen. The recommended thresholds is 700ms
367-
frozenFrames += numFrames;
368-
}
369-
if (frameTime > Constants.SLOW_FRAME_TIME) {
370-
// Slow frames are anything above 16ms (i.e. 60 frames/second)
371-
slowFrames += numFrames;
372-
}
373-
}
374-
}
375-
}
376-
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) {
377362
// All metrics are zero, no need to send screen trace.
378-
// return;
379-
}
380-
// Only incrementMetric if corresponding metric is non-zero.
381-
if (totalFrames > 0) {
382-
screenTrace.putMetric(Constants.CounterNames.FRAMES_TOTAL.toString(), totalFrames);
383-
}
384-
if (slowFrames > 0) {
385-
screenTrace.putMetric(Constants.CounterNames.FRAMES_SLOW.toString(), slowFrames);
386-
}
387-
if (frozenFrames > 0) {
388-
screenTrace.putMetric(Constants.CounterNames.FRAMES_FROZEN.toString(), frozenFrames);
389-
}
390-
if (Utils.isDebugLoggingEnabled(activity.getApplicationContext())) {
391-
logger.debug(
392-
"sendScreenTrace name:"
393-
+ getScreenTraceName(activity)
394-
+ " _fr_tot:"
395-
+ totalFrames
396-
+ " _fr_slo:"
397-
+ slowFrames
398-
+ " _fr_fzn:"
399-
+ frozenFrames);
363+
return;
400364
}
365+
ScreenTraceUtil.addFrameCounters(screenTrace, perfFrameMetrics);
401366
// Stop and record trace
402367
screenTrace.stop();
403368
}

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

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,19 @@
2020
import androidx.fragment.app.FragmentManager;
2121
import com.google.android.gms.common.util.VisibleForTesting;
2222
import com.google.firebase.perf.logging.AndroidLogger;
23+
import com.google.firebase.perf.metrics.FrameMetricsCalculator;
24+
import com.google.firebase.perf.metrics.FrameMetricsCalculator.PerfFrameMetrics;
2325
import com.google.firebase.perf.metrics.Trace;
2426
import com.google.firebase.perf.transport.TransportManager;
2527
import com.google.firebase.perf.util.Clock;
2628
import com.google.firebase.perf.util.Constants;
29+
import com.google.firebase.perf.util.ScreenTraceUtil;
2730
import java.util.WeakHashMap;
2831

2932
public class FragmentStateMonitor extends FragmentManager.FragmentLifecycleCallbacks {
3033
private static final AndroidLogger logger = AndroidLogger.getInstance();
3134
private final WeakHashMap<Fragment, Trace> fragmentToTraceMap = new WeakHashMap<>();
35+
private final WeakHashMap<Fragment, PerfFrameMetrics> fragmentToMetricsMap = new WeakHashMap<>();
3236
private final Clock clock;
3337
private final TransportManager transportManager;
3438
private final AppStateMonitor appStateMonitor;
@@ -73,8 +77,11 @@ public void onFragmentResumed(@NonNull FragmentManager fm, @NonNull Fragment f)
7377
fragmentTrace.putAttribute(
7478
Constants.ACTIVITY_ATTRIBUTE_KEY, f.getActivity().getClass().getSimpleName());
7579
}
76-
7780
fragmentToTraceMap.put(f, fragmentTrace);
81+
82+
PerfFrameMetrics perfFrameMetrics =
83+
FrameMetricsCalculator.calculateFrameMetrics(this.frameMetricsAggregator.getMetrics());
84+
fragmentToMetricsMap.put(f, perfFrameMetrics);
7885
}
7986

8087
@Override
@@ -89,14 +96,33 @@ public void onFragmentPaused(@NonNull FragmentManager fm, @NonNull Fragment f) {
8996

9097
Trace fragmentTrace = fragmentToTraceMap.get(f);
9198
fragmentToTraceMap.remove(f);
99+
PerfFrameMetrics prePerfFrameMetrics = fragmentToMetricsMap.get(f);
100+
fragmentToMetricsMap.remove(f);
92101

93-
// TODO: Add frame metrics
102+
PerfFrameMetrics curPerfFrameMetrics =
103+
FrameMetricsCalculator.calculateFrameMetrics(this.frameMetricsAggregator.getMetrics());
94104

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));
95116
fragmentTrace.stop();
96117
}
97118

98119
@VisibleForTesting
99120
WeakHashMap<Fragment, Trace> getFragmentToTraceMap() {
100121
return fragmentToTraceMap;
101122
}
123+
124+
@VisibleForTesting
125+
WeakHashMap<Fragment, PerfFrameMetrics> getFragmentToMetricsMap() {
126+
return fragmentToMetricsMap;
127+
}
102128
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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.metrics;
16+
17+
import android.util.SparseIntArray;
18+
import androidx.annotation.NonNull;
19+
import androidx.annotation.Nullable;
20+
import androidx.core.app.FrameMetricsAggregator;
21+
import com.google.firebase.perf.util.Constants;
22+
23+
/**
24+
* FrameMetricsCalculator helps calculate total frames, slow frames, and frozen frames from metrics
25+
* collected by {@link FrameMetricsAggregator}
26+
*
27+
* @hide
28+
*/
29+
public class FrameMetricsCalculator {
30+
public static class PerfFrameMetrics {
31+
int totalFrames = 0;
32+
int slowFrames = 0;
33+
int frozenFrames = 0;
34+
35+
public PerfFrameMetrics(int totalFrames, int slowFrames, int frozenFrames) {
36+
this.totalFrames = totalFrames;
37+
this.slowFrames = slowFrames;
38+
this.frozenFrames = frozenFrames;
39+
}
40+
41+
public int getFrozenFrames() {
42+
return frozenFrames;
43+
}
44+
45+
public int getSlowFrames() {
46+
return slowFrames;
47+
}
48+
49+
public int getTotalFrames() {
50+
return totalFrames;
51+
}
52+
}
53+
54+
/**
55+
* Calculate total frames, slow frames, and frozen frames from SparseIntArray[] recorded by {@link
56+
* FrameMetricsAggregator}.
57+
*
58+
* @param arr the metrics data collected by {@link FrameMetricsAggregator#getMetrics()}
59+
* @return the frame metrics
60+
*/
61+
public static @NonNull PerfFrameMetrics calculateFrameMetrics(@Nullable SparseIntArray[] arr) {
62+
int totalFrames = 0;
63+
int slowFrames = 0;
64+
int frozenFrames = 0;
65+
66+
if (arr != null) {
67+
SparseIntArray frameTimes = arr[FrameMetricsAggregator.TOTAL_INDEX];
68+
if (frameTimes != null) {
69+
for (int i = 0; i < frameTimes.size(); i++) {
70+
int frameTime = frameTimes.keyAt(i);
71+
int numFrames = frameTimes.valueAt(i);
72+
totalFrames += numFrames;
73+
if (frameTime > Constants.FROZEN_FRAME_TIME) {
74+
// Frozen frames mean the app appear frozen. The recommended thresholds is 700ms
75+
frozenFrames += numFrames;
76+
}
77+
if (frameTime > Constants.SLOW_FRAME_TIME) {
78+
// Slow frames are anything above 16ms (i.e. 60 frames/second)
79+
slowFrames += numFrames;
80+
}
81+
}
82+
}
83+
}
84+
// Only incrementMetric if corresponding metric is non-zero.
85+
return new PerfFrameMetrics(totalFrames, slowFrames, frozenFrames);
86+
}
87+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
//
6+
// You may obtain a copy of the License at
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.util;
16+
17+
import com.google.firebase.perf.logging.AndroidLogger;
18+
import com.google.firebase.perf.metrics.FrameMetricsCalculator.PerfFrameMetrics;
19+
import com.google.firebase.perf.metrics.Trace;
20+
21+
/** Utility class for screen traces. */
22+
public class ScreenTraceUtil {
23+
private static final AndroidLogger logger = AndroidLogger.getInstance();
24+
25+
/**
26+
* Set the metrics of total frames, slow frames, and frozen frames for the given screen trace.
27+
*
28+
* @param screenTrace a screen trace
29+
* @param perfFrameMetrics frame metrics calculated by {@link
30+
* com.google.firebase.perf.metrics.FrameMetricsCalculator#calculateFrameMetrics}
31+
* @return the screen trace with frame metrics added.
32+
*/
33+
public static Trace addFrameCounters(Trace screenTrace, PerfFrameMetrics perfFrameMetrics) {
34+
// Only putMetric if corresponding metric is greater than zero.
35+
if (perfFrameMetrics.getTotalFrames() > 0) {
36+
screenTrace.putMetric(
37+
Constants.CounterNames.FRAMES_TOTAL.toString(), perfFrameMetrics.getTotalFrames());
38+
}
39+
if (perfFrameMetrics.getSlowFrames() > 0) {
40+
screenTrace.putMetric(
41+
Constants.CounterNames.FRAMES_SLOW.toString(), perfFrameMetrics.getSlowFrames());
42+
}
43+
if (perfFrameMetrics.getFrozenFrames() > 0) {
44+
screenTrace.putMetric(
45+
Constants.CounterNames.FRAMES_FROZEN.toString(), perfFrameMetrics.getFrozenFrames());
46+
}
47+
logger.debug(
48+
"Screen trace: "
49+
+ screenTrace.getClass().getSimpleName()
50+
+ " _fr_tot:"
51+
+ perfFrameMetrics.getTotalFrames()
52+
+ " _fr_slo:"
53+
+ perfFrameMetrics.getSlowFrames()
54+
+ " _fr_fzn:"
55+
+ perfFrameMetrics.getFrozenFrames());
56+
return screenTrace;
57+
}
58+
}

0 commit comments

Comments
 (0)