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.
5 #include "cc/metrics/dropped_frame_counter.h"
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"
24 using SmoothnessStrategy = DroppedFrameCounter::SmoothnessStrategy;
26 FrameInfo CreateStubFrameInfo(bool is_dropped) {
27 return CreateFakeFrameInfo(is_dropped
28 ? FrameInfo::FrameFinalState::kDropped
29 : FrameInfo::FrameFinalState::kPresentedAll);
32 class TestCustomMetricsRecorder : public CustomMetricRecorder {
34 TestCustomMetricsRecorder() = default;
35 ~TestCustomMetricsRecorder() override = default;
37 // CustomMetricRecorder:
38 void ReportPercentDroppedFramesInOneSecoundWindow(
39 double percentage) override {
40 ++percent_dropped_frames_count_;
41 last_percent_dropped_frames_ = percentage;
44 int percent_dropped_frames_count() const {
45 return percent_dropped_frames_count_;
48 double last_percent_dropped_frames() const {
49 return last_percent_dropped_frames_;
53 int percent_dropped_frames_count_ = 0;
54 double last_percent_dropped_frames_ = 0;
57 class DroppedFrameCounterTestBase : public LayerTreeTest {
59 DroppedFrameCounterTestBase() = default;
60 ~DroppedFrameCounterTestBase() override = default;
62 virtual void SetUpTestConfigAndExpectations() = 0;
64 void InitializeSettings(LayerTreeSettings* settings) override {
65 settings->commit_to_active_tree = false;
68 void SetupTree() override {
69 LayerTreeTest::SetupTree();
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_);
80 void RunTest(CompositorMode mode) override {
81 SetUpTestConfigAndExpectations();
82 LayerTreeTest::RunTest(mode);
85 void BeginTest() override {
86 ASSERT_GT(config_.animation_frames, 0u);
88 // Start with requesting main-frames.
89 PostSetNeedsCommitToMainThread();
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_);
103 // Compositor thread function overrides:
104 void WillBeginImplFrameOnThread(LayerTreeHostImpl* host_impl,
105 const viz::BeginFrameArgs& args,
106 bool has_damage) override {
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));
115 if (skip_main_thread_next_frame_) {
116 skip_main_thread_next_frame_ = false;
118 // Request update from the main-thread too.
119 host_impl->SetNeedsCommit();
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;
128 base::AutoLock lock(wait_lock_);
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;
141 void DidReceivePresentationTimeOnThread(
142 LayerTreeHostImpl* host_impl,
143 uint32_t frame_token,
144 const gfx::PresentationFeedback& feedback) override {
146 if (presented_frames_ < config_.animation_frames)
149 auto* dropped_frame_counter = host_impl->dropped_frame_counter();
150 DCHECK(dropped_frame_counter);
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();
159 // Main-thread function overrides:
160 void BeginMainFrame(const viz::BeginFrameArgs& args) override {
164 bool should_wait = false;
165 if (config_.should_drop_main_every > 0) {
167 args.frame_id.sequence_number % config_.should_drop_main_every == 0;
171 base::WaitableEvent wait{base::WaitableEvent::ResetPolicy::MANUAL,
172 base::WaitableEvent::InitialState::NOT_SIGNALED};
174 base::AutoLock lock(wait_lock_);
179 base::AutoLock lock(wait_lock_);
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);
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
200 uint32_t should_drop_main_every = 0;
201 uint32_t animation_frames = 0;
202 bool should_register_main_thread_animation = false;
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
208 struct TestExpectation {
209 uint32_t min_partial = 0;
210 uint32_t min_dropped = 0;
211 uint32_t min_dropped_smoothness = 0;
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_;
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;
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;
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;
238 bool skip_main_thread_next_frame_ = false;
241 class DroppedFrameCounterNoDropTest : public DroppedFrameCounterTestBase {
243 ~DroppedFrameCounterNoDropTest() override = default;
245 void SetUpTestConfigAndExpectations() override {
246 config_.animation_frames = 28;
247 config_.should_register_main_thread_animation = false;
249 expect_.min_partial = 0;
250 expect_.min_dropped = 0;
251 expect_.min_dropped_smoothness = 0;
255 MULTI_THREAD_TEST_F(DroppedFrameCounterNoDropTest);
257 class DroppedFrameCounterMainDropsNoSmoothness
258 : public DroppedFrameCounterTestBase {
260 ~DroppedFrameCounterMainDropsNoSmoothness() override = default;
262 void SetUpTestConfigAndExpectations() override {
263 config_.animation_frames = 28;
264 config_.should_drop_main_every = 5;
265 config_.should_register_main_thread_animation = false;
267 expect_.min_partial = 5;
268 expect_.min_dropped_smoothness = 0;
272 // TODO(crbug.com/1115376) Disabled for flakiness.
273 // MULTI_THREAD_TEST_F(DroppedFrameCounterMainDropsNoSmoothness);
275 class DroppedFrameCounterMainDropsSmoothnessTest
276 : public DroppedFrameCounterTestBase {
278 ~DroppedFrameCounterMainDropsSmoothnessTest() override = default;
280 void SetUpTestConfigAndExpectations() override {
281 config_.animation_frames = 28;
282 config_.should_drop_main_every = 5;
283 config_.should_register_main_thread_animation = true;
285 expect_.min_partial = 5;
286 expect_.min_dropped_smoothness = 5;
290 // TODO(crbug.com/1115376) Disabled for flakiness.
291 // MULTI_THREAD_TEST_F(DroppedFrameCounterMainDropsSmoothnessTest);
293 class DroppedFrameCounterTest : public testing::Test {
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();
301 ~DroppedFrameCounterTest() override = default;
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));
312 frame_time_ += interval_;
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;
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);
333 frame_time_ += interval_;
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);
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);
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);
355 frame_time_ += interval_;
358 void AdvancetimeByIntervals(int interval_count) {
359 frame_time_ += interval_ * interval_count;
362 double MaxPercentDroppedFrame() {
363 return dropped_frame_counter_.sliding_window_max_percent_dropped();
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();
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();
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();
387 double PercentDroppedFrame95Percentile() {
388 return dropped_frame_counter_.SlidingWindow95PercentilePercentDropped(
389 smoothness_strategy_);
392 double PercentDroppedFrameMedian() {
393 return dropped_frame_counter_.SlidingWindowMedianPercentDropped(
394 smoothness_strategy_);
397 double PercentDroppedFrameVariance() {
398 return dropped_frame_counter_.SlidingWindowPercentDroppedVariance(
399 smoothness_strategy_);
402 const DroppedFrameCounter::SlidingWindowHistogram*
403 GetSlidingWindowHistogram() {
404 return dropped_frame_counter_.GetSlidingWindowHistogram(
405 smoothness_strategy_);
408 double GetTotalFramesInWindow() { return base::Seconds(1) / interval_; }
410 void SetInterval(base::TimeDelta interval) { interval_ = interval; }
412 base::TimeTicks GetNextFrameTime() const { return frame_time_ + interval_; }
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;
424 for (size_t i = 0; i < buckets.size(); i++) {
425 if (std::abs(buckets[i] - expected_buckets[i]) > epsilon) {
426 buckets_match = false;
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);
436 return buckets_match;
440 DroppedFrameCounter dropped_frame_counter_;
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
451 SmoothnessStrategy smoothness_strategy_;
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_;
463 // Test class that supports parameterized tests for each of the different
464 // SmoothnessStrategy.
466 // TODO(jonross): when we build the other strategies parameterize the
468 class SmoothnessStrategyDroppedFrameCounterTest
469 : public DroppedFrameCounterTest,
470 public testing::WithParamInterface<SmoothnessStrategy> {
472 SmoothnessStrategyDroppedFrameCounterTest()
473 : DroppedFrameCounterTest(GetParam()) {}
474 ~SmoothnessStrategyDroppedFrameCounterTest() override = default;
475 SmoothnessStrategyDroppedFrameCounterTest(
476 const SmoothnessStrategyDroppedFrameCounterTest&) = delete;
477 SmoothnessStrategyDroppedFrameCounterTest& operator=(
478 const SmoothnessStrategyDroppedFrameCounterTest&) = delete;
481 INSTANTIATE_TEST_SUITE_P(
483 SmoothnessStrategyDroppedFrameCounterTest,
484 ::testing::Values(SmoothnessStrategy::kDefaultStrategy));
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);
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);
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);
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);
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);
523 TEST_P(SmoothnessStrategyDroppedFrameCounterTest, MaxPercentDroppedChanges) {
524 // First 60 frames have 20% dropped.
525 SimulateFrameSequence({false, false, false, false, true}, 12);
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);
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);
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);
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);
559 TEST_F(DroppedFrameCounterTest, MaxPercentDroppedWithIdleFrames) {
560 // First 20 frames have 4 frames dropped (20%).
561 SimulateFrameSequence({false, false, false, false, true}, 4);
563 // Then no frames are added for 20 intervals.
564 AdvancetimeByIntervals(20);
566 // Then 20 frames have 16 frames dropped (60%).
567 SimulateFrameSequence({false, false, true, true, true}, 4);
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);
575 TEST_F(DroppedFrameCounterTest, NoCrashForIntervalLargerThanWindow) {
576 SetInterval(base::Milliseconds(1000));
577 SimulateFrameSequence({false, false}, 1);
580 TEST_P(SmoothnessStrategyDroppedFrameCounterTest, Percentile95WithIdleFrames) {
582 // . 4s of 20% dropped frames.
583 // . 96s of idle time.
584 // The 96%ile dropped-frame metric should be 0.
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;
591 "kFps must be a multiple of 5 because this test depends on it.");
592 SetInterval(kInterval);
594 const auto* histogram = GetSlidingWindowHistogram();
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);
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);
604 // A single frame to flush the pipeline.
605 SimulateFrameSequence({false}, 1);
607 EXPECT_EQ(histogram->total_count(), 100u * kFps);
608 EXPECT_EQ(histogram->GetPercentDroppedFramePercentile(0.96), 0u);
609 EXPECT_GT(histogram->GetPercentDroppedFramePercentile(0.97), 0u);
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.
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;
626 "kFps must be a multiple of 5 because this test depends on it.");
627 SetInterval(kInterval);
629 const auto* histogram = GetSlidingWindowHistogram();
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);
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);
640 // A single frame to flush the pipeline.
641 SimulateFrameSequence({false}, 1);
643 EXPECT_EQ(histogram->GetPercentDroppedFramePercentile(0.95), 20u);
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.
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;
660 "kFps must be a multiple of 5 because this test depends on it.");
661 SetInterval(kInterval);
663 const auto* histogram = GetSlidingWindowHistogram();
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);
669 // Idle for 96s before hiding the page.
670 AdvancetimeByIntervals(kFps * 97);
671 dropped_frame_counter_.ResetPendingFrames(GetNextFrameTime());
672 AdvancetimeByIntervals(kFps * 97);
674 // A single frame to flush the pipeline.
675 SimulateFrameSequence({false}, 1);
677 EXPECT_EQ(histogram->GetPercentDroppedFramePercentile(0.96), 0u);
678 EXPECT_GT(histogram->GetPercentDroppedFramePercentile(0.97), 0u);
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.
689 // Testing for when those frames are sufficient to change the current maximum
690 // PercentDroppedFrames.
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)
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);
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);
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);
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);
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);
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);
732 // First 2 seconds with 20% dropped frames.
733 SimulateFrameSequence({false, false, false, false, true}, (kFps / 5) * 2);
735 // Have a pending frame which would hold the frames in queue.
736 SimulatePendingFrame(1);
738 // One second with 40% dropped frames.
739 SimulateFrameSequence({false, false, false, true, true}, (kFps / 5));
741 // On the first 2 seconds are accounted for and pdf is 20%.
742 EXPECT_EQ(MaxPercentDroppedFrame(), 20);
744 dropped_frame_counter_.ResetPendingFrames(GetNextFrameTime());
746 // After resetting the pending frames, the pdf would be 40%.
747 EXPECT_EQ(MaxPercentDroppedFrame(), 40);
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);
756 // First 2 seconds with 20% dropped frames.
757 SimulateFrameSequence({false, false, false, false, true}, (kFps / 5) * 2);
759 // Have a pending frame which would hold the frames in queue.
760 SimulatePendingFrame(1);
762 // Another 2 seconds with 40% dropped frames.
763 SimulateFrameSequence({false, false, false, true, true}, (kFps / 5) * 2);
765 EXPECT_EQ(MaxPercentDroppedFrame(), 20u);
767 dropped_frame_counter_.Reset(); // Simulating gpu thread crash
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);
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);
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}));
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}));
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}));
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}));
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}));
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}));
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}));
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);
831 // Add a second with 40% dropped frames. Nothing should be added to the
833 SimulateFrameSequence({false, false, false, true, true}, kFps / 5);
834 EXPECT_TRUE(CheckSmoothnessBuckets({0, 0, 0, 0, 0, 0, 0}));
836 // Add a second with 80% dropped frames. All very bad buckets should have some
838 SimulateFrameSequence({false, true, true, true, true}, kFps / 5);
839 EXPECT_TRUE(CheckSmoothnessBuckets({0, 0, 0, 0, 22, 64, 14}));
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}));
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}));
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
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}));
863 TEST_F(DroppedFrameCounterTest, FramesInFlightWhenFcpReceived) {
864 // Start five frames in flight.
865 std::vector<viz::BeginFrameArgs> pending_frames = SimulatePendingFrame(5);
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);
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));
877 EXPECT_EQ(dropped_frame_counter_.total_smoothness_dropped(), 2u);
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);
886 SimulateForkedFrame(true, false);
887 EXPECT_EQ(dropped_frame_counter_.total_smoothness_dropped(), 1u);
889 SimulateForkedFrame(false, true);
890 EXPECT_EQ(dropped_frame_counter_.total_smoothness_dropped(), 2u);
892 SimulateForkedFrame(true, true);
893 EXPECT_EQ(dropped_frame_counter_.total_smoothness_dropped(), 3u);
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;
902 "kFps must be a multiple of 5 because this test depends on it.");
903 SetInterval(kInterval);
905 // Prepare a second of pending frames, and send FCP after the last of these
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);
915 // End each of the pending frames as dropped. These shouldn't affect any of
917 for (const auto& frame : pending_frames) {
918 dropped_frame_counter_.OnEndFrame(frame, CreateStubFrameInfo(true));
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);
927 // Next two seconds are 20% dropped frames.
928 SimulateFrameSequence({false, false, false, false, true}, (kFps / 5) * 2);
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);
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);
945 TEST_F(DroppedFrameCounterTest, ReportForUI) {
946 constexpr auto kInterval = base::Milliseconds(10);
947 constexpr size_t kFps = base::Seconds(1) / kInterval;
950 "kFps must be a multiple of 5 because this test depends on it.");
951 SetInterval(kInterval);
953 dropped_frame_counter_.EnableReporForUI();
954 TestCustomMetricsRecorder recorder;
956 // 4 seconds with 20% dropped frames.
957 SimulateFrameSequence({false, false, false, false, true}, (kFps / 5) * 4);
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);