// 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_OBSERVATION_BUFFER_H_ #define NET_NQE_OBSERVATION_BUFFER_H_ #include #include #include #include #include #include #include "base/macros.h" #include "base/time/default_tick_clock.h" #include "base/time/tick_clock.h" #include "base/time/time.h" #include "net/base/net_export.h" #include "net/nqe/network_quality_observation.h" #include "net/nqe/network_quality_observation_source.h" #include "net/nqe/weighted_observation.h" namespace net { namespace nqe { namespace internal { // Stores observations sorted by time. template class NET_EXPORT_PRIVATE ObservationBuffer { public: ObservationBuffer(double weight_multiplier_per_second, double weight_multiplier_per_dbm) : weight_multiplier_per_second_(weight_multiplier_per_second), weight_multiplier_per_dbm_(weight_multiplier_per_dbm), tick_clock_(new base::DefaultTickClock()) { static_assert(kMaximumObservationsBufferSize > 0U, "Minimum size of observation buffer must be > 0"); DCHECK_LE(0.0, weight_multiplier_per_second_); DCHECK_GE(1.0, weight_multiplier_per_second_); DCHECK_LE(0.0, weight_multiplier_per_dbm_); DCHECK_GE(1.0, weight_multiplier_per_dbm_); } ~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& observation) { DCHECK_LE(observations_.size(), static_cast(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(kMaximumObservationsBufferSize)); } // Returns the number of observations in this buffer. size_t Size() const { return static_cast(observations_.size()); } // Returns the capacity of this buffer. size_t Capacity() const { return static_cast(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 of all observations made on or after |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|. |current_signal_strength_dbm| is the current // signal strength in dBm. // |result| must not be null. // TODO(tbansal): Move out param |result| as the last param of the function. bool GetPercentile(base::TimeTicks begin_timestamp, int32_t current_signal_strength_dbm, ValueType* result, int percentile, const std::vector& disallowed_observation_sources) const { // Stores weighted observations in increasing order by value. std::vector> weighted_observations; // Total weight of all observations in |weighted_observations|. double total_weight = 0.0; ComputeWeightedObservations(begin_timestamp, current_signal_strength_dbm, &weighted_observations, &total_weight, disallowed_observation_sources); if (weighted_observations.empty()) return false; 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; } // Returns true iff the weighted average of the observations in this // buffer is available. Sets |result| to the computed weighted average value // of all observations made on or after |begin_timestamp|. If the value is // unavailable, false is returned and |result| is not modified. The unweighted // average value is unavailable if all the values in the observation buffer // are older than |begin_timestamp|. |current_signal_strength_dbm| is the // current signal strength in dBm. |result| must not be null. bool GetWeightedAverage(base::TimeTicks begin_timestamp, int32_t current_signal_strength_dbm, const std::vector& disallowed_observation_sources, ValueType* result) const { // Stores weighted observations in increasing order by value. std::vector> weighted_observations; // Total weight of all observations in |weighted_observations|. double total_weight = 0.0; ComputeWeightedObservations(begin_timestamp, current_signal_strength_dbm, &weighted_observations, &total_weight, disallowed_observation_sources); if (weighted_observations.empty()) return false; // Weighted average is the sum of observations times their respective // weights, divided by the sum of the weights of all observations. double total_weight_times_value = 0.0; for (const auto& weighted_observation : weighted_observations) { total_weight_times_value += (weighted_observation.weight * ConvertValueTypeToDouble(weighted_observation.value)); } ConvertDoubleToValueType(total_weight_times_value / total_weight, result); return true; } // Returns true iff the unweighted average of the observations in this buffer // is available. Sets |result| to the computed unweighted average value of // all observations made on or after |begin_timestamp|. If the value is // unavailable, false is returned and |result| is not modified. The weighted // average value is unavailable if all the values in the observation buffer // are older than |begin_timestamp|. |current_signal_strength_dbm| is the // current signal strength in dBm. |result| must not be null. bool GetUnweightedAverage(base::TimeTicks begin_timestamp, int32_t current_signal_strength_dbm, const std::vector& disallowed_observation_sources, ValueType* result) const { // Stores weighted observations in increasing order by value. std::vector> weighted_observations; // Total weight of all observations in |weighted_observations|. double total_weight = 0.0; ComputeWeightedObservations(begin_timestamp, current_signal_strength_dbm, &weighted_observations, &total_weight, disallowed_observation_sources); if (weighted_observations.empty()) return false; // The unweighted average is the sum of all observations divided by the // number of observations. double total_value = 0.0; for (const auto& weighted_observation : weighted_observations) total_value += ConvertValueTypeToDouble(weighted_observation.value); ConvertDoubleToValueType(total_value / weighted_observations.size(), result); return true; } void SetTickClockForTesting(std::unique_ptr tick_clock) { tick_clock_ = std::move(tick_clock); } private: // Maximum number of observations that can be held in the ObservationBuffer. static const size_t kMaximumObservationsBufferSize = 300; // Convert different ValueTypes to double to make it possible to perform // arithmetic operations on them. double ConvertValueTypeToDouble(base::TimeDelta input) const { return input.InMilliseconds(); } double ConvertValueTypeToDouble(int32_t input) const { return input; } // Convert double to different ValueTypes. void ConvertDoubleToValueType(double input, base::TimeDelta* output) const { *output = base::TimeDelta::FromMilliseconds(input); } void ConvertDoubleToValueType(double input, int32_t* output) const { *output = input; } // 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. |current_signal_strength_dbm| is the current signal strength // (in dBm) when the observation was taken, and is set to INT32_MIN if the // signal strength is currently unavailable. This method 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, int32_t current_signal_strength_dbm, std::vector>* weighted_observations, double* total_weight, const std::vector& disallowed_observation_sources) const { DCHECK_GE(Capacity(), Size()); weighted_observations->clear(); double total_weight_observations = 0.0; base::TimeTicks now = tick_clock_->NowTicks(); 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 time_weight = pow(weight_multiplier_per_second_, time_since_sample_taken.InSeconds()); double signal_strength_weight = 1.0; if (current_signal_strength_dbm != INT32_MIN && observation.signal_strength_dbm != INT32_MIN && current_signal_strength_dbm != INT32_MAX && observation.signal_strength_dbm != INT32_MAX) { int32_t signal_strength_weight_diff = std::abs( current_signal_strength_dbm - observation.signal_strength_dbm); signal_strength_weight = pow(weight_multiplier_per_dbm_, signal_strength_weight_diff); } double weight = time_weight * signal_strength_weight; weight = std::max(DBL_MIN, std::min(1.0, weight)); weighted_observations->push_back( WeightedObservation(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; DCHECK_LE(0.0, *total_weight); DCHECK(weighted_observations->empty() || 0.0 < *total_weight); // |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()); } // Holds observations sorted by time, with the oldest observation at the // front of the queue. std::deque> 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_; // The factor by which the weight of an observation reduces for every dbM // difference in the current signal strength, and the signal strength at // which the observation was taken. // For example, if the observation was taken at 90 dBm, and current signal // strength is 95 dBm, the weight of the observation would be: // |weight_multiplier_per_dbm_| ^ 5. const double weight_multiplier_per_dbm_; std::unique_ptr tick_clock_; DISALLOW_COPY_AND_ASSIGN(ObservationBuffer); }; } // namespace internal } // namespace nqe } // namespace net #endif // NET_NQE_OBSERVATION_BUFFER_H_