diff --git a/webrtc/video/end_to_end_tests.cc b/webrtc/video/end_to_end_tests.cc index de497f6d53..b7ae199de0 100644 --- a/webrtc/video/end_to_end_tests.cc +++ b/webrtc/video/end_to_end_tests.cc @@ -2391,6 +2391,9 @@ void EndToEndTest::VerifyHistogramStats(bool use_rtx, EXPECT_EQ(1, metrics::NumSamples(video_prefix + "EncodeTimeInMs")); EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.DecodeTimeInMs")); + EXPECT_EQ(1, metrics::NumSamples(video_prefix + "NumberOfPauseEvents")); + EXPECT_EQ(1, metrics::NumSamples(video_prefix + "PausedTimeInPercent")); + EXPECT_EQ(1, metrics::NumSamples(video_prefix + "BitrateSentInKbps")); EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.BitrateReceivedInKbps")); EXPECT_EQ(1, metrics::NumSamples(video_prefix + "MediaBitrateSentInKbps")); diff --git a/webrtc/video/send_statistics_proxy.cc b/webrtc/video/send_statistics_proxy.cc index 6c249f70a9..bd1441f903 100644 --- a/webrtc/video/send_statistics_proxy.cc +++ b/webrtc/video/send_statistics_proxy.cc @@ -322,6 +322,16 @@ void SendStatisticsProxy::UmaSamplesContainer::UpdateHistograms( int64_t elapsed_sec = (clock_->TimeInMilliseconds() - first_rtp_stats_time_ms_) / 1000; if (elapsed_sec >= metrics::kMinRunTimeInSeconds) { + RTC_HISTOGRAMS_COUNTS_100(kIndex, uma_prefix_ + "NumberOfPauseEvents", + target_rate_updates_.pause_resume_events); + + int paused_time_percent = + paused_time_counter_.Percent(metrics::kMinRunTimeInSeconds * 1000); + if (paused_time_percent != -1) { + RTC_HISTOGRAMS_PERCENTAGE(kIndex, uma_prefix_ + "PausedTimeInPercent", + paused_time_percent); + } + StreamDataCounters rtp; StreamDataCounters rtx; AccumulateRtxStats(current_stats, rtp_config.rtx.ssrcs, &rtp, &rtx); @@ -467,6 +477,25 @@ void SendStatisticsProxy::OnInactiveSsrc(uint32_t ssrc) { void SendStatisticsProxy::OnSetEncoderTargetRate(uint32_t bitrate_bps) { rtc::CritScope lock(&crit_); + if (uma_container_->target_rate_updates_.last_ms == -1 && bitrate_bps == 0) + return; // Start on first non-zero bitrate, may initially be zero. + + int64_t now = clock_->TimeInMilliseconds(); + if (uma_container_->target_rate_updates_.last_ms != -1) { + bool was_paused = stats_.target_media_bitrate_bps == 0; + int64_t diff_ms = now - uma_container_->target_rate_updates_.last_ms; + uma_container_->paused_time_counter_.Add(was_paused, diff_ms); + + // Use last to not include update when stream is stopped and video disabled. + if (uma_container_->target_rate_updates_.last_paused_or_resumed) + ++uma_container_->target_rate_updates_.pause_resume_events; + + // Check if video is paused/resumed. + uma_container_->target_rate_updates_.last_paused_or_resumed = + (bitrate_bps == 0) != was_paused; + } + uma_container_->target_rate_updates_.last_ms = now; + stats_.target_media_bitrate_bps = bitrate_bps; } @@ -699,10 +728,11 @@ void SendStatisticsProxy::SampleCounter::Add(int sample) { ++num_samples; } -int SendStatisticsProxy::SampleCounter::Avg(int min_required_samples) const { +int SendStatisticsProxy::SampleCounter::Avg( + int64_t min_required_samples) const { if (num_samples < min_required_samples || num_samples == 0) return -1; - return (sum + (num_samples / 2)) / num_samples; + return static_cast((sum + (num_samples / 2)) / num_samples); } void SendStatisticsProxy::BoolSampleCounter::Add(bool sample) { @@ -711,18 +741,24 @@ void SendStatisticsProxy::BoolSampleCounter::Add(bool sample) { ++num_samples; } +void SendStatisticsProxy::BoolSampleCounter::Add(bool sample, int64_t count) { + if (sample) + sum += count; + num_samples += count; +} int SendStatisticsProxy::BoolSampleCounter::Percent( - int min_required_samples) const { + int64_t min_required_samples) const { return Fraction(min_required_samples, 100.0f); } int SendStatisticsProxy::BoolSampleCounter::Permille( - int min_required_samples) const { + int64_t min_required_samples) const { return Fraction(min_required_samples, 1000.0f); } int SendStatisticsProxy::BoolSampleCounter::Fraction( - int min_required_samples, float multiplier) const { + int64_t min_required_samples, + float multiplier) const { if (num_samples < min_required_samples || num_samples == 0) return -1; return static_cast((sum * multiplier / num_samples) + 0.5f); diff --git a/webrtc/video/send_statistics_proxy.h b/webrtc/video/send_statistics_proxy.h index ec8bb6d28c..68d17e4a7a 100644 --- a/webrtc/video/send_statistics_proxy.h +++ b/webrtc/video/send_statistics_proxy.h @@ -113,30 +113,38 @@ class SendStatisticsProxy : public CpuOveruseMetricsObserver, SampleCounter() : sum(0), num_samples(0) {} ~SampleCounter() {} void Add(int sample); - int Avg(int min_required_samples) const; + int Avg(int64_t min_required_samples) const; private: - int sum; - int num_samples; + int64_t sum; + int64_t num_samples; }; class BoolSampleCounter { public: BoolSampleCounter() : sum(0), num_samples(0) {} ~BoolSampleCounter() {} void Add(bool sample); - int Percent(int min_required_samples) const; - int Permille(int min_required_samples) const; + void Add(bool sample, int64_t count); + int Percent(int64_t min_required_samples) const; + int Permille(int64_t min_required_samples) const; private: - int Fraction(int min_required_samples, float multiplier) const; - int sum; - int num_samples; + int Fraction(int64_t min_required_samples, float multiplier) const; + int64_t sum; + int64_t num_samples; }; struct StatsUpdateTimes { StatsUpdateTimes() : resolution_update_ms(0), bitrate_update_ms(0) {} int64_t resolution_update_ms; int64_t bitrate_update_ms; }; + struct TargetRateUpdates { + TargetRateUpdates() + : pause_resume_events(0), last_paused_or_resumed(false), last_ms(-1) {} + int pause_resume_events; + bool last_paused_or_resumed; + int64_t last_ms; + }; struct QpCounters { SampleCounter vp8; // QP range: 0-127 SampleCounter vp9; // QP range: 0-255 @@ -192,6 +200,8 @@ class SendStatisticsProxy : public CpuOveruseMetricsObserver, RateCounter sent_fps_counter_; int64_t first_rtcp_stats_time_ms_; int64_t first_rtp_stats_time_ms_; + BoolSampleCounter paused_time_counter_; + TargetRateUpdates target_rate_updates_; ReportBlockStats report_block_stats_; const VideoSendStream::Stats start_stats_; std::map diff --git a/webrtc/video/send_statistics_proxy_unittest.cc b/webrtc/video/send_statistics_proxy_unittest.cc index acb8a306ca..7fa3ae3638 100644 --- a/webrtc/video/send_statistics_proxy_unittest.cc +++ b/webrtc/video/send_statistics_proxy_unittest.cc @@ -88,6 +88,13 @@ class SendStatisticsProxyTest : public ::testing::Test { return it->second; } + void UpdateDataCounters(uint32_t ssrc) { + StreamDataCountersCallback* proxy = + static_cast(statistics_proxy_.get()); + StreamDataCounters counters; + proxy->DataCountersUpdated(counters, ssrc); + } + void ExpectEqual(VideoSendStream::Stats one, VideoSendStream::Stats other) { EXPECT_EQ(one.input_frame_rate, other.input_frame_rate); EXPECT_EQ(one.encode_frame_rate, other.encode_frame_rate); @@ -510,6 +517,114 @@ TEST_F(SendStatisticsProxyTest, CodecTypeHistogramIsUpdated) { EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.Encoder.CodecType")); } +TEST_F(SendStatisticsProxyTest, PauseEventHistogramIsUpdated) { + // First RTP packet sent. + UpdateDataCounters(kFirstSsrc); + + // Min runtime has passed. + fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds * 1000); + statistics_proxy_.reset(); + EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.NumberOfPauseEvents")); + EXPECT_EQ(1, metrics::NumEvents("WebRTC.Video.NumberOfPauseEvents", 0)); +} + +TEST_F(SendStatisticsProxyTest, + PauseEventHistogramIsNotUpdatedIfMinRuntimeHasNotPassed) { + // First RTP packet sent. + UpdateDataCounters(kFirstSsrc); + + // Min runtime has not passed. + fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds * 1000 - 1); + statistics_proxy_.reset(); + EXPECT_EQ(0, metrics::NumSamples("WebRTC.Video.NumberOfPauseEvents")); + EXPECT_EQ(0, metrics::NumSamples("WebRTC.Video.PausedTimeInPercent")); +} + +TEST_F(SendStatisticsProxyTest, + PauseEventHistogramIsNotUpdatedIfNoMediaIsSent) { + // First RTP packet not sent. + fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds * 1000); + statistics_proxy_.reset(); + EXPECT_EQ(0, metrics::NumSamples("WebRTC.Video.NumberOfPauseEvents")); +} + +TEST_F(SendStatisticsProxyTest, NoPauseEvent) { + // First RTP packet sent and min runtime passed. + UpdateDataCounters(kFirstSsrc); + + // No change. Video: 10000 ms, paused: 0 ms (0%). + statistics_proxy_->OnSetEncoderTargetRate(50000); + fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds * 1000); + statistics_proxy_->OnSetEncoderTargetRate(0); // VideoSendStream::Stop + + statistics_proxy_.reset(); + EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.NumberOfPauseEvents")); + EXPECT_EQ(1, metrics::NumEvents("WebRTC.Video.NumberOfPauseEvents", 0)); + EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.PausedTimeInPercent")); + EXPECT_EQ(1, metrics::NumEvents("WebRTC.Video.PausedTimeInPercent", 0)); +} + +TEST_F(SendStatisticsProxyTest, OnePauseEvent) { + // First RTP packet sent and min runtime passed. + UpdateDataCounters(kFirstSsrc); + + // One change. Video: 7000 ms, paused: 3000 ms (30%). + statistics_proxy_->OnSetEncoderTargetRate(50000); + fake_clock_.AdvanceTimeMilliseconds(7000); + statistics_proxy_->OnSetEncoderTargetRate(0); + fake_clock_.AdvanceTimeMilliseconds(3000); + statistics_proxy_->OnSetEncoderTargetRate(0); // VideoSendStream::Stop + + statistics_proxy_.reset(); + EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.NumberOfPauseEvents")); + EXPECT_EQ(1, metrics::NumEvents("WebRTC.Video.NumberOfPauseEvents", 1)); + EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.PausedTimeInPercent")); + EXPECT_EQ(1, metrics::NumEvents("WebRTC.Video.PausedTimeInPercent", 30)); +} + +TEST_F(SendStatisticsProxyTest, TwoPauseEvents) { + // First RTP packet sent. + UpdateDataCounters(kFirstSsrc); + + // Two changes. Video: 19000 ms, paused: 1000 ms (5%). + statistics_proxy_->OnSetEncoderTargetRate(0); + fake_clock_.AdvanceTimeMilliseconds(1000); + statistics_proxy_->OnSetEncoderTargetRate(50000); // Starts on bitrate > 0. + fake_clock_.AdvanceTimeMilliseconds(7000); + statistics_proxy_->OnSetEncoderTargetRate(60000); + fake_clock_.AdvanceTimeMilliseconds(3000); + statistics_proxy_->OnSetEncoderTargetRate(0); + fake_clock_.AdvanceTimeMilliseconds(250); + statistics_proxy_->OnSetEncoderTargetRate(0); + fake_clock_.AdvanceTimeMilliseconds(750); + statistics_proxy_->OnSetEncoderTargetRate(60000); + fake_clock_.AdvanceTimeMilliseconds(5000); + statistics_proxy_->OnSetEncoderTargetRate(50000); + fake_clock_.AdvanceTimeMilliseconds(4000); + statistics_proxy_->OnSetEncoderTargetRate(0); // VideoSendStream::Stop + + statistics_proxy_.reset(); + EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.NumberOfPauseEvents")); + EXPECT_EQ(1, metrics::NumEvents("WebRTC.Video.NumberOfPauseEvents", 2)); + EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.PausedTimeInPercent")); + EXPECT_EQ(1, metrics::NumEvents("WebRTC.Video.PausedTimeInPercent", 5)); +} + +TEST_F(SendStatisticsProxyTest, + PausedTimeHistogramIsNotUpdatedIfMinRuntimeHasNotPassed) { + // First RTP packet sent. + UpdateDataCounters(kFirstSsrc); + fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds * 1000); + + // Min runtime has not passed. + statistics_proxy_->OnSetEncoderTargetRate(50000); + fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds * 1000 - 1); + statistics_proxy_->OnSetEncoderTargetRate(0); // VideoSendStream::Stop + + statistics_proxy_.reset(); + EXPECT_EQ(0, metrics::NumSamples("WebRTC.Video.PausedTimeInPercent")); +} + TEST_F(SendStatisticsProxyTest, VerifyQpHistogramStats_Vp8) { EncodedImage encoded_image; CodecSpecificInfo codec_info; @@ -739,6 +854,18 @@ TEST_F(SendStatisticsProxyTest, GetStatsReportsBandwidthLimitedResolution) { EXPECT_TRUE(statistics_proxy_->GetStats().bw_limited_resolution); } +TEST_F(SendStatisticsProxyTest, GetStatsReportsTargetMediaBitrate) { + // Initially zero. + EXPECT_EQ(0, statistics_proxy_->GetStats().target_media_bitrate_bps); + + const int kBitrate = 100000; + statistics_proxy_->OnSetEncoderTargetRate(kBitrate); + EXPECT_EQ(kBitrate, statistics_proxy_->GetStats().target_media_bitrate_bps); + + statistics_proxy_->OnSetEncoderTargetRate(0); + EXPECT_EQ(0, statistics_proxy_->GetStats().target_media_bitrate_bps); +} + TEST_F(SendStatisticsProxyTest, NoSubstreams) { uint32_t excluded_ssrc = std::max(