Add stats for forced software encoder fallback for VP8.

Stats added for number of forced SW fallback changes per minute and percentage of time fallback is enabled for sent video streams:

- "WebRTC.Video.Encoder.ForcedSwFallbackChangesPerMinute.Vp8"
- "WebRTC.Video.Encoder.ForcedSwFallbackTimeInPercent.Vp8"

BUG=webrtc:6634

Review-Url: https://codereview.webrtc.org/3012863002
Cr-Commit-Position: refs/heads/master@{#19862}
diff --git a/video/send_statistics_proxy.cc b/video/send_statistics_proxy.cc
index 3eff46e..4042a51 100644
--- a/video/send_statistics_proxy.cc
+++ b/video/send_statistics_proxy.cc
@@ -19,12 +19,17 @@
 #include "modules/video_coding/include/video_codec_interface.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/logging.h"
+#include "system_wrappers/include/field_trial.h"
 #include "system_wrappers/include/metrics.h"
 
 namespace webrtc {
 namespace {
 const float kEncodeTimeWeigthFactor = 0.5f;
 
+const char kVp8ForcedFallbackEncoderFieldTrial[] =
+    "WebRTC-VP8-Forced-Fallback-Encoder";
+const char kVp8SwCodecName[] = "libvpx";
+
 // Used by histograms. Values of entries should not be changed.
 enum HistogramCodecType {
   kVideoUnknown = 0,
@@ -68,6 +73,38 @@
                             PayloadNameToHistogramCodecType(payload_name),
                             kVideoMax);
 }
+
+bool IsForcedFallbackPossible(const CodecSpecificInfo* codec_info) {
+  return codec_info->codecType == kVideoCodecVP8 &&
+         codec_info->codecSpecific.VP8.simulcastIdx == 0 &&
+         (codec_info->codecSpecific.VP8.temporalIdx == 0 ||
+          codec_info->codecSpecific.VP8.temporalIdx == kNoTemporalIdx);
+}
+
+rtc::Optional<int> GetFallbackIntervalFromFieldTrial() {
+  if (!webrtc::field_trial::IsEnabled(kVp8ForcedFallbackEncoderFieldTrial))
+    return rtc::Optional<int>();
+
+  std::string group =
+      webrtc::field_trial::FindFullName(kVp8ForcedFallbackEncoderFieldTrial);
+  if (group.empty())
+    return rtc::Optional<int>();
+
+  int low_kbps;
+  int high_kbps;
+  int min_low_ms;
+  int min_pixels;
+  if (sscanf(group.c_str(), "Enabled-%d,%d,%d,%d", &low_kbps, &high_kbps,
+             &min_low_ms, &min_pixels) != 4) {
+    return rtc::Optional<int>();
+  }
+
+  if (min_low_ms <= 0 || min_pixels <= 0 || low_kbps <= 0 ||
+      high_kbps <= low_kbps) {
+    return rtc::Optional<int>();
+  }
+  return rtc::Optional<int>(min_low_ms);
+}
 }  // namespace
 
 
@@ -80,6 +117,7 @@
     : clock_(clock),
       payload_name_(config.encoder_settings.payload_name),
       rtp_config_(config.rtp),
+      min_first_fallback_interval_ms_(GetFallbackIntervalFromFieldTrial()),
       content_type_(content_type),
       start_ms_(clock->TimeInMilliseconds()),
       last_sent_frame_timestamp_(0),
@@ -410,6 +448,21 @@
     }
   }
 
