• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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