Calculate and report to UMA 95th percentile of Interframe Delay

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 <sprang@webrtc.org>
Commit-Queue: Ilya Nikolaevskiy <ilnik@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20184}
This commit is contained in:
Ilya Nikolaevskiy 2017-10-06 12:29:47 +02:00 committed by Commit Bot
parent b6ca72154f
commit daa4f7abf5
3 changed files with 161 additions and 1 deletions

View File

@ -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<uint32_t> 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<uint8_t> 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<uint32_t>
ReceiveStatisticsProxy::HistogramPercentileCounter::GetPercentile(
float fraction) {
RTC_CHECK_LE(fraction, 1);
RTC_CHECK_GE(fraction, 0);
if (total_elements_ == 0)
return rtc::Optional<uint32_t>();
int elements_to_skip =
static_cast<int>(std::ceil(total_elements_ * fraction)) - 1;
if (elements_to_skip < 0)
elements_to_skip = 0;
if (elements_to_skip >= static_cast<int>(total_elements_))
elements_to_skip = total_elements_ - 1;
if (elements_to_skip < static_cast<int>(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<uint32_t>(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<uint32_t>(it->first);
}
}
RTC_NOTREACHED();
return rtc::Optional<uint32_t>();
}
} // namespace webrtc

View File

@ -13,6 +13,7 @@
#include <map>
#include <string>
#include <vector>
#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<uint32_t> GetPercentile(float fraction);
private:
std::vector<size_t> histogram_low_;
std::map<uint32_t, size_t> 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_);

View File

@ -12,6 +12,7 @@
#include <limits>
#include <memory>
#include <utility>
#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<uint8_t>(), 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<uint8_t>(), 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<uint8_t>(), 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<uint8_t>(), content_type);
}
// Final sample is outlier and 10 times as big.
fake_clock_.AdvanceTimeMilliseconds(10 * kInterFrameDelayMs);
statistics_proxy_->OnDecodedFrame(rtc::Optional<uint8_t>(), 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<int> 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<std::pair<float, uint32_t>> 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