[M108 Migration][VD] Avoid pending frame counter becoming negative
[platform/framework/web/chromium-efl.git] / cc / metrics / dropped_frame_counter_unittest.cc
1 // Copyright 2020 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 "cc/metrics/dropped_frame_counter.h"
6
7 #include <vector>
8
9 #include "base/memory/raw_ptr.h"
10 #include "base/synchronization/lock.h"
11 #include "base/synchronization/waitable_event.h"
12 #include "base/time/time.h"
13 #include "build/chromeos_buildflags.h"
14 #include "cc/animation/animation_host.h"
15 #include "cc/metrics/custom_metrics_recorder.h"
16 #include "cc/test/fake_content_layer_client.h"
17 #include "cc/test/fake_frame_info.h"
18 #include "cc/test/fake_picture_layer.h"
19 #include "cc/test/layer_tree_test.h"
20
21 namespace cc {
22 namespace {
23
24 using SmoothnessStrategy = DroppedFrameCounter::SmoothnessStrategy;
25
26 FrameInfo CreateStubFrameInfo(bool is_dropped) {
27   return CreateFakeFrameInfo(is_dropped
28                                  ? FrameInfo::FrameFinalState::kDropped
29                                  : FrameInfo::FrameFinalState::kPresentedAll);
30 }
31
32 class TestCustomMetricsRecorder : public CustomMetricRecorder {
33  public:
34   TestCustomMetricsRecorder() = default;
35   ~TestCustomMetricsRecorder() override = default;
36
37   // CustomMetricRecorder:
38   void ReportPercentDroppedFramesInOneSecoundWindow(
39       double percentage) override {
40     ++percent_dropped_frames_count_;
41     last_percent_dropped_frames_ = percentage;
42   }
43
44   int percent_dropped_frames_count() const {
45     return percent_dropped_frames_count_;
46   }
47
48   double last_percent_dropped_frames() const {
49     return last_percent_dropped_frames_;
50   }
51
52  private:
53   int percent_dropped_frames_count_ = 0;
54   double last_percent_dropped_frames_ = 0;
55 };
56
57 class DroppedFrameCounterTestBase : public LayerTreeTest {
58  public:
59   DroppedFrameCounterTestBase() = default;
60   ~DroppedFrameCounterTestBase() override = default;
61
62   virtual void SetUpTestConfigAndExpectations() = 0;
63
64   void InitializeSettings(LayerTreeSettings* settings) override {
65     settings->commit_to_active_tree = false;
66   }
67
68   void SetupTree() override {
69     LayerTreeTest::SetupTree();
70
71     Layer* root_layer = layer_tree_host()->root_layer();
72     scroll_layer_ = FakePictureLayer::Create(&client_);
73     // Set up the layer so it always has something to paint.
74     scroll_layer_->set_always_update_resources(true);
75     scroll_layer_->SetBounds({3, 3});
76     client_.set_bounds({3, 3});
77     root_layer->AddChild(scroll_layer_);
78   }
79
80   void RunTest(CompositorMode mode) override {
81     SetUpTestConfigAndExpectations();
82     LayerTreeTest::RunTest(mode);
83   }
84
85   void BeginTest() override {
86     ASSERT_GT(config_.animation_frames, 0u);
87
88     // Start with requesting main-frames.
89     PostSetNeedsCommitToMainThread();
90   }
91
92   void AfterTest() override {
93     EXPECT_GE(total_frames_, config_.animation_frames);
94     // It is possible to drop even more frame than what the test expects (e.g.
95     // in slower machines, slower builds such as asan/tsan builds, etc.), since
96     // the test does not strictly control both threads and deadlines. Therefore,
97     // it is not possible to check for strict equality here.
98     EXPECT_LE(expect_.min_partial, partial_);
99     EXPECT_LE(expect_.min_dropped, dropped_);
100     EXPECT_LE(expect_.min_dropped_smoothness, dropped_smoothness_);
101   }
102
103   // Compositor thread function overrides:
104   void WillBeginImplFrameOnThread(LayerTreeHostImpl* host_impl,
105                                   const viz::BeginFrameArgs& args,
106                                   bool has_damage) override {
107     if (TestEnded())
108       return;
109
110     // Request a re-draw, and set a non-empty damage region (otherwise the
111     // draw is aborted with 'no damage').
112     host_impl->SetNeedsRedraw();
113     host_impl->SetViewportDamage(gfx::Rect(0, 0, 10, 20));
114
115     if (skip_main_thread_next_frame_) {
116       skip_main_thread_next_frame_ = false;
117     } else {
118       // Request update from the main-thread too.
119       host_impl->SetNeedsCommit();
120     }
121   }
122
123   void DrawLayersOnThread(LayerTreeHostImpl* host_impl) override {
124     // If the main-thread is blocked, then unblock it once the compositor thread
125     // has already drawn a frame.
126     base::WaitableEvent* wait = nullptr;
127     {
128       base::AutoLock lock(wait_lock_);
129       wait = wait_;
130     }
131
132     if (wait) {
133       // When the main-thread blocks during a frame, skip the main-thread for
134       // the next frame, so that the main-thread can be in sync with the
135       // compositor thread again.
136       skip_main_thread_next_frame_ = true;
137       wait->Signal();
138     }
139   }
140
141   void DidReceivePresentationTimeOnThread(
142       LayerTreeHostImpl* host_impl,
143       uint32_t frame_token,
144       const gfx::PresentationFeedback& feedback) override {
145     ++presented_frames_;
146     if (presented_frames_ < config_.animation_frames)
147       return;
148
149     auto* dropped_frame_counter = host_impl->dropped_frame_counter();
150     DCHECK(dropped_frame_counter);
151
152     total_frames_ = dropped_frame_counter->total_frames();
153     partial_ = dropped_frame_counter->total_partial();
154     dropped_ = dropped_frame_counter->total_dropped();
155     dropped_smoothness_ = dropped_frame_counter->total_smoothness_dropped();
156     EndTest();
157   }
158
159   // Main-thread function overrides:
160   void BeginMainFrame(const viz::BeginFrameArgs& args) override {
161     if (TestEnded())
162       return;
163
164     bool should_wait = false;
165     if (config_.should_drop_main_every > 0) {
166       should_wait =
167           args.frame_id.sequence_number % config_.should_drop_main_every == 0;
168     }
169
170     if (should_wait) {
171       base::WaitableEvent wait{base::WaitableEvent::ResetPolicy::MANUAL,
172                                base::WaitableEvent::InitialState::NOT_SIGNALED};
173       {
174         base::AutoLock lock(wait_lock_);
175         wait_ = &wait;
176       }
177       wait.Wait();
178       {
179         base::AutoLock lock(wait_lock_);
180         wait_ = nullptr;
181       }
182     }
183
184     // Make some changes so that the main-thread needs to push updates to the
185     // compositor thread (i.e. force a commit).
186     auto const bounds = scroll_layer_->bounds();
187     scroll_layer_->SetBounds({bounds.width(), bounds.height() + 1});
188     if (config_.should_register_main_thread_animation) {
189       animation_host()->SetAnimationCounts(1);
190       animation_host()->SetCurrentFrameHadRaf(true);
191       animation_host()->SetNextFrameHasPendingRaf(true);
192     }
193   }
194
195  protected:
196   // The test configuration options. This is set before the test starts, and
197   // remains unchanged after that. So it is safe to read these fields from
198   // either threads.
199   struct TestConfig {
200     uint32_t should_drop_main_every = 0;
201     uint32_t animation_frames = 0;
202     bool should_register_main_thread_animation = false;
203   } config_;
204
205   // The test expectations. This is set before the test starts, and
206   // remains unchanged after that. So it is safe to read these fields from
207   // either threads.
208   struct TestExpectation {
209     uint32_t min_partial = 0;
210     uint32_t min_dropped = 0;
211     uint32_t min_dropped_smoothness = 0;
212   } expect_;
213
214  private:
215   // Set up a dummy picture layer so that every begin-main frame requires a
216   // commit (without the dummy layer, the main-thread never has to paint, which
217   // causes an early 'no damage' abort of the main-frame.
218   FakeContentLayerClient client_;
219   scoped_refptr<FakePictureLayer> scroll_layer_;
220
221   // This field is used only on the compositor thread to track how many frames
222   // have been processed.
223   uint32_t presented_frames_ = 0;
224
225   // The |wait_| event is used when the test wants to deliberately force the
226   // main-thread to block while processing begin-main-frames.
227   base::Lock wait_lock_;
228   raw_ptr<base::WaitableEvent> wait_ = nullptr;
229
230   // These fields are populated in the compositor thread when the desired number
231   // of frames have been processed. These fields are subsequently compared
232   // against the expectation after the test ends.
233   uint32_t total_frames_ = 0;
234   uint32_t partial_ = 0;
235   uint32_t dropped_ = 0;
236   uint32_t dropped_smoothness_ = 0;
237
238   bool skip_main_thread_next_frame_ = false;
239 };
240
241 class DroppedFrameCounterNoDropTest : public DroppedFrameCounterTestBase {
242  public:
243   ~DroppedFrameCounterNoDropTest() override = default;
244
245   void SetUpTestConfigAndExpectations() override {
246     config_.animation_frames = 28;
247     config_.should_register_main_thread_animation = false;
248
249     expect_.min_partial = 0;
250     expect_.min_dropped = 0;
251     expect_.min_dropped_smoothness = 0;
252   }
253 };
254
255 MULTI_THREAD_TEST_F(DroppedFrameCounterNoDropTest);
256
257 class DroppedFrameCounterMainDropsNoSmoothness
258     : public DroppedFrameCounterTestBase {
259  public:
260   ~DroppedFrameCounterMainDropsNoSmoothness() override = default;
261
262   void SetUpTestConfigAndExpectations() override {
263     config_.animation_frames = 28;
264     config_.should_drop_main_every = 5;
265     config_.should_register_main_thread_animation = false;
266
267     expect_.min_partial = 5;
268     expect_.min_dropped_smoothness = 0;
269   }
270 };
271
272 // TODO(crbug.com/1115376) Disabled for flakiness.
273 // MULTI_THREAD_TEST_F(DroppedFrameCounterMainDropsNoSmoothness);
274
275 class DroppedFrameCounterMainDropsSmoothnessTest
276     : public DroppedFrameCounterTestBase {
277  public:
278   ~DroppedFrameCounterMainDropsSmoothnessTest() override = default;
279
280   void SetUpTestConfigAndExpectations() override {
281     config_.animation_frames = 28;
282     config_.should_drop_main_every = 5;
283     config_.should_register_main_thread_animation = true;
284
285     expect_.min_partial = 5;
286     expect_.min_dropped_smoothness = 5;
287   }
288 };
289
290 // TODO(crbug.com/1115376) Disabled for flakiness.
291 // MULTI_THREAD_TEST_F(DroppedFrameCounterMainDropsSmoothnessTest);
292
293 class DroppedFrameCounterTest : public testing::Test {
294  public:
295   explicit DroppedFrameCounterTest(SmoothnessStrategy smoothness_strategy =
296                                        SmoothnessStrategy::kDefaultStrategy)
297       : smoothness_strategy_(smoothness_strategy) {
298     dropped_frame_counter_.set_total_counter(&total_frame_counter_);
299     dropped_frame_counter_.OnFcpReceived();
300   }
301   ~DroppedFrameCounterTest() override = default;
302
303   // For each boolean in frame_states produces a frame
304   void SimulateFrameSequence(std::vector<bool> frame_states, int repeat) {
305     for (int i = 0; i < repeat; i++) {
306       for (auto is_dropped : frame_states) {
307         viz::BeginFrameArgs args_ = SimulateBeginFrameArgs();
308         dropped_frame_counter_.OnBeginFrame(args_, /*is_scroll_active=*/false);
309         dropped_frame_counter_.OnEndFrame(args_,
310                                           CreateStubFrameInfo(is_dropped));
311         sequence_number_++;
312         frame_time_ += interval_;
313       }
314     }
315   }
316
317   // Make a sequence of frame states where the first |dropped_frames| out of
318   // |total_frames| are dropped.
319   std::vector<bool> MakeFrameSequence(int dropped_frames, int total_frames) {
320     std::vector<bool> frame_states(total_frames, false);
321     for (int i = 0; i < dropped_frames; i++) {
322       frame_states[i] = true;
323     }
324     return frame_states;
325   }
326
327   std::vector<viz::BeginFrameArgs> SimulatePendingFrame(int repeat) {
328     std::vector<viz::BeginFrameArgs> args(repeat);
329     for (int i = 0; i < repeat; i++) {
330       args[i] = SimulateBeginFrameArgs();
331       dropped_frame_counter_.OnBeginFrame(args[i], /*is_scroll_active=*/false);
332       sequence_number_++;
333       frame_time_ += interval_;
334     }
335     return args;
336   }
337
338   // Simulate a main and impl thread update on the same frame.
339   void SimulateForkedFrame(bool main_dropped, bool impl_dropped) {
340     viz::BeginFrameArgs args_ = SimulateBeginFrameArgs();
341     dropped_frame_counter_.OnBeginFrame(args_, /*is_scroll_active=*/false);
342     dropped_frame_counter_.OnBeginFrame(args_, /*is_scroll_active=*/false);
343
344     // End the 'main thread' arm of the fork.
345     auto main_info = CreateStubFrameInfo(main_dropped);
346     main_info.main_thread_response = FrameInfo::MainThreadResponse::kIncluded;
347     dropped_frame_counter_.OnEndFrame(args_, main_info);
348
349     // End the 'compositor thread' arm of the fork.
350     auto impl_info = CreateStubFrameInfo(impl_dropped);
351     impl_info.main_thread_response = FrameInfo::MainThreadResponse::kMissing;
352     dropped_frame_counter_.OnEndFrame(args_, impl_info);
353
354     sequence_number_++;
355     frame_time_ += interval_;
356   }
357
358   void AdvancetimeByIntervals(int interval_count) {
359     frame_time_ += interval_ * interval_count;
360   }
361
362   double MaxPercentDroppedFrame() {
363     return dropped_frame_counter_.sliding_window_max_percent_dropped();
364   }
365
366   double MaxPercentDroppedFrameAfter1Sec() {
367     auto percent_dropped =
368         dropped_frame_counter_.max_percent_dropped_After_1_sec();
369     EXPECT_TRUE(percent_dropped.has_value());
370     return percent_dropped.value();
371   }
372
373   double MaxPercentDroppedFrameAfter2Sec() {
374     auto percent_dropped =
375         dropped_frame_counter_.max_percent_dropped_After_2_sec();
376     EXPECT_TRUE(percent_dropped.has_value());
377     return percent_dropped.value();
378   }
379
380   double MaxPercentDroppedFrameAfter5Sec() {
381     auto percent_dropped =
382         dropped_frame_counter_.max_percent_dropped_After_5_sec();
383     EXPECT_TRUE(percent_dropped.has_value());
384     return percent_dropped.value();
385   }
386
387   double PercentDroppedFrame95Percentile() {
388     return dropped_frame_counter_.SlidingWindow95PercentilePercentDropped(
389         smoothness_strategy_);
390   }
391
392   double PercentDroppedFrameMedian() {
393     return dropped_frame_counter_.SlidingWindowMedianPercentDropped(
394         smoothness_strategy_);
395   }
396
397   double PercentDroppedFrameVariance() {
398     return dropped_frame_counter_.SlidingWindowPercentDroppedVariance(
399         smoothness_strategy_);
400   }
401
402   const DroppedFrameCounter::SlidingWindowHistogram*
403   GetSlidingWindowHistogram() {
404     return dropped_frame_counter_.GetSlidingWindowHistogram(
405         smoothness_strategy_);
406   }
407
408   double GetTotalFramesInWindow() { return base::Seconds(1) / interval_; }
409
410   void SetInterval(base::TimeDelta interval) { interval_ = interval; }
411
412   base::TimeTicks GetNextFrameTime() const { return frame_time_ + interval_; }
413
414   // Wrap calls with EXPECT_TRUE. Logs the buckets and returns false if they
415   // don't match (within a given epsilon).
416   bool CheckSmoothnessBuckets(std::vector<double> expected_buckets) {
417     constexpr double epsilon = 0.001;
418     bool buckets_match = true;
419     std::vector<double> buckets =
420         GetSlidingWindowHistogram()->GetPercentDroppedFrameBuckets();
421     if (buckets.size() != expected_buckets.size()) {
422       buckets_match = false;
423     } else {
424       for (size_t i = 0; i < buckets.size(); i++) {
425         if (std::abs(buckets[i] - expected_buckets[i]) > epsilon) {
426           buckets_match = false;
427           break;
428         }
429       }
430     }
431     if (!buckets_match) {
432       LOG(ERROR) << "Smoothness buckets do not match!";
433       LOG(ERROR) << "Expected: " << testing::PrintToString(expected_buckets);
434       LOG(ERROR) << "  Actual: " << testing::PrintToString(buckets);
435     }
436     return buckets_match;
437   }
438
439  public:
440   DroppedFrameCounter dropped_frame_counter_;
441
442  private:
443   TotalFrameCounter total_frame_counter_;
444   uint64_t sequence_number_ = 1;
445   uint64_t source_id_ = 1;
446   raw_ptr<const base::TickClock> tick_clock_ =
447       base::DefaultTickClock::GetInstance();
448   base::TimeTicks frame_time_ = tick_clock_->NowTicks();
449   base::TimeDelta interval_ = base::Microseconds(16667);  // 16.667 ms
450
451   SmoothnessStrategy smoothness_strategy_;
452
453   viz::BeginFrameArgs SimulateBeginFrameArgs() {
454     viz::BeginFrameId current_id_(source_id_, sequence_number_);
455     viz::BeginFrameArgs args = viz::BeginFrameArgs();
456     args.frame_id = current_id_;
457     args.frame_time = frame_time_;
458     args.interval = interval_;
459     return args;
460   }
461 };
462
463 // Test class that supports parameterized tests for each of the different
464 // SmoothnessStrategy.
465 //
466 // TODO(jonross): when we build the other strategies parameterize the
467 // expectations.
468 class SmoothnessStrategyDroppedFrameCounterTest
469     : public DroppedFrameCounterTest,
470       public testing::WithParamInterface<SmoothnessStrategy> {
471  public:
472   SmoothnessStrategyDroppedFrameCounterTest()
473       : DroppedFrameCounterTest(GetParam()) {}
474   ~SmoothnessStrategyDroppedFrameCounterTest() override = default;
475   SmoothnessStrategyDroppedFrameCounterTest(
476       const SmoothnessStrategyDroppedFrameCounterTest&) = delete;
477   SmoothnessStrategyDroppedFrameCounterTest& operator=(
478       const SmoothnessStrategyDroppedFrameCounterTest&) = delete;
479 };
480
481 INSTANTIATE_TEST_SUITE_P(
482     DefaultStrategy,
483     SmoothnessStrategyDroppedFrameCounterTest,
484     ::testing::Values(SmoothnessStrategy::kDefaultStrategy));
485
486 TEST_P(SmoothnessStrategyDroppedFrameCounterTest, SimplePattern1) {
487   // 2 out of every 3 frames are dropped (In total 80 frames out of 120).
488   SimulateFrameSequence({true, true, true, false, true, false}, 20);
489
490   // The max is the following window:
491   //    16 * <sequence> + {true, true, true, false
492   // Which means a max of 67 dropped frames.
493   EXPECT_EQ(std::round(MaxPercentDroppedFrame()), 67);
494   EXPECT_EQ(PercentDroppedFrame95Percentile(), 67);  // all values are in the
495   // 65th-67th bucket, and as a result 95th percentile is also 67.
496   EXPECT_EQ(PercentDroppedFrameMedian(), 65);
497   EXPECT_LE(PercentDroppedFrameVariance(), 1);
498 }
499
500 TEST_P(SmoothnessStrategyDroppedFrameCounterTest, SimplePattern2) {
501   // 1 out of every 5 frames are dropped (In total 24 frames out of 120).
502   SimulateFrameSequence({false, false, false, false, true}, 24);
503
504   double expected_percent_dropped_frame = (12 / GetTotalFramesInWindow()) * 100;
505   EXPECT_FLOAT_EQ(MaxPercentDroppedFrame(), expected_percent_dropped_frame);
506   EXPECT_EQ(PercentDroppedFrame95Percentile(),
507             20);  // all values are in the
508   // 20th bucket, and as a result 95th percentile is also 20.
509   EXPECT_EQ(PercentDroppedFrameMedian(), 20);
510   EXPECT_LE(PercentDroppedFrameVariance(), 1);
511 }
512
513 TEST_P(SmoothnessStrategyDroppedFrameCounterTest, IncompleteWindow) {
514   // There are only 5 frames submitted, so Max, 95pct, median and variance
515   // should report zero.
516   SimulateFrameSequence({false, false, false, false, true}, 1);
517   EXPECT_EQ(MaxPercentDroppedFrame(), 0.0);
518   EXPECT_EQ(PercentDroppedFrame95Percentile(), 0);
519   EXPECT_EQ(PercentDroppedFrameMedian(), 0);
520   EXPECT_LE(PercentDroppedFrameVariance(), 1);
521 }
522
523 TEST_P(SmoothnessStrategyDroppedFrameCounterTest, MaxPercentDroppedChanges) {
524   // First 60 frames have 20% dropped.
525   SimulateFrameSequence({false, false, false, false, true}, 12);
526
527   double expected_percent_dropped_frame1 =
528       (12 / GetTotalFramesInWindow()) * 100;
529   EXPECT_EQ(MaxPercentDroppedFrame(), expected_percent_dropped_frame1);
530   EXPECT_FLOAT_EQ(PercentDroppedFrame95Percentile(),
531                   20);  // There is only one
532   // element in the histogram and that is 20.
533   EXPECT_EQ(PercentDroppedFrameMedian(), 20);
534   EXPECT_LE(PercentDroppedFrameVariance(), 1);
535
536   // 30 new frames are added that have 18 dropped frames.
537   // and the 30 frame before that had 6 dropped frames.
538   // So in total in the window has 24 frames dropped out of 60 frames.
539   SimulateFrameSequence({false, false, true, true, true}, 6);
540   double expected_percent_dropped_frame2 =
541       (24 / GetTotalFramesInWindow()) * 100;
542   EXPECT_FLOAT_EQ(MaxPercentDroppedFrame(), expected_percent_dropped_frame2);
543
544   // 30 new frames are added that have 24 dropped frames.
545   // and the 30 frame before that had 18 dropped frames.
546   // So in total in the window has 42 frames dropped out of 60 frames.
547   SimulateFrameSequence({false, true, true, true, true}, 6);
548   double expected_percent_dropped_frame3 =
549       (42 / GetTotalFramesInWindow()) * 100;
550   EXPECT_FLOAT_EQ(MaxPercentDroppedFrame(), expected_percent_dropped_frame3);
551
552   // Percent dropped frame of window increases gradually to 70%.
553   // 1 value exist when we reach 60 frames and 1 value thereafter for each
554   // frame added. So there 61 values in histogram. Last value is 70 (2 sampels)
555   // and then 67 with 1 sample, which would be the 95th percentile.
556   EXPECT_EQ(PercentDroppedFrame95Percentile(), 67);
557 }
558
559 TEST_F(DroppedFrameCounterTest, MaxPercentDroppedWithIdleFrames) {
560   // First 20 frames have 4 frames dropped (20%).
561   SimulateFrameSequence({false, false, false, false, true}, 4);
562
563   // Then no frames are added for 20 intervals.
564   AdvancetimeByIntervals(20);
565
566   // Then 20 frames have 16 frames dropped (60%).
567   SimulateFrameSequence({false, false, true, true, true}, 4);
568
569   // So in total, there are 40 frames in the 1 second window with 16 dropped
570   // frames (40% in total).
571   double expected_percent_dropped_frame = (16 / GetTotalFramesInWindow()) * 100;
572   EXPECT_FLOAT_EQ(MaxPercentDroppedFrame(), expected_percent_dropped_frame);
573 }
574
575 TEST_F(DroppedFrameCounterTest, NoCrashForIntervalLargerThanWindow) {
576   SetInterval(base::Milliseconds(1000));
577   SimulateFrameSequence({false, false}, 1);
578 }
579
580 TEST_P(SmoothnessStrategyDroppedFrameCounterTest, Percentile95WithIdleFrames) {
581   // Test scenario:
582   //  . 4s of 20% dropped frames.
583   //  . 96s of idle time.
584   // The 96%ile dropped-frame metric should be 0.
585
586   // Set an interval that rounds up nicely with 1 second.
587   constexpr auto kInterval = base::Milliseconds(10);
588   constexpr size_t kFps = base::Seconds(1) / kInterval;
589   static_assert(
590       kFps % 5 == 0,
591       "kFps must be a multiple of 5 because this test depends on it.");
592   SetInterval(kInterval);
593
594   const auto* histogram = GetSlidingWindowHistogram();
595
596   // First 4 seconds with 20% dropped frames.
597   SimulateFrameSequence({false, false, false, false, true}, (kFps / 5) * 4);
598   EXPECT_EQ(histogram->GetPercentDroppedFramePercentile(0.95), 20u);
599
600   // Then no frames are added for 97s. Note that this 1s more than 96 seconds,
601   // because the last second remains in the sliding window.
602   AdvancetimeByIntervals(kFps * 97);
603
604   // A single frame to flush the pipeline.
605   SimulateFrameSequence({false}, 1);
606
607   EXPECT_EQ(histogram->total_count(), 100u * kFps);
608   EXPECT_EQ(histogram->GetPercentDroppedFramePercentile(0.96), 0u);
609   EXPECT_GT(histogram->GetPercentDroppedFramePercentile(0.97), 0u);
610 }
611
612 TEST_P(SmoothnessStrategyDroppedFrameCounterTest,
613        Percentile95WithIdleFramesWhileHidden) {
614   // The test scenario is the same as |Percentile95WithIdleFrames| test:
615   //  . 4s of 20% dropped frames.
616   //  . 96s of idle time.
617   // However, the 96s of idle time happens *after* the page becomes invisible
618   // (e.g. after a tab-switch). In this case, the idle time *should not*
619   // contribute to the sliding window.
620
621   // Set an interval that rounds up nicely with 1 second.
622   constexpr auto kInterval = base::Milliseconds(10);
623   constexpr size_t kFps = base::Seconds(1) / kInterval;
624   static_assert(
625       kFps % 5 == 0,
626       "kFps must be a multiple of 5 because this test depends on it.");
627   SetInterval(kInterval);
628
629   const auto* histogram = GetSlidingWindowHistogram();
630
631   // First 4 seconds with 20% dropped frames.
632   SimulateFrameSequence({false, false, false, false, true}, (kFps / 5) * 4);
633   EXPECT_EQ(histogram->GetPercentDroppedFramePercentile(0.95), 20u);
634
635   // Hide the page (thus resetting the pending frames), then idle for 96s before
636   // producing a single frame.
637   dropped_frame_counter_.ResetPendingFrames(GetNextFrameTime());
638   AdvancetimeByIntervals(kFps * 97);
639
640   // A single frame to flush the pipeline.
641   SimulateFrameSequence({false}, 1);
642
643   EXPECT_EQ(histogram->GetPercentDroppedFramePercentile(0.95), 20u);
644 }
645
646 TEST_P(SmoothnessStrategyDroppedFrameCounterTest,
647        Percentile95WithIdleFramesThenHide) {
648   // The test scenario is the same as |Percentile95WithIdleFramesWhileHidden|:
649   //  . 4s of 20% dropped frames.
650   //  . 96s of idle time.
651   // However, the 96s of idle time happens *before* the page becomes invisible
652   // (e.g. after a tab-switch). In this case, the idle time *should*
653   // contribute to the sliding window.
654
655   // Set an interval that rounds up nicely with 1 second.
656   constexpr auto kInterval = base::Milliseconds(10);
657   constexpr size_t kFps = base::Seconds(1) / kInterval;
658   static_assert(
659       kFps % 5 == 0,
660       "kFps must be a multiple of 5 because this test depends on it.");
661   SetInterval(kInterval);
662
663   const auto* histogram = GetSlidingWindowHistogram();
664
665   // First 4 seconds with 20% dropped frames.
666   SimulateFrameSequence({false, false, false, false, true}, (kFps / 5) * 4);
667   EXPECT_EQ(histogram->GetPercentDroppedFramePercentile(0.95), 20u);
668
669   // Idle for 96s before hiding the page.
670   AdvancetimeByIntervals(kFps * 97);
671   dropped_frame_counter_.ResetPendingFrames(GetNextFrameTime());
672   AdvancetimeByIntervals(kFps * 97);
673
674   // A single frame to flush the pipeline.
675   SimulateFrameSequence({false}, 1);
676
677   EXPECT_EQ(histogram->GetPercentDroppedFramePercentile(0.96), 0u);
678   EXPECT_GT(histogram->GetPercentDroppedFramePercentile(0.97), 0u);
679 }
680
681 // Tests that when ResetPendingFrames updates the sliding window, that the max
682 // PercentDroppedFrames is also updated accordingly. (https://crbug.com/1225307)
683 TEST_P(SmoothnessStrategyDroppedFrameCounterTest,
684        ResetPendingFramesUpdatesMaxPercentDroppedFrames) {
685   // This tests a scenario where gaps in frame production lead to having
686   // leftover frames in the sliding window for calculations of
687   // ResetPendingFrames.
688   //
689   // Testing for when those frames are sufficient to change the current maximum
690   // PercentDroppedFrames.
691   //
692   // This has been first seen in GpuCrash_InfoForDualHardwareGpus which forces
693   // a GPU crash. Introducing long periods of idle while the Renderer waits for
694   // a new GPU Process. (https://crbug.com/1164647)
695
696   // Set an interval that rounds up nicely with 1 second.
697   constexpr auto kInterval = base::Milliseconds(10);
698   constexpr size_t kFps = base::Seconds(1) / kInterval;
699   SetInterval(kInterval);
700
701   // One good frame
702   SimulateFrameSequence({false}, 1);
703   // Advance 1s so that when we process the first window, we go from having
704   // enough frames in the interval, to no longer having enough.
705   AdvancetimeByIntervals(kFps);
706
707   // The first frame should fill up the sliding window. It isn't dropped, so
708   // there should be 0 dropped frames. This will pop the first reported frame.
709   // The second frame is dropped, however we are now tracking less frames than
710   // the 1s window. So we won't use it in calculations yet.
711   SimulateFrameSequence({false, true}, 1);
712   EXPECT_EQ(dropped_frame_counter_.sliding_window_max_percent_dropped(), 0u);
713
714   // Advance 1s so that we will attempt to update the window when resetting the
715   // pending frames. The pending dropped frame above should be calculated here,
716   // and the max percentile should be updated.
717   AdvancetimeByIntervals(kFps);
718   dropped_frame_counter_.ResetPendingFrames(GetNextFrameTime());
719   EXPECT_GT(dropped_frame_counter_.sliding_window_max_percent_dropped(), 0u);
720
721   // There should be enough sliding windows reported with 0 dropped frames that
722   // the 95th percentile stays at 0.
723   EXPECT_EQ(PercentDroppedFrame95Percentile(), 0u);
724 }
725
726 TEST_F(DroppedFrameCounterTest, ResetPendingFramesAccountingForPendingFrames) {
727   // Set an interval that rounds up nicely with 1 second.
728   constexpr auto kInterval = base::Milliseconds(10);
729   constexpr size_t kFps = base::Seconds(1) / kInterval;
730   SetInterval(kInterval);
731
732   // First 2 seconds with 20% dropped frames.
733   SimulateFrameSequence({false, false, false, false, true}, (kFps / 5) * 2);
734
735   // Have a pending frame which would hold the frames in queue.
736   SimulatePendingFrame(1);
737
738   // One second with 40% dropped frames.
739   SimulateFrameSequence({false, false, false, true, true}, (kFps / 5));
740
741   // On the first 2 seconds are accounted for and pdf is 20%.
742   EXPECT_EQ(MaxPercentDroppedFrame(), 20);
743
744   dropped_frame_counter_.ResetPendingFrames(GetNextFrameTime());
745
746   // After resetting the pending frames, the pdf would be 40%.
747   EXPECT_EQ(MaxPercentDroppedFrame(), 40);
748 }
749
750 TEST_F(DroppedFrameCounterTest, Reset) {
751   // Set an interval that rounds up nicely with 1 second.
752   constexpr auto kInterval = base::Milliseconds(10);
753   constexpr size_t kFps = base::Seconds(1) / kInterval;
754   SetInterval(kInterval);
755
756   // First 2 seconds with 20% dropped frames.
757   SimulateFrameSequence({false, false, false, false, true}, (kFps / 5) * 2);
758
759   // Have a pending frame which would hold the frames in queue.
760   SimulatePendingFrame(1);
761
762   // Another 2 seconds with 40% dropped frames.
763   SimulateFrameSequence({false, false, false, true, true}, (kFps / 5) * 2);
764
765   EXPECT_EQ(MaxPercentDroppedFrame(), 20u);
766
767   dropped_frame_counter_.Reset();  // Simulating gpu thread crash
768
769   // After reset the max percent dropped frame would be 0 and frames in queue
770   // behind the pending frame would not affect it.
771   EXPECT_EQ(MaxPercentDroppedFrame(), 0u);
772 }
773
774 TEST_F(DroppedFrameCounterTest, ConsistentSmoothnessRatings) {
775   // Set an interval that rounds up nicely with 1 second.
776   constexpr auto kInterval = base::Milliseconds(10);
777   constexpr size_t kFps = base::Seconds(1) / kInterval;
778   static_assert(kFps == 100,
779                 "kFps must be 100 because this test depends on it.");
780   SetInterval(kInterval);
781
782   // Add 5 seconds with 2% dropped frames. This should be in the first bucket.
783   SimulateFrameSequence(MakeFrameSequence(1, 50), (kFps / 50) * 5);
784   EXPECT_TRUE(CheckSmoothnessBuckets({100, 0, 0, 0, 0, 0, 0}));
785
786   // Add 5 seconds with 5% dropped frames. This should be in the second bucket.
787   dropped_frame_counter_.Reset();
788   dropped_frame_counter_.OnFcpReceived();
789   SimulateFrameSequence(MakeFrameSequence(1, 20), (kFps / 20) * 5);
790   EXPECT_TRUE(CheckSmoothnessBuckets({0, 100, 0, 0, 0, 0, 0}));
791
792   // Add 5 seconds with 10% dropped frames. This should be in the third bucket.
793   dropped_frame_counter_.Reset();
794   dropped_frame_counter_.OnFcpReceived();
795   SimulateFrameSequence(MakeFrameSequence(1, 10), (kFps / 10) * 5);
796   EXPECT_TRUE(CheckSmoothnessBuckets({0, 0, 100, 0, 0, 0, 0}));
797
798   // Add 5 seconds with 20% dropped frames. This should be in the fourth bucket.
799   dropped_frame_counter_.Reset();
800   dropped_frame_counter_.OnFcpReceived();
801   SimulateFrameSequence({false, false, false, false, true}, (kFps / 5) * 5);
802   EXPECT_TRUE(CheckSmoothnessBuckets({0, 0, 0, 100, 0, 0, 0}));
803
804   // Add 5 seconds with 40% dropped frames. This should be in the fifth bucket.
805   dropped_frame_counter_.Reset();
806   dropped_frame_counter_.OnFcpReceived();
807   SimulateFrameSequence({false, false, false, true, true}, (kFps / 5) * 5);
808   EXPECT_TRUE(CheckSmoothnessBuckets({0, 0, 0, 0, 100, 0, 0}));
809
810   // Add 5 seconds with 60% dropped frames. This should be in the sixth bucket.
811   dropped_frame_counter_.Reset();
812   dropped_frame_counter_.OnFcpReceived();
813   SimulateFrameSequence({false, false, true, true, true}, (kFps / 5) * 5);
814   EXPECT_TRUE(CheckSmoothnessBuckets({0, 0, 0, 0, 0, 100, 0}));
815
816   // Add 5 seconds with 80% dropped frames. This should be in the last bucket.
817   dropped_frame_counter_.Reset();
818   dropped_frame_counter_.OnFcpReceived();
819   SimulateFrameSequence({false, true, true, true, true}, (kFps / 5) * 5);
820   EXPECT_TRUE(CheckSmoothnessBuckets({0, 0, 0, 0, 0, 0, 100}));
821 }
822
823 TEST_F(DroppedFrameCounterTest, MovingSmoothnessRatings) {
824   // Set an interval that rounds up nicely with 1 second.
825   constexpr auto kInterval = base::Milliseconds(10);
826   constexpr size_t kFps = base::Seconds(1) / kInterval;
827   static_assert(kFps == 100,
828                 "kFps must be 100 because this test depends on it.");
829   SetInterval(kInterval);
830
831   // Add a second with 40% dropped frames. Nothing should be added to the
832   // histogram yet.
833   SimulateFrameSequence({false, false, false, true, true}, kFps / 5);
834   EXPECT_TRUE(CheckSmoothnessBuckets({0, 0, 0, 0, 0, 0, 0}));
835
836   // Add a second with 80% dropped frames. All very bad buckets should have some
837   // entries.
838   SimulateFrameSequence({false, true, true, true, true}, kFps / 5);
839   EXPECT_TRUE(CheckSmoothnessBuckets({0, 0, 0, 0, 22, 64, 14}));
840
841   // Add a second with 10% dropped frames. Should be mostly very bad, with a few
842   // bad and okay windows.
843   SimulateFrameSequence(MakeFrameSequence(1, 10), kFps / 10);
844   EXPECT_TRUE(CheckSmoothnessBuckets({0, 0, 1, 9, 29, 50, 11}));
845
846   // Add a second with 5% dropped frames, and a second with no dropped frames.
847   // The sliding window should shift from ok to very good over time.
848   SimulateFrameSequence(MakeFrameSequence(1, 20), kFps / 20);
849   SimulateFrameSequence({false}, kFps);
850   EXPECT_TRUE(CheckSmoothnessBuckets({15, 12.5, 23, 4.5, 14.5, 25, 5.5}));
851
852   // Clear the counter, then add a second with 100% dropped frames and a second
853   // with 0% dropped frames. As the sliding window shifts each integer percent
854   // (other than 100%) should be reported once, exactly matching the size of
855   // each bucket.
856   dropped_frame_counter_.Reset();
857   dropped_frame_counter_.OnFcpReceived();
858   SimulateFrameSequence({true}, kFps);
859   SimulateFrameSequence({false}, kFps);
860   EXPECT_TRUE(CheckSmoothnessBuckets({3, 3, 6, 13, 25, 25, 25}));
861 }
862
863 TEST_F(DroppedFrameCounterTest, FramesInFlightWhenFcpReceived) {
864   // Start five frames in flight.
865   std::vector<viz::BeginFrameArgs> pending_frames = SimulatePendingFrame(5);
866
867   // Set that FCP was received after the third frame starts, but before it ends.
868   base::TimeTicks time_fcp_sent =
869       pending_frames[2].frame_time + pending_frames[2].interval / 2;
870   dropped_frame_counter_.SetTimeFcpReceivedForTesting(time_fcp_sent);
871
872   // End each of the frames as dropped. The first three should not count for
873   // smoothness, only the last two.
874   for (const auto& frame : pending_frames) {
875     dropped_frame_counter_.OnEndFrame(frame, CreateStubFrameInfo(true));
876   }
877   EXPECT_EQ(dropped_frame_counter_.total_smoothness_dropped(), 2u);
878 }
879
880 TEST_F(DroppedFrameCounterTest, ForkedCompositorFrameReporter) {
881   // Run different combinations of main and impl threads dropping, make sure
882   // only one frame is counted as dropped each time.
883   SimulateForkedFrame(false, false);
884   EXPECT_EQ(dropped_frame_counter_.total_smoothness_dropped(), 0u);
885
886   SimulateForkedFrame(true, false);
887   EXPECT_EQ(dropped_frame_counter_.total_smoothness_dropped(), 1u);
888
889   SimulateForkedFrame(false, true);
890   EXPECT_EQ(dropped_frame_counter_.total_smoothness_dropped(), 2u);
891
892   SimulateForkedFrame(true, true);
893   EXPECT_EQ(dropped_frame_counter_.total_smoothness_dropped(), 3u);
894 }
895
896 TEST_F(DroppedFrameCounterTest, WorstSmoothnessTiming) {
897   // Set an interval that rounds up nicely with 1 second.
898   constexpr auto kInterval = base::Milliseconds(10);
899   constexpr size_t kFps = base::Seconds(1) / kInterval;
900   static_assert(
901       kFps % 5 == 0,
902       "kFps must be a multiple of 5 because this test depends on it.");
903   SetInterval(kInterval);
904
905   // Prepare a second of pending frames, and send FCP after the last of these
906   // frames.
907   dropped_frame_counter_.Reset();
908   std::vector<viz::BeginFrameArgs> pending_frames = SimulatePendingFrame(kFps);
909   const auto& last_frame = pending_frames.back();
910   base::TimeTicks time_fcp_sent =
911       last_frame.frame_time + last_frame.interval / 2;
912   dropped_frame_counter_.OnFcpReceived();
913   dropped_frame_counter_.SetTimeFcpReceivedForTesting(time_fcp_sent);
914
915   // End each of the pending frames as dropped. These shouldn't affect any of
916   // the metrics.
917   for (const auto& frame : pending_frames) {
918     dropped_frame_counter_.OnEndFrame(frame, CreateStubFrameInfo(true));
919   }
920
921   // After FCP time, add a second each of 80% and 60%, and three seconds of 40%
922   // dropped frames. This should be five seconds total.
923   SimulateFrameSequence({false, true, true, true, true}, kFps / 5);
924   SimulateFrameSequence({false, false, true, true, true}, kFps / 5);
925   SimulateFrameSequence({false, false, false, true, true}, (kFps / 5) * 3);
926
927   // Next two seconds are 20% dropped frames.
928   SimulateFrameSequence({false, false, false, false, true}, (kFps / 5) * 2);
929
930   // The first 1, 2, and 5 seconds shouldn't be recorded in the corresponding
931   // max dropped after N seconds metrics.
932   EXPECT_FLOAT_EQ(MaxPercentDroppedFrame(), 80);
933   EXPECT_FLOAT_EQ(MaxPercentDroppedFrameAfter1Sec(), 60);
934   EXPECT_FLOAT_EQ(MaxPercentDroppedFrameAfter2Sec(), 40);
935   EXPECT_FLOAT_EQ(MaxPercentDroppedFrameAfter5Sec(), 20);
936
937   // Next second is 100% dropped frames, all metrics should include this.
938   SimulateFrameSequence({true}, kFps);
939   EXPECT_FLOAT_EQ(MaxPercentDroppedFrame(), 100);
940   EXPECT_FLOAT_EQ(MaxPercentDroppedFrameAfter1Sec(), 100);
941   EXPECT_FLOAT_EQ(MaxPercentDroppedFrameAfter2Sec(), 100);
942   EXPECT_FLOAT_EQ(MaxPercentDroppedFrameAfter5Sec(), 100);
943 }
944
945 TEST_F(DroppedFrameCounterTest, ReportForUI) {
946   constexpr auto kInterval = base::Milliseconds(10);
947   constexpr size_t kFps = base::Seconds(1) / kInterval;
948   static_assert(
949       kFps % 5 == 0,
950       "kFps must be a multiple of 5 because this test depends on it.");
951   SetInterval(kInterval);
952
953   dropped_frame_counter_.EnableReporForUI();
954   TestCustomMetricsRecorder recorder;
955
956   // 4 seconds with 20% dropped frames.
957   SimulateFrameSequence({false, false, false, false, true}, (kFps / 5) * 4);
958
959   // Recorded more than 1 samples of 20% dropped frame percentage.
960   EXPECT_GE(recorder.percent_dropped_frames_count(), 1);
961   EXPECT_EQ(recorder.last_percent_dropped_frames(), 20.0f);
962 }
963
964 }  // namespace
965 }  // namespace cc