1 /*
2  *  Copyright (c) 2013 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 "webrtc/video/send_statistics_proxy.h"
12 
13 #include <algorithm>
14 #include <cmath>
15 #include <map>
16 
17 #include "webrtc/base/checks.h"
18 #include "webrtc/base/logging.h"
19 #include "webrtc/system_wrappers/include/critical_section_wrapper.h"
20 #include "webrtc/system_wrappers/include/metrics.h"
21 
22 namespace webrtc {
23 namespace {
24 const float kEncodeTimeWeigthFactor = 0.5f;
25 
26 // Used by histograms. Values of entries should not be changed.
27 enum HistogramCodecType {
28   kVideoUnknown = 0,
29   kVideoVp8 = 1,
30   kVideoVp9 = 2,
31   kVideoH264 = 3,
32   kVideoMax = 64,
33 };
34 
GetUmaPrefix(VideoEncoderConfig::ContentType content_type)35 const char* GetUmaPrefix(VideoEncoderConfig::ContentType content_type) {
36   switch (content_type) {
37     case VideoEncoderConfig::ContentType::kRealtimeVideo:
38       return "WebRTC.Video.";
39     case VideoEncoderConfig::ContentType::kScreen:
40       return "WebRTC.Video.Screenshare.";
41   }
42   RTC_NOTREACHED();
43   return nullptr;
44 }
45 
PayloadNameToHistogramCodecType(const std::string & payload_name)46 HistogramCodecType PayloadNameToHistogramCodecType(
47     const std::string& payload_name) {
48   if (payload_name == "VP8") {
49     return kVideoVp8;
50   } else if (payload_name == "VP9") {
51     return kVideoVp9;
52   } else if (payload_name == "H264") {
53     return kVideoH264;
54   } else {
55     return kVideoUnknown;
56   }
57 }
58 
UpdateCodecTypeHistogram(const std::string & payload_name)59 void UpdateCodecTypeHistogram(const std::string& payload_name) {
60   RTC_HISTOGRAM_ENUMERATION_SPARSE("WebRTC.Video.Encoder.CodecType",
61       PayloadNameToHistogramCodecType(payload_name), kVideoMax);
62 }
63 }  // namespace
64 
65 
66 const int SendStatisticsProxy::kStatsTimeoutMs = 5000;
67 
SendStatisticsProxy(Clock * clock,const VideoSendStream::Config & config,VideoEncoderConfig::ContentType content_type)68 SendStatisticsProxy::SendStatisticsProxy(
69     Clock* clock,
70     const VideoSendStream::Config& config,
71     VideoEncoderConfig::ContentType content_type)
72     : clock_(clock),
73       config_(config),
74       content_type_(content_type),
75       last_sent_frame_timestamp_(0),
76       encode_time_(kEncodeTimeWeigthFactor),
77       uma_container_(new UmaSamplesContainer(GetUmaPrefix(content_type_))) {
78   UpdateCodecTypeHistogram(config_.encoder_settings.payload_name);
79 }
80 
~SendStatisticsProxy()81 SendStatisticsProxy::~SendStatisticsProxy() {}
82 
UmaSamplesContainer(const char * prefix)83 SendStatisticsProxy::UmaSamplesContainer::UmaSamplesContainer(
84     const char* prefix)
85     : uma_prefix_(prefix),
86       max_sent_width_per_timestamp_(0),
87       max_sent_height_per_timestamp_(0),
88       input_frame_rate_tracker_(100u, 10u),
89       sent_frame_rate_tracker_(100u, 10u) {}
90 
~UmaSamplesContainer()91 SendStatisticsProxy::UmaSamplesContainer::~UmaSamplesContainer() {
92   UpdateHistograms();
93 }
94 
UpdateHistograms()95 void SendStatisticsProxy::UmaSamplesContainer::UpdateHistograms() {
96   const int kMinRequiredSamples = 200;
97   int in_width = input_width_counter_.Avg(kMinRequiredSamples);
98   int in_height = input_height_counter_.Avg(kMinRequiredSamples);
99   int in_fps = round(input_frame_rate_tracker_.ComputeTotalRate());
100   if (in_width != -1) {
101     RTC_HISTOGRAM_COUNTS_SPARSE_10000(uma_prefix_ + "InputWidthInPixels",
102                                       in_width);
103     RTC_HISTOGRAM_COUNTS_SPARSE_10000(uma_prefix_ + "InputHeightInPixels",
104                                       in_height);
105     RTC_HISTOGRAM_COUNTS_SPARSE_100(uma_prefix_ + "InputFramesPerSecond",
106                                     in_fps);
107   }
108   int sent_width = sent_width_counter_.Avg(kMinRequiredSamples);
109   int sent_height = sent_height_counter_.Avg(kMinRequiredSamples);
110   int sent_fps = round(sent_frame_rate_tracker_.ComputeTotalRate());
111   if (sent_width != -1) {
112     RTC_HISTOGRAM_COUNTS_SPARSE_10000(uma_prefix_ + "SentWidthInPixels",
113                                       sent_width);
114     RTC_HISTOGRAM_COUNTS_SPARSE_10000(uma_prefix_ + "SentHeightInPixels",
115                                       sent_height);
116     RTC_HISTOGRAM_COUNTS_SPARSE_100(uma_prefix_ + "SentFramesPerSecond",
117                                     sent_fps);
118   }
119   int encode_ms = encode_time_counter_.Avg(kMinRequiredSamples);
120   if (encode_ms != -1)
121     RTC_HISTOGRAM_COUNTS_SPARSE_1000(uma_prefix_ + "EncodeTimeInMs", encode_ms);
122 
123   int key_frames_permille = key_frame_counter_.Permille(kMinRequiredSamples);
124   if (key_frames_permille != -1) {
125     RTC_HISTOGRAM_COUNTS_SPARSE_1000(uma_prefix_ + "KeyFramesSentInPermille",
126                                      key_frames_permille);
127   }
128   int quality_limited =
129       quality_limited_frame_counter_.Percent(kMinRequiredSamples);
130   if (quality_limited != -1) {
131     RTC_HISTOGRAM_PERCENTAGE_SPARSE(
132         uma_prefix_ + "QualityLimitedResolutionInPercent", quality_limited);
133   }
134   int downscales = quality_downscales_counter_.Avg(kMinRequiredSamples);
135   if (downscales != -1) {
136     RTC_HISTOGRAM_ENUMERATION_SPARSE(
137         uma_prefix_ + "QualityLimitedResolutionDownscales", downscales, 20);
138   }
139   int bw_limited = bw_limited_frame_counter_.Percent(kMinRequiredSamples);
140   if (bw_limited != -1) {
141     RTC_HISTOGRAM_PERCENTAGE_SPARSE(
142         uma_prefix_ + "BandwidthLimitedResolutionInPercent", bw_limited);
143   }
144   int num_disabled = bw_resolutions_disabled_counter_.Avg(kMinRequiredSamples);
145   if (num_disabled != -1) {
146     RTC_HISTOGRAM_ENUMERATION_SPARSE(
147         uma_prefix_ + "BandwidthLimitedResolutionsDisabled", num_disabled, 10);
148   }
149   int delay_ms = delay_counter_.Avg(kMinRequiredSamples);
150   if (delay_ms != -1)
151     RTC_HISTOGRAM_COUNTS_SPARSE_100000(uma_prefix_ + "SendSideDelayInMs",
152                                        delay_ms);
153 
154   int max_delay_ms = max_delay_counter_.Avg(kMinRequiredSamples);
155   if (max_delay_ms != -1) {
156     RTC_HISTOGRAM_COUNTS_SPARSE_100000(uma_prefix_ + "SendSideDelayMaxInMs",
157                                        max_delay_ms);
158   }
159 }
160 
SetContentType(VideoEncoderConfig::ContentType content_type)161 void SendStatisticsProxy::SetContentType(
162     VideoEncoderConfig::ContentType content_type) {
163   rtc::CritScope lock(&crit_);
164   if (content_type_ != content_type) {
165     uma_container_.reset(new UmaSamplesContainer(GetUmaPrefix(content_type)));
166     content_type_ = content_type;
167   }
168 }
169 
OnEncoderImplementationName(const char * implementation_name)170 void SendStatisticsProxy::OnEncoderImplementationName(
171     const char* implementation_name) {
172   rtc::CritScope lock(&crit_);
173   stats_.encoder_implementation_name = implementation_name;
174 }
175 
OnOutgoingRate(uint32_t framerate,uint32_t bitrate)176 void SendStatisticsProxy::OnOutgoingRate(uint32_t framerate, uint32_t bitrate) {
177   rtc::CritScope lock(&crit_);
178   stats_.encode_frame_rate = framerate;
179   stats_.media_bitrate_bps = bitrate;
180 }
181 
CpuOveruseMetricsUpdated(const CpuOveruseMetrics & metrics)182 void SendStatisticsProxy::CpuOveruseMetricsUpdated(
183     const CpuOveruseMetrics& metrics) {
184   rtc::CritScope lock(&crit_);
185   stats_.encode_usage_percent = metrics.encode_usage_percent;
186 }
187 
OnSuspendChange(bool is_suspended)188 void SendStatisticsProxy::OnSuspendChange(bool is_suspended) {
189   rtc::CritScope lock(&crit_);
190   stats_.suspended = is_suspended;
191 }
192 
GetStats()193 VideoSendStream::Stats SendStatisticsProxy::GetStats() {
194   rtc::CritScope lock(&crit_);
195   PurgeOldStats();
196   stats_.input_frame_rate =
197       round(uma_container_->input_frame_rate_tracker_.ComputeRate());
198   return stats_;
199 }
200 
PurgeOldStats()201 void SendStatisticsProxy::PurgeOldStats() {
202   int64_t old_stats_ms = clock_->TimeInMilliseconds() - kStatsTimeoutMs;
203   for (std::map<uint32_t, VideoSendStream::StreamStats>::iterator it =
204            stats_.substreams.begin();
205        it != stats_.substreams.end(); ++it) {
206     uint32_t ssrc = it->first;
207     if (update_times_[ssrc].resolution_update_ms <= old_stats_ms) {
208       it->second.width = 0;
209       it->second.height = 0;
210     }
211   }
212 }
213 
GetStatsEntry(uint32_t ssrc)214 VideoSendStream::StreamStats* SendStatisticsProxy::GetStatsEntry(
215     uint32_t ssrc) {
216   std::map<uint32_t, VideoSendStream::StreamStats>::iterator it =
217       stats_.substreams.find(ssrc);
218   if (it != stats_.substreams.end())
219     return &it->second;
220 
221   if (std::find(config_.rtp.ssrcs.begin(), config_.rtp.ssrcs.end(), ssrc) ==
222           config_.rtp.ssrcs.end() &&
223       std::find(config_.rtp.rtx.ssrcs.begin(),
224                 config_.rtp.rtx.ssrcs.end(),
225                 ssrc) == config_.rtp.rtx.ssrcs.end()) {
226     return nullptr;
227   }
228 
229   return &stats_.substreams[ssrc];  // Insert new entry and return ptr.
230 }
231 
OnInactiveSsrc(uint32_t ssrc)232 void SendStatisticsProxy::OnInactiveSsrc(uint32_t ssrc) {
233   rtc::CritScope lock(&crit_);
234   VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
235   if (stats == nullptr)
236     return;
237 
238   stats->total_bitrate_bps = 0;
239   stats->retransmit_bitrate_bps = 0;
240   stats->height = 0;
241   stats->width = 0;
242 }
243 
OnSetRates(uint32_t bitrate_bps,int framerate)244 void SendStatisticsProxy::OnSetRates(uint32_t bitrate_bps, int framerate) {
245   rtc::CritScope lock(&crit_);
246   stats_.target_media_bitrate_bps = bitrate_bps;
247 }
248 
OnSendEncodedImage(const EncodedImage & encoded_image,const RTPVideoHeader * rtp_video_header)249 void SendStatisticsProxy::OnSendEncodedImage(
250     const EncodedImage& encoded_image,
251     const RTPVideoHeader* rtp_video_header) {
252   size_t simulcast_idx =
253       rtp_video_header != nullptr ? rtp_video_header->simulcastIdx : 0;
254   if (simulcast_idx >= config_.rtp.ssrcs.size()) {
255     LOG(LS_ERROR) << "Encoded image outside simulcast range (" << simulcast_idx
256                   << " >= " << config_.rtp.ssrcs.size() << ").";
257     return;
258   }
259   uint32_t ssrc = config_.rtp.ssrcs[simulcast_idx];
260 
261   rtc::CritScope lock(&crit_);
262   VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
263   if (stats == nullptr)
264     return;
265 
266   stats->width = encoded_image._encodedWidth;
267   stats->height = encoded_image._encodedHeight;
268   update_times_[ssrc].resolution_update_ms = clock_->TimeInMilliseconds();
269 
270   uma_container_->key_frame_counter_.Add(encoded_image._frameType ==
271                                          kVideoFrameKey);
272 
273   stats_.bw_limited_resolution =
274       encoded_image.adapt_reason_.quality_resolution_downscales > 0 ||
275       encoded_image.adapt_reason_.bw_resolutions_disabled > 0;
276 
277   if (encoded_image.adapt_reason_.quality_resolution_downscales != -1) {
278     bool downscaled =
279         encoded_image.adapt_reason_.quality_resolution_downscales > 0;
280     uma_container_->quality_limited_frame_counter_.Add(downscaled);
281     if (downscaled) {
282       uma_container_->quality_downscales_counter_.Add(
283           encoded_image.adapt_reason_.quality_resolution_downscales);
284     }
285   }
286   if (encoded_image.adapt_reason_.bw_resolutions_disabled != -1) {
287     bool bw_limited = encoded_image.adapt_reason_.bw_resolutions_disabled > 0;
288     uma_container_->bw_limited_frame_counter_.Add(bw_limited);
289     if (bw_limited) {
290       uma_container_->bw_resolutions_disabled_counter_.Add(
291           encoded_image.adapt_reason_.bw_resolutions_disabled);
292     }
293   }
294 
295   // TODO(asapersson): This is incorrect if simulcast layers are encoded on
296   // different threads and there is no guarantee that one frame of all layers
297   // are encoded before the next start.
298   if (last_sent_frame_timestamp_ > 0 &&
299       encoded_image._timeStamp != last_sent_frame_timestamp_) {
300     uma_container_->sent_frame_rate_tracker_.AddSamples(1);
301     uma_container_->sent_width_counter_.Add(
302         uma_container_->max_sent_width_per_timestamp_);
303     uma_container_->sent_height_counter_.Add(
304         uma_container_->max_sent_height_per_timestamp_);
305     uma_container_->max_sent_width_per_timestamp_ = 0;
306     uma_container_->max_sent_height_per_timestamp_ = 0;
307   }
308   last_sent_frame_timestamp_ = encoded_image._timeStamp;
309   uma_container_->max_sent_width_per_timestamp_ =
310       std::max(uma_container_->max_sent_width_per_timestamp_,
311                static_cast<int>(encoded_image._encodedWidth));
312   uma_container_->max_sent_height_per_timestamp_ =
313       std::max(uma_container_->max_sent_height_per_timestamp_,
314                static_cast<int>(encoded_image._encodedHeight));
315 }
316 
OnIncomingFrame(int width,int height)317 void SendStatisticsProxy::OnIncomingFrame(int width, int height) {
318   rtc::CritScope lock(&crit_);
319   uma_container_->input_frame_rate_tracker_.AddSamples(1);
320   uma_container_->input_width_counter_.Add(width);
321   uma_container_->input_height_counter_.Add(height);
322 }
323 
OnEncodedFrame(int encode_time_ms)324 void SendStatisticsProxy::OnEncodedFrame(int encode_time_ms) {
325   rtc::CritScope lock(&crit_);
326   uma_container_->encode_time_counter_.Add(encode_time_ms);
327   encode_time_.Apply(1.0f, encode_time_ms);
328   stats_.avg_encode_time_ms = round(encode_time_.filtered());
329 }
330 
RtcpPacketTypesCounterUpdated(uint32_t ssrc,const RtcpPacketTypeCounter & packet_counter)331 void SendStatisticsProxy::RtcpPacketTypesCounterUpdated(
332     uint32_t ssrc,
333     const RtcpPacketTypeCounter& packet_counter) {
334   rtc::CritScope lock(&crit_);
335   VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
336   if (stats == nullptr)
337     return;
338 
339   stats->rtcp_packet_type_counts = packet_counter;
340 }
341 
StatisticsUpdated(const RtcpStatistics & statistics,uint32_t ssrc)342 void SendStatisticsProxy::StatisticsUpdated(const RtcpStatistics& statistics,
343                                             uint32_t ssrc) {
344   rtc::CritScope lock(&crit_);
345   VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
346   if (stats == nullptr)
347     return;
348 
349   stats->rtcp_stats = statistics;
350 }
351 
CNameChanged(const char * cname,uint32_t ssrc)352 void SendStatisticsProxy::CNameChanged(const char* cname, uint32_t ssrc) {
353 }
354 
DataCountersUpdated(const StreamDataCounters & counters,uint32_t ssrc)355 void SendStatisticsProxy::DataCountersUpdated(
356     const StreamDataCounters& counters,
357     uint32_t ssrc) {
358   rtc::CritScope lock(&crit_);
359   VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
360   RTC_DCHECK(stats != nullptr)
361       << "DataCountersUpdated reported for unknown ssrc: " << ssrc;
362 
363   stats->rtp_stats = counters;
364 }
365 
Notify(const BitrateStatistics & total_stats,const BitrateStatistics & retransmit_stats,uint32_t ssrc)366 void SendStatisticsProxy::Notify(const BitrateStatistics& total_stats,
367                                  const BitrateStatistics& retransmit_stats,
368                                  uint32_t ssrc) {
369   rtc::CritScope lock(&crit_);
370   VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
371   if (stats == nullptr)
372     return;
373 
374   stats->total_bitrate_bps = total_stats.bitrate_bps;
375   stats->retransmit_bitrate_bps = retransmit_stats.bitrate_bps;
376 }
377 
FrameCountUpdated(const FrameCounts & frame_counts,uint32_t ssrc)378 void SendStatisticsProxy::FrameCountUpdated(const FrameCounts& frame_counts,
379                                             uint32_t ssrc) {
380   rtc::CritScope lock(&crit_);
381   VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
382   if (stats == nullptr)
383     return;
384 
385   stats->frame_counts = frame_counts;
386 }
387 
SendSideDelayUpdated(int avg_delay_ms,int max_delay_ms,uint32_t ssrc)388 void SendStatisticsProxy::SendSideDelayUpdated(int avg_delay_ms,
389                                                int max_delay_ms,
390                                                uint32_t ssrc) {
391   rtc::CritScope lock(&crit_);
392   VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
393   if (stats == nullptr)
394     return;
395   stats->avg_delay_ms = avg_delay_ms;
396   stats->max_delay_ms = max_delay_ms;
397 
398   uma_container_->delay_counter_.Add(avg_delay_ms);
399   uma_container_->max_delay_counter_.Add(max_delay_ms);
400 }
401 
Add(int sample)402 void SendStatisticsProxy::SampleCounter::Add(int sample) {
403   sum += sample;
404   ++num_samples;
405 }
406 
Avg(int min_required_samples) const407 int SendStatisticsProxy::SampleCounter::Avg(int min_required_samples) const {
408   if (num_samples < min_required_samples || num_samples == 0)
409     return -1;
410   return (sum + (num_samples / 2)) / num_samples;
411 }
412 
Add(bool sample)413 void SendStatisticsProxy::BoolSampleCounter::Add(bool sample) {
414   if (sample)
415     ++sum;
416   ++num_samples;
417 }
418 
Percent(int min_required_samples) const419 int SendStatisticsProxy::BoolSampleCounter::Percent(
420     int min_required_samples) const {
421   return Fraction(min_required_samples, 100.0f);
422 }
423 
Permille(int min_required_samples) const424 int SendStatisticsProxy::BoolSampleCounter::Permille(
425     int min_required_samples) const {
426   return Fraction(min_required_samples, 1000.0f);
427 }
428 
Fraction(int min_required_samples,float multiplier) const429 int SendStatisticsProxy::BoolSampleCounter::Fraction(
430     int min_required_samples, float multiplier) const {
431   if (num_samples < min_required_samples || num_samples == 0)
432     return -1;
433   return static_cast<int>((sum * multiplier / num_samples) + 0.5f);
434 }
435 }  // namespace webrtc
436