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/time/time.h"
14 #include "base/trace_event/base_tracing.h"
15 #include "jank_metric_uma_recorder.h"
16
17 namespace base::android {
18
19 namespace {
20
21 // Histogram min, max and no. of buckets.
22 constexpr int kVsyncCountsMin = 1;
23 constexpr int kVsyncCountsMax = 50;
24 constexpr int kVsyncCountsBuckets = 25;
25
26 enum class PerScrollHistogramType {
27 kPercentage = 0,
28 kMax = 1,
29 kSum = 2,
30 };
31
GetPerScrollHistogramName(JankScenario scenario,int num_frames,PerScrollHistogramType type)32 const char* GetPerScrollHistogramName(JankScenario scenario,
33 int num_frames,
34 PerScrollHistogramType type) {
35 #define HISTOGRAM_NAME(hist_scenario, hist_type, length) \
36 "Android.FrameTimelineJank." #hist_scenario "." #hist_type \
37 "." \
38 "PerScroll." #length
39 if (scenario == JankScenario::WEBVIEW_SCROLLING) {
40 if (type == PerScrollHistogramType::kPercentage) {
41 if (num_frames <= 16) {
42 return HISTOGRAM_NAME(WebviewScrolling, DelayedFramesPercentage, Small);
43 } else if (num_frames <= 64) {
44 return HISTOGRAM_NAME(WebviewScrolling, DelayedFramesPercentage,
45 Medium);
46 } else {
47 return HISTOGRAM_NAME(WebviewScrolling, DelayedFramesPercentage, Large);
48 }
49 } else if (type == PerScrollHistogramType::kMax) {
50 if (num_frames <= 16) {
51 return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsMax, Small);
52 } else if (num_frames <= 64) {
53 return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsMax, Medium);
54 } else {
55 return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsMax, Large);
56 }
57 } else {
58 DCHECK_EQ(type, PerScrollHistogramType::kSum);
59 if (num_frames <= 16) {
60 return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsSum, Small);
61 } else if (num_frames <= 64) {
62 return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsSum, Medium);
63 } else {
64 return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsSum, Large);
65 }
66 }
67 } else {
68 DCHECK_EQ(scenario, JankScenario::FEED_SCROLLING);
69 if (type == PerScrollHistogramType::kPercentage) {
70 if (num_frames <= 16) {
71 return HISTOGRAM_NAME(FeedScrolling, DelayedFramesPercentage, Small);
72 } else if (num_frames <= 64) {
73 return HISTOGRAM_NAME(FeedScrolling, DelayedFramesPercentage, Medium);
74 } else {
75 return HISTOGRAM_NAME(FeedScrolling, DelayedFramesPercentage, Large);
76 }
77 } else if (type == PerScrollHistogramType::kMax) {
78 if (num_frames <= 16) {
79 return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsMax, Small);
80 } else if (num_frames <= 64) {
81 return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsMax, Medium);
82 } else {
83 return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsMax, Large);
84 }
85 } else {
86 DCHECK_EQ(type, PerScrollHistogramType::kSum);
87 if (num_frames <= 16) {
88 return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsSum, Small);
89 } else if (num_frames <= 64) {
90 return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsSum, Medium);
91 } else {
92 return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsSum, Large);
93 }
94 }
95 }
96 #undef HISTOGRAM_NAME
97 }
98
99 // Emits trace event for all scenarios and per scroll histograms for webview and
100 // 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)101 void EmitMetrics(JankScenario scenario,
102 int janky_frame_count,
103 int missed_vsyncs_max,
104 int missed_vsyncs_sum,
105 int num_presented_frames,
106 int64_t reporting_interval_start_time,
107 int64_t reporting_interval_duration) {
108 DCHECK_GT(num_presented_frames, 0);
109 int delayed_frames_percentage =
110 (100 * janky_frame_count) / num_presented_frames;
111 if (reporting_interval_start_time > 0) {
112 // The following code does nothing if base tracing is disabled.
113 [[maybe_unused]] int non_janky_frame_count =
114 num_presented_frames - janky_frame_count;
115 [[maybe_unused]] auto t = perfetto::Track(static_cast<uint64_t>(
116 reporting_interval_start_time + static_cast<int>(scenario)));
117 TRACE_EVENT_BEGIN(
118 "android_webview.timeline,android.ui.jank",
119 "JankMetricsReportingInterval", t,
120 base::TimeTicks::FromUptimeMillis(reporting_interval_start_time),
121 "janky_frames", janky_frame_count, "non_janky_frames",
122 non_janky_frame_count, "scenario", static_cast<int>(scenario),
123 "delayed_frames_percentage", delayed_frames_percentage,
124 "missed_vsyns_max", missed_vsyncs_max, "missed_vsyncs_sum",
125 missed_vsyncs_sum);
126 TRACE_EVENT_END(
127 "android_webview.timeline,android.ui.jank", t,
128 base::TimeTicks::FromUptimeMillis(
129 (reporting_interval_start_time + reporting_interval_duration)));
130 }
131
132 if (scenario != JankScenario::WEBVIEW_SCROLLING &&
133 scenario != JankScenario::FEED_SCROLLING) {
134 return;
135 }
136 base::UmaHistogramPercentage(
137 GetPerScrollHistogramName(scenario, num_presented_frames,
138 PerScrollHistogramType::kPercentage),
139 delayed_frames_percentage);
140 base::UmaHistogramCustomCounts(
141 GetPerScrollHistogramName(scenario, num_presented_frames,
142 PerScrollHistogramType::kMax),
143 missed_vsyncs_max, kVsyncCountsMin, kVsyncCountsMax, kVsyncCountsBuckets);
144 base::UmaHistogramCustomCounts(
145 GetPerScrollHistogramName(scenario, num_presented_frames,
146 PerScrollHistogramType::kSum),
147 missed_vsyncs_sum, kVsyncCountsMin, kVsyncCountsMax, kVsyncCountsBuckets);
148 }
149
150 } // namespace
151
GetAndroidFrameTimelineJankHistogramName(JankScenario scenario)152 const char* GetAndroidFrameTimelineJankHistogramName(JankScenario scenario) {
153 #define HISTOGRAM_NAME(x) "Android.FrameTimelineJank.FrameJankStatus." #x
154 switch (scenario) {
155 case JankScenario::PERIODIC_REPORTING:
156 return HISTOGRAM_NAME(Total);
157 case JankScenario::OMNIBOX_FOCUS:
158 return HISTOGRAM_NAME(OmniboxFocus);
159 case JankScenario::NEW_TAB_PAGE:
160 return HISTOGRAM_NAME(NewTabPage);
161 case JankScenario::STARTUP:
162 return HISTOGRAM_NAME(Startup);
163 case JankScenario::TAB_SWITCHER:
164 return HISTOGRAM_NAME(TabSwitcher);
165 case JankScenario::OPEN_LINK_IN_NEW_TAB:
166 return HISTOGRAM_NAME(OpenLinkInNewTab);
167 case JankScenario::START_SURFACE_HOMEPAGE:
168 return HISTOGRAM_NAME(StartSurfaceHomepage);
169 case JankScenario::START_SURFACE_TAB_SWITCHER:
170 return HISTOGRAM_NAME(StartSurfaceTabSwitcher);
171 case JankScenario::FEED_SCROLLING:
172 return HISTOGRAM_NAME(FeedScrolling);
173 case JankScenario::WEBVIEW_SCROLLING:
174 return HISTOGRAM_NAME(WebviewScrolling);
175 default:
176 return HISTOGRAM_NAME(UNKNOWN);
177 }
178 #undef HISTOGRAM_NAME
179 }
180
GetAndroidFrameTimelineDurationHistogramName(JankScenario scenario)181 const char* GetAndroidFrameTimelineDurationHistogramName(
182 JankScenario scenario) {
183 #define HISTOGRAM_NAME(x) "Android.FrameTimelineJank.Duration." #x
184 switch (scenario) {
185 case JankScenario::PERIODIC_REPORTING:
186 return HISTOGRAM_NAME(Total);
187 case JankScenario::OMNIBOX_FOCUS:
188 return HISTOGRAM_NAME(OmniboxFocus);
189 case JankScenario::NEW_TAB_PAGE:
190 return HISTOGRAM_NAME(NewTabPage);
191 case JankScenario::STARTUP:
192 return HISTOGRAM_NAME(Startup);
193 case JankScenario::TAB_SWITCHER:
194 return HISTOGRAM_NAME(TabSwitcher);
195 case JankScenario::OPEN_LINK_IN_NEW_TAB:
196 return HISTOGRAM_NAME(OpenLinkInNewTab);
197 case JankScenario::START_SURFACE_HOMEPAGE:
198 return HISTOGRAM_NAME(StartSurfaceHomepage);
199 case JankScenario::START_SURFACE_TAB_SWITCHER:
200 return HISTOGRAM_NAME(StartSurfaceTabSwitcher);
201 case JankScenario::FEED_SCROLLING:
202 return HISTOGRAM_NAME(FeedScrolling);
203 case JankScenario::WEBVIEW_SCROLLING:
204 return HISTOGRAM_NAME(WebviewScrolling);
205 default:
206 return HISTOGRAM_NAME(UNKNOWN);
207 }
208 #undef HISTOGRAM_NAME
209 }
210
211 // This function is called from Java with JNI, it's declared in
212 // base/base_jni/JankMetricUMARecorder_jni.h which is an autogenerated
213 // header. The actual implementation is in RecordJankMetrics for simpler
214 // 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)215 void JNI_JankMetricUMARecorder_RecordJankMetrics(
216 JNIEnv* env,
217 const base::android::JavaParamRef<jlongArray>& java_durations_ns,
218 const base::android::JavaParamRef<jintArray>& java_missed_vsyncs,
219 jlong java_reporting_interval_start_time,
220 jlong java_reporting_interval_duration,
221 jint java_scenario_enum) {
222 RecordJankMetrics(env, java_durations_ns, java_missed_vsyncs,
223 java_reporting_interval_start_time,
224 java_reporting_interval_duration, java_scenario_enum);
225 }
226
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)227 void RecordJankMetrics(
228 JNIEnv* env,
229 const base::android::JavaParamRef<jlongArray>& java_durations_ns,
230 const base::android::JavaParamRef<jintArray>& java_missed_vsyncs,
231 jlong java_reporting_interval_start_time,
232 jlong java_reporting_interval_duration,
233 jint java_scenario_enum) {
234 std::vector<int64_t> durations_ns;
235 JavaLongArrayToInt64Vector(env, java_durations_ns, &durations_ns);
236
237 std::vector<int> missed_vsyncs;
238 JavaIntArrayToIntVector(env, java_missed_vsyncs, &missed_vsyncs);
239
240 JankScenario scenario = static_cast<JankScenario>(java_scenario_enum);
241
242 const char* frame_duration_histogram_name =
243 GetAndroidFrameTimelineDurationHistogramName(scenario);
244 const char* janky_frames_per_scenario_histogram_name =
245 GetAndroidFrameTimelineJankHistogramName(scenario);
246
247 for (const int64_t frame_duration_ns : durations_ns) {
248 base::UmaHistogramTimes(frame_duration_histogram_name,
249 base::Nanoseconds(frame_duration_ns));
250 }
251
252 int janky_frame_count = 0;
253 int missed_vsyncs_max = 0;
254 int missed_vsyncs_sum = 0;
255 const int num_presented_frames = static_cast<int>(missed_vsyncs.size());
256
257 for (int curr_frame_missed_vsyncs : missed_vsyncs) {
258 bool is_janky = curr_frame_missed_vsyncs > 0;
259 if (curr_frame_missed_vsyncs > missed_vsyncs_max) {
260 missed_vsyncs_max = curr_frame_missed_vsyncs;
261 }
262 missed_vsyncs_sum += curr_frame_missed_vsyncs;
263
264 base::UmaHistogramEnumeration(
265 janky_frames_per_scenario_histogram_name,
266 is_janky ? FrameJankStatus::kJanky : FrameJankStatus::kNonJanky);
267 if (is_janky) {
268 ++janky_frame_count;
269 }
270 }
271
272 if (num_presented_frames > 0) {
273 EmitMetrics(scenario, janky_frame_count, missed_vsyncs_max,
274 missed_vsyncs_sum, num_presented_frames,
275 java_reporting_interval_start_time,
276 java_reporting_interval_duration);
277 }
278 }
279
280 } // namespace base::android
281