From daa4f7abf5eebfee89b8974cec154a719ab0414a Mon Sep 17 00:00:00 2001 From: Ilya Nikolaevskiy Date: Fri, 6 Oct 2017 12:29:47 +0200 Subject: [PATCH] Calculate and report to UMA 95th percentile of Interframe Delay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Histogram based percentile counter is added in ReceiveStatisticsProxy. New 95th percentile metric is reported in the same way as interframe delay. Bug: webrtc:8347 Change-Id: I5e476cbb6361dd341cdb97c37d883c3923e5f611 Reviewed-on: https://webrtc-review.googlesource.com/6880 Reviewed-by: Erik Språng Commit-Queue: Ilya Nikolaevskiy Cr-Commit-Position: refs/heads/master@{#20184} --- video/receive_statistics_proxy.cc | 85 ++++++++++++++++++++++ video/receive_statistics_proxy.h | 23 ++++++ video/receive_statistics_proxy_unittest.cc | 54 +++++++++++++- 3 files changed, 161 insertions(+), 1 deletion(-) diff --git a/video/receive_statistics_proxy.cc b/video/receive_statistics_proxy.cc index b7977380ee..858f451da1 100644 --- a/video/receive_statistics_proxy.cc +++ b/video/receive_statistics_proxy.cc @@ -50,6 +50,11 @@ const int kMovingMaxWindowMs = 10000; // How large window we use to calculate the framerate/bitrate. const int kRateStatisticsWindowSizeMs = 1000; +// Some sane ballpark estimate for maximum common value of inter-frame delay. +// Values below that will be stored explicitly in the array, +// values above - in the map. +const int kMaxCommonInterframeDelayMs = 500; + std::string UmaPrefixForContentType(VideoContentType content_type) { std::stringstream ss; ss << "WebRTC.Video"; @@ -281,6 +286,16 @@ void ReceiveStatisticsProxy::UpdateHistograms() { << " " << interframe_delay_max_ms; } + rtc::Optional interframe_delay_95p_ms = + stats.interframe_delay_percentiles.GetPercentile(0.95f); + if (interframe_delay_95p_ms && interframe_delay_ms != -1) { + RTC_HISTOGRAM_COUNTS_SPARSE_10000( + uma_prefix + ".InterframeDelay95PercentileInMs" + uma_suffix, + *interframe_delay_95p_ms); + LOG(LS_INFO) << uma_prefix << ".InterframeDelay95PercentileInMs" + << uma_suffix << " " << *interframe_delay_95p_ms; + } + int width = stats.received_width.Avg(kMinRequiredSamples); if (width != -1) { RTC_HISTOGRAM_COUNTS_SPARSE_10000( @@ -643,6 +658,8 @@ void ReceiveStatisticsProxy::OnDecodedFrame(rtc::Optional qp, RTC_DCHECK_GE(interframe_delay_ms, 0); interframe_delay_max_moving_.Add(interframe_delay_ms, now); content_specific_stats->interframe_delay_counter.Add(interframe_delay_ms); + content_specific_stats->interframe_delay_percentiles.Add( + interframe_delay_ms); content_specific_stats->flow_duration_ms += interframe_delay_ms; } last_decoded_frame_time_ms_.emplace(now); @@ -788,6 +805,9 @@ void ReceiveStatisticsProxy::OnRttUpdate(int64_t avg_rtt_ms, avg_rtt_ms_ = avg_rtt_ms; } +ReceiveStatisticsProxy::ContentSpecificStats::ContentSpecificStats() + : interframe_delay_percentiles(kMaxCommonInterframeDelayMs) {} + void ReceiveStatisticsProxy::ContentSpecificStats::Add( const ContentSpecificStats& other) { e2e_delay_counter.Add(other.e2e_delay_counter); @@ -799,6 +819,71 @@ void ReceiveStatisticsProxy::ContentSpecificStats::Add( qp_counter.Add(other.qp_counter); frame_counts.key_frames += other.frame_counts.key_frames; frame_counts.delta_frames += other.frame_counts.delta_frames; + interframe_delay_percentiles.Add(other.interframe_delay_percentiles); } +ReceiveStatisticsProxy::HistogramPercentileCounter::HistogramPercentileCounter( + size_t long_tail_boundary) + : histogram_low_(long_tail_boundary), + long_tail_boundary_(long_tail_boundary), + total_elements_(0), + total_elements_low_(0) {} + +void ReceiveStatisticsProxy::HistogramPercentileCounter::Add( + const HistogramPercentileCounter& other) { + uint32_t value; + for (value = 0; value < other.long_tail_boundary_; ++value) { + Add(value, other.histogram_low_[value]); + } + for (auto it = other.histogram_high_.begin(); + it != other.histogram_high_.end(); ++it) { + Add(it->first, it->second); + } +} + +void ReceiveStatisticsProxy::HistogramPercentileCounter::Add(uint32_t value, + size_t count) { + if (value < long_tail_boundary_) { + histogram_low_[value] += count; + total_elements_low_ += count; + } else { + histogram_high_[value] += count; + } + total_elements_ += count; +} + +void ReceiveStatisticsProxy::HistogramPercentileCounter::Add(uint32_t value) { + Add(value, 1); +} + +rtc::Optional +ReceiveStatisticsProxy::HistogramPercentileCounter::GetPercentile( + float fraction) { + RTC_CHECK_LE(fraction, 1); + RTC_CHECK_GE(fraction, 0); + if (total_elements_ == 0) + return rtc::Optional(); + int elements_to_skip = + static_cast(std::ceil(total_elements_ * fraction)) - 1; + if (elements_to_skip < 0) + elements_to_skip = 0; + if (elements_to_skip >= static_cast(total_elements_)) + elements_to_skip = total_elements_ - 1; + if (elements_to_skip < static_cast(total_elements_low_)) { + for (uint32_t value = 0; value < long_tail_boundary_; ++value) { + elements_to_skip -= histogram_low_[value]; + if (elements_to_skip < 0) + return rtc::Optional(value); + } + } else { + elements_to_skip -= total_elements_low_; + for (auto it = histogram_high_.begin(); it != histogram_high_.end(); ++it) { + elements_to_skip -= it->second; + if (elements_to_skip < 0) + return rtc::Optional(it->first); + } + } + RTC_NOTREACHED(); + return rtc::Optional(); +} } // namespace webrtc diff --git a/video/receive_statistics_proxy.h b/video/receive_statistics_proxy.h index b98faf8eb1..9d4c231609 100644 --- a/video/receive_statistics_proxy.h +++ b/video/receive_statistics_proxy.h @@ -13,6 +13,7 @@ #include #include +#include #include "api/optional.h" #include "call/video_receive_stream.h" @@ -94,6 +95,25 @@ class ReceiveStatisticsProxy : public VCMReceiveStatisticsCallback, // Implements CallStatsObserver. void OnRttUpdate(int64_t avg_rtt_ms, int64_t max_rtt_ms) override; + class HistogramPercentileCounter { + public: + // Values below |long_tail_boundary| are stored in the array. + // Values above - in the map. + explicit HistogramPercentileCounter(size_t long_tail_boundary); + void Add(uint32_t value); + void Add(uint32_t value, size_t count); + void Add(const HistogramPercentileCounter& other); + // Argument should be from 0 to 1. + rtc::Optional GetPercentile(float fraction); + + private: + std::vector histogram_low_; + std::map histogram_high_; + const size_t long_tail_boundary_; + size_t total_elements_; + size_t total_elements_low_; + }; + private: struct SampleCounter { SampleCounter() : sum(0), num_samples(0) {} @@ -114,6 +134,8 @@ class ReceiveStatisticsProxy : public VCMReceiveStatisticsCallback, }; struct ContentSpecificStats { + ContentSpecificStats(); + void Add(const ContentSpecificStats& other); SampleCounter e2e_delay_counter; @@ -124,6 +146,7 @@ class ReceiveStatisticsProxy : public VCMReceiveStatisticsCallback, SampleCounter received_height; SampleCounter qp_counter; FrameCounts frame_counts; + HistogramPercentileCounter interframe_delay_percentiles; }; void UpdateHistograms() RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_); diff --git a/video/receive_statistics_proxy_unittest.cc b/video/receive_statistics_proxy_unittest.cc index 3903d7f6fe..df4305b812 100644 --- a/video/receive_statistics_proxy_unittest.cc +++ b/video/receive_statistics_proxy_unittest.cc @@ -12,6 +12,7 @@ #include #include +#include #include "api/video/i420_buffer.h" #include "api/video/video_frame.h" @@ -805,7 +806,7 @@ TEST_P(ReceiveStatisticsProxyTest, InterFrameDelaysAreReported) { statistics_proxy_->OnDecodedFrame(rtc::Optional(), content_type); fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); } - // One extra with with double the interval. + // One extra with double the interval. fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); statistics_proxy_->OnDecodedFrame(rtc::Optional(), content_type); @@ -829,6 +830,36 @@ TEST_P(ReceiveStatisticsProxyTest, InterFrameDelaysAreReported) { } } +TEST_P(ReceiveStatisticsProxyTest, InterFrameDelaysPercentilesAreReported) { + const VideoContentType content_type = GetParam(); + const int kInterFrameDelayMs = 33; + const int kLastFivePercentsSamples = kMinRequiredSamples * 5 / 100; + for (int i = 0; i <= kMinRequiredSamples - kLastFivePercentsSamples; ++i) { + fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); + statistics_proxy_->OnDecodedFrame(rtc::Optional(), content_type); + } + // Last 5% of intervals are double in size. + for (int i = 0; i < kLastFivePercentsSamples; ++i) { + fake_clock_.AdvanceTimeMilliseconds(2 * kInterFrameDelayMs); + statistics_proxy_->OnDecodedFrame(rtc::Optional(), content_type); + } + // Final sample is outlier and 10 times as big. + fake_clock_.AdvanceTimeMilliseconds(10 * kInterFrameDelayMs); + statistics_proxy_->OnDecodedFrame(rtc::Optional(), content_type); + + statistics_proxy_.reset(); + const int kExpectedInterFrame = kInterFrameDelayMs * 2; + if (videocontenttypehelpers::IsScreenshare(content_type)) { + EXPECT_EQ(kExpectedInterFrame, + metrics::MinSample( + "WebRTC.Video.Screenshare.InterframeDelay95PercentileInMs")); + } else { + EXPECT_EQ( + kExpectedInterFrame, + metrics::MinSample("WebRTC.Video.InterframeDelay95PercentileInMs")); + } +} + TEST_P(ReceiveStatisticsProxyTest, MaxInterFrameDelayOnlyWithValidAverage) { const VideoContentType content_type = GetParam(); const int kInterFrameDelayMs = 33; @@ -969,4 +1000,25 @@ TEST_P(ReceiveStatisticsProxyTest, StatsAreSlicedOnSimulcastAndExperiment) { } } +TEST_F(ReceiveStatisticsProxyTest, PercentileCounterReturnsCorrectPercentiles) { + ReceiveStatisticsProxy::HistogramPercentileCounter counter(10); + const std::vector kTestValues = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; + + EXPECT_FALSE(counter.GetPercentile(0.5f)); + // Pairs of {fraction, percentile value} computed by hand + // for |kTestValues|. + const std::vector> kTestPercentiles = { + {0.0f, 1}, {0.01f, 1}, {0.5f, 10}, {0.9f, 18}, + {0.95f, 19}, {0.99f, 20}, {1.0f, 20}}; + size_t i; + for (i = 0; i < kTestValues.size(); ++i) { + counter.Add(kTestValues[i]); + } + for (i = 0; i < kTestPercentiles.size(); ++i) { + EXPECT_EQ(kTestPercentiles[i].second, + counter.GetPercentile(kTestPercentiles[i].first).value_or(0)); + } +} + } // namespace webrtc