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