1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
cc / metrics / frame_sorter_unittest.cc [blame]
// Copyright 2020 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_sorter.h"
#include <string>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/strings/string_number_conversions.h"
#include "cc/metrics/frame_info.h"
#include "cc/test/fake_frame_info.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cc {
// Test class for FrameSorter
class FrameSorterTest : public testing::Test {
public:
FrameSorterTest()
: frame_sorter_(base::BindRepeating(&FrameSorterTest::FlushFrame,
base::Unretained(this))) {
IncreaseSourceId();
}
~FrameSorterTest() override = default;
viz::BeginFrameArgs GetNextFrameArgs() {
uint64_t sequence_number = next_frame_sequence_number_++;
return viz::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, next_frame_source_id_, sequence_number,
last_begin_frame_time_,
last_begin_frame_time_ + viz::BeginFrameArgs::DefaultInterval(),
viz::BeginFrameArgs::DefaultInterval(), viz::BeginFrameArgs::NORMAL);
}
void IncreaseSourceId() {
next_frame_source_id_++;
current_frame_id_ = -1;
args_.clear();
next_frame_sequence_number_ = viz::BeginFrameArgs::kStartingFrameNumber;
}
// Simulates a sequence of queries
// Possible queries:
// "S{frame_number}": Start frame.
// "D{frame_number}": End frame as dropped.
// "P{frame_number}": End frame as none-dropped (presented).
// "E{frame_number}": Frame expects twp acks.
// "I": Increase source_id (simulating a crash).
// "R": Reset the frame sorter.
// Method expects the start of frames to be in order starting with 1.
void SimulateQueries(std::vector<std::string> queries) {
// Keeps track of how many times a frame is terminated.
std::map<int, int> end_counters;
for (auto& query : queries) {
int id;
base::StringToInt(query.substr(1), &id);
if (id > current_frame_id_) {
current_frame_id_++;
args_.push_back(GetNextFrameArgs());
}
switch (query[0]) {
case 'S':
frame_sorter_.AddNewFrame(args_[id]);
break;
case 'D': {
++end_counters[id];
FrameInfo info =
CreateFakeFrameInfo(FrameInfo::FrameFinalState::kDropped);
if (end_counters[id] == 1) {
// For the first response to the frame, mark it as not including
// update from the main-thread.
info.main_thread_response = FrameInfo::MainThreadResponse::kMissing;
} else {
DCHECK_EQ(2, end_counters[id]);
info.main_thread_response =
FrameInfo::MainThreadResponse::kIncluded;
}
frame_sorter_.AddFrameResult(args_[id], info);
break;
}
case 'P': {
++end_counters[id];
FrameInfo info =
CreateFakeFrameInfo(FrameInfo::FrameFinalState::kPresentedAll);
if (end_counters[id] == 1) {
// For the first response to the frame, mark it as not including
// update from the main-thread.
info.main_thread_response = FrameInfo::MainThreadResponse::kMissing;
} else {
DCHECK_EQ(2, end_counters[id]);
info.main_thread_response =
FrameInfo::MainThreadResponse::kIncluded;
}
frame_sorter_.AddFrameResult(args_[id], info);
break;
}
case 'I':
IncreaseSourceId();
break;
case 'R':
frame_sorter_.Reset();
break;
}
}
}
void ValidateResults(
std::vector<std::pair<uint64_t, bool>> expected_results) {
EXPECT_EQ(sorted_frames_.size(), expected_results.size());
int result_index = 0;
for (auto result : expected_results) {
auto& sorted_frame = sorted_frames_[result_index];
EXPECT_EQ(sorted_frame.first.frame_id.sequence_number, result.first + 1);
EXPECT_EQ(sorted_frame.second, result.second);
result_index++;
}
}
private:
void FlushFrame(const viz::BeginFrameArgs& args, const FrameInfo& frame) {
sorted_frames_.emplace_back(args, frame.IsDroppedAffectingSmoothness());
}
FrameSorter frame_sorter_;
std::vector<std::pair<const viz::BeginFrameArgs, bool>> sorted_frames_;
base::TimeTicks last_begin_frame_time_ = base::TimeTicks::Now();
uint64_t next_frame_source_id_ = 0;
uint64_t next_frame_sequence_number_ =
viz::BeginFrameArgs::kStartingFrameNumber;
std::vector<viz::BeginFrameArgs> args_ = {};
int current_frame_id_ = -1;
};
TEST_F(FrameSorterTest, TestSortingFrames) {
// Frame end in order of F0, F2, F1, F3, but they should be flushed in the
// order they began (eg. F0, F1, F2, F3).
std::vector<std::string> queries = {"S0", "S1", "P0", "S2",
"P2", "S3", "P1", "D3"};
std::vector<std::pair<uint64_t, bool>> expected_results = {
{0, false}, {1, false}, {2, false}, {3, true}};
SimulateQueries(queries);
ValidateResults(expected_results);
}
TEST_F(FrameSorterTest, ResetInMiddleOfAcks) {
// Reset occur in between the start and ack of F1 & F3, so except these two
// all other frames should be flushed in order in the sorted_frames_.
std::vector<std::string> queries = {"S0", "S1", "P0", "S2", "P2", "S3",
"R", "S4", "P1", "D4", "D3"};
std::vector<std::pair<uint64_t, bool>> expected_results = {
{0, false}, {2, false}, {4, true}};
SimulateQueries(queries);
ValidateResults(expected_results);
}
TEST_F(FrameSorterTest, ExpectingMultipleAcks) {
// F1 has multiple acks and the final ack is received at the end. Which makes
// other frames to wait in pending tree before being flushed, all in order.
// Also in any of the duplicated acks report a dropped frame, the overall
// status for that frame should be dropped.
std::vector<std::string> queries = {"S0", "S1", "P0", "S1", "S2", "P2",
"S3", "S4", "P1", "D3", "D4", "D1"};
std::vector<std::pair<uint64_t, bool>> expected_results = {
{0, false}, {1, true}, {2, false}, {3, true}, {4, true}};
SimulateQueries(queries);
ValidateResults(expected_results);
}
TEST_F(FrameSorterTest, ExpectingMultipleAcksWithReset) {
// Combination of last two tests. Reset occurs in middle of start and ack of
// F1 and F3. Also F1 expects two acks, which both are received after reset.
std::vector<std::string> queries = {"S0", "S1", "P0", "S1", "S2", "P2", "S3",
"R", "S4", "P1", "D1", "D4", "D3"};
std::vector<std::pair<uint64_t, bool>> expected_results = {
{0, false}, {2, false}, {4, true}};
SimulateQueries(queries);
ValidateResults(expected_results);
}
TEST_F(FrameSorterTest, ExpectingMultipleAcksWithReset2) {
// Reset occurs in middle of the two starts of F0.
// This is to test if F1 will be flushed properly while being in between the
// two acks of F0, which should be ignored as a result of reset.
std::vector<std::string> queries = {"S0", "R", "S0", "P0", "S1", "D0", "D1"};
std::vector<std::pair<uint64_t, bool>> expected_results = {{1, true}};
SimulateQueries(queries);
ValidateResults(expected_results);
}
TEST_F(FrameSorterTest, ExpectingMultipleAcksWithSourceIdIncrease) {
// Reset and increase in source_id occurs in middle start and ack of F1.
// This is to simulate a navigation. So the initial F0 has source_id of 0
// while the F0 after reset has source_id of 1, and because of that the
// start and ack for F0 after the reset is not ignored (The ignore is only
// needed when frame has the same source_id and sequence_number).
std::vector<std::string> queries = {"S0", "S0", "S1", "S1", "I", "R",
"S0", "S0", "P0", "S1", "P1", "D0"};
std::vector<std::pair<uint64_t, bool>> expected_results = {{0, true},
{1, false}};
SimulateQueries(queries);
ValidateResults(expected_results);
}
} // namespace cc