Add per frame decode time histograms for 4k/HD and VP9/H264

Add new histograms
WebRTC.Video.DecodeTimePerFrameInMs.[codec].[resolution].[decoder]
These histograms are more explicit than the existing histogram
WebRTC.VideoDecodTimeMs, since they allow to see performance per
codec/resolution/decoder and also contain per frame statistics instead
of an average decode time.

There's a killswitch, WebRTC-DecodeTimeHistogramsKillSwitch, that can be
used to disable the histograms.

Bug: chromium:1007526
Change-Id: I9f75127b4bc5341e9f406c64ed91164564290b26
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/157881
Reviewed-by: Åsa Persson <asapersson@webrtc.org>
Commit-Queue: Johannes Kron <kron@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29572}
diff --git a/video/receive_statistics_proxy.cc b/video/receive_statistics_proxy.cc
index 42ab7e1..d8bde94 100644
--- a/video/receive_statistics_proxy.cc
+++ b/video/receive_statistics_proxy.cc
@@ -20,6 +20,7 @@
 #include "rtc_base/strings/string_builder.h"
 #include "rtc_base/time_utils.h"
 #include "system_wrappers/include/clock.h"
+#include "system_wrappers/include/field_trial.h"
 #include "system_wrappers/include/metrics.h"
 
 namespace webrtc {
@@ -85,6 +86,8 @@
     : clock_(clock),
       config_(*config),
       start_ms_(clock->TimeInMilliseconds()),
+      enable_decode_time_histograms_(
+          !field_trial::IsEnabled("WebRTC-DecodeTimeHistogramsKillSwitch")),
       last_sample_time_(clock->TimeInMilliseconds()),
       fps_threshold_(kLowFpsThreshold,
                      kHighFpsThreshold,
@@ -553,6 +556,61 @@
   stats_.network_frame_rate = static_cast<int>(framerate);
 }
 
+void ReceiveStatisticsProxy::UpdateDecodeTimeHistograms(
+    int width,
+    int height,
+    int decode_time_ms) const {
+  bool is_4k = (width == 3840 || width == 4096) && height == 2160;
+  bool is_hd = width == 1920 && height == 1080;
+  // Only update histograms for 4k/HD and VP9/H264.
+  if ((is_4k || is_hd) && (last_codec_type_ == kVideoCodecVP9 ||
+                           last_codec_type_ == kVideoCodecH264)) {
+    const std::string kDecodeTimeUmaPrefix =
+        "WebRTC.Video.DecodeTimePerFrameInMs.";
+
+    // Each histogram needs its own line for it to not be reused in the wrong
+    // way when the format changes.
+    if (last_codec_type_ == kVideoCodecVP9) {
+      bool is_sw_decoder =
+          stats_.decoder_implementation_name.compare(0, 6, "libvpx") == 0;
+      if (is_4k) {
+        if (is_sw_decoder)
+          RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "Vp9.4k.Sw",
+                                    decode_time_ms);
+        else
+          RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "Vp9.4k.Hw",
+                                    decode_time_ms);
+      } else {
+        if (is_sw_decoder)
+          RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "Vp9.Hd.Sw",
+                                    decode_time_ms);
+        else
+          RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "Vp9.Hd.Hw",
+                                    decode_time_ms);
+      }
+    } else {
+      bool is_sw_decoder =
+          stats_.decoder_implementation_name.compare(0, 6, "FFmpeg") == 0;
+      if (is_4k) {
+        if (is_sw_decoder)
+          RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "H264.4k.Sw",
+                                    decode_time_ms);
+        else
+          RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "H264.4k.Hw",
+                                    decode_time_ms);
+
+      } else {
+        if (is_sw_decoder)
+          RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "H264.Hd.Sw",
+                                    decode_time_ms);
+        else
+          RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "H264.Hd.Hw",
+                                    decode_time_ms);
+      }
+    }
+  }
+}
+
 VideoReceiveStream::Stats ReceiveStatisticsProxy::GetStats() const {
   rtc::CritScope lock(&crit_);
   // Get current frame rates here, as only updating them on new frames prevents
@@ -697,6 +755,10 @@
   decode_time_counter_.Add(decode_time_ms);
   stats_.decode_ms = decode_time_ms;
   stats_.total_decode_time_ms += decode_time_ms;
+  if (enable_decode_time_histograms_) {
+    UpdateDecodeTimeHistograms(frame.width(), frame.height(), decode_time_ms);
+  }
+
   last_content_type_ = content_type;
   decode_fps_estimator_.Update(1, now_ms);
   if (last_decoded_frame_time_ms_) {
diff --git a/video/receive_statistics_proxy.h b/video/receive_statistics_proxy.h
index 9c3a117..40608a8 100644
--- a/video/receive_statistics_proxy.h
+++ b/video/receive_statistics_proxy.h
@@ -128,6 +128,11 @@
   void UpdateFramerate(int64_t now_ms) const
       RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
 
+  void UpdateDecodeTimeHistograms(int width,
+                                  int height,
+                                  int decode_time_ms) const
+      RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
+
   Clock* const clock_;
   // Ownership of this object lies with the owner of the ReceiveStatisticsProxy
   // instance.  Lifetime is guaranteed to outlive |this|.
@@ -138,6 +143,7 @@
   // then no longer store a pointer to the object).
   const VideoReceiveStream::Config& config_;
   const int64_t start_ms_;
+  const bool enable_decode_time_histograms_;
 
   rtc::CriticalSection crit_;
   int64_t last_sample_time_ RTC_GUARDED_BY(crit_);
diff --git a/video/receive_statistics_proxy_unittest.cc b/video/receive_statistics_proxy_unittest.cc
index fffc42c..66adb83 100644
--- a/video/receive_statistics_proxy_unittest.cc
+++ b/video/receive_statistics_proxy_unittest.cc
@@ -16,12 +16,14 @@
 #include <tuple>
 #include <utility>
 
+#include "absl/types/optional.h"
 #include "api/scoped_refptr.h"
 #include "api/video/i420_buffer.h"
 #include "api/video/video_frame.h"
 #include "api/video/video_frame_buffer.h"
 #include "api/video/video_rotation.h"
 #include "system_wrappers/include/metrics.h"
+#include "test/field_trial.h"
 #include "test/gtest.h"
 
 namespace webrtc {
@@ -1548,4 +1550,142 @@
                   "WebRTC.Video.InterframeDelayInMs.ExperimentGroup0"));
   }
 }
