xref: /aosp_15_r20/external/cronet/base/android/jank_metric_uma_recorder_unittest.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 <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