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 <jni.h>
8
9 #include <cstddef>
10 #include <cstdint>
11 #include <vector>
12
13 #include "base/android/jni_android.h"
14 #include "base/android/jni_array.h"
15 #include "base/android/jni_string.h"
16 #include "base/metrics/histogram.h"
17 #include "base/test/metrics/histogram_tester.h"
18 #include "jank_metric_uma_recorder.h"
19 #include "testing/gmock/include/gmock/gmock.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21
22 using ::testing::ElementsAre;
23 using ::testing::IsEmpty;
24
25 namespace base::android {
26 namespace {
27
GenerateJavaLongArray(JNIEnv * env,const int64_t long_array[],const size_t array_length)28 jlongArray GenerateJavaLongArray(JNIEnv* env,
29 const int64_t long_array[],
30 const size_t array_length) {
31 ScopedJavaLocalRef<jlongArray> java_long_array =
32 ToJavaLongArray(env, long_array, array_length);
33
34 return java_long_array.Release();
35 }
36
37 // Durations are received in nanoseconds, but are recorded to UMA in
38 // milliseconds.
39 const int64_t kDurations[] = {
40 1'000'000, // 1ms
41 2'000'000, // 2ms
42 30'000'000, // 30ms
43 10'000'000, // 10ms
44 60'000'000, // 60ms
45 1'000'000, // 1ms
46 1'000'000, // 1ms
47 20'000'000, // 20ms
48 };
49 const size_t kDurationsLen = std::size(kDurations);
50
GenerateJavaIntArray(JNIEnv * env,const int int_array[],const size_t array_length)51 jintArray GenerateJavaIntArray(JNIEnv* env,
52 const int int_array[],
53 const size_t array_length) {
54 ScopedJavaLocalRef<jintArray> java_int_array =
55 ToJavaIntArray(env, int_array, array_length);
56
57 return java_int_array.Release();
58 }
59
60 const int kMissedVsyncs[] = {
61 0, 0, 2, 0, 1, 0, 0, 0,
62 };
63 const size_t kMissedVsyncsLen = kDurationsLen;
64
65 static_assert(kDurationsLen == kMissedVsyncsLen);
66 const size_t kNumFrames = kDurationsLen;
67
68 struct ScrollTestCase {
69 JankScenario scenario;
70 std::string test_name;
71 int num_frames;
72 std::string suffix;
73 };
74
75 } // namespace
76
TEST(JankMetricUMARecorder,TestUMARecording)77 TEST(JankMetricUMARecorder, TestUMARecording) {
78
79 JNIEnv* env = AttachCurrentThread();
80
81 jlongArray java_durations =
82 GenerateJavaLongArray(env, kDurations, kDurationsLen);
83
84 jintArray java_missed_vsyncs =
85 GenerateJavaIntArray(env, kMissedVsyncs, kMissedVsyncsLen);
86
87 const int kMinScenario = static_cast<int>(JankScenario::PERIODIC_REPORTING);
88 const int kMaxScenario = static_cast<int>(JankScenario::MAX_VALUE);
89 // keep one histogram tester outside to ensure that each histogram is a
90 // different one rather than just the same string over and over.
91 HistogramTester complete_histogram_tester;
92 size_t total_histograms = 0;
93 for (int i = kMinScenario; i < kMaxScenario; ++i) {
94 if ((i == static_cast<int>(JankScenario::WEBVIEW_SCROLLING)) ||
95 (i == static_cast<int>(JankScenario::FEED_SCROLLING))) {
96 continue;
97 }
98 // HistogramTester takes a snapshot of currently incremented counters so
99 // everything is scoped to just this iteration of the for loop.
100 HistogramTester histogram_tester;
101
102 RecordJankMetrics(
103 env,
104 /* java_durations_ns= */
105 base::android::JavaParamRef<jlongArray>(env, java_durations),
106 /* java_missed_vsyncs = */
107 base::android::JavaParamRef<jintArray>(env, java_missed_vsyncs),
108 /* java_reporting_interval_start_time = */ 0,
109 /* java_reporting_interval_duration = */ 1000,
110 /* java_scenario_enum = */ i);
111
112 const std::string kDurationName =
113 GetAndroidFrameTimelineDurationHistogramName(
114 static_cast<JankScenario>(i));
115 const std::string kJankyName =
116 GetAndroidFrameTimelineJankHistogramName(static_cast<JankScenario>(i));
117
118 // Only one Duration and one Jank scenario should be incremented.
119 base::HistogramTester::CountsMap count_map =
120 histogram_tester.GetTotalCountsForPrefix("Android.FrameTimelineJank.");
121 EXPECT_EQ(count_map.size(), 2ul);
122 EXPECT_EQ(count_map[kDurationName], 8) << kDurationName;
123 EXPECT_EQ(count_map[kJankyName], 8) << kJankyName;
124 // And we should be two more then last iteration, but don't do any other
125 // verification because each iteration will do their own.
126 base::HistogramTester::CountsMap total_count_map =
127 complete_histogram_tester.GetTotalCountsForPrefix(
128 "Android.FrameTimelineJank.");
129 EXPECT_EQ(total_count_map.size(), total_histograms + 2);
130 total_histograms += 2;
131
132 EXPECT_THAT(histogram_tester.GetAllSamples(kDurationName),
133 ElementsAre(Bucket(1, 3), Bucket(2, 1), Bucket(10, 1),
134 Bucket(20, 1), Bucket(29, 1), Bucket(57, 1)))
135 << kDurationName;
136 EXPECT_THAT(histogram_tester.GetAllSamples(kJankyName),
137 ElementsAre(Bucket(FrameJankStatus::kJanky, 2),
138 Bucket(FrameJankStatus::kNonJanky, 6)))
139 << kJankyName;
140 }
141 }
142
TEST(JankMetricUMARecorder,TestWebviewScrollingScenario)143 TEST(JankMetricUMARecorder, TestWebviewScrollingScenario) {
144 JNIEnv* env = AttachCurrentThread();
145
146 jlongArray java_durations =
147 GenerateJavaLongArray(env, kDurations, kDurationsLen);
148 jintArray java_missed_vsyncs =
149 GenerateJavaIntArray(env, kMissedVsyncs, kMissedVsyncsLen);
150
151 const int scenario = static_cast<int>(JankScenario::WEBVIEW_SCROLLING);
152 HistogramTester histogram_tester;
153 RecordJankMetrics(
154 env,
155 /* java_durations_ns= */
156 base::android::JavaParamRef<jlongArray>(env, java_durations),
157 /* java_missed_vsyncs = */
158 base::android::JavaParamRef<jintArray>(env, java_missed_vsyncs),
159 /* java_reporting_interval_start_time = */ 0,
160 /* java_reporting_interval_duration = */ 1000, scenario);
161
162 const std::string kDurationName =
163 "Android.FrameTimelineJank.Duration.WebviewScrolling";
164 const std::string kJankyName =
165 "Android.FrameTimelineJank.FrameJankStatus.WebviewScrolling";
166 histogram_tester.ExpectTotalCount(kDurationName, 0);
167 histogram_tester.ExpectTotalCount(kJankyName, 0);
168 }
169
TEST(JankMetricUMARecorder,TestCombinedWebviewScrollingScenario)170 TEST(JankMetricUMARecorder, TestCombinedWebviewScrollingScenario) {
171 JNIEnv* env = AttachCurrentThread();
172
173 jlongArray java_durations =
174 GenerateJavaLongArray(env, kDurations, kDurationsLen);
175 jintArray java_missed_vsyncs =
176 GenerateJavaIntArray(env, kMissedVsyncs, kMissedVsyncsLen);
177
178 const int scenario =
179 static_cast<int>(JankScenario::COMBINED_WEBVIEW_SCROLLING);
180 HistogramTester histogram_tester;
181 RecordJankMetrics(
182 env,
183 /* java_durations_ns= */
184 base::android::JavaParamRef<jlongArray>(env, java_durations),
185 /* java_missed_vsyncs = */
186 base::android::JavaParamRef<jintArray>(env, java_missed_vsyncs),
187 /* java_reporting_interval_start_time = */ 0,
188 /* java_reporting_interval_duration = */ 1000, scenario);
189
190 // |COMBINED_WEBVIEW_SCROLLING| scenario uses 'WebviewScrolling' suffix for
191 // emitting the per frame metrics.
192 const std::string kDurationName =
193 "Android.FrameTimelineJank.Duration.WebviewScrolling";
194 const std::string kJankyName =
195 "Android.FrameTimelineJank.FrameJankStatus.WebviewScrolling";
196
197 histogram_tester.ExpectTotalCount(kDurationName, kNumFrames);
198 histogram_tester.ExpectTotalCount(kJankyName, kNumFrames);
199 }
200
201 class JankMetricUMARecorderPerScrollTests
202 : public testing::Test,
203 public testing::WithParamInterface<ScrollTestCase> {};
204 INSTANTIATE_TEST_SUITE_P(
205 JankMetricUMARecorderPerScrollTests,
206 JankMetricUMARecorderPerScrollTests,
207 testing::ValuesIn<ScrollTestCase>({
208 {JankScenario::WEBVIEW_SCROLLING, "EmitsSmallScrollHistogramInWebview",
209 10, ".Small"},
210 {JankScenario::WEBVIEW_SCROLLING, "EmitsMediumScrollHistogramInWebview",
211 50, ".Medium"},
212 {JankScenario::WEBVIEW_SCROLLING, "EmitsLargeScrollHistogramInWebview",
213 65, ".Large"},
214 {JankScenario::FEED_SCROLLING, "EmitsSmallScrollHistogramInFeed", 10,
215 ".Small"},
216 {JankScenario::FEED_SCROLLING, "EmitsMediumScrollHistogramInFeed", 50,
217 ".Medium"},
218 {JankScenario::FEED_SCROLLING, "EmitsLargeScrollHistogramInFeed", 65,
219 ".Large"},
220 }),
221 [](const testing::TestParamInfo<
__anon990ece0c0202(const testing::TestParamInfo< JankMetricUMARecorderPerScrollTests::ParamType>& info) 222 JankMetricUMARecorderPerScrollTests::ParamType>& info) {
223 return info.param.test_name;
224 });
225
TEST_P(JankMetricUMARecorderPerScrollTests,EmitsPerScrollHistograms)226 TEST_P(JankMetricUMARecorderPerScrollTests, EmitsPerScrollHistograms) {
227 const ScrollTestCase& params = GetParam();
228
229 JNIEnv* env = AttachCurrentThread();
230 HistogramTester histogram_tester;
231 std::vector<int64_t> durations = {1000000L, 1000000L, 1000000L};
232 std::vector<int> missed_vsyncs = {0, 3, 1};
233 const int expected_janky_frames = 2;
234 const int expected_vsyncs_max = 3;
235 const int expected_vsyncs_sum = 4;
236
237 for (int i = durations.size(); i < params.num_frames; i++) {
238 durations.push_back(1000000L);
239 missed_vsyncs.push_back(0);
240 }
241
242 jlongArray java_durations =
243 GenerateJavaLongArray(env, durations.data(), durations.size());
244 jintArray java_missed_vsyncs =
245 GenerateJavaIntArray(env, missed_vsyncs.data(), missed_vsyncs.size());
246
247 RecordJankMetrics(
248 env, base::android::JavaParamRef<jlongArray>(env, java_durations),
249 base::android::JavaParamRef<jintArray>(env, java_missed_vsyncs),
250 /* java_reporting_interval_start_time = */ 0,
251 /* java_reporting_interval_duration = */ 1000,
252 static_cast<int>(params.scenario));
253
254 int expected_delayed_frames_percentage =
255 (100 * expected_janky_frames) / params.num_frames;
256 std::string scenario_name = "";
257 if (params.scenario == JankScenario::WEBVIEW_SCROLLING) {
258 scenario_name = "WebviewScrolling";
259 } else {
260 DCHECK_EQ(params.scenario, JankScenario::FEED_SCROLLING);
261 scenario_name = "FeedScrolling";
262 }
263 std::string delayed_frames_histogram = "Android.FrameTimelineJank." +
264 scenario_name +
265 ".DelayedFramesPercentage."
266 "PerScroll";
267 std::string missed_vsyncs_max_histogram = "Android.FrameTimelineJank." +
268 scenario_name +
269 ".MissedVsyncsMax."
270 "PerScroll";
271 std::string missed_vsyncs_sum_histogram = "Android.FrameTimelineJank." +
272 scenario_name +
273 ".MissedVsyncsSum."
274 "PerScroll";
275 // Should emit non-bucketed scroll histograms.
276 histogram_tester.ExpectUniqueSample(delayed_frames_histogram,
277 expected_delayed_frames_percentage, 1);
278 histogram_tester.ExpectUniqueSample(missed_vsyncs_max_histogram,
279 expected_vsyncs_max, 1);
280 histogram_tester.ExpectUniqueSample(missed_vsyncs_sum_histogram,
281 expected_vsyncs_sum, 1);
282
283 // Should emit bucketed scroll histograms, suffixed with scroll size like
284 // Small, Medium, Large.
285 histogram_tester.ExpectUniqueSample(delayed_frames_histogram + params.suffix,
286 expected_delayed_frames_percentage, 1);
287 histogram_tester.ExpectUniqueSample(
288 missed_vsyncs_max_histogram + params.suffix, expected_vsyncs_max, 1);
289 histogram_tester.ExpectUniqueSample(
290 missed_vsyncs_sum_histogram + params.suffix, expected_vsyncs_sum, 1);
291 }
292
293 } // namespace base::android
294