blob: 65264926e40161b1bd4aee0b326598bc35554c4d [file] [log] [blame]
Tommi822a8742020-05-11 00:42:30 +02001/*
2 * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "video/call_stats2.h"
12
13#include <memory>
14
15#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
16#include "modules/utility/include/process_thread.h"
17#include "rtc_base/task_utils/to_queued_task.h"
18#include "rtc_base/thread.h"
19#include "system_wrappers/include/metrics.h"
20#include "test/gmock.h"
21#include "test/gtest.h"
22#include "test/run_loop.h"
23
24using ::testing::AnyNumber;
25using ::testing::InvokeWithoutArgs;
26using ::testing::Return;
27
28namespace webrtc {
29namespace internal {
30
31class MockStatsObserver : public CallStatsObserver {
32 public:
33 MockStatsObserver() {}
34 virtual ~MockStatsObserver() {}
35
Danil Chapovalov91fdc602020-05-14 19:17:51 +020036 MOCK_METHOD(void, OnRttUpdate, (int64_t, int64_t), (override));
Tommi822a8742020-05-11 00:42:30 +020037};
38
39class CallStats2Test : public ::testing::Test {
40 public:
Etienne Pierre-Doraycc474372021-02-10 15:51:36 -050041 CallStats2Test() {
42 call_stats_.EnsureStarted();
43 process_thread_->Start();
44 }
Tommi822a8742020-05-11 00:42:30 +020045
46 ~CallStats2Test() override { process_thread_->Stop(); }
47
48 // Queues an rtt update call on the process thread.
49 void AsyncSimulateRttUpdate(int64_t rtt) {
50 RtcpRttStats* rtcp_rtt_stats = call_stats_.AsRtcpRttStats();
51 process_thread_->PostTask(ToQueuedTask(
52 [rtcp_rtt_stats, rtt] { rtcp_rtt_stats->OnRttUpdate(rtt); }));
53 }
54
55 protected:
56 void FlushProcessAndWorker() {
57 process_thread_->PostTask(
58 ToQueuedTask([this] { loop_.PostTask([this]() { loop_.Quit(); }); }));
59 loop_.Run();
60 }
61
62 test::RunLoop loop_;
Niels Möller679f1cb2021-12-21 15:45:30 +010063 // TODO(nisse): Don't use process thread.
Tommi822a8742020-05-11 00:42:30 +020064 std::unique_ptr<ProcessThread> process_thread_{
65 ProcessThread::Create("CallStats")};
66 // Note: Since rtc::Thread doesn't support injecting a Clock, we're going
67 // to be using a mix of the fake clock (used by CallStats) as well as the
68 // system clock (used by rtc::Thread). This isn't ideal and will result in
69 // the tests taking longer to execute in some cases than they need to.
70 SimulatedClock fake_clock_{12345};
71 CallStats call_stats_{&fake_clock_, loop_.task_queue()};
72};
73
74TEST_F(CallStats2Test, AddAndTriggerCallback) {
75 static constexpr const int64_t kRtt = 25;
76
77 MockStatsObserver stats_observer;
78 EXPECT_CALL(stats_observer, OnRttUpdate(kRtt, kRtt))
79 .Times(1)
80 .WillOnce(InvokeWithoutArgs([this] { loop_.Quit(); }));
81
82 call_stats_.RegisterStatsObserver(&stats_observer);
83 EXPECT_EQ(-1, call_stats_.LastProcessedRtt());
84
85 AsyncSimulateRttUpdate(kRtt);
86 loop_.Run();
87
88 EXPECT_EQ(kRtt, call_stats_.LastProcessedRtt());
89
90 call_stats_.DeregisterStatsObserver(&stats_observer);
91}
92
93TEST_F(CallStats2Test, ProcessTime) {
94 static constexpr const int64_t kRtt = 100;
95 static constexpr const int64_t kRtt2 = 80;
96
97 MockStatsObserver stats_observer;
98
99 EXPECT_CALL(stats_observer, OnRttUpdate(kRtt, kRtt))
100 .Times(2)
101 .WillOnce(InvokeWithoutArgs([this] {
102 // Advance clock and verify we get an update.
Tommia0a44802020-05-13 18:27:26 +0200103 fake_clock_.AdvanceTimeMilliseconds(CallStats::kUpdateInterval.ms());
Tommi822a8742020-05-11 00:42:30 +0200104 }))
105 .WillRepeatedly(InvokeWithoutArgs([this] {
106 AsyncSimulateRttUpdate(kRtt2);
107 // Advance clock just too little to get an update.
Tommia0a44802020-05-13 18:27:26 +0200108 fake_clock_.AdvanceTimeMilliseconds(CallStats::kUpdateInterval.ms() -
109 1);
Tommi822a8742020-05-11 00:42:30 +0200110 }));
111
112 // In case you're reading this and wondering how this number is arrived at,
113 // please see comments in the ChangeRtt test that go into some detail.
114 static constexpr const int64_t kLastAvg = 94;
115 EXPECT_CALL(stats_observer, OnRttUpdate(kLastAvg, kRtt2))
116 .Times(1)
117 .WillOnce(InvokeWithoutArgs([this] { loop_.Quit(); }));
118
119 call_stats_.RegisterStatsObserver(&stats_observer);
120
121 AsyncSimulateRttUpdate(kRtt);
122 loop_.Run();
123
124 call_stats_.DeregisterStatsObserver(&stats_observer);
125}
126
127// Verify all observers get correct estimates and observers can be added and
128// removed.
129TEST_F(CallStats2Test, MultipleObservers) {
130 MockStatsObserver stats_observer_1;
131 call_stats_.RegisterStatsObserver(&stats_observer_1);
132 // Add the second observer twice, there should still be only one report to the
133 // observer.
134 MockStatsObserver stats_observer_2;
135 call_stats_.RegisterStatsObserver(&stats_observer_2);
136 call_stats_.RegisterStatsObserver(&stats_observer_2);
137
138 static constexpr const int64_t kRtt = 100;
139
140 // Verify both observers are updated.
141 EXPECT_CALL(stats_observer_1, OnRttUpdate(kRtt, kRtt))
142 .Times(AnyNumber())
143 .WillRepeatedly(Return());
144 EXPECT_CALL(stats_observer_2, OnRttUpdate(kRtt, kRtt))
145 .Times(AnyNumber())
146 .WillOnce(InvokeWithoutArgs([this] { loop_.Quit(); }))
147 .WillRepeatedly(Return());
148 AsyncSimulateRttUpdate(kRtt);
149 loop_.Run();
150
151 // Deregister the second observer and verify update is only sent to the first
152 // observer.
153 call_stats_.DeregisterStatsObserver(&stats_observer_2);
154
155 EXPECT_CALL(stats_observer_1, OnRttUpdate(kRtt, kRtt))
156 .Times(AnyNumber())
157 .WillOnce(InvokeWithoutArgs([this] { loop_.Quit(); }))
158 .WillRepeatedly(Return());
159 EXPECT_CALL(stats_observer_2, OnRttUpdate(kRtt, kRtt)).Times(0);
160 AsyncSimulateRttUpdate(kRtt);
161 loop_.Run();
162
163 // Deregister the first observer.
164 call_stats_.DeregisterStatsObserver(&stats_observer_1);
165
166 // Now make sure we don't get any callbacks.
167 EXPECT_CALL(stats_observer_1, OnRttUpdate(kRtt, kRtt)).Times(0);
168 EXPECT_CALL(stats_observer_2, OnRttUpdate(kRtt, kRtt)).Times(0);
169 AsyncSimulateRttUpdate(kRtt);
170
171 // Flush the queue on the process thread to make sure we return after
172 // Process() has been called.
173 FlushProcessAndWorker();
174}
175
176// Verify increasing and decreasing rtt triggers callbacks with correct values.
177TEST_F(CallStats2Test, ChangeRtt) {
178 // NOTE: This test assumes things about how old reports are removed
179 // inside of call_stats.cc. The threshold ms value is 1500ms, but it's not
180 // clear here that how the clock is advanced, affects that algorithm and
181 // subsequently the average reported rtt.
182
183 MockStatsObserver stats_observer;
184 call_stats_.RegisterStatsObserver(&stats_observer);
185
186 static constexpr const int64_t kFirstRtt = 100;
187 static constexpr const int64_t kLowRtt = kFirstRtt - 20;
188 static constexpr const int64_t kHighRtt = kFirstRtt + 20;
189
190 EXPECT_CALL(stats_observer, OnRttUpdate(kFirstRtt, kFirstRtt))
191 .Times(1)
192 .WillOnce(InvokeWithoutArgs([this] {
193 fake_clock_.AdvanceTimeMilliseconds(1000);
194 AsyncSimulateRttUpdate(kHighRtt); // Reported at T1 (1000ms).
195 }));
196
197 // NOTE: This relies on the internal algorithms of call_stats.cc.
198 // There's a weight factor there (0.3), that weighs the previous average to
199 // the new one by 70%, so the number 103 in this case is arrived at like so:
200 // (100) / 1 * 0.7 + (100+120)/2 * 0.3 = 103
201 static constexpr const int64_t kAvgRtt1 = 103;
202 EXPECT_CALL(stats_observer, OnRttUpdate(kAvgRtt1, kHighRtt))
203 .Times(1)
204 .WillOnce(InvokeWithoutArgs([this] {
205 // This interacts with an internal implementation detail in call_stats
206 // that decays the oldest rtt value. See more below.
207 fake_clock_.AdvanceTimeMilliseconds(1000);
208 AsyncSimulateRttUpdate(kLowRtt); // Reported at T2 (2000ms).
209 }));
210
211 // Increase time enough for a new update, but not too much to make the
212 // rtt invalid. Report a lower rtt and verify the old/high value still is sent
213 // in the callback.
214
215 // Here, enough time must have passed in order to remove exactly the first
216 // report and nothing else (>1500ms has passed since the first rtt).
217 // So, this value is arrived by doing:
218 // (kAvgRtt1)/1 * 0.7 + (kHighRtt+kLowRtt)/2 * 0.3 = 102.1
219 static constexpr const int64_t kAvgRtt2 = 102;
220 EXPECT_CALL(stats_observer, OnRttUpdate(kAvgRtt2, kHighRtt))
221 .Times(1)
222 .WillOnce(InvokeWithoutArgs([this] {
223 // Advance time to make the high report invalid, the lower rtt should
224 // now be in the callback.
225 fake_clock_.AdvanceTimeMilliseconds(1000);
226 }));
227
228 static constexpr const int64_t kAvgRtt3 = 95;
229 EXPECT_CALL(stats_observer, OnRttUpdate(kAvgRtt3, kLowRtt))
230 .Times(1)
231 .WillOnce(InvokeWithoutArgs([this] { loop_.Quit(); }));
232
233 // Trigger the first rtt value and set off the chain of callbacks.
234 AsyncSimulateRttUpdate(kFirstRtt); // Reported at T0 (0ms).
235 loop_.Run();
236
237 call_stats_.DeregisterStatsObserver(&stats_observer);
238}
239
240TEST_F(CallStats2Test, LastProcessedRtt) {
241 MockStatsObserver stats_observer;
242 call_stats_.RegisterStatsObserver(&stats_observer);
243
244 static constexpr const int64_t kRttLow = 10;
245 static constexpr const int64_t kRttHigh = 30;
246 // The following two average numbers dependend on average + weight
247 // calculations in call_stats.cc.
248 static constexpr const int64_t kAvgRtt1 = 13;
249 static constexpr const int64_t kAvgRtt2 = 15;
250
251 EXPECT_CALL(stats_observer, OnRttUpdate(kRttLow, kRttLow))
252 .Times(1)
253 .WillOnce(InvokeWithoutArgs([this] {
254 EXPECT_EQ(kRttLow, call_stats_.LastProcessedRtt());
255 // Don't advance the clock to make sure that low and high rtt values
256 // are associated with the same time stamp.
257 AsyncSimulateRttUpdate(kRttHigh);
258 }));
259
260 EXPECT_CALL(stats_observer, OnRttUpdate(kAvgRtt1, kRttHigh))
261 .Times(AnyNumber())
262 .WillOnce(InvokeWithoutArgs([this] {
263 EXPECT_EQ(kAvgRtt1, call_stats_.LastProcessedRtt());
Tommia0a44802020-05-13 18:27:26 +0200264 fake_clock_.AdvanceTimeMilliseconds(CallStats::kUpdateInterval.ms());
Tommi822a8742020-05-11 00:42:30 +0200265 AsyncSimulateRttUpdate(kRttLow);
266 AsyncSimulateRttUpdate(kRttHigh);
267 }))
268 .WillRepeatedly(Return());
269
270 EXPECT_CALL(stats_observer, OnRttUpdate(kAvgRtt2, kRttHigh))
271 .Times(AnyNumber())
272 .WillOnce(InvokeWithoutArgs([this] {
273 EXPECT_EQ(kAvgRtt2, call_stats_.LastProcessedRtt());
274 loop_.Quit();
275 }))
276 .WillRepeatedly(Return());
277
278 // Set a first values and verify that LastProcessedRtt initially returns the
279 // average rtt.
Tommia0a44802020-05-13 18:27:26 +0200280 fake_clock_.AdvanceTimeMilliseconds(CallStats::kUpdateInterval.ms());
Tommi822a8742020-05-11 00:42:30 +0200281 AsyncSimulateRttUpdate(kRttLow);
282 loop_.Run();
283 EXPECT_EQ(kAvgRtt2, call_stats_.LastProcessedRtt());
284
285 call_stats_.DeregisterStatsObserver(&stats_observer);
286}
287
288TEST_F(CallStats2Test, ProducesHistogramMetrics) {
289 metrics::Reset();
290 static constexpr const int64_t kRtt = 123;
291 MockStatsObserver stats_observer;
292 call_stats_.RegisterStatsObserver(&stats_observer);
293 EXPECT_CALL(stats_observer, OnRttUpdate(kRtt, kRtt))
294 .Times(AnyNumber())
295 .WillRepeatedly(InvokeWithoutArgs([this] { loop_.Quit(); }));
296
297 AsyncSimulateRttUpdate(kRtt);
298 loop_.Run();
299 fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds *
Tommia0a44802020-05-13 18:27:26 +0200300 CallStats::kUpdateInterval.ms());
Tommi822a8742020-05-11 00:42:30 +0200301 AsyncSimulateRttUpdate(kRtt);
302 loop_.Run();
303
304 call_stats_.DeregisterStatsObserver(&stats_observer);
305
306 call_stats_.UpdateHistogramsForTest();
307
308 EXPECT_METRIC_EQ(1, metrics::NumSamples(
309 "WebRTC.Video.AverageRoundTripTimeInMilliseconds"));
310 EXPECT_METRIC_EQ(
311 1, metrics::NumEvents("WebRTC.Video.AverageRoundTripTimeInMilliseconds",
312 kRtt));
313}
314
315} // namespace internal
316} // namespace webrtc