From e76b3abf610bc4acd386dbc5de9aff00a64823a3 Mon Sep 17 00:00:00 2001 From: Johannes Kron Date: Tue, 22 Oct 2019 13:22:26 +0200 Subject: [PATCH] Add per frame decode time histograms for 4k/HD and VP9/H264 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Commit-Queue: Johannes Kron Cr-Commit-Position: refs/heads/master@{#29572} --- video/receive_statistics_proxy.cc | 62 +++++++++ video/receive_statistics_proxy.h | 6 + video/receive_statistics_proxy_unittest.cc | 140 +++++++++++++++++++++ 3 files changed, 208 insertions(+) diff --git a/video/receive_statistics_proxy.cc b/video/receive_statistics_proxy.cc index 42ab7e19ec..d8bde9490b 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 @@ ReceiveStatisticsProxy::ReceiveStatisticsProxy( : 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 @@ void ReceiveStatisticsProxy::UpdateFramerate(int64_t now_ms) const { stats_.network_frame_rate = static_cast(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 @@ void ReceiveStatisticsProxy::OnDecodedFrame(const VideoFrame& frame, 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 9c3a117c5e..40608a8568 100644 --- a/video/receive_statistics_proxy.h +++ b/video/receive_statistics_proxy.h @@ -128,6 +128,11 @@ class ReceiveStatisticsProxy : public VCMReceiveStatisticsCallback, 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 @@ class ReceiveStatisticsProxy : public VCMReceiveStatisticsCallback, // 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 fffc42ce0d..66adb83aea 100644 --- a/video/receive_statistics_proxy_unittest.cc +++ b/video/receive_statistics_proxy_unittest.cc @@ -16,12 +16,14 @@ #include #include +#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 @@ TEST_P(ReceiveStatisticsProxyTestWithContent, "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>, + 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