+  if (fallback_info_.is_possible) {
+    // Double interval since there is some time before fallback may occur.
+    const int kMinRunTimeMs = 2 * metrics::kMinRunTimeInSeconds * 1000;
+    int64_t elapsed_ms = fallback_info_.elapsed_ms;
+    int fallback_time_percent = fallback_active_counter_.Percent(kMinRunTimeMs);
+    if (fallback_time_percent != -1 && elapsed_ms >= kMinRunTimeMs) {
+      RTC_HISTOGRAMS_PERCENTAGE(
+          kIndex, uma_prefix_ + "Encoder.ForcedSwFallbackTimeInPercent.Vp8",
+          fallback_time_percent);
+      RTC_HISTOGRAMS_COUNTS_100(
+          kIndex, uma_prefix_ + "Encoder.ForcedSwFallbackChangesPerMinute.Vp8",
+          fallback_info_.on_off_events * 60 / (elapsed_ms / 1000));
+    }
+  }
+
   AggregatedStats total_bytes_per_sec = total_byte_counter_.GetStats();
   if (total_bytes_per_sec.num_samples > kMinRequiredPeriodicSamples) {
     RTC_HISTOGRAMS_COUNTS_10000(kIndex, uma_prefix_ + "BitrateSentInKbps",
@@ -620,6 +673,57 @@
   stats_.target_media_bitrate_bps = bitrate_bps;
 }
 
+void SendStatisticsProxy::UpdateEncoderFallbackStats(
+    const CodecSpecificInfo* codec_info) {
+  if (!min_first_fallback_interval_ms_ ||
+      !uma_container_->fallback_info_.is_possible) {
+    return;
+  }
+
+  if (!IsForcedFallbackPossible(codec_info)) {
+    uma_container_->fallback_info_.is_possible = false;
+    return;
+  }
+
+  FallbackEncoderInfo* fallback_info = &uma_container_->fallback_info_;
+
+  const int64_t now_ms = clock_->TimeInMilliseconds();
+  bool is_active = fallback_info->is_active;
+  if (codec_info->codec_name != stats_.encoder_implementation_name) {
+    // Implementation changed.
+    is_active = strcmp(codec_info->codec_name, kVp8SwCodecName) == 0;
+    if (!is_active && stats_.encoder_implementation_name != kVp8SwCodecName) {
+      // First or not a VP8 SW change, update stats on next call.
+      return;
+    }
+    if (is_active && fallback_info->on_off_events == 0) {
+      // The minimum set time should have passed for the first fallback (else
+      // skip to avoid fallback due to failure).
+      int64_t elapsed_ms = fallback_info->elapsed_ms;
+      if (fallback_info->last_update_ms)
+        elapsed_ms += now_ms - *(fallback_info->last_update_ms);
+      if (elapsed_ms < *min_first_fallback_interval_ms_) {
+        fallback_info->is_possible = false;
+        return;
+      }
+    }
+    ++fallback_info->on_off_events;
+  }
+
+  if (fallback_info->last_update_ms) {
+    int64_t diff_ms = now_ms - *(fallback_info->last_update_ms);
+    // If the time diff since last update is greater than |max_frame_diff_ms|,
+    // video is considered paused/muted and the change is not included.
+    if (diff_ms < fallback_info->max_frame_diff_ms) {
+      uma_container_->fallback_active_counter_.Add(fallback_info->is_active,
+                                                   diff_ms);
+      fallback_info->elapsed_ms += diff_ms;
+    }
+  }
+  fallback_info->is_active = is_active;
+  fallback_info->last_update_ms.emplace(now_ms);
+}
+
 void SendStatisticsProxy::OnSendEncodedImage(
     const EncodedImage& encoded_image,
     const CodecSpecificInfo* codec_info) {
@@ -634,6 +738,7 @@
       simulcast_idx = codec_info->codecSpecific.generic.simulcast_idx;
     }
     if (codec_info->codec_name) {
+      UpdateEncoderFallbackStats(codec_info);
       stats_.encoder_implementation_name = codec_info->codec_name;
     }
   }
diff --git a/video/send_statistics_proxy.h b/video/send_statistics_proxy.h
index f2255ad..3516830 100644
--- a/video/send_statistics_proxy.h
+++ b/video/send_statistics_proxy.h
@@ -150,6 +150,14 @@
     bool last_paused_or_resumed;
     int64_t last_ms;
   };
+  struct FallbackEncoderInfo {
+    bool is_possible = true;
+    bool is_active = false;
+    int on_off_events = 0;
+    int64_t elapsed_ms = 0;
+    rtc::Optional<int64_t> last_update_ms;
+    const int max_frame_diff_ms = 2000;
+  };
   struct StatsTimer {
     void Start(int64_t now_ms);
     void Stop(int64_t now_ms);
@@ -173,9 +181,13 @@
       const VideoStreamEncoder::AdaptCounts& quality_counts)
       RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
 
