xref: /aosp_15_r20/external/cronet/base/android/jank_metric_uma_recorder.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/android/jank_metric_uma_recorder.h"
6 
7 #include <cstdint>
8 
9 #include "base/android/jni_android.h"
10 #include "base/android/jni_array.h"
11 #include "base/base_jni/JankMetricUMARecorder_jni.h"
12 #include "base/metrics/histogram_functions.h"
13 #include "base/notreached.h"
14 #include "base/time/time.h"
15 #include "base/trace_event/base_tracing.h"
16 #include "jank_metric_uma_recorder.h"
17 
18 namespace base::android {
19 
20 namespace {
21 
22 // Histogram min, max and no. of buckets.
23 constexpr int kVsyncCountsMin = 1;
24 constexpr int kVsyncCountsMax = 50;
25 constexpr int kVsyncCountsBuckets = 25;
26 
27 enum class PerScrollHistogramType {
28   kPercentage = 0,
29   kMax = 1,
30   kSum = 2,
31 };
32 
GetPerScrollHistogramName(JankScenario scenario,int num_frames,PerScrollHistogramType type,bool with_scroll_size_suffix)33 const char* GetPerScrollHistogramName(JankScenario scenario,
34                                       int num_frames,
35                                       PerScrollHistogramType type,
36                                       bool with_scroll_size_suffix) {
37 #define HISTOGRAM_NAME(hist_scenario, hist_type, length)     \
38   "Android.FrameTimelineJank." #hist_scenario "." #hist_type \
39   "."                                                        \
40   "PerScroll" #length
41   if (scenario == JankScenario::WEBVIEW_SCROLLING) {
42     if (type == PerScrollHistogramType::kPercentage) {
43       if (!with_scroll_size_suffix) {
44         return HISTOGRAM_NAME(WebviewScrolling, DelayedFramesPercentage,
45                               /*no suffix*/);
46       }
47       if (num_frames <= 16) {
48         return HISTOGRAM_NAME(WebviewScrolling, DelayedFramesPercentage,
49                               .Small);
50       } else if (num_frames <= 64) {
51         return HISTOGRAM_NAME(WebviewScrolling, DelayedFramesPercentage,
52                               .Medium);
53       } else {
54         return HISTOGRAM_NAME(WebviewScrolling, DelayedFramesPercentage,
55                               .Large);
56       }
57     } else if (type == PerScrollHistogramType::kMax) {
58       if (!with_scroll_size_suffix) {
59         return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsMax, /*no suffix*/);
60       }
61       if (num_frames <= 16) {
62         return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsMax, .Small);
63       } else if (num_frames <= 64) {
64         return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsMax, .Medium);
65       } else {
66         return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsMax, .Large);
67       }
68     } else {
69       DCHECK_EQ(type, PerScrollHistogramType::kSum);
70       if (!with_scroll_size_suffix) {
71         return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsSum, /*no suffix*/);
72       }
73       if (num_frames <= 16) {
74         return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsSum, .Small);
75       } else if (num_frames <= 64) {
76         return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsSum, .Medium);
77       } else {
78         return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsSum, .Large);
79       }
80     }
81   } else {
82     DCHECK_EQ(scenario, JankScenario::FEED_SCROLLING);
83     if (type == PerScrollHistogramType::kPercentage) {
84       if (!with_scroll_size_suffix) {
85         return HISTOGRAM_NAME(FeedScrolling, DelayedFramesPercentage,
86                               /*no suffix*/);
87       }
88       if (num_frames <= 16) {
89         return HISTOGRAM_NAME(FeedScrolling, DelayedFramesPercentage, .Small);
90       } else if (num_frames <= 64) {
91         return HISTOGRAM_NAME(FeedScrolling, DelayedFramesPercentage, .Medium);
92       } else {
93         return HISTOGRAM_NAME(FeedScrolling, DelayedFramesPercentage, .Large);
94       }
95     } else if (type == PerScrollHistogramType::kMax) {
96       if (!with_scroll_size_suffix) {
97         return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsMax, /*no suffix*/);
98       }
99       if (num_frames <= 16) {
100         return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsMax, .Small);
101       } else if (num_frames <= 64) {
102         return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsMax, .Medium);
103       } else {
104         return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsMax, .Large);
105       }
106     } else {
107       DCHECK_EQ(type, PerScrollHistogramType::kSum);
108       if (!with_scroll_size_suffix) {
109         return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsSum, /*no suffix*/);
110       }
111       if (num_frames <= 16) {
112         return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsSum, .Small);
113       } else if (num_frames <= 64) {
114         return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsSum, .Medium);
115       } else {
116         return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsSum, .Large);
117       }
118     }
119   }
120 #undef HISTOGRAM_NAME
121 }
122 
123 // Emits trace event for all scenarios and per scroll histograms for webview and
124 // feed scrolling scenarios.
EmitMetrics(JankScenario scenario,int janky_frame_count,int missed_vsyncs_max,int missed_vsyncs_sum,int num_presented_frames,int64_t reporting_interval_start_time,int64_t reporting_interval_duration)125 void EmitMetrics(JankScenario scenario,
126                  int janky_frame_count,
127                  int missed_vsyncs_max,
128                  int missed_vsyncs_sum,
129                  int num_presented_frames,
130                  int64_t reporting_interval_start_time,
131                  int64_t reporting_interval_duration) {
132   DCHECK_GT(num_presented_frames, 0);
133   int delayed_frames_percentage =
134       (100 * janky_frame_count) / num_presented_frames;
135   if (reporting_interval_start_time > 0) {
136     // The following code does nothing if base tracing is disabled.
137     [[maybe_unused]] int non_janky_frame_count =
138         num_presented_frames - janky_frame_count;
139     [[maybe_unused]] auto t = perfetto::Track(static_cast<uint64_t>(
140         reporting_interval_start_time + static_cast<int>(scenario)));
141     TRACE_EVENT_BEGIN(
142         "android_webview.timeline,android.ui.jank",
143         "JankMetricsReportingInterval", t,
144         base::TimeTicks::FromUptimeMillis(reporting_interval_start_time),
145         "janky_frames", janky_frame_count, "non_janky_frames",
146         non_janky_frame_count, "scenario", static_cast<int>(scenario),
147         "delayed_frames_percentage", delayed_frames_percentage,
148         "missed_vsyns_max", missed_vsyncs_max, "missed_vsyncs_sum",
149         missed_vsyncs_sum);
150     TRACE_EVENT_END(
151         "android_webview.timeline,android.ui.jank", t,
152         base::TimeTicks::FromUptimeMillis(
153             (reporting_interval_start_time + reporting_interval_duration)));
154   }
155 
156   if (scenario != JankScenario::WEBVIEW_SCROLLING &&
157       scenario != JankScenario::FEED_SCROLLING) {
158     return;
159   }
160   // Emit non-bucketed per scroll metrics.
161   base::UmaHistogramPercentage(
162       GetPerScrollHistogramName(scenario, num_presented_frames,
163                                 PerScrollHistogramType::kPercentage,
164                                 /*with_scroll_size_suffix=*/false),
165       delayed_frames_percentage);
166   base::UmaHistogramCustomCounts(
167       GetPerScrollHistogramName(scenario, num_presented_frames,
168                                 PerScrollHistogramType::kMax,
169                                 /*with_scroll_size_suffix=*/false),
170       missed_vsyncs_max, kVsyncCountsMin, kVsyncCountsMax, kVsyncCountsBuckets);
171   base::UmaHistogramCustomCounts(
172       GetPerScrollHistogramName(scenario, num_presented_frames,
173                                 PerScrollHistogramType::kSum,
174                                 /*with_scroll_size_suffix=*/false),
175       missed_vsyncs_sum, kVsyncCountsMin, kVsyncCountsMax, kVsyncCountsBuckets);
176 
177   // Emit bucketed per scroll metrics where scrolls are divided into three
178   // buckets Small, Medium, Large.
179   base::UmaHistogramPercentage(
180       GetPerScrollHistogramName(scenario, num_presented_frames,
181                                 PerScrollHistogramType::kPercentage,
182                                 /*with_scroll_size_suffix=*/true),
183       delayed_frames_percentage);
184   base::UmaHistogramCustomCounts(
185       GetPerScrollHistogramName(scenario, num_presented_frames,
186                                 PerScrollHistogramType::kMax,
187                                 /*with_scroll_size_suffix=*/true),
188       missed_vsyncs_max, kVsyncCountsMin, kVsyncCountsMax, kVsyncCountsBuckets);
189   base::UmaHistogramCustomCounts(
190       GetPerScrollHistogramName(scenario, num_presented_frames,
191                                 PerScrollHistogramType::kSum,
192                                 /*with_scroll_size_suffix=*/true),
193       missed_vsyncs_sum, kVsyncCountsMin, kVsyncCountsMax, kVsyncCountsBuckets);
194 }
195 
196 }  // namespace
197 
GetAndroidFrameTimelineJankHistogramName(JankScenario scenario)198 const char* GetAndroidFrameTimelineJankHistogramName(JankScenario scenario) {
199 #define HISTOGRAM_NAME(x) "Android.FrameTimelineJank.FrameJankStatus." #x
200   switch (scenario) {
201     case JankScenario::PERIODIC_REPORTING:
202       return HISTOGRAM_NAME(Total);
203     case JankScenario::OMNIBOX_FOCUS:
204       return HISTOGRAM_NAME(OmniboxFocus);
205     case JankScenario::NEW_TAB_PAGE:
206       return HISTOGRAM_NAME(NewTabPage);
207     case JankScenario::STARTUP:
208       return HISTOGRAM_NAME(Startup);
209     case JankScenario::TAB_SWITCHER:
210       return HISTOGRAM_NAME(TabSwitcher);
211     case JankScenario::OPEN_LINK_IN_NEW_TAB:
212       return HISTOGRAM_NAME(OpenLinkInNewTab);
213     case JankScenario::START_SURFACE_HOMEPAGE:
214       return HISTOGRAM_NAME(StartSurfaceHomepage);
215     case JankScenario::START_SURFACE_TAB_SWITCHER:
216       return HISTOGRAM_NAME(StartSurfaceTabSwitcher);
217     case JankScenario::FEED_SCROLLING:
218       return HISTOGRAM_NAME(FeedScrolling);
219     case JankScenario::WEBVIEW_SCROLLING:
220       return HISTOGRAM_NAME(WebviewScrolling);
221     case JankScenario::COMBINED_WEBVIEW_SCROLLING:
222       // Emit per frame metrics for combined scrolling scenario with same
223       // histogram name as webview scrolling. This is fine since we don't emit
224       // per frame metrics for |WEBVIEW_SCROLLING| scenario.
225       return HISTOGRAM_NAME(WebviewScrolling);
226     default:
227       NOTREACHED();
228       return "";
229   }
230 #undef HISTOGRAM_NAME
231 }
232 
GetAndroidFrameTimelineDurationHistogramName(JankScenario scenario)233 const char* GetAndroidFrameTimelineDurationHistogramName(
234     JankScenario scenario) {
235 #define HISTOGRAM_NAME(x) "Android.FrameTimelineJank.Duration." #x
236   switch (scenario) {
237     case JankScenario::PERIODIC_REPORTING:
238       return HISTOGRAM_NAME(Total);
239     case JankScenario::OMNIBOX_FOCUS:
240       return HISTOGRAM_NAME(OmniboxFocus);
241     case JankScenario::NEW_TAB_PAGE:
242       return HISTOGRAM_NAME(NewTabPage);
243     case JankScenario::STARTUP:
244       return HISTOGRAM_NAME(Startup);
245     case JankScenario::TAB_SWITCHER:
246       return HISTOGRAM_NAME(TabSwitcher);
247     case JankScenario::OPEN_LINK_IN_NEW_TAB:
248       return HISTOGRAM_NAME(OpenLinkInNewTab);
249     case JankScenario::START_SURFACE_HOMEPAGE:
250       return HISTOGRAM_NAME(StartSurfaceHomepage);
251     case JankScenario::START_SURFACE_TAB_SWITCHER:
252       return HISTOGRAM_NAME(StartSurfaceTabSwitcher);
253     case JankScenario::FEED_SCROLLING:
254       return HISTOGRAM_NAME(FeedScrolling);
255     case JankScenario::WEBVIEW_SCROLLING:
256       return HISTOGRAM_NAME(WebviewScrolling);
257     case JankScenario::COMBINED_WEBVIEW_SCROLLING:
258       // Emit per frame metrics for combined scrolling scenario with same
259       // histogram name as webview scrolling. This is fine since we don't emit
260       // per frame metrics for |WEBVIEW_SCROLLING| scenario.
261       return HISTOGRAM_NAME(WebviewScrolling);
262     default:
263       NOTREACHED();
264       return "";
265   }
266 #undef HISTOGRAM_NAME
267 }
268 
269 // This function is called from Java with JNI, it's declared in
270 // base/base_jni/JankMetricUMARecorder_jni.h which is an autogenerated
271 // header. The actual implementation is in RecordJankMetrics for simpler
272 // testing.
JNI_JankMetricUMARecorder_RecordJankMetrics(JNIEnv * env,const base::android::JavaParamRef<jlongArray> & java_durations_ns,const base::android::JavaParamRef<jintArray> & java_missed_vsyncs,jlong java_reporting_interval_start_time,jlong java_reporting_interval_duration,jint java_scenario_enum)273 void JNI_JankMetricUMARecorder_RecordJankMetrics(
274     JNIEnv* env,
275     const base::android::JavaParamRef<jlongArray>& java_durations_ns,
276     const base::android::JavaParamRef<jintArray>& java_missed_vsyncs,
277     jlong java_reporting_interval_start_time,
278     jlong java_reporting_interval_duration,
279     jint java_scenario_enum) {
280   RecordJankMetrics(env, java_durations_ns, java_missed_vsyncs,
281                     java_reporting_interval_start_time,
282                     java_reporting_interval_duration, java_scenario_enum);
283 }
284 
RecordJankMetrics(JNIEnv * env,const base::android::JavaParamRef<jlongArray> & java_durations_ns,const base::android::JavaParamRef<jintArray> & java_missed_vsyncs,jlong java_reporting_interval_start_time,jlong java_reporting_interval_duration,jint java_scenario_enum)285 void RecordJankMetrics(
286     JNIEnv* env,
287     const base::android::JavaParamRef<jlongArray>& java_durations_ns,
288     const base::android::JavaParamRef<jintArray>& java_missed_vsyncs,
289     jlong java_reporting_interval_start_time,
290     jlong java_reporting_interval_duration,
291     jint java_scenario_enum) {
292   std::vector<int64_t> durations_ns;
293   JavaLongArrayToInt64Vector(env, java_durations_ns, &durations_ns);
294 
295   std::vector<int> missed_vsyncs;
296   JavaIntArrayToIntVector(env, java_missed_vsyncs, &missed_vsyncs);
297 
298   JankScenario scenario = static_cast<JankScenario>(java_scenario_enum);
299 
300   const char* frame_duration_histogram_name =
301       GetAndroidFrameTimelineDurationHistogramName(scenario);
302   const char* janky_frames_per_scenario_histogram_name =
303       GetAndroidFrameTimelineJankHistogramName(scenario);
304 
305   // We don't want to emit per frame metircs for WEBVIEW SCROLLING scenario
306   // which tracks individual scrolls differentiated by gesture_scroll_id.
307   // Scroll related per frame metrics are emitted from
308   // COMBINED_WEBVIEW_SCROLLING scenario to avoid emitting duplicate metrics for
309   // overlapping scrolls.
310   const bool emit_per_frame_metrics =
311       scenario != JankScenario::WEBVIEW_SCROLLING;
312 
313   if (emit_per_frame_metrics) {
314     for (const int64_t frame_duration_ns : durations_ns) {
315       base::UmaHistogramTimes(frame_duration_histogram_name,
316                               base::Nanoseconds(frame_duration_ns));
317     }
318   }
319 
320   int janky_frame_count = 0;
321   int missed_vsyncs_max = 0;
322   int missed_vsyncs_sum = 0;
323   const int num_presented_frames = static_cast<int>(missed_vsyncs.size());
324 
325   for (int curr_frame_missed_vsyncs : missed_vsyncs) {
326     bool is_janky = curr_frame_missed_vsyncs > 0;
327     if (curr_frame_missed_vsyncs > missed_vsyncs_max) {
328       missed_vsyncs_max = curr_frame_missed_vsyncs;
329     }
330     missed_vsyncs_sum += curr_frame_missed_vsyncs;
331 
332     if (emit_per_frame_metrics) {
333       base::UmaHistogramEnumeration(
334           janky_frames_per_scenario_histogram_name,
335           is_janky ? FrameJankStatus::kJanky : FrameJankStatus::kNonJanky);
336     }
337     if (is_janky) {
338       ++janky_frame_count;
339     }
340   }
341 
342   if (num_presented_frames > 0) {
343     EmitMetrics(scenario, janky_frame_count, missed_vsyncs_max,
344                 missed_vsyncs_sum, num_presented_frames,
345                 java_reporting_interval_start_time,
346                 java_reporting_interval_duration);
347   }
348 }
349 
350 }  // namespace base::android
351