1*6777b538SAndroid Build Coastguard Worker // Copyright 2017 The Chromium Authors
2*6777b538SAndroid Build Coastguard Worker // Use of this source code is governed by a BSD-style license that can be
3*6777b538SAndroid Build Coastguard Worker // found in the LICENSE file.
4*6777b538SAndroid Build Coastguard Worker
5*6777b538SAndroid Build Coastguard Worker #include "net/nqe/observation_buffer.h"
6*6777b538SAndroid Build Coastguard Worker
7*6777b538SAndroid Build Coastguard Worker #include <float.h>
8*6777b538SAndroid Build Coastguard Worker
9*6777b538SAndroid Build Coastguard Worker #include <algorithm>
10*6777b538SAndroid Build Coastguard Worker #include <utility>
11*6777b538SAndroid Build Coastguard Worker
12*6777b538SAndroid Build Coastguard Worker #include "base/time/default_tick_clock.h"
13*6777b538SAndroid Build Coastguard Worker #include "base/time/time.h"
14*6777b538SAndroid Build Coastguard Worker #include "net/nqe/network_quality_estimator_params.h"
15*6777b538SAndroid Build Coastguard Worker #include "net/nqe/weighted_observation.h"
16*6777b538SAndroid Build Coastguard Worker
17*6777b538SAndroid Build Coastguard Worker namespace net::nqe::internal {
18*6777b538SAndroid Build Coastguard Worker
ObservationBuffer(const NetworkQualityEstimatorParams * params,const base::TickClock * tick_clock,double weight_multiplier_per_second,double weight_multiplier_per_signal_level)19*6777b538SAndroid Build Coastguard Worker ObservationBuffer::ObservationBuffer(
20*6777b538SAndroid Build Coastguard Worker const NetworkQualityEstimatorParams* params,
21*6777b538SAndroid Build Coastguard Worker const base::TickClock* tick_clock,
22*6777b538SAndroid Build Coastguard Worker double weight_multiplier_per_second,
23*6777b538SAndroid Build Coastguard Worker double weight_multiplier_per_signal_level)
24*6777b538SAndroid Build Coastguard Worker : params_(params),
25*6777b538SAndroid Build Coastguard Worker weight_multiplier_per_second_(weight_multiplier_per_second),
26*6777b538SAndroid Build Coastguard Worker weight_multiplier_per_signal_level_(weight_multiplier_per_signal_level),
27*6777b538SAndroid Build Coastguard Worker tick_clock_(tick_clock) {
28*6777b538SAndroid Build Coastguard Worker DCHECK_LT(0u, params_->observation_buffer_size());
29*6777b538SAndroid Build Coastguard Worker DCHECK_LE(0.0, weight_multiplier_per_second_);
30*6777b538SAndroid Build Coastguard Worker DCHECK_GE(1.0, weight_multiplier_per_second_);
31*6777b538SAndroid Build Coastguard Worker DCHECK_LE(0.0, weight_multiplier_per_signal_level_);
32*6777b538SAndroid Build Coastguard Worker DCHECK_GE(1.0, weight_multiplier_per_signal_level_);
33*6777b538SAndroid Build Coastguard Worker DCHECK(params_);
34*6777b538SAndroid Build Coastguard Worker DCHECK(tick_clock_);
35*6777b538SAndroid Build Coastguard Worker }
36*6777b538SAndroid Build Coastguard Worker
ObservationBuffer(const ObservationBuffer & other)37*6777b538SAndroid Build Coastguard Worker ObservationBuffer::ObservationBuffer(const ObservationBuffer& other)
38*6777b538SAndroid Build Coastguard Worker : params_(other.params_),
39*6777b538SAndroid Build Coastguard Worker weight_multiplier_per_second_(other.weight_multiplier_per_second_),
40*6777b538SAndroid Build Coastguard Worker weight_multiplier_per_signal_level_(
41*6777b538SAndroid Build Coastguard Worker other.weight_multiplier_per_signal_level_),
42*6777b538SAndroid Build Coastguard Worker tick_clock_(other.tick_clock_) {
43*6777b538SAndroid Build Coastguard Worker DCHECK(other.observations_.empty());
44*6777b538SAndroid Build Coastguard Worker }
45*6777b538SAndroid Build Coastguard Worker
46*6777b538SAndroid Build Coastguard Worker ObservationBuffer::~ObservationBuffer() = default;
47*6777b538SAndroid Build Coastguard Worker
AddObservation(const Observation & observation)48*6777b538SAndroid Build Coastguard Worker void ObservationBuffer::AddObservation(const Observation& observation) {
49*6777b538SAndroid Build Coastguard Worker DCHECK_LE(observations_.size(), params_->observation_buffer_size());
50*6777b538SAndroid Build Coastguard Worker
51*6777b538SAndroid Build Coastguard Worker // Observations must be in the non-decreasing order of the timestamps.
52*6777b538SAndroid Build Coastguard Worker DCHECK(observations_.empty() ||
53*6777b538SAndroid Build Coastguard Worker observation.timestamp() >= observations_.back().timestamp());
54*6777b538SAndroid Build Coastguard Worker
55*6777b538SAndroid Build Coastguard Worker DCHECK(observation.signal_strength() == INT32_MIN ||
56*6777b538SAndroid Build Coastguard Worker (observation.signal_strength() >= 0 &&
57*6777b538SAndroid Build Coastguard Worker observation.signal_strength() <= 4));
58*6777b538SAndroid Build Coastguard Worker
59*6777b538SAndroid Build Coastguard Worker // Evict the oldest element if the buffer is already full.
60*6777b538SAndroid Build Coastguard Worker if (observations_.size() == params_->observation_buffer_size())
61*6777b538SAndroid Build Coastguard Worker observations_.pop_front();
62*6777b538SAndroid Build Coastguard Worker
63*6777b538SAndroid Build Coastguard Worker observations_.push_back(observation);
64*6777b538SAndroid Build Coastguard Worker DCHECK_LE(observations_.size(), params_->observation_buffer_size());
65*6777b538SAndroid Build Coastguard Worker }
66*6777b538SAndroid Build Coastguard Worker
GetPercentile(base::TimeTicks begin_timestamp,int32_t current_signal_strength,int percentile,size_t * observations_count) const67*6777b538SAndroid Build Coastguard Worker std::optional<int32_t> ObservationBuffer::GetPercentile(
68*6777b538SAndroid Build Coastguard Worker base::TimeTicks begin_timestamp,
69*6777b538SAndroid Build Coastguard Worker int32_t current_signal_strength,
70*6777b538SAndroid Build Coastguard Worker int percentile,
71*6777b538SAndroid Build Coastguard Worker size_t* observations_count) const {
72*6777b538SAndroid Build Coastguard Worker DCHECK(current_signal_strength == INT32_MIN ||
73*6777b538SAndroid Build Coastguard Worker (current_signal_strength >= 0 && current_signal_strength <= 4));
74*6777b538SAndroid Build Coastguard Worker
75*6777b538SAndroid Build Coastguard Worker // Stores weighted observations in increasing order by value.
76*6777b538SAndroid Build Coastguard Worker std::vector<WeightedObservation> weighted_observations;
77*6777b538SAndroid Build Coastguard Worker
78*6777b538SAndroid Build Coastguard Worker // Total weight of all observations in |weighted_observations|.
79*6777b538SAndroid Build Coastguard Worker double total_weight = 0.0;
80*6777b538SAndroid Build Coastguard Worker
81*6777b538SAndroid Build Coastguard Worker ComputeWeightedObservations(begin_timestamp, current_signal_strength,
82*6777b538SAndroid Build Coastguard Worker &weighted_observations, &total_weight);
83*6777b538SAndroid Build Coastguard Worker
84*6777b538SAndroid Build Coastguard Worker if (observations_count) {
85*6777b538SAndroid Build Coastguard Worker // |observations_count| may be null.
86*6777b538SAndroid Build Coastguard Worker *observations_count = weighted_observations.size();
87*6777b538SAndroid Build Coastguard Worker }
88*6777b538SAndroid Build Coastguard Worker
89*6777b538SAndroid Build Coastguard Worker if (weighted_observations.empty())
90*6777b538SAndroid Build Coastguard Worker return std::nullopt;
91*6777b538SAndroid Build Coastguard Worker
92*6777b538SAndroid Build Coastguard Worker double desired_weight = percentile / 100.0 * total_weight;
93*6777b538SAndroid Build Coastguard Worker
94*6777b538SAndroid Build Coastguard Worker double cumulative_weight_seen_so_far = 0.0;
95*6777b538SAndroid Build Coastguard Worker for (const auto& weighted_observation : weighted_observations) {
96*6777b538SAndroid Build Coastguard Worker cumulative_weight_seen_so_far += weighted_observation.weight;
97*6777b538SAndroid Build Coastguard Worker if (cumulative_weight_seen_so_far >= desired_weight)
98*6777b538SAndroid Build Coastguard Worker return weighted_observation.value;
99*6777b538SAndroid Build Coastguard Worker }
100*6777b538SAndroid Build Coastguard Worker
101*6777b538SAndroid Build Coastguard Worker // Computation may reach here due to floating point errors. This may happen
102*6777b538SAndroid Build Coastguard Worker // if |percentile| was 100 (or close to 100), and |desired_weight| was
103*6777b538SAndroid Build Coastguard Worker // slightly larger than |total_weight| (due to floating point errors).
104*6777b538SAndroid Build Coastguard Worker // In this case, we return the highest |value| among all observations.
105*6777b538SAndroid Build Coastguard Worker // This is same as value of the last observation in the sorted vector.
106*6777b538SAndroid Build Coastguard Worker return weighted_observations.at(weighted_observations.size() - 1).value;
107*6777b538SAndroid Build Coastguard Worker }
108*6777b538SAndroid Build Coastguard Worker
RemoveObservationsWithSource(bool deleted_observation_sources[NETWORK_QUALITY_OBSERVATION_SOURCE_MAX])109*6777b538SAndroid Build Coastguard Worker void ObservationBuffer::RemoveObservationsWithSource(
110*6777b538SAndroid Build Coastguard Worker bool deleted_observation_sources[NETWORK_QUALITY_OBSERVATION_SOURCE_MAX]) {
111*6777b538SAndroid Build Coastguard Worker base::EraseIf(observations_,
112*6777b538SAndroid Build Coastguard Worker [deleted_observation_sources](const Observation& observation) {
113*6777b538SAndroid Build Coastguard Worker return deleted_observation_sources[static_cast<size_t>(
114*6777b538SAndroid Build Coastguard Worker observation.source())];
115*6777b538SAndroid Build Coastguard Worker });
116*6777b538SAndroid Build Coastguard Worker }
117*6777b538SAndroid Build Coastguard Worker
ComputeWeightedObservations(const base::TimeTicks & begin_timestamp,int32_t current_signal_strength,std::vector<WeightedObservation> * weighted_observations,double * total_weight) const118*6777b538SAndroid Build Coastguard Worker void ObservationBuffer::ComputeWeightedObservations(
119*6777b538SAndroid Build Coastguard Worker const base::TimeTicks& begin_timestamp,
120*6777b538SAndroid Build Coastguard Worker int32_t current_signal_strength,
121*6777b538SAndroid Build Coastguard Worker std::vector<WeightedObservation>* weighted_observations,
122*6777b538SAndroid Build Coastguard Worker double* total_weight) const {
123*6777b538SAndroid Build Coastguard Worker DCHECK_GE(Capacity(), Size());
124*6777b538SAndroid Build Coastguard Worker
125*6777b538SAndroid Build Coastguard Worker weighted_observations->clear();
126*6777b538SAndroid Build Coastguard Worker double total_weight_observations = 0.0;
127*6777b538SAndroid Build Coastguard Worker base::TimeTicks now = tick_clock_->NowTicks();
128*6777b538SAndroid Build Coastguard Worker
129*6777b538SAndroid Build Coastguard Worker for (const auto& observation : observations_) {
130*6777b538SAndroid Build Coastguard Worker if (observation.timestamp() < begin_timestamp)
131*6777b538SAndroid Build Coastguard Worker continue;
132*6777b538SAndroid Build Coastguard Worker
133*6777b538SAndroid Build Coastguard Worker base::TimeDelta time_since_sample_taken = now - observation.timestamp();
134*6777b538SAndroid Build Coastguard Worker double time_weight =
135*6777b538SAndroid Build Coastguard Worker pow(weight_multiplier_per_second_, time_since_sample_taken.InSeconds());
136*6777b538SAndroid Build Coastguard Worker
137*6777b538SAndroid Build Coastguard Worker double signal_strength_weight = 1.0;
138*6777b538SAndroid Build Coastguard Worker if (current_signal_strength >= 0 && observation.signal_strength() >= 0) {
139*6777b538SAndroid Build Coastguard Worker int32_t signal_strength_weight_diff =
140*6777b538SAndroid Build Coastguard Worker std::abs(current_signal_strength - observation.signal_strength());
141*6777b538SAndroid Build Coastguard Worker signal_strength_weight =
142*6777b538SAndroid Build Coastguard Worker pow(weight_multiplier_per_signal_level_, signal_strength_weight_diff);
143*6777b538SAndroid Build Coastguard Worker }
144*6777b538SAndroid Build Coastguard Worker
145*6777b538SAndroid Build Coastguard Worker double weight = time_weight * signal_strength_weight;
146*6777b538SAndroid Build Coastguard Worker weight = std::clamp(weight, DBL_MIN, 1.0);
147*6777b538SAndroid Build Coastguard Worker
148*6777b538SAndroid Build Coastguard Worker weighted_observations->push_back(
149*6777b538SAndroid Build Coastguard Worker WeightedObservation(observation.value(), weight));
150*6777b538SAndroid Build Coastguard Worker total_weight_observations += weight;
151*6777b538SAndroid Build Coastguard Worker }
152*6777b538SAndroid Build Coastguard Worker
153*6777b538SAndroid Build Coastguard Worker // Sort the samples by value in ascending order.
154*6777b538SAndroid Build Coastguard Worker std::sort(weighted_observations->begin(), weighted_observations->end());
155*6777b538SAndroid Build Coastguard Worker *total_weight = total_weight_observations;
156*6777b538SAndroid Build Coastguard Worker
157*6777b538SAndroid Build Coastguard Worker DCHECK_LE(0.0, *total_weight);
158*6777b538SAndroid Build Coastguard Worker DCHECK(weighted_observations->empty() || 0.0 < *total_weight);
159*6777b538SAndroid Build Coastguard Worker
160*6777b538SAndroid Build Coastguard Worker // |weighted_observations| may have a smaller size than |observations_|
161*6777b538SAndroid Build Coastguard Worker // since the former contains only the observations later than
162*6777b538SAndroid Build Coastguard Worker // |begin_timestamp|.
163*6777b538SAndroid Build Coastguard Worker DCHECK_GE(observations_.size(), weighted_observations->size());
164*6777b538SAndroid Build Coastguard Worker }
165*6777b538SAndroid Build Coastguard Worker
Capacity() const166*6777b538SAndroid Build Coastguard Worker size_t ObservationBuffer::Capacity() const {
167*6777b538SAndroid Build Coastguard Worker return params_->observation_buffer_size();
168*6777b538SAndroid Build Coastguard Worker }
169*6777b538SAndroid Build Coastguard Worker
170*6777b538SAndroid Build Coastguard Worker } // namespace net::nqe::internal
171