+  void UpdateEncoderFallbackStats(const CodecSpecificInfo* codec_info)
+      RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
+
   Clock* const clock_;
   const std::string payload_name_;
   const VideoSendStream::Config::Rtp rtp_config_;
+  const rtc::Optional<int> min_first_fallback_interval_ms_;
   rtc::CriticalSection crit_;
   VideoEncoderConfig::ContentType content_type_ RTC_GUARDED_BY(crit_);
   const int64_t start_ms_;
@@ -232,6 +244,8 @@
     StatsTimer quality_adapt_timer_;
     BoolSampleCounter paused_time_counter_;
     TargetRateUpdates target_rate_updates_;
+    BoolSampleCounter fallback_active_counter_;
+    FallbackEncoderInfo fallback_info_;
     ReportBlockStats report_block_stats_;
     const VideoSendStream::Stats start_stats_;
 
diff --git a/video/send_statistics_proxy_unittest.cc b/video/send_statistics_proxy_unittest.cc
index 912908c..af9f674 100644
--- a/video/send_statistics_proxy_unittest.cc
+++ b/video/send_statistics_proxy_unittest.cc
@@ -17,6 +17,7 @@
 
 #include "system_wrappers/include/metrics.h"
 #include "system_wrappers/include/metrics_default.h"
+#include "test/field_trial.h"
 #include "test/gtest.h"
 
 namespace webrtc {
@@ -31,6 +32,7 @@
 const int kHeight = 480;
 const int kQpIdx0 = 21;
 const int kQpIdx1 = 39;
+const int kMinFirstFallbackIntervalMs = 1500;
 const CodecSpecificInfo kDefaultCodecInfo = []() {
   CodecSpecificInfo codec_info;
   codec_info.codecType = kVideoCodecVP8;
@@ -41,8 +43,12 @@
 
 class SendStatisticsProxyTest : public ::testing::Test {
  public:
-  SendStatisticsProxyTest()
-      : fake_clock_(1234), config_(GetTestConfig()), avg_delay_ms_(0),
+  SendStatisticsProxyTest() : SendStatisticsProxyTest("") {}
+  explicit SendStatisticsProxyTest(const std::string& field_trials)
+      : override_field_trials_(field_trials),
+        fake_clock_(1234),
+        config_(GetTestConfig()),
+        avg_delay_ms_(0),
         max_delay_ms_(0) {}
   virtual ~SendStatisticsProxyTest() {}
 
@@ -140,6 +146,7 @@
     }
   }
 
+  test::ScopedFieldTrials override_field_trials_;
   SimulatedClock fake_clock_;
   std::unique_ptr<SendStatisticsProxy> statistics_proxy_;
   VideoSendStream::Config config_;
@@ -1799,4 +1806,165 @@
   EXPECT_EQ(0, metrics::NumSamples("WebRTC.Video.FecBitrateSentInKbps"));
 }
 
