Chromium Code Reviews| Index: webrtc/modules/remote_bitrate_estimator/test/metric_recorder.cc |
| diff --git a/webrtc/modules/remote_bitrate_estimator/test/metric_recorder.cc b/webrtc/modules/remote_bitrate_estimator/test/metric_recorder.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..23853cc493e40d38599f5c774bc15008f8a472dd |
| --- /dev/null |
| +++ b/webrtc/modules/remote_bitrate_estimator/test/metric_recorder.cc |
| @@ -0,0 +1,365 @@ |
| +/* |
| + * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. |
| + * |
| + * Use of this source code is governed by a BSD-style license |
| + * that can be found in the LICENSE file in the root of the source |
| + * tree. An additional intellectual property rights grant can be found |
| + * in the file PATENTS. All contributing project authors may |
| + * be found in the AUTHORS file in the root of the source tree. |
| + */ |
| + |
| +#include "webrtc/modules/remote_bitrate_estimator/test/metric_recorder.h" |
| + |
| +#include <algorithm> |
| + |
| +namespace webrtc { |
| +namespace testing { |
| +namespace bwe { |
| + |
| +namespace { |
| + |
| +template <typename T> |
| +T Sum(const std::vector<T>& input) { |
| + T total = 0; |
| + for (auto it = input.begin(); it != input.end(); ++it) { |
| + total += *it; |
| + } |
| + return total; |
| +} |
| + |
| +template <typename T> |
| +double Average(const std::vector<T>& array, size_t size) { |
| + return static_cast<double>(Sum(array)) / size; |
| +} |
| + |
| +template <typename T> |
| +std::vector<T> Abs(const std::vector<T>& input) { |
| + std::vector<T> output; |
| + for (auto it = input.begin(); it != input.end(); ++it) { |
| + output.push_back(std::abs(*it)); |
| + } |
| + return output; |
| +} |
| + |
| +template <typename T> |
| +std::vector<double> Pow(const std::vector<T>& input, double p) { |
| + std::vector<double> output; |
| + for (auto it = input.begin(); it != input.end(); ++it) { |
| + output.push_back(pow(static_cast<double>(*it), p)); |
| + } |
| + return output; |
| +} |
| + |
| +template <typename T> |
| +double StandardDeviation(const std::vector<T>& array, size_t size) { |
| + double mean = Average(array, size); |
| + std::vector<double> square_values = Pow(array, 2.0); |
| + double var = Average(square_values, size) - mean * mean; |
| + return sqrt(var); |
| +} |
| + |
| +// Holder mean, Manhattan distance for p=1, EuclidianNorm/sqrt(n) for p=2. |
| +template <typename T> |
| +double NormLp(const std::vector<T>& array, size_t size, double p) { |
| + std::vector<T> abs_values = Abs(array); |
| + std::vector<double> pow_values = Pow(abs_values, p); |
| + return pow(Sum(pow_values) / size, 1.0 / p); |
| +} |
| + |
| +template <typename T> |
| +std::vector<T> PositiveFilter(const std::vector<T>& input) { |
| + std::vector<T> output(input); |
| + for (auto it = output.begin(); it != output.end(); ++it) { |
|
stefan-webrtc
2015/07/02 11:03:41
Can't you do this?
for (T val : output)
val = va
magalhaesc
2015/07/02 17:06:18
Done.
|
| + (*it) = (*it) > 0 ? (*it) : 0; |
| + } |
| + return output; |
| +} |
| + |
| +template <typename T> |
| +std::vector<T> NegativeFilter(const std::vector<T>& input) { |
| + std::vector<T> output(input); |
| + for (auto it = output.begin(); it != output.end(); ++it) { |
| + (*it) = (*it) < 0 ? -(*it) : 0; |
| + } |
| + return output; |
| +} |
| +} // namespace |
| + |
| +MetricRecorder::MetricRecorder(const std::string algorithm_name, |
| + int flow_id, |
| + PacketSender* packet_sender, |
| + ChokeFilter* choke_filter) |
| + : algorithm_name_(algorithm_name), |
| + flow_id_(flow_id), |
| + packet_sender_(packet_sender), |
| + choke_filter_(choke_filter), |
| + now_ms_(0), |
| + delays_ms_(), |
| + throughput_bytes_(), |
| + weighted_estimate_error_(), |
| + last_unweighted_estimate_error_(0), |
| + optimal_throughput_bits_(0), |
| + last_available_bitrate_per_flow_kbps_(0), |
| + start_computing_metrics_ms_(0), |
| + started_computing_metrics_(false) { |
| + std::fill(last_plot_ms_, last_plot_ms_ + kNumMetrics, 0); |
| + std::fill(plot_, plot_ + kNumMetrics, true); |
| + plot_[kObjective] = false; |
| + plot_[kAvailablePerFlow] = false; |
| +} |
| + |
| +void MetricRecorder::PlotLine(int windows_id, |
| + const std::string& prefix, |
| + int64_t x, |
| + int64_t y) { |
| + BWE_TEST_LOGGING_PLOT_WITH_NAME(windows_id, prefix, x, y, algorithm_name_); |
| +} |
| + |
| +void MetricRecorder::PlotThroughput(const std::string& prefix, |
| + int64_t time_ms, |
| + int64_t bitrate_kbps) { |
| + if (!plot_[kThroughput]) |
| + return; |
| + static const int kThroughputPlotIntervalMs = 100; |
| + if (time_ms - last_plot_ms_[kThroughput] > kThroughputPlotIntervalMs) { |
| + PlotLine(0, prefix, time_ms, bitrate_kbps); |
| + last_plot_ms_[kThroughput] = time_ms; |
| + } |
| +} |
| + |
| +void MetricRecorder::PlotDelay(const std::string& prefix, |
| + int64_t time_ms, |
| + int64_t delay_ms) { |
| + if (!plot_[kDelay]) |
| + return; |
| + static const int kDelayPlotIntervalMs = 100; |
| + if (time_ms - last_plot_ms_[kDelay] > kDelayPlotIntervalMs) { |
| + PlotLine(1, prefix, time_ms, delay_ms); |
| + last_plot_ms_[kDelay] = time_ms; |
| + } |
| +} |
| + |
| +void MetricRecorder::PlotLoss(const std::string& prefix, |
| + int64_t time_ms, |
| + double loss) { |
| + if (!plot_[kLoss]) |
| + return; |
| + static const int kPacketLossPlotIntervalMs = 500; |
| + if (time_ms - last_plot_ms_[kLoss] > kPacketLossPlotIntervalMs) { |
| + PlotLine(2, prefix, time_ms, loss); |
| + last_plot_ms_[kLoss] = time_ms; |
| + } |
| +} |
| + |
| +void MetricRecorder::PlotObjective(const std::string& prefix, int64_t time_ms) { |
| + if (!plot_[kObjective]) |
| + return; |
| + static const int kMetricPlotIntervalMs = 1000; |
| + if (time_ms - last_plot_ms_[kObjective] > kMetricPlotIntervalMs) { |
| + PlotLine(3, prefix, time_ms, ObjectiveFunction()); |
| + last_plot_ms_[kObjective] = time_ms; |
| + } |
| +} |
| + |
| +void MetricRecorder::PlotTotalAvailableCapacity(const std::string& prefix, |
| + int64_t time_ms) { |
| + if (!plot_[kTotalAvailable]) |
| + return; |
| + static const int kCapacityPlotIntervalMs = 1000; |
| + if (time_ms - last_plot_ms_[kTotalAvailable] > kCapacityPlotIntervalMs) { |
| + BWE_TEST_LOGGING_PLOT_WITH_NAME(0, prefix, time_ms, GetTotalAvailableKbps(), |
| + "Available"); |
| + last_plot_ms_[kTotalAvailable] = time_ms; |
| + } |
| +} |
| + |
| +void MetricRecorder::PlotAvailableCapacityPerFlow(const std::string& prefix, |
| + int64_t time_ms) { |
| + if (!plot_[kAvailablePerFlow]) |
| + return; |
| + static const int kCapacityPlotIntervalMs = 1000; |
| + if (time_ms - last_plot_ms_[kAvailablePerFlow] > kCapacityPlotIntervalMs) { |
| + BWE_TEST_LOGGING_PLOT_WITH_NAME( |
| + 0, prefix, time_ms, GetAvailablePerFlowKbps(), "Available_per_flow"); |
| + last_plot_ms_[kAvailablePerFlow] = time_ms; |
| + } |
| +} |
| + |
| +uint32_t MetricRecorder::GetTotalAvailableKbps() { |
| + return choke_filter_->TotalAvailableKbps(); |
| +} |
| + |
| +uint32_t MetricRecorder::GetAvailablePerFlowKbps() { |
| + return choke_filter_->AvailablePerFlowKbps(flow_id_); |
| +} |
| + |
| +uint32_t MetricRecorder::GetSendingEstimateKbps() { |
| + return packet_sender_->TargetBitrateKbps(); |
| +} |
| + |
| +void MetricRecorder::Update(int64_t time_ms) { |
| + now_ms_ = std::max(now_ms_, time_ms); |
| + last_available_bitrate_per_flow_kbps_ = GetAvailablePerFlowKbps(); |
| +} |
| + |
| +void MetricRecorder::PushDelayMs(int64_t delay_ms, int64_t arrival_time_ms) { |
| + if (ShouldRecord(arrival_time_ms)) { |
| + delays_ms_.push_back(delay_ms); |
| + } |
| +} |
| + |
| +void MetricRecorder::PushThroughputBytes(size_t payload_size, |
| + int64_t arrival_time_ms) { |
| + if (ShouldRecord(arrival_time_ms)) { |
| + throughput_bytes_.push_back(payload_size); |
| + |
| + int64_t current_available_per_flow_kbps = |
| + static_cast<int64_t>(GetAvailablePerFlowKbps()); |
| + |
| + int64_t current_bitrate_diff_kbps = |
| + static_cast<int64_t>(GetSendingEstimateKbps()) - |
| + current_available_per_flow_kbps; |
| + |
| + // now_ms_ was still not updated here. |
| + weighted_estimate_error_.push_back( |
| + ((current_bitrate_diff_kbps + last_unweighted_estimate_error_) * |
| + (arrival_time_ms - now_ms_)) / |
| + 2); |
| + |
| + optimal_throughput_bits_ += ((current_available_per_flow_kbps + |
| + last_available_bitrate_per_flow_kbps_) * |
| + (arrival_time_ms - now_ms_)) / |
| + 2; |
| + } |
| +} |
| + |
| +bool MetricRecorder::ShouldRecord(int64_t arrival_time_ms) { |
| + if (arrival_time_ms >= start_computing_metrics_ms_) { |
| + if (!started_computing_metrics_) { |
| + start_computing_metrics_ms_ = arrival_time_ms; |
| + now_ms_ = arrival_time_ms; |
| + started_computing_metrics_ = true; |
| + } |
| + return true; |
| + } else { |
| + return false; |
| + } |
| +} |
| + |
| +// The weighted_estimate_error_ was weighted based on time windows. |
| +// This function scales back the result before plotting. |
| +double MetricRecorder::Renormalize(double x) { |
| + size_t num_packets_received = delays_ms_.size(); |
| + return (x * num_packets_received) / now_ms_; |
| +} |
| + |
| +inline double U(int64_t x, double alpha) { |
| + if (alpha == 1.0) { |
| + return log(static_cast<double>(x)); |
| + } |
| + return pow(static_cast<double>(x), 1.0 - alpha) / (1.0 - alpha); |
| +} |
| + |
| +inline double U(size_t x, double alpha) { |
| + return U(static_cast<int64_t>(x), alpha); |
| +} |
| + |
| +// TODO(magalhaesc): Update ObjectiveFunction. |
| +double MetricRecorder::ObjectiveFunction() { |
| + const double kDelta = 0.15; // Delay penalty factor. |
| + const double kAlpha = 1.0; |
| + const double kBeta = 1.0; |
| + |
| + double throughput_metric = U(Sum(throughput_bytes_), kAlpha); |
| + double delay_penalty = kDelta * U(Sum(delays_ms_), kBeta); |
| + |
| + return throughput_metric - delay_penalty; |
| +} |
| + |
| +void MetricRecorder::PlotThroughputHistogram(const std::string& title, |
| + const std::string& bwe_name, |
| + int num_flows, |
| + int64_t extra_offset_ms, |
| + const std::string optimum_id) { |
| + size_t num_packets_received = delays_ms_.size(); |
| + |
| + int64_t duration_ms = now_ms_ - start_computing_metrics_ms_ - extra_offset_ms; |
| + |
| + double average_bitrate_kbps = |
| + static_cast<double>(8 * Sum(throughput_bytes_) / duration_ms); |
| + |
| + double optimal_bitrate_per_flow_kbps = |
| + static_cast<double>(optimal_throughput_bits_ / duration_ms); |
| + |
| + std::vector<int64_t> positive = PositiveFilter(weighted_estimate_error_); |
| + std::vector<int64_t> negative = NegativeFilter(weighted_estimate_error_); |
| + |
| + double p_error = Renormalize(NormLp(positive, num_packets_received, 1.0)); |
| + double n_error = Renormalize(NormLp(negative, num_packets_received, 1.0)); |
| + |
| + // Prevent the error to be too close to zero (plotting issue). |
| + double extra_error = average_bitrate_kbps / 500; |
| + |
| + std::string optimum_title = |
| + optimum_id.empty() ? "optimal_bitrate" : "optimal_bitrates#" + optimum_id; |
| + |
| + BWE_TEST_LOGGING_LABEL(4, title, "average_bitrate_(kbps)", num_flows); |
| + BWE_TEST_LOGGING_LIMITERRORBAR( |
| + 4, bwe_name, average_bitrate_kbps, |
| + average_bitrate_kbps - n_error - extra_error, |
| + average_bitrate_kbps + p_error + extra_error, "estimate_error", |
| + optimal_bitrate_per_flow_kbps, optimum_title, flow_id_); |
| + |
| + // Silencing unused variable compiling error. |
| + RTC_UNUSED(p_error); |
| + RTC_UNUSED(n_error); |
| + RTC_UNUSED(extra_error); |
| + RTC_UNUSED(optimal_bitrate_per_flow_kbps); |
| +} |
| + |
| +void MetricRecorder::PlotThroughputHistogram(const std::string& title, |
| + const std::string& bwe_name, |
| + int num_flows, |
| + int64_t extra_offset_ms) { |
| + PlotThroughputHistogram(title, bwe_name, num_flows, extra_offset_ms, ""); |
| +} |
| + |
| +void MetricRecorder::PlotDelayHistogram(const std::string& title, |
| + const std::string& bwe_name, |
| + int num_flows) { |
| + size_t num_packets_received = delays_ms_.size(); |
| + double average_delay_ms = Average(delays_ms_, num_packets_received); |
| + |
| + // Prevent the error to be too close to zero (plotting issue). |
| + double extra_error = average_delay_ms / 500; |
| + |
| + double tenth_sigma_ms = |
| + StandardDeviation(delays_ms_, num_packets_received) / 10.0 + extra_error; |
| + |
| + BWE_TEST_LOGGING_LABEL(5, title, "average_delay_(ms)", num_flows) |
| + BWE_TEST_LOGGING_ERRORBAR( |
| + 5, bwe_name, average_delay_ms, average_delay_ms - tenth_sigma_ms, |
| + average_delay_ms + tenth_sigma_ms, "sigma/10", flow_id_); |
| + |
| + // Silencing unused variable compiling error. |
| + RTC_UNUSED(tenth_sigma_ms); |
| +} |
| + |
| +void MetricRecorder::PlotLossHistogram(const std::string& title, |
| + const std::string& bwe_name, |
| + int num_flows, |
| + float global_loss_ratio) { |
| + BWE_TEST_LOGGING_LABEL(6, title, "packet_loss_ratio_(%)", num_flows) |
| + BWE_TEST_LOGGING_BAR(6, bwe_name, 100.0f * global_loss_ratio, flow_id_); |
| +} |
| + |
| +void MetricRecorder::PlotObjectiveHistogram(const std::string& title, |
| + const std::string& bwe_name, |
| + int num_flows) { |
| + BWE_TEST_LOGGING_LABEL(7, title, "objective_function", num_flows) |
| + BWE_TEST_LOGGING_BAR(7, bwe_name, ObjectiveFunction(), flow_id_); |
| +} |
| + |
| +} // namespace bwe |
| +} // namespace testing |
| +} // namespace webrtc |