+
+class DecodeTimeHistogramsKillswitch {
+ public:
+  explicit DecodeTimeHistogramsKillswitch(bool disable_histograms)
+      : field_trial_(disable_histograms
+                         ? "WebRTC-DecodeTimeHistogramsKillSwitch/Enabled/"
+                         : "") {}
+
+ private:
+  webrtc::test::ScopedFieldTrials field_trial_;
+};
+
+class ReceiveStatisticsProxyTestWithDecodeTimeHistograms
+    : public DecodeTimeHistogramsKillswitch,
+      public ::testing::WithParamInterface<
+          std::tuple<bool, int, int, int, VideoCodecType, std::string>>,
+      public ReceiveStatisticsProxyTest {
+ public:
+  ReceiveStatisticsProxyTestWithDecodeTimeHistograms()
+      : DecodeTimeHistogramsKillswitch(std::get<0>(GetParam())) {}
+
+ protected:
+  const std::string kUmaPrefix = "WebRTC.Video.DecodeTimePerFrameInMs.";
+  const int expected_number_of_samples_ = {std::get<1>(GetParam())};
+  const int width_ = {std::get<2>(GetParam())};
+  const int height_ = {std::get<3>(GetParam())};
+  const VideoCodecType codec_type_ = {std::get<4>(GetParam())};
+  const std::string implementation_name_ = {std::get<5>(GetParam())};
+  const std::string uma_histogram_name_ =
+      kUmaPrefix + (codec_type_ == kVideoCodecVP9 ? "Vp9." : "H264.") +
+      (height_ == 2160 ? "4k." : "Hd.") +
+      (implementation_name_.compare("ExternalDecoder") == 0 ? "Hw" : "Sw");
+};
+
+TEST_P(ReceiveStatisticsProxyTestWithDecodeTimeHistograms,
+       DecodeTimeHistogramsUpdated) {
+  constexpr int kNumberOfFrames = 10;
+  constexpr int kDecodeTimeMs = 7;
+  constexpr int kFrameDurationMs = 1000 / 60;
+
+  webrtc::VideoFrame frame = CreateFrame(width_, height_);
+
+  statistics_proxy_->OnDecoderImplementationName(implementation_name_.c_str());
+  statistics_proxy_->OnPreDecode(codec_type_, /*qp=*/0);
+
+  for (int i = 0; i < kNumberOfFrames; ++i) {
+    statistics_proxy_->OnDecodedFrame(frame, /*qp=*/absl::nullopt,
+                                      kDecodeTimeMs,
+                                      VideoContentType::UNSPECIFIED);
+    fake_clock_.AdvanceTimeMilliseconds(kFrameDurationMs);
+  }
+
+  EXPECT_EQ(expected_number_of_samples_,
+            metrics::NumSamples(uma_histogram_name_));
+  EXPECT_EQ(expected_number_of_samples_,
+            metrics::NumEvents(uma_histogram_name_, kDecodeTimeMs));
+}
+
+const auto kVp94kHw = std::make_tuple(/*killswitch=*/false,
+                                      /*expected_number_of_samples=*/10,
+                                      /*width=*/3840,
+                                      /*height=*/2160,
+                                      kVideoCodecVP9,
+                                      /*implementation=*/"ExternalDecoder");
+const auto kVp94kSw = std::make_tuple(/*killswitch=*/false,
+                                      /*expected_number_of_samples=*/10,
+                                      /*width=*/3840,
+                                      /*height=*/2160,
+                                      kVideoCodecVP9,
+                                      /*implementation=*/"libvpx");
+const auto kVp9HdHw = std::make_tuple(/*killswitch=*/false,
+                                      /*expected_number_of_samples=*/10,
+                                      /*width=*/1920,
+                                      /*height=*/1080,
+                                      kVideoCodecVP9,
+                                      /*implementation=*/"ExternalDecoder");
+const auto kVp9HdSw = std::make_tuple(/*killswitch=*/false,
+                                      /*expected_number_of_samples=*/10,
+                                      /*width=*/1920,
+                                      /*height=*/1080,
+                                      kVideoCodecVP9,
+                                      /*implementation=*/"libvpx");
+const auto kH2644kHw = std::make_tuple(/*killswitch=*/false,
+                                       /*expected_number_of_samples=*/10,
+                                       /*width=*/3840,
+                                       /*height=*/2160,
+                                       kVideoCodecH264,
+                                       /*implementation=*/"ExternalDecoder");
+const auto kH2644kSw = std::make_tuple(/*killswitch=*/false,
+                                       /*expected_number_of_samples=*/10,
+                                       /*width=*/3840,
+                                       /*height=*/2160,
+                                       kVideoCodecH264,
+                                       /*implementation=*/"FFmpeg");
+const auto kH264HdHw = std::make_tuple(/*killswitch=*/false,
+                                       /*expected_number_of_samples=*/10,
+                                       /*width=*/1920,
+                                       /*height=*/1080,
+                                       kVideoCodecH264,
+                                       /*implementation=*/"ExternalDecoder");
+const auto kH264HdSw = std::make_tuple(/*killswitch=*/false,
+                                       /*expected_number_of_samples=*/10,
+                                       /*width=*/1920,
+                                       /*height=*/1080,
+                                       kVideoCodecH264,
+                                       /*implementation=*/"FFmpeg");
+
+INSTANTIATE_TEST_SUITE_P(AllHistogramsPopulated,
+                         ReceiveStatisticsProxyTestWithDecodeTimeHistograms,
+                         ::testing::Values(kVp94kHw,
+                                           kVp94kSw,
+                                           kVp9HdHw,
+                                           kVp9HdSw,
+                                           kH2644kHw,
+                                           kH2644kSw,
+                                           kH264HdHw,
+                                           kH264HdSw));
+
+const auto kKillswitchDisabled =
+    std::make_tuple(/*killswitch=*/false,
+                    /*expected_number_of_samples=*/10,
+                    /*width=*/1920,
+                    /*height=*/1080,
+                    kVideoCodecVP9,
+                    /*implementation=*/"libvpx");
+const auto kKillswitchEnabled =
+    std::make_tuple(/*killswitch=*/true,
+                    /*expected_number_of_samples=*/0,
+                    /*width=*/1920,
+                    /*height=*/1080,
+                    kVideoCodecVP9,
+                    /*implementation=*/"libvpx");
+
+INSTANTIATE_TEST_SUITE_P(KillswitchEffective,
+                         ReceiveStatisticsProxyTestWithDecodeTimeHistograms,
+                         ::testing::Values(kKillswitchDisabled,
+                                           kKillswitchEnabled));
+
 }  // namespace webrtc