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