Add histogram stats for number of cpu/quality adapt changes per minute for sent video streams:

- "WebRTC.Video.AdaptChangesPerMinute.Cpu"
- "WebRTC.Video.AdaptChangesPerMinute.Quality"

BUG=webrtc:6634

Review-Url: https://codereview.webrtc.org/2786593003
Cr-Commit-Position: refs/heads/master@{#17535}
diff --git a/webrtc/video/send_statistics_proxy.cc b/webrtc/video/send_statistics_proxy.cc
index 3b9134b..afaeed9 100644
--- a/webrtc/video/send_statistics_proxy.cc
+++ b/webrtc/video/send_statistics_proxy.cc
@@ -293,6 +293,24 @@
     }
   }
 
+  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 @@
       "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 @@
   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 @@
   }
 
   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 @@
   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 312186a..23558ab 100644
--- a/webrtc/video/send_statistics_proxy.h
+++ b/webrtc/video/send_statistics_proxy.h
@@ -59,7 +59,7 @@
 
   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 @@
     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 @@
     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 7b44dde..b3aa506 100644
--- a/webrtc/video/send_statistics_proxy_unittest.cc
+++ b/webrtc/video/send_statistics_proxy_unittest.cc
@@ -369,10 +369,12 @@
 
 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 @@
   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<StreamDataCountersCallback*>(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<StreamDataCountersCallback*>(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 aa5e593..ecf3a54 100644
--- a/webrtc/video/vie_encoder.cc
+++ b/webrtc/video/vie_encoder.cc
@@ -541,7 +541,7 @@
 
   const std::vector<int>& 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);