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