Chromium Code Reviews| Index: net/nqe/network_quality_observation.h |
| diff --git a/net/nqe/network_quality_observation.h b/net/nqe/network_quality_observation.h |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..a7eefcb5e54fdc56ab59ef2665f923c208b171ba |
| --- /dev/null |
| +++ b/net/nqe/network_quality_observation.h |
| @@ -0,0 +1,235 @@ |
| +// Copyright 2016 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#ifndef NET_NQE_NETWORK_QUALITY_OBSERVATION_H_ |
| +#define NET_NQE_NETWORK_QUALITY_OBSERVATION_H_ |
| + |
| +#include <float.h> |
| + |
| +#include <algorithm> |
| +#include <deque> |
| +#include <vector> |
| + |
| +#include "base/gtest_prod_util.h" |
| +#include "base/macros.h" |
| +#include "base/time/time.h" |
| +#include "net/base/net_export.h" |
| +#include "net/nqe/network_quality_observation_source.h" |
| + |
| +namespace net { |
| + |
| +namespace nqe { |
| + |
| +namespace internal { |
| + |
| +// Records observations of network quality metrics (such as round trip time |
| +// or throughput), along with the time the observation was made. Observations |
| +// can be made at several places in the network stack, thus the observation |
| +// source is provided as well. ValueType must be numerical so that statistics |
| +// such as median, average can be computed. |
| +template <typename ValueType> |
| +struct NET_EXPORT_PRIVATE Observation { |
| + Observation(const ValueType& value, |
| + base::TimeTicks timestamp, |
| + NetworkQualityObservationSource source) |
| + : value(value), timestamp(timestamp), source(source) { |
| + DCHECK(!timestamp.is_null()); |
| + } |
| + ~Observation() {} |
| + |
| + // Value of the observation. |
| + const ValueType value; |
| + |
| + // Time when the observation was taken. |
| + const base::TimeTicks timestamp; |
| + |
| + // The source of the observation. |
| + const NetworkQualityObservationSource source; |
| +}; |
| + |
| +// Holds an observation and its weight. |
| +template <typename ValueType> |
| +struct NET_EXPORT_PRIVATE WeightedObservation { |
| + WeightedObservation(ValueType value, double weight) |
| + : value(value), weight(weight) {} |
| + WeightedObservation(const WeightedObservation& other) |
| + : WeightedObservation(other.value, other.weight) {} |
| + |
| + WeightedObservation& operator=(const WeightedObservation& other) { |
| + value = other.value; |
| + weight = other.weight; |
| + return *this; |
| + } |
| + |
| + // Required for sorting the samples in the ascending order of values. |
| + bool operator<(const WeightedObservation& other) const { |
| + return (value < other.value); |
| + } |
| + |
| + // Value of the sample. |
| + ValueType value; |
| + |
| + // Weight of the sample. This is computed based on how much time has passed |
| + // since the sample was taken. |
| + double weight; |
| +}; |
| + |
| +// Stores observations sorted by time. |
| +template <typename ValueType> |
| +class NET_EXPORT ObservationBuffer { |
|
bengr
2016/05/03 21:59:45
Why not move each of these structs/class to a sepa
tbansal1
2016/05/04 22:07:24
Done.
|
| + public: |
| + explicit ObservationBuffer(double weight_multiplier_per_second) |
| + : weight_multiplier_per_second_(weight_multiplier_per_second) { |
| + static_assert(kMaximumObservationsBufferSize > 0U, |
| + "Minimum size of observation buffer must be > 0"); |
| + DCHECK_GE(weight_multiplier_per_second_, 0.0); |
| + DCHECK_LE(weight_multiplier_per_second_, 1.0); |
| + } |
| + |
| + ~ObservationBuffer() {} |
| + |
| + // Adds |observation| to the buffer. The oldest observation in the buffer |
| + // will be evicted to make room if the buffer is already full. |
| + void AddObservation(const Observation<ValueType>& observation) { |
| + DCHECK_LE(observations_.size(), |
| + static_cast<size_t>(kMaximumObservationsBufferSize)); |
| + // Evict the oldest element if the buffer is already full. |
| + if (observations_.size() == kMaximumObservationsBufferSize) |
| + observations_.pop_front(); |
| + |
| + observations_.push_back(observation); |
| + DCHECK_LE(observations_.size(), |
| + static_cast<size_t>(kMaximumObservationsBufferSize)); |
| + } |
| + |
| + // Returns the number of observations in this buffer. |
| + size_t Size() const { return observations_.size(); } |
| + |
| + // Returns the capacity of this buffer. |
| + size_t Capacity() const { return kMaximumObservationsBufferSize; } |
| + |
| + // Clears the observations stored in this buffer. |
| + void Clear() { observations_.clear(); } |
| + |
| + // Returns true iff the |percentile| value of the observations in this |
| + // buffer is available. Sets |result| to the computed |percentile| |
| + // value among all observations since |begin_timestamp|. If the value is |
| + // unavailable, false is returned and |result| is not modified. Percentile |
| + // value is unavailable if all the values in observation buffer are older |
| + // than |begin_timestamp|. |
| + // |result| must not be null. |
| + bool GetPercentile(const base::TimeTicks& begin_timestamp, |
| + ValueType* result, |
| + int percentile, |
| + const std::vector<NetworkQualityObservationSource>& |
| + disallowed_observation_sources) const { |
| + DCHECK(result); |
| + DCHECK_GE(static_cast<size_t>(kMaximumObservationsBufferSize), Size()); |
| + // Stores WeightedObservation in increasing order of value. |
| + std::vector<WeightedObservation<ValueType>> weighted_observations; |
| + |
| + // Total weight of all observations in |weighted_observations|. |
| + double total_weight = 0.0; |
| + |
| + ComputeWeightedObservations(begin_timestamp, weighted_observations, |
| + &total_weight, disallowed_observation_sources); |
| + if (weighted_observations.empty()) |
| + return false; |
| + |
| + DCHECK(!weighted_observations.empty()); |
| + DCHECK_GT(total_weight, 0.0); |
| + |
| + // |weighted_observations| may have a smaller size than observations_ since |
| + // the former contains only the observations later than begin_timestamp. |
| + DCHECK_GE(observations_.size(), weighted_observations.size()); |
| + |
| + double desired_weight = percentile / 100.0 * total_weight; |
| + |
| + double cumulative_weight_seen_so_far = 0.0; |
| + for (const auto& weighted_observation : weighted_observations) { |
| + cumulative_weight_seen_so_far += weighted_observation.weight; |
| + |
| + if (cumulative_weight_seen_so_far >= desired_weight) { |
| + *result = weighted_observation.value; |
| + return true; |
| + } |
| + } |
| + |
| + // Computation may reach here due to floating point errors. This may happen |
| + // if |percentile| was 100 (or close to 100), and |desired_weight| was |
| + // slightly larger than |total_weight| (due to floating point errors). |
| + // In this case, we return the highest |value| among all observations. |
| + // This is same as value of the last observation in the sorted vector. |
| + *result = weighted_observations.at(weighted_observations.size() - 1).value; |
| + return true; |
| + } |
| + |
| + private: |
| + // Maximum number of observations that can be held in the ObservationBuffer. |
| + static const size_t kMaximumObservationsBufferSize = 300; |
| + |
| + // Computes the weighted observations and stores them in |
| + // |weighted_observations| sorted by ascending |WeightedObservation.value|. |
| + // Only the observations with timestamp later than |begin_timestamp| are |
| + // considered. Also, sets |total_weight| to the total weight of all |
| + // observations. Should be called only when there is at least one |
| + // observation in the buffer. |
| + void ComputeWeightedObservations( |
| + const base::TimeTicks& begin_timestamp, |
| + std::vector<WeightedObservation<ValueType>>& weighted_observations, |
| + double* total_weight, |
| + const std::vector<NetworkQualityObservationSource>& |
| + disallowed_observation_sources) const { |
| + DCHECK_GE(static_cast<size_t>(kMaximumObservationsBufferSize), Size()); |
| + |
| + weighted_observations.clear(); |
| + double total_weight_observations = 0.0; |
| + base::TimeTicks now = base::TimeTicks::Now(); |
| + |
| + for (const auto& observation : observations_) { |
| + if (observation.timestamp < begin_timestamp) |
| + continue; |
| + bool disallowed = false; |
| + for (const auto& disallowed_source : disallowed_observation_sources) { |
| + if (disallowed_source == observation.source) |
| + disallowed = true; |
| + } |
| + if (disallowed) |
| + continue; |
| + base::TimeDelta time_since_sample_taken = now - observation.timestamp; |
| + double weight = pow(weight_multiplier_per_second_, |
| + time_since_sample_taken.InSeconds()); |
| + weight = std::max(DBL_MIN, std::min(1.0, weight)); |
| + |
| + weighted_observations.push_back( |
| + WeightedObservation<ValueType>(observation.value, weight)); |
| + total_weight_observations += weight; |
| + } |
| + |
| + // Sort the samples by value in ascending order. |
| + std::sort(weighted_observations.begin(), weighted_observations.end()); |
| + *total_weight = total_weight_observations; |
| + } |
| + |
| + // Holds observations sorted by time, with the oldest observation at the |
| + // front of the queue. |
| + std::deque<Observation<ValueType>> observations_; |
| + |
| + // The factor by which the weight of an observation reduces every second. |
| + // For example, if an observation is 6 seconds old, its weight would be: |
| + // weight_multiplier_per_second_ ^ 6 |
| + // Calculated from |kHalfLifeSeconds| by solving the following equation: |
| + // weight_multiplier_per_second_ ^ kHalfLifeSeconds = 0.5 |
| + const double weight_multiplier_per_second_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(ObservationBuffer); |
| +}; |
| + |
| +} // namespace internal |
| + |
| +} // namespace nqe |
| + |
| +} // namespace net |
| + |
| +#endif // NET_NQE_NETWORK_QUALITY_OBSERVATION_H_ |