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