[go: nahoru, domu]

blob: 03c14b9c6fca2ab11617a0063d0cabb46d4d1004 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/metrics/frame_sequence_tracker.h"
#include <string>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "cc/metrics/compositor_frame_reporting_controller.h"
#include "cc/metrics/frame_sequence_tracker_collection.h"
#include "cc/metrics/frame_sorter.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/presentation_feedback.h"
namespace cc {
namespace {
const char* ParseNumber(const char* str, uint64_t* retvalue) {
uint64_t number = 0;
for (; *str >= '0' && *str <= '9'; ++str) {
number *= 10;
number += *str - '0';
}
*retvalue = number;
return str;
}
} // namespace
class FrameSequenceTrackerTest : public testing::Test {
public:
const uint32_t kImplDamage = 0x1;
const uint32_t kMainDamage = 0x2;
FrameSequenceTrackerTest()
: compositor_frame_reporting_controller_(
std::make_unique<CompositorFrameReportingController>(
/*should_report_histograms=*/true,
/*should_report_ukm=*/false,
/*layer_tree_host_id=*/1)),
collection_(/*is_single_threaded=*/false,
compositor_frame_reporting_controller_.get()),
sorter_(base::BindRepeating(&FrameSequenceTrackerTest::OnFrameResult,
base::Unretained(this))) {
tracker_ = collection_.StartScrollSequence(
FrameSequenceTrackerType::kTouchScroll,
FrameInfo::SmoothEffectDrivingThread::kCompositor);
}
~FrameSequenceTrackerTest() override = default;
void CreateNewTracker(FrameInfo::SmoothEffectDrivingThread thread_type =
FrameInfo::SmoothEffectDrivingThread::kCompositor) {
tracker_ = collection_.StartScrollSequence(
FrameSequenceTrackerType::kTouchScroll, thread_type);
}
viz::BeginFrameArgs CreateBeginFrameArgs(
uint64_t source_id,
uint64_t sequence_number,
base::TimeTicks now = base::TimeTicks::Now()) {
auto interval = base::Milliseconds(16);
auto deadline = now + interval;
return viz::BeginFrameArgs::Create(BEGINFRAME_FROM_HERE, source_id,
sequence_number, now, deadline, interval,
viz::BeginFrameArgs::NORMAL);
}
void StartFrames(const viz::BeginFrameArgs& args) {
collection_.NotifyBeginImplFrame(args);
}
uint32_t DispatchCompleteFrame(const viz::BeginFrameArgs& args,
uint32_t damage_type,
bool has_missing_content = false) {
StartFrames(args);
if (damage_type & kImplDamage) {
uint32_t frame_token = NextFrameToken();
collection_.NotifyFrameEnd(args, args);
return frame_token;
} else {
collection_.NotifyFrameEnd(args, args);
}
return 0;
}
uint32_t NextFrameToken() {
static uint32_t frame_token = 0;
return ++frame_token;
}
// Check whether a type of tracker exists in |frame_trackers_| or not.
bool TrackerExists(FrameSequenceTrackerType type) const {
auto key =
std::make_pair(type, FrameInfo::SmoothEffectDrivingThread::kUnknown);
if (type == FrameSequenceTrackerType::kTouchScroll ||
type == FrameSequenceTrackerType::kWheelScroll ||
type == FrameSequenceTrackerType::kScrollbarScroll) {
key = std::make_pair(type,
FrameInfo::SmoothEffectDrivingThread::kCompositor);
if (!collection_.frame_trackers_.contains(key))
key = std::make_pair(type, FrameInfo::SmoothEffectDrivingThread::kMain);
}
return collection_.frame_trackers_.contains(key);
}
bool RemovalTrackerExists(unsigned index,
FrameSequenceTrackerType type) const {
DCHECK_GT(collection_.removal_trackers_.size(), index);
return collection_.removal_trackers_[index]->type() == type;
}
void GenerateSequence(const char* str) {
const uint64_t source_id = 1;
viz::BeginFrameArgs last_activated_main_args;
while (*str) {
const char command = *str++;
uint64_t sequence = 0, dummy = 0, last_activated_main = 0;
switch (command) {
case 'b':
case 'd':
case 'D':
case 'p':
case 'P':
case 'n':
ASSERT_EQ(*str, '(') << command;
str = ParseNumber(++str, &sequence);
ASSERT_EQ(*str, ')');
++str;
break;
case 'N':
ASSERT_EQ(*str, '(');
str = ParseNumber(++str, &dummy);
ASSERT_EQ(*str, ',');
str = ParseNumber(++str, &sequence);
ASSERT_EQ(*str, ')');
++str;
break;
case 'e':
ASSERT_EQ(*str, '(');
str = ParseNumber(++str, &sequence);
ASSERT_EQ(*str, ',');
str = ParseNumber(++str, &last_activated_main);
ASSERT_EQ(*str, ')');
++str;
break;
case 'R':
break;
default:
NOTREACHED() << command << str;
}
switch (command) {
case 'b':
collection_.NotifyBeginImplFrame(
CreateBeginFrameArgs(source_id, sequence));
break;
case 'd': {
auto args = CreateBeginFrameArgs(source_id, sequence);
FrameInfo frame_info;
frame_info.final_state = FrameInfo::FrameFinalState::kDropped;
frame_info.smooth_thread = FrameInfo::SmoothThread::kSmoothCompositor;
collection_.AddSortedFrame(args, frame_info);
break;
}
case 'D': {
auto args = CreateBeginFrameArgs(source_id, sequence);
FrameInfo frame_info;
frame_info.final_state = FrameInfo::FrameFinalState::kDropped;
frame_info.smooth_thread = FrameInfo::SmoothThread::kSmoothMain;
collection_.AddSortedFrame(args, frame_info);
break;
}
case 'p': {
auto args = CreateBeginFrameArgs(source_id, sequence);
FrameInfo frame_info;
frame_info.final_state = FrameInfo::FrameFinalState::kPresentedAll;
frame_info.smooth_thread = FrameInfo::SmoothThread::kSmoothCompositor;
collection_.AddSortedFrame(args, frame_info);
break;
}
case 'P': {
auto args = CreateBeginFrameArgs(source_id, sequence);
FrameInfo frame_info;
frame_info.final_state = FrameInfo::FrameFinalState::kPresentedAll;
frame_info.smooth_thread = FrameInfo::SmoothThread::kSmoothMain;
collection_.AddSortedFrame(args, frame_info);
break;
}
case 'R':
collection_.NotifyPauseFrameProduction();
break;
case 'n': {
auto args = CreateBeginFrameArgs(source_id, sequence);
FrameInfo frame_info;
frame_info.final_state = FrameInfo::FrameFinalState::kNoUpdateDesired;
collection_.AddSortedFrame(args, frame_info);
break;
}
case 'e': {
auto args = CreateBeginFrameArgs(source_id, sequence);
collection_.NotifyFrameEnd(args, args);
break;
}
case 'N': {
auto args = CreateBeginFrameArgs(source_id, sequence);
FrameInfo frame_info;
frame_info.final_state = FrameInfo::FrameFinalState::kNoUpdateDesired;
frame_info.smooth_thread = FrameInfo::SmoothThread::kSmoothMain;
collection_.AddSortedFrame(args, frame_info);
break;
}
default:
NOTREACHED();
}
}
}
void ReportMetrics() { tracker_->metrics_->ReportMetrics(); }
base::TimeDelta TimeDeltaToReport() const {
return tracker_->time_delta_to_report_;
}
unsigned NumberOfTrackers() const {
return collection_.frame_trackers_.size();
}
unsigned NumberOfCustomTrackers() const {
return collection_.custom_frame_trackers_.size();
}
unsigned NumberOfRemovalTrackers() const {
return collection_.removal_trackers_.size();
}
uint64_t BeginImplFrameDataPreviousSequence() const {
return tracker_->begin_impl_frame_data_.previous_sequence;
}
void IncrementFramesExpected(uint32_t frames) {
tracker_->metrics_->v3_.frames_expected += frames;
}
uint32_t frames_expected() const {
return tracker_->metrics_->v3_.frames_expected;
}
uint32_t frames_produced() const {
return tracker_->metrics_->v3_.frames_expected -
tracker_->metrics_->v3_.frames_dropped;
}
FrameSequenceTracker::TerminationStatus GetTerminationStatus(
FrameSequenceTracker* tracker) {
return tracker->termination_status_;
}
FrameSequenceTracker::TerminationStatus GetTerminationStatus() {
return tracker_->termination_status_;
}
// FrameSorter callback.
void OnFrameResult(const viz::BeginFrameArgs& args,
const FrameInfo& frame_info) {
collection_.AddSortedFrame(args, frame_info);
}
protected:
std::unique_ptr<CompositorFrameReportingController>
compositor_frame_reporting_controller_;
FrameSequenceTrackerCollection collection_;
raw_ptr<FrameSequenceTracker, DanglingUntriaged> tracker_;
FrameSorter sorter_;
};
// Tests that the tracker works correctly when the source-id for the
// begin-frames change.
TEST_F(FrameSequenceTrackerTest, SourceIdChangeDuringSequence) {
const uint64_t source_1 = 1;
uint64_t sequence_1 = 0;
// Dispatch some frames, both causing damage to impl/main, and both impl and
// main providing damage to the frame.
auto args_1 = CreateBeginFrameArgs(source_1, ++sequence_1);
DispatchCompleteFrame(args_1, kImplDamage | kMainDamage);
args_1 = CreateBeginFrameArgs(source_1, ++sequence_1);
DispatchCompleteFrame(args_1, kImplDamage | kMainDamage);
// Start a new tracker.
CreateNewTracker();
// Change the source-id, and start an impl frame. This time, the main-frame
// does not provide any damage.
const uint64_t source_2 = 2;
uint64_t sequence_2 = 0;
auto args_2 = CreateBeginFrameArgs(source_2, ++sequence_2);
collection_.NotifyBeginImplFrame(args_2);
}
TEST_F(FrameSequenceTrackerTest, TestNotifyFramePresented) {
collection_.StartSequence(FrameSequenceTrackerType::kCompositorAnimation);
collection_.StartSequence(FrameSequenceTrackerType::kMainThreadAnimation);
EXPECT_EQ(NumberOfTrackers(), 3u);
collection_.StopSequence(FrameSequenceTrackerType::kCompositorAnimation);
EXPECT_EQ(NumberOfTrackers(), 2u);
EXPECT_TRUE(TrackerExists(FrameSequenceTrackerType::kMainThreadAnimation));
EXPECT_TRUE(TrackerExists(FrameSequenceTrackerType::kTouchScroll));
// StopSequence should have destroyed all trackers because there is no frame
// awaiting presentation.
EXPECT_EQ(NumberOfRemovalTrackers(), 0u);
}
TEST_F(FrameSequenceTrackerTest, ReportMetricsAtFixedInterval) {
const uint64_t source = 1;
uint64_t sequence = 0;
base::TimeDelta first_time_delta = base::Seconds(1);
auto args = CreateBeginFrameArgs(source, ++sequence,
base::TimeTicks::Now() + first_time_delta);
// args.frame_time is less than 5s of the tracker creation time, so won't
// schedule this tracker to report its throughput.
collection_.NotifyBeginImplFrame(args);
collection_.NotifyFrameEnd(args, args);
EXPECT_EQ(NumberOfTrackers(), 1u);
EXPECT_EQ(NumberOfRemovalTrackers(), 0u);
IncrementFramesExpected(101u);
// Now args.frame_time is 5s since the tracker creation time, so this tracker
// should be scheduled to report its throughput.
args = CreateBeginFrameArgs(source, ++sequence,
args.frame_time + TimeDeltaToReport());
collection_.NotifyBeginImplFrame(args);
collection_.NotifyFrameEnd(args, args);
FrameInfo frame_info;
frame_info.final_state = FrameInfo::FrameFinalState::kPresentedAll;
collection_.AddSortedFrame(args, frame_info);
EXPECT_EQ(NumberOfTrackers(), 1u);
// At NotifyFrameEnd, the tracker is removed from removal_tracker_ list.
EXPECT_EQ(NumberOfRemovalTrackers(), 0u);
}
TEST_F(FrameSequenceTrackerTest, MainFrameNoDamageTracking) {
const uint64_t source = 1;
uint64_t sequence = 0;
const auto first_args = CreateBeginFrameArgs(source, ++sequence);
DispatchCompleteFrame(first_args, kImplDamage | kMainDamage);
// Now, start the next frame, but for main, respond with the previous args.
const auto second_args = CreateBeginFrameArgs(source, ++sequence);
StartFrames(second_args);
collection_.NotifyFrameEnd(second_args, second_args);
// Start and submit the next frame, with no damage from main.
auto args = CreateBeginFrameArgs(source, ++sequence);
collection_.NotifyBeginImplFrame(args);
collection_.NotifyFrameEnd(args, args);
// Now, submit a frame with damage from main from |second_args|.
args = CreateBeginFrameArgs(source, ++sequence);
StartFrames(args);
collection_.NotifyFrameEnd(args, args);
}
TEST_F(FrameSequenceTrackerTest, SimpleSequenceOneFrame) {
const char sequence[] = "b(1)e(1,0)P(1)";
GenerateSequence(sequence);
EXPECT_EQ(frames_expected(), 1u);
EXPECT_EQ(frames_produced(), 1u);
}
TEST_F(FrameSequenceTrackerTest, SimpleSequenceOneFrameNoDamage) {
const char sequence[] = "b(1)N(1,1)n(1)e(1,0)";
GenerateSequence(sequence);
EXPECT_EQ(frames_expected(), 2u);
EXPECT_EQ(frames_produced(), 2u);
const char second_sequence[] = "b(2)n(2)N(2,2)e(2,0)";
GenerateSequence(second_sequence);
EXPECT_EQ(frames_expected(), 4u);
EXPECT_EQ(frames_produced(), 4u);
}
TEST_F(FrameSequenceTrackerTest, MultipleNoDamageNotifications) {
const char sequence[] = "b(1)n(1)n(1)e(1,0)";
GenerateSequence(sequence);
EXPECT_EQ(frames_expected(), 2u);
EXPECT_EQ(frames_produced(), 2u);
}
TEST_F(FrameSequenceTrackerTest, MultipleNoDamageNotificationsFromMain) {
const char sequence[] = "b(1)N(1,1)n(1)N(0,1)e(1,0)";
GenerateSequence(sequence);
EXPECT_EQ(frames_expected(), 3u);
EXPECT_EQ(frames_produced(), 3u);
}
TEST_F(FrameSequenceTrackerTest, DelayedMainFrameNoDamage) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
const char sequence[] = "b(1)n(1)e(1,0)b(2)n(2)e(2,0)b(3)N(0,1)n(3)e(3,0)";
GenerateSequence(sequence);
EXPECT_EQ(frames_expected(), 0u);
EXPECT_EQ(frames_produced(), 0u);
}
TEST_F(FrameSequenceTrackerTest, DelayedMainFrameNoDamageFromOlderFrame) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
// Start a sequence, and receive a 'no damage' from an earlier frame.
const char second_sequence[] = "b(2)N(2,1)n(2)N(2,2)e(2,0)";
GenerateSequence(second_sequence);
EXPECT_EQ(frames_expected(), 0u);
EXPECT_EQ(frames_produced(), 0u);
}
// This tests when a BeginMainFrame leads to No Damage, after the next Main
// Frame has started. This should not crash.
TEST_F(FrameSequenceTrackerTest, DelayedMainFrameNoDamageAfterNextMainFrame) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
const char sequence[] = "b(1)n(1)e(1,0)b(2)N(0,1)n(2)N(0,2)e(2,0)";
GenerateSequence(sequence);
EXPECT_EQ(frames_expected(), 0u);
EXPECT_EQ(frames_produced(), 0u);
}
TEST_F(FrameSequenceTrackerTest, StateResetDuringSequence) {
const char sequence[] = "b(1)n(1)N(1,1)Re(1,0)b(2)n(2)e(2,0)";
GenerateSequence(sequence);
EXPECT_EQ(frames_expected(), 3u);
EXPECT_EQ(frames_produced(), 3u);
}
TEST_F(FrameSequenceTrackerTest, SequenceStateResetsDuringFrame) {
const char sequence[] = "b(1)Rn(1)e(1,0)";
GenerateSequence(sequence);
EXPECT_EQ(frames_expected(), 1u);
EXPECT_EQ(frames_produced(), 1u);
GenerateSequence("b(2)e(2,0)P(1)b(4)");
EXPECT_EQ(frames_expected(), 2u);
EXPECT_EQ(frames_produced(), 2u);
}
// b(2417)B(0,2417)E(2417)n(2417)N(2417,2417)
TEST_F(FrameSequenceTrackerTest, SequenceNumberReset) {
const char sequence[] = "b(6)n(6)e(6,0)Rb(1)N(1,1)n(1)e(1,0)b(2)n(2)e(2,0)";
GenerateSequence(sequence);
EXPECT_EQ(frames_expected(), 1u);
}
TEST_F(FrameSequenceTrackerTest, MainThroughputWithHighLatency) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
const char sequence[] = "b(1)n(1)e(1,0)b(2)e(2,1)P(1)D(1)";
GenerateSequence(sequence);
EXPECT_EQ(frames_expected(), 2u);
EXPECT_EQ(frames_produced(), 1u);
}
TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame1) {
GenerateSequence("b(1)e(1,0)b(4)e(4,0)");
collection_.StopSequence(FrameSequenceTrackerType::kTouchScroll);
EXPECT_EQ(NumberOfRemovalTrackers(), 1u);
FrameSequenceTracker* removal_tracker =
collection_.GetRemovalTrackerForTesting(
FrameSequenceTrackerType::kTouchScroll);
EXPECT_EQ(GetTerminationStatus(removal_tracker),
FrameSequenceTracker::TerminationStatus::kScheduledForTermination);
GenerateSequence("P(1)");
// There is still one impl-frame not processed not, so the tracker is not yet
// ready for termination.
EXPECT_EQ(NumberOfRemovalTrackers(), 1u);
EXPECT_EQ(GetTerminationStatus(removal_tracker),
FrameSequenceTracker::TerminationStatus::kScheduledForTermination);
}
// All the following cases are for one complete impl + one incomplete:
// b(1)e(1,0)xxxxxxxxP(1)
// The 'xxxxx' is an incomplete impl frame that has no damage, it could be
// 1. b(2)e(2,0)P(1)n(2), and StopSequence happens anywhere after e and
// before P.
// 2. b(2)e(2,0)P(1)n(2), and StopSequence can happen anywhere after e and
// before P. In this case, the tracker is not ready for termination yet at P
// because the sorted n(2) has not been called yet.
TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame10) {
GenerateSequence("b(1)e(1,0)b(2)e(2,0)");
collection_.StopSequence(FrameSequenceTrackerType::kTouchScroll);
EXPECT_EQ(NumberOfRemovalTrackers(), 1u);
FrameSequenceTracker* removal_tracker =
collection_.GetRemovalTrackerForTesting(
FrameSequenceTrackerType::kTouchScroll);
EXPECT_EQ(GetTerminationStatus(removal_tracker),
FrameSequenceTracker::TerminationStatus::kScheduledForTermination);
GenerateSequence("P(1)");
EXPECT_EQ(NumberOfRemovalTrackers(), 1u);
EXPECT_EQ(GetTerminationStatus(removal_tracker),
FrameSequenceTracker::TerminationStatus::kScheduledForTermination);
}
TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame11) {
GenerateSequence("b(1)e(1,0)b(2)e(2,0)");
collection_.StopSequence(FrameSequenceTrackerType::kTouchScroll);
EXPECT_EQ(NumberOfRemovalTrackers(), 1u);
FrameSequenceTracker* removal_tracker =
collection_.GetRemovalTrackerForTesting(
FrameSequenceTrackerType::kTouchScroll);
EXPECT_EQ(GetTerminationStatus(removal_tracker),
FrameSequenceTracker::TerminationStatus::kScheduledForTermination);
GenerateSequence("P(1)");
EXPECT_EQ(NumberOfRemovalTrackers(), 1u);
EXPECT_EQ(GetTerminationStatus(removal_tracker),
FrameSequenceTracker::TerminationStatus::kScheduledForTermination);
}
// This test ensure that the tracker would terminate at e.
TEST_F(FrameSequenceTrackerTest, TrackLastImplFrame24) {
GenerateSequence("b(1)P(1)");
collection_.StopSequence(FrameSequenceTrackerType::kTouchScroll);
EXPECT_EQ(NumberOfRemovalTrackers(), 1u);
GenerateSequence("e(1,0)p(1)");
EXPECT_EQ(NumberOfRemovalTrackers(), 0u);
}
// Termination is triggered after a new tree is committed and activated. Due to
// this we will have started a BeginImplFrame that is not actually a part of the
// sequence. When this occurs we terminate as soon as the most recently
// submitted frame has bee processed.
TEST_F(FrameSequenceTrackerTest, IgnoreImplFrameBeforeTermination) {
GenerateSequence("b(1)e(1,0)b(2)");
collection_.StopSequence(FrameSequenceTrackerType::kTouchScroll);
EXPECT_EQ(NumberOfRemovalTrackers(), 1u);
FrameSequenceTracker* removal_tracker =
collection_.GetRemovalTrackerForTesting(
FrameSequenceTrackerType::kTouchScroll);
EXPECT_EQ(GetTerminationStatus(removal_tracker),
FrameSequenceTracker::TerminationStatus::kScheduledForTermination);
GenerateSequence("e(2,0)");
EXPECT_EQ(NumberOfRemovalTrackers(), 1u);
EXPECT_EQ(GetTerminationStatus(removal_tracker),
FrameSequenceTracker::TerminationStatus::kScheduledForTermination);
GenerateSequence("P(1)");
EXPECT_EQ(NumberOfRemovalTrackers(), 0u);
}
TEST_F(FrameSequenceTrackerTest, TerminationWithNullPresentationTimeStamp) {
GenerateSequence("b(1)");
collection_.StopSequence(FrameSequenceTrackerType::kTouchScroll);
EXPECT_EQ(NumberOfRemovalTrackers(), 1u);
// Even if the presentation timestamp is null, as long as this presentation
// is acking the last impl frame, we consider that impl frame completed and
// so the tracker is ready for termination.
GenerateSequence("e(1,0)p(1)");
EXPECT_EQ(NumberOfRemovalTrackers(), 0u);
}
TEST_F(FrameSequenceTrackerTest, OffScreenMainDamage1) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
const char sequence[] = "b(1)n(1)e(1,0)b(2)n(2)e(2,1)b(3)n(3)e(3,2)";
GenerateSequence(sequence);
EXPECT_EQ(frames_expected(), 0u);
// At E(2), B(0,1) is treated no damage.
EXPECT_EQ(frames_expected(), 0u);
}
TEST_F(FrameSequenceTrackerTest, OffScreenMainDamage2) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
const char sequence[] =
"b(1)n(1)e(1,0)b(2)n(2)e(2,1)b(3)n(3)e(3,1)b(4)n(4)e(4,1)b(8)n(8)e(8,2)";
GenerateSequence(sequence);
EXPECT_EQ(frames_expected(), 0u);
// At E(2), B(0,1) is treated as no damage.
EXPECT_EQ(frames_expected(), 0u);
}
TEST_F(FrameSequenceTrackerTest, OffScreenMainDamage3) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
const char sequence[] =
"b(34)n(34)e(34,0)b(35)n(35)e(35,0)b(36)n(36)e(36,34)b(39)e(39,34)";
GenerateSequence(sequence);
// No damage frames are not expected.
EXPECT_EQ(frames_expected(), 0u);
}
TEST_F(FrameSequenceTrackerTest, OffScreenMainDamage4) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
const char sequence[] = "b(9)n(9)Re(9,0)b(11)n(11)e(11,9)b(12)e(12,11)b(13)";
GenerateSequence(sequence);
// No damage frames are not expected.
EXPECT_EQ(frames_expected(), 0u);
}
TEST_F(FrameSequenceTrackerTest, OffScreenMainDamage5) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
const char sequence[] =
"b(1)e(1,0)b(2)n(2)e(2,0)b(3)n(3)e(3,0)b(4)n(4)e(4,3)";
GenerateSequence(sequence);
// At E(4), we treat B(1,3) as if it had no damage.
EXPECT_EQ(frames_expected(), 0u);
}
TEST_F(FrameSequenceTrackerTest, OffScreenMainDamage6) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
const char sequence[] = "b(1)e(1,1)b(2)n(2)N(2,2)e(2,2)b(3)n(3)N(3,3)e(3,3)";
GenerateSequence(sequence);
// No damage frames are not expected.
EXPECT_EQ(frames_expected(), 0u);
}
TEST_F(FrameSequenceTrackerTest, OffScreenMainDamage7) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
const char sequence[] = "b(8)n(8)e(8,0)b(9)e(9,9)b(10)e(10,9)";
GenerateSequence(sequence);
// No damage frames are not expected.
EXPECT_EQ(frames_expected(), 0u);
}
TEST_F(FrameSequenceTrackerTest, OffScreenMainDamage8) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
const char sequence[] =
"b(18)n(18)N(18,18)Re(18,18)b(20)N(20,20)n(20)N(0,20)e(20,18)b(21)e(21,"
"21)";
GenerateSequence(sequence);
// No damage frames are not expected.
EXPECT_EQ(frames_expected(), 0u);
}
TEST_F(FrameSequenceTrackerTest, OffScreenMainDamage9) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
const char sequence[] =
"b(78)n(78)Re(78,0)Rb(82)n(82)N(82,82)Re(82,82)b(86)n(86)e(86,86)b(87)e("
"87,86)";
GenerateSequence(sequence);
// No damage frames are not expected.
EXPECT_EQ(frames_expected(), 0u);
}
TEST_F(FrameSequenceTrackerTest, OffScreenMainDamage10) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
const char sequence[] =
"b(2)n(2)N(2,2)e(2,2)b(3)n(3)N(3,3)e(3,3)b(4)n(4)N(4,4)e(4,4)b(5)n(5)N(5,"
"5)e(5,5)b(6)n(6)e(6,5)Rb(8)n(8)N(8,8)e(8,8)";
GenerateSequence(sequence);
EXPECT_EQ(frames_expected(), 0u);
}
// A presentation with a frame token that is > the main frame token submitted.
TEST_F(FrameSequenceTrackerTest, MainThreadPresentWithNonMatchedToken) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
const char sequence[] = "b(1)e(1,0)b(2)e(2,1)P(2)";
GenerateSequence(sequence);
EXPECT_EQ(frames_expected(), 1u);
EXPECT_EQ(frames_produced(), 1u);
}
TEST_F(FrameSequenceTrackerTest, CoalescedMainThreadPresent) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
const char sequence[] = "b(1)e(1,1)b(2)e(2,2)D(1)P(2)";
GenerateSequence(sequence);
EXPECT_EQ(frames_expected(), 2u);
EXPECT_EQ(frames_produced(), 1u);
}
TEST_F(FrameSequenceTrackerTest, MainThreadPresentWithNullTimeStamp) {
CreateNewTracker(FrameInfo::SmoothEffectDrivingThread::kMain);
const char sequence[] = "b(1)e(1,1)D(1)";
GenerateSequence(sequence);
EXPECT_EQ(frames_expected(), 1u);
// No presentation, no main frame produced.
EXPECT_EQ(frames_produced(), 0u);
GenerateSequence("b(2)e(2,0)P(2)");
// We are creating a second frame, it will be expected
EXPECT_EQ(frames_expected(), 2u);
// The main frame update is caught up here.
EXPECT_EQ(frames_produced(), 1u);
}
TEST_F(FrameSequenceTrackerTest, TrackerTypeEncoding) {
// The test begins with a kTouchScroll tracker
EXPECT_EQ(NumberOfTrackers(), 1u);
ActiveFrameSequenceTrackers active_encoded =
collection_.FrameSequenceTrackerActiveTypes();
EXPECT_EQ(active_encoded, 16); // 1 << 4
}
TEST_F(FrameSequenceTrackerTest, CustomTrackers) {
CustomTrackerResults results;
collection_.set_custom_tracker_results_added_callback(
base::BindLambdaForTesting([&](const CustomTrackerResults& reported) {
for (const auto& pair : reported)
results[pair.first] = pair.second;
}));
// Start custom tracker 1.
collection_.StartCustomSequence(1);
EXPECT_EQ(1u, NumberOfCustomTrackers());
// No reports.
EXPECT_EQ(0u, results.size());
// Start custom tracker 2 and 3 in addition to 1.
collection_.StartCustomSequence(2);
collection_.StartCustomSequence(3);
EXPECT_EQ(3u, NumberOfCustomTrackers());
// All custom trackers are running. No reports.
EXPECT_EQ(0u, results.size());
// Tracker 2 is stopped and scheduled to terminate.
collection_.StopCustomSequence(2);
EXPECT_EQ(2u, NumberOfCustomTrackers());
// Tracker 2 has zero expected frames.
EXPECT_EQ(1u, results.size());
EXPECT_EQ(0u, results[2].frames_expected_v3);
// Simple sequence of one frame.
const char sequence[] = "b(1)e(1,0)P(1)";
GenerateSequence(sequence);
// Stop all custom trackers.
collection_.StopCustomSequence(1);
collection_.StopCustomSequence(3);
EXPECT_EQ(0u, NumberOfCustomTrackers());
// Tracker 1 and 3 and should report.
EXPECT_EQ(3u, results.size());
EXPECT_EQ(0u, results[1].frames_dropped_v3);
EXPECT_EQ(1u, results[1].frames_expected_v3);
EXPECT_EQ(0u, results[2].frames_dropped_v3);
EXPECT_EQ(0u, results[2].frames_expected_v3);
EXPECT_EQ(0u, results[3].frames_dropped_v3);
EXPECT_EQ(1u, results[3].frames_expected_v3);
}
TEST_F(FrameSequenceTrackerTest, CustomTrackerOutOfOrderFramesMissingV3Data) {
CustomTrackerResults results;
collection_.set_custom_tracker_results_added_callback(
base::BindLambdaForTesting([&](const CustomTrackerResults& reported) {
for (const auto& pair : reported) {
results[pair.first] = pair.second;
}
}));
// Start custom tracker 1.
collection_.StartCustomSequence(1);
EXPECT_EQ(1u, NumberOfCustomTrackers());
const uint64_t source = 1;
uint64_t sequence = 0;
// Dispatch 2 frames: frame 0 and frame 1.
auto frame0_args = CreateBeginFrameArgs(source, ++sequence);
DispatchCompleteFrame(frame0_args, kImplDamage | kMainDamage);
sorter_.AddNewFrame(frame0_args);
auto frame1_args = CreateBeginFrameArgs(source, ++sequence);
DispatchCompleteFrame(frame1_args, kImplDamage | kMainDamage);
sorter_.AddNewFrame(frame1_args);
// Frame 1 gets its result before frame 0.
FrameInfo frame_info;
frame_info.final_state = FrameInfo::FrameFinalState::kPresentedAll;
frame_info.smooth_thread = FrameInfo::SmoothThread::kSmoothMain;
frame_info.scroll_thread = FrameInfo::SmoothEffectDrivingThread::kMain;
frame_info.has_missing_content = false;
frame_info.sequence_number = frame1_args.frame_id.sequence_number;
sorter_.AddFrameResult(frame1_args, frame_info);
// Stop the tracker.
collection_.StopCustomSequence(1);
// Frame 0 gets its result after tracker is stopped. FrameSorter flushes all
// frames and metrics for both frames should be recorded for v3.
sorter_.AddFrameResult(frame0_args, frame_info);
// Frame 2 is dispatched after the tracker is stopped and should be ignored.
auto frame2_args = CreateBeginFrameArgs(source, ++sequence);
DispatchCompleteFrame(frame2_args, kImplDamage | kMainDamage);
sorter_.AddNewFrame(frame2_args);
sorter_.AddFrameResult(frame2_args, frame_info);
// Trigger metrics report.
collection_.ClearAll();
// There is one report for tracker id 1 and 2 expected frames (frame 0 and 1).
ASSERT_EQ(1u, results.size());
EXPECT_EQ(2u, results[1].frames_expected_v3);
}
} // namespace cc