diff --git a/webrtc/video/send_statistics_proxy.cc b/webrtc/video/send_statistics_proxy.cc index 3b9134b08e..afaeed904d 100644 --- a/webrtc/video/send_statistics_proxy.cc +++ b/webrtc/video/send_statistics_proxy.cc @@ -293,6 +293,24 @@ void SendStatisticsProxy::UmaSamplesContainer::UpdateHistograms( } } + quality_scaling_timer_.Stop(clock_->TimeInMilliseconds()); + int64_t elapsed_sec = quality_scaling_timer_.total_ms / 1000; + if (elapsed_sec >= metrics::kMinRunTimeInSeconds) { + int quality_changes = current_stats.number_of_quality_adapt_changes - + start_stats_.number_of_quality_adapt_changes; + RTC_HISTOGRAMS_COUNTS_100(kIndex, + uma_prefix_ + "AdaptChangesPerMinute.Quality", + quality_changes * 60 / elapsed_sec); + } + cpu_scaling_timer_.Stop(clock_->TimeInMilliseconds()); + elapsed_sec = cpu_scaling_timer_.total_ms / 1000; + if (elapsed_sec >= metrics::kMinRunTimeInSeconds) { + int cpu_changes = current_stats.number_of_cpu_adapt_changes - + start_stats_.number_of_cpu_adapt_changes; + RTC_HISTOGRAMS_COUNTS_100(kIndex, uma_prefix_ + "AdaptChangesPerMinute.Cpu", + cpu_changes * 60 / elapsed_sec); + } + if (first_rtcp_stats_time_ms_ != -1) { int64_t elapsed_sec = (clock_->TimeInMilliseconds() - first_rtcp_stats_time_ms_) / 1000; @@ -684,15 +702,29 @@ void SendStatisticsProxy::OnIncomingFrame(int width, int height) { "ssrc", rtp_config_.ssrcs[0]); } -void SendStatisticsProxy::SetCpuScalingStats(bool cpu_restricted_resolution) { +void SendStatisticsProxy::SetCpuScalingStats(int num_cpu_downscales) { rtc::CritScope lock(&crit_); - stats_.cpu_limited_resolution = cpu_restricted_resolution; + stats_.cpu_limited_resolution = num_cpu_downscales > 0; + + if (num_cpu_downscales >= 0) { + // Scaling enabled. + uma_container_->cpu_scaling_timer_.Start(clock_->TimeInMilliseconds()); + } else { + uma_container_->cpu_scaling_timer_.Stop(clock_->TimeInMilliseconds()); + } } void SendStatisticsProxy::SetQualityScalingStats(int num_quality_downscales) { rtc::CritScope lock(&crit_); quality_downscales_ = num_quality_downscales; stats_.bw_limited_resolution = quality_downscales_ > 0; + + if (num_quality_downscales >= 0) { + // Scaling enabled. + uma_container_->quality_scaling_timer_.Start(clock_->TimeInMilliseconds()); + } else { + uma_container_->quality_scaling_timer_.Stop(clock_->TimeInMilliseconds()); + } } void SendStatisticsProxy::OnCpuRestrictedResolutionChanged( @@ -700,7 +732,7 @@ void SendStatisticsProxy::OnCpuRestrictedResolutionChanged( rtc::CritScope lock(&crit_); stats_.cpu_limited_resolution = cpu_restricted_resolution; ++stats_.number_of_cpu_adapt_changes; - TRACE_EVENT_INSTANT0("webrtc_stats", "WebRTC.Video.AdaptationChanges"); + TRACE_EVENT_INSTANT0("webrtc_stats", "WebRTC.Video.CpuAdaptationChanges"); } void SendStatisticsProxy::OnQualityRestrictedResolutionChanged( @@ -761,8 +793,12 @@ void SendStatisticsProxy::DataCountersUpdated( } stats->rtp_stats = counters; - if (uma_container_->first_rtp_stats_time_ms_ == -1) - uma_container_->first_rtp_stats_time_ms_ = clock_->TimeInMilliseconds(); + if (uma_container_->first_rtp_stats_time_ms_ == -1) { + int64_t now_ms = clock_->TimeInMilliseconds(); + uma_container_->first_rtp_stats_time_ms_ = now_ms; + uma_container_->cpu_scaling_timer_.Restart(now_ms); + uma_container_->quality_scaling_timer_.Restart(now_ms); + } uma_container_->total_byte_counter_.Set(counters.transmitted.TotalBytes(), ssrc); @@ -818,6 +854,24 @@ void SendStatisticsProxy::SendSideDelayUpdated(int avg_delay_ms, uma_container_->max_delay_counter_.Add(max_delay_ms); } +void SendStatisticsProxy::StatsTimer::Start(int64_t now_ms) { + if (start_ms == -1) + start_ms = now_ms; +} + +void SendStatisticsProxy::StatsTimer::Stop(int64_t now_ms) { + if (start_ms != -1) { + total_ms += now_ms - start_ms; + start_ms = -1; + } +} + +void SendStatisticsProxy::StatsTimer::Restart(int64_t now_ms) { + total_ms = 0; + if (start_ms != -1) + start_ms = now_ms; +} + void SendStatisticsProxy::SampleCounter::Add(int sample) { sum += sample; ++num_samples; diff --git a/webrtc/video/send_statistics_proxy.h b/webrtc/video/send_statistics_proxy.h index 312186a1f9..23558ab8f6 100644 --- a/webrtc/video/send_statistics_proxy.h +++ b/webrtc/video/send_statistics_proxy.h @@ -59,7 +59,7 @@ class SendStatisticsProxy : public CpuOveruseMetricsObserver, void OnCpuRestrictedResolutionChanged(bool cpu_restricted_resolution); void OnQualityRestrictedResolutionChanged(int num_quality_downscales); - void SetCpuScalingStats(bool cpu_restricted_resolution); + void SetCpuScalingStats(int num_cpu_downscales); // -1: disabled. void SetQualityScalingStats(int num_quality_downscales); // -1: disabled. void OnEncoderStatsUpdate(uint32_t framerate, uint32_t bitrate); @@ -144,10 +144,17 @@ class SendStatisticsProxy : public CpuOveruseMetricsObserver, bool last_paused_or_resumed; int64_t last_ms; }; + struct StatsTimer { + void Start(int64_t now_ms); + void Stop(int64_t now_ms); + void Restart(int64_t now_ms); + int64_t start_ms = -1; + int64_t total_ms = 0; + }; struct QpCounters { - SampleCounter vp8; // QP range: 0-127 - SampleCounter vp9; // QP range: 0-255 - SampleCounter h264; // QP range: 0-51 + SampleCounter vp8; // QP range: 0-127. + SampleCounter vp9; // QP range: 0-255. + SampleCounter h264; // QP range: 0-51. }; void PurgeOldStats() EXCLUSIVE_LOCKS_REQUIRED(crit_); VideoSendStream::StreamStats* GetStatsEntry(uint32_t ssrc) @@ -207,6 +214,8 @@ class SendStatisticsProxy : public CpuOveruseMetricsObserver, RateAccCounter fec_byte_counter_; int64_t first_rtcp_stats_time_ms_; int64_t first_rtp_stats_time_ms_; + StatsTimer cpu_scaling_timer_; + StatsTimer quality_scaling_timer_; BoolSampleCounter paused_time_counter_; TargetRateUpdates target_rate_updates_; ReportBlockStats report_block_stats_; diff --git a/webrtc/video/send_statistics_proxy_unittest.cc b/webrtc/video/send_statistics_proxy_unittest.cc index 7b44dde469..b3aa50667d 100644 --- a/webrtc/video/send_statistics_proxy_unittest.cc +++ b/webrtc/video/send_statistics_proxy_unittest.cc @@ -369,10 +369,12 @@ TEST_F(SendStatisticsProxyTest, OnSendEncodedImageWithoutQpQpSumWontExist) { TEST_F(SendStatisticsProxyTest, SetCpuScalingUpdatesStats) { EXPECT_FALSE(statistics_proxy_->GetStats().cpu_limited_resolution); - statistics_proxy_->SetCpuScalingStats(true); - EXPECT_TRUE(statistics_proxy_->GetStats().cpu_limited_resolution); - statistics_proxy_->SetCpuScalingStats(false); + statistics_proxy_->SetCpuScalingStats(-1); EXPECT_FALSE(statistics_proxy_->GetStats().cpu_limited_resolution); + statistics_proxy_->SetCpuScalingStats(0); + EXPECT_FALSE(statistics_proxy_->GetStats().cpu_limited_resolution); + statistics_proxy_->SetCpuScalingStats(1); + EXPECT_TRUE(statistics_proxy_->GetStats().cpu_limited_resolution); } TEST_F(SendStatisticsProxyTest, SetQualityScalingUpdatesStats) { @@ -419,6 +421,191 @@ TEST_F(SendStatisticsProxyTest, GetStatsReportsQualityResolutionChanges) { EXPECT_EQ(4, statistics_proxy_->GetStats().number_of_quality_adapt_changes); } +TEST_F(SendStatisticsProxyTest, AdaptChangesNotReported_ScalingNotEnabled) { + // Min runtime has passed. + fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds * 1000); + statistics_proxy_.reset(); + EXPECT_EQ(0, metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Cpu")); + EXPECT_EQ(0, + metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Quality")); +} + +TEST_F(SendStatisticsProxyTest, AdaptChangesNotReported_MinRuntimeNotPassed) { + // Enable scaling. + statistics_proxy_->SetQualityScalingStats(0); + statistics_proxy_->SetCpuScalingStats(0); + // Min runtime has not passed. + fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds * 1000 - 1); + statistics_proxy_.reset(); + EXPECT_EQ(0, metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Cpu")); + EXPECT_EQ(0, + metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Quality")); +} + +TEST_F(SendStatisticsProxyTest, ZeroCpuAdaptChangesReported) { + // Enable scaling. + statistics_proxy_->SetCpuScalingStats(0); + // Min runtime has passed. + fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds * 1000); + statistics_proxy_.reset(); + EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Cpu")); + EXPECT_EQ(1, metrics::NumEvents("WebRTC.Video.AdaptChangesPerMinute.Cpu", 0)); +} + +TEST_F(SendStatisticsProxyTest, ZeroQualityAdaptChangesReported) { + // Enable scaling. + statistics_proxy_->SetQualityScalingStats(0); + // Min runtime has passed. + fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds * 1000); + statistics_proxy_.reset(); + EXPECT_EQ(1, + metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Quality")); + EXPECT_EQ( + 1, metrics::NumEvents("WebRTC.Video.AdaptChangesPerMinute.Quality", 0)); +} + +TEST_F(SendStatisticsProxyTest, CpuAdaptChangesReported) { + // Enable scaling. + // Adapt changes: 1, elapsed time: 10 ms => 6 per minute. + statistics_proxy_->SetCpuScalingStats(0); + statistics_proxy_->OnCpuRestrictedResolutionChanged(true); + fake_clock_.AdvanceTimeMilliseconds(10000); + statistics_proxy_.reset(); + EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Cpu")); + EXPECT_EQ(1, metrics::NumEvents("WebRTC.Video.AdaptChangesPerMinute.Cpu", 6)); +} + +TEST_F(SendStatisticsProxyTest, AdaptChangesStatsExcludesDisabledTime) { + // Disable scaling. + statistics_proxy_->SetQualityScalingStats(-1); + fake_clock_.AdvanceTimeMilliseconds(10000); + + // Enable scaling. + // Adapt changes: 2, elapsed time: 20 ms. + statistics_proxy_->SetQualityScalingStats(0); + fake_clock_.AdvanceTimeMilliseconds(5000); + statistics_proxy_->SetQualityScalingStats(1); + fake_clock_.AdvanceTimeMilliseconds(9000); + statistics_proxy_->OnQualityRestrictedResolutionChanged(1); + fake_clock_.AdvanceTimeMilliseconds(6000); + statistics_proxy_->OnQualityRestrictedResolutionChanged(2); + + // Disable scaling. + statistics_proxy_->SetQualityScalingStats(-1); + fake_clock_.AdvanceTimeMilliseconds(30000); + + // Enable scaling. + // Adapt changes: 1, elapsed time: 10 ms. + statistics_proxy_->SetQualityScalingStats(0); + statistics_proxy_->OnQualityRestrictedResolutionChanged(1); + fake_clock_.AdvanceTimeMilliseconds(10000); + + // Disable scaling. + statistics_proxy_->SetQualityScalingStats(-1); + fake_clock_.AdvanceTimeMilliseconds(5000); + statistics_proxy_->SetQualityScalingStats(-1); + fake_clock_.AdvanceTimeMilliseconds(20000); + + // Adapt changes: 3, elapsed time: 30 ms => 6 per minute. + statistics_proxy_.reset(); + EXPECT_EQ(1, + metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Quality")); + EXPECT_EQ( + 1, metrics::NumEvents("WebRTC.Video.AdaptChangesPerMinute.Quality", 6)); +} + +TEST_F(SendStatisticsProxyTest, AdaptChangesStatsRestartsOnFirstSentPacket) { + StreamDataCounters counters; + StreamDataCountersCallback* proxy = + static_cast(statistics_proxy_.get()); + + // Send first packet, scaling enabled. + // Elapsed time before first packet is sent should be excluded. + statistics_proxy_->SetQualityScalingStats(0); + fake_clock_.AdvanceTimeMilliseconds(10000); + proxy->DataCountersUpdated(counters, kFirstSsrc); + + // Adapt changes: 1, elapsed time: 10 ms. + fake_clock_.AdvanceTimeMilliseconds(10000); + statistics_proxy_->OnQualityRestrictedResolutionChanged(1); + proxy->DataCountersUpdated(counters, kFirstSsrc); + + // Adapt changes: 1, elapsed time: 10 ms => 6 per minute. + statistics_proxy_.reset(); + EXPECT_EQ(1, + metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Quality")); + EXPECT_EQ( + 1, metrics::NumEvents("WebRTC.Video.AdaptChangesPerMinute.Quality", 6)); +} + +TEST_F(SendStatisticsProxyTest, AdaptChangesStatsStartedAfterFirstSentPacket) { + StreamDataCounters counters; + StreamDataCountersCallback* proxy = + static_cast(statistics_proxy_.get()); + + // Enable and disable scaling. + statistics_proxy_->SetCpuScalingStats(0); + fake_clock_.AdvanceTimeMilliseconds(60000); + statistics_proxy_->SetCpuScalingStats(-1); + + // Send first packet, scaling disabled. + // Elapsed time before first packet is sent should be excluded. + proxy->DataCountersUpdated(counters, kFirstSsrc); + fake_clock_.AdvanceTimeMilliseconds(60000); + + // Enable scaling. + statistics_proxy_->SetCpuScalingStats(0); + fake_clock_.AdvanceTimeMilliseconds(10000); + proxy->DataCountersUpdated(counters, kFirstSsrc); + + // Adapt changes: 1, elapsed time: 20 ms. + fake_clock_.AdvanceTimeMilliseconds(10000); + statistics_proxy_->OnCpuRestrictedResolutionChanged(true); + + // Adapt changes: 1, elapsed time: 20 ms => 3 per minute. + statistics_proxy_.reset(); + EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Cpu")); + EXPECT_EQ(1, metrics::NumEvents("WebRTC.Video.AdaptChangesPerMinute.Cpu", 3)); +} + +TEST_F(SendStatisticsProxyTest, AdaptChangesReportedAfterContentSwitch) { + // Enable scaling. + statistics_proxy_->SetCpuScalingStats(0); + + // Adapt changes: 2, elapsed time: 15 ms => 8 per minute. + statistics_proxy_->OnCpuRestrictedResolutionChanged(true); + fake_clock_.AdvanceTimeMilliseconds(6000); + statistics_proxy_->OnCpuRestrictedResolutionChanged(true); + fake_clock_.AdvanceTimeMilliseconds(9000); + + // Switch content type, real-time stats should be updated. + VideoEncoderConfig config; + config.content_type = VideoEncoderConfig::ContentType::kScreen; + statistics_proxy_->OnEncoderReconfigured(config, 50); + EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Cpu")); + EXPECT_EQ(1, metrics::NumEvents("WebRTC.Video.AdaptChangesPerMinute.Cpu", 8)); + EXPECT_EQ(0, + metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Quality")); + + // Enable scaling. + statistics_proxy_->SetCpuScalingStats(0); + + // Adapt changes: 4, elapsed time: 120 ms => 2 per minute. + statistics_proxy_->OnCpuRestrictedResolutionChanged(true); + statistics_proxy_->OnCpuRestrictedResolutionChanged(true); + statistics_proxy_->OnCpuRestrictedResolutionChanged(true); + statistics_proxy_->OnCpuRestrictedResolutionChanged(true); + fake_clock_.AdvanceTimeMilliseconds(120000); + + statistics_proxy_.reset(); + EXPECT_EQ(1, metrics::NumSamples( + "WebRTC.Video.Screenshare.AdaptChangesPerMinute.Cpu")); + EXPECT_EQ(1, metrics::NumEvents( + "WebRTC.Video.Screenshare.AdaptChangesPerMinute.Cpu", 2)); + EXPECT_EQ(0, metrics::NumSamples( + "WebRTC.Video.Screenshare.AdaptChangesPerMinute.Quality")); +} + TEST_F(SendStatisticsProxyTest, SwitchContentTypeUpdatesHistograms) { for (int i = 0; i < SendStatisticsProxy::kMinRequiredMetricsSamples; ++i) statistics_proxy_->OnIncomingFrame(kWidth, kHeight); diff --git a/webrtc/video/vie_encoder.cc b/webrtc/video/vie_encoder.cc index aa5e593ed8..ecf3a547ee 100644 --- a/webrtc/video/vie_encoder.cc +++ b/webrtc/video/vie_encoder.cc @@ -541,7 +541,7 @@ void ViEEncoder::ConfigureQualityScaler() { const std::vector& scale_counters = GetScaleCounters(); stats_proxy_->SetCpuScalingStats( - degradation_preference_allows_scaling ? scale_counters[kCpu] > 0 : false); + degradation_preference_allows_scaling ? scale_counters[kCpu] : -1); stats_proxy_->SetQualityScalingStats( quality_scaling_allowed ? scale_counters[kQuality] : -1);