+TEST_F(SendStatisticsProxyTest, GetStatsReportsEncoderImplementationName) {
+  const char* kName = "encoderName";
+  EncodedImage encoded_image;
+  CodecSpecificInfo codec_info;
+  codec_info.codec_name = kName;
+  statistics_proxy_->OnSendEncodedImage(encoded_image, &codec_info);
+  EXPECT_STREQ(
+      kName, statistics_proxy_->GetStats().encoder_implementation_name.c_str());
+}
+
+class ForcedFallbackTest : public SendStatisticsProxyTest {
+ public:
+  explicit ForcedFallbackTest(const std::string& field_trials)
+      : SendStatisticsProxyTest(field_trials) {
+    codec_info_.codecType = kVideoCodecVP8;
+    codec_info_.codecSpecific.VP8.simulcastIdx = 0;
+    codec_info_.codecSpecific.VP8.temporalIdx = 0;
+    codec_info_.codec_name = "fake_codec";
+  }
+
+  ~ForcedFallbackTest() override {}
+
+ protected:
+  void InsertEncodedFrames(int num_frames, int interval_ms) {
+    // First frame is not updating stats, insert initial frame.
+    if (statistics_proxy_->GetStats().frames_encoded == 0) {
+      statistics_proxy_->OnSendEncodedImage(encoded_image_, &codec_info_);
+    }
+    for (int i = 0; i < num_frames; ++i) {
+      statistics_proxy_->OnSendEncodedImage(encoded_image_, &codec_info_);
+      fake_clock_.AdvanceTimeMilliseconds(interval_ms);
+    }
+    // Add frame to include last time interval.
+    statistics_proxy_->OnSendEncodedImage(encoded_image_, &codec_info_);
+  }
+
+  EncodedImage encoded_image_;
+  CodecSpecificInfo codec_info_;
+  const std::string kPrefix = "WebRTC.Video.Encoder.ForcedSw";
+  const int kFrameIntervalMs = 1000;
+  const int kMinFrames = 20;  // Min run time 20 sec.
+};
+
+class ForcedFallbackDisabled : public ForcedFallbackTest {
+ public:
+  ForcedFallbackDisabled()
+      : ForcedFallbackTest("WebRTC-VP8-Forced-Fallback-Encoder/Disabled/") {}
+};
+
+class ForcedFallbackEnabled : public ForcedFallbackTest {
+ public:
+  ForcedFallbackEnabled()
+      : ForcedFallbackTest("WebRTC-VP8-Forced-Fallback-Encoder/Enabled-1,2," +
+                           std::to_string(kMinFirstFallbackIntervalMs) +
+                           ",4/") {}
+};
+
+TEST_F(ForcedFallbackEnabled, StatsNotUpdatedIfMinRunTimeHasNotPassed) {
+  InsertEncodedFrames(kMinFrames, kFrameIntervalMs - 1);
+  statistics_proxy_.reset();
+  EXPECT_EQ(0, metrics::NumSamples(kPrefix + "FallbackTimeInPercent.Vp8"));
+  EXPECT_EQ(0, metrics::NumSamples(kPrefix + "FallbackChangesPerMinute.Vp8"));
+}
+
+TEST_F(ForcedFallbackEnabled, StatsUpdated) {
+  InsertEncodedFrames(kMinFrames, kFrameIntervalMs);
+  statistics_proxy_.reset();
+  EXPECT_EQ(1, metrics::NumSamples(kPrefix + "FallbackTimeInPercent.Vp8"));
+  EXPECT_EQ(1, metrics::NumEvents(kPrefix + "FallbackTimeInPercent.Vp8", 0));
+  EXPECT_EQ(1, metrics::NumSamples(kPrefix + "FallbackChangesPerMinute.Vp8"));
+  EXPECT_EQ(1, metrics::NumEvents(kPrefix + "FallbackChangesPerMinute.Vp8", 0));
+}
+
+TEST_F(ForcedFallbackEnabled, StatsNotUpdatedIfNotVp8) {
+  codec_info_.codecType = kVideoCodecVP9;
+  InsertEncodedFrames(kMinFrames, kFrameIntervalMs);
+  statistics_proxy_.reset();
+  EXPECT_EQ(0, metrics::NumSamples(kPrefix + "FallbackTimeInPercent.Vp8"));
+  EXPECT_EQ(0, metrics::NumSamples(kPrefix + "FallbackChangesPerMinute.Vp8"));
+}
+
+TEST_F(ForcedFallbackEnabled, StatsNotUpdatedForTemporalLayers) {
+  codec_info_.codecSpecific.VP8.temporalIdx = 1;
+  InsertEncodedFrames(kMinFrames, kFrameIntervalMs);
+  statistics_proxy_.reset();
+  EXPECT_EQ(0, metrics::NumSamples(kPrefix + "FallbackTimeInPercent.Vp8"));
+  EXPECT_EQ(0, metrics::NumSamples(kPrefix + "FallbackChangesPerMinute.Vp8"));
+}
+
+TEST_F(ForcedFallbackEnabled, StatsNotUpdatedForSimulcast) {
+  codec_info_.codecSpecific.VP8.simulcastIdx = 1;
+  InsertEncodedFrames(kMinFrames, kFrameIntervalMs);
+  statistics_proxy_.reset();
+  EXPECT_EQ(0, metrics::NumSamples(kPrefix + "FallbackTimeInPercent.Vp8"));
+  EXPECT_EQ(0, metrics::NumSamples(kPrefix + "FallbackChangesPerMinute.Vp8"));
+}
+
+TEST_F(ForcedFallbackDisabled, StatsNotUpdatedIfNoFieldTrial) {
+  InsertEncodedFrames(kMinFrames, kFrameIntervalMs);
+  statistics_proxy_.reset();
+  EXPECT_EQ(0, metrics::NumSamples(kPrefix + "FallbackTimeInPercent.Vp8"));
+  EXPECT_EQ(0, metrics::NumSamples(kPrefix + "FallbackChangesPerMinute.Vp8"));
+}
+
+TEST_F(ForcedFallbackEnabled, OneFallbackEvent) {
+  // One change. Video: 20000 ms, fallback: 5000 ms (25%).
+  InsertEncodedFrames(15, 1000);
+  codec_info_.codec_name = "libvpx";
+  InsertEncodedFrames(5, 1000);
+
+  statistics_proxy_.reset();
+  EXPECT_EQ(1, metrics::NumSamples(kPrefix + "FallbackTimeInPercent.Vp8"));
+  EXPECT_EQ(1, metrics::NumEvents(kPrefix + "FallbackTimeInPercent.Vp8", 25));
+  EXPECT_EQ(1, metrics::NumSamples(kPrefix + "FallbackChangesPerMinute.Vp8"));
+  EXPECT_EQ(1, metrics::NumEvents(kPrefix + "FallbackChangesPerMinute.Vp8", 3));
+}
+
+TEST_F(ForcedFallbackEnabled, ThreeFallbackEvents) {
+  codec_info_.codecSpecific.VP8.temporalIdx = kNoTemporalIdx;  // Should work.
+  const int kMaxFrameDiffMs = 2000;
+
+  // Three changes. Video: 60000 ms, fallback: 15000 ms (25%).
+  InsertEncodedFrames(10, 1000);
+  codec_info_.codec_name = "libvpx";
+  InsertEncodedFrames(15, 500);
+  codec_info_.codec_name = "notlibvpx";
+  InsertEncodedFrames(20, 1000);
+  InsertEncodedFrames(3, kMaxFrameDiffMs);  // Should not be included.
+  InsertEncodedFrames(10, 1000);
+  codec_info_.codec_name = "notlibvpx2";
+  InsertEncodedFrames(10, 500);
+  codec_info_.codec_name = "libvpx";
+  InsertEncodedFrames(15, 500);
+
+  statistics_proxy_.reset();
+  EXPECT_EQ(1, metrics::NumSamples(kPrefix + "FallbackTimeInPercent.Vp8"));
+  EXPECT_EQ(1, metrics::NumEvents(kPrefix + "FallbackTimeInPercent.Vp8", 25));
+  EXPECT_EQ(1, metrics::NumSamples(kPrefix + "FallbackChangesPerMinute.Vp8"));
+  EXPECT_EQ(1, metrics::NumEvents(kPrefix + "FallbackChangesPerMinute.Vp8", 3));
+}
+
+TEST_F(ForcedFallbackEnabled, NoFallbackIfMinIntervalHasNotPassed) {
+  InsertEncodedFrames(1, kMinFirstFallbackIntervalMs - 1);
+  codec_info_.codec_name = "libvpx";
+  InsertEncodedFrames(kMinFrames, kFrameIntervalMs);
+
+  statistics_proxy_.reset();
+  EXPECT_EQ(0, metrics::NumSamples(kPrefix + "FallbackTimeInPercent.Vp8"));
+  EXPECT_EQ(0, metrics::NumSamples(kPrefix + "FallbackChangesPerMinute.Vp8"));
+}
+
+TEST_F(ForcedFallbackEnabled, FallbackIfMinIntervalPassed) {
+  InsertEncodedFrames(1, kMinFirstFallbackIntervalMs);
+  codec_info_.codec_name = "libvpx";
+  InsertEncodedFrames(kMinFrames, kFrameIntervalMs);
+
+  statistics_proxy_.reset();
+  EXPECT_EQ(1, metrics::NumSamples(kPrefix + "FallbackTimeInPercent.Vp8"));
+  EXPECT_EQ(1, metrics::NumSamples(kPrefix + "FallbackChangesPerMinute.Vp8"));
+}
+
 }  // namespace webrtc