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