// Copyright 2015 The Chromium Authors // 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_ESTIMATOR_H_ #define NET_NQE_NETWORK_QUALITY_ESTIMATOR_H_ #include #include #include #include #include "base/gtest_prod_util.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h" #include "base/sequence_checker.h" #include "base/time/time.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "net/base/net_export.h" #include "net/base/network_change_notifier.h" #include "net/log/net_log_with_source.h" #include "net/nqe/cached_network_quality.h" #include "net/nqe/effective_connection_type.h" #include "net/nqe/effective_connection_type_observer.h" #include "net/nqe/event_creator.h" #include "net/nqe/network_id.h" #include "net/nqe/network_quality.h" #include "net/nqe/network_quality_estimator_params.h" #include "net/nqe/network_quality_observation.h" #include "net/nqe/network_quality_observation_source.h" #include "net/nqe/network_quality_store.h" #include "net/nqe/observation_buffer.h" #include "net/nqe/peer_to_peer_connections_count_observer.h" #include "net/nqe/rtt_throughput_estimates_observer.h" #include "net/nqe/socket_watcher_factory.h" #include "third_party/abseil-cpp/absl/types/optional.h" namespace base { class TickClock; } // namespace base namespace net { class NetLog; namespace nqe::internal { class ThroughputAnalyzer; } // namespace nqe::internal class URLRequest; // NetworkQualityEstimator provides network quality estimates (quality of the // full paths to all origins that have been connected to). // The estimates are based on the observed organic traffic. // A NetworkQualityEstimator instance is attached to URLRequestContexts and // observes the traffic of URLRequests spawned from the URLRequestContexts. // A single instance of NQE can be attached to multiple URLRequestContexts, // thereby increasing the single NQE instance's accuracy by providing more // observed traffic characteristics. class NET_EXPORT_PRIVATE NetworkQualityEstimator : public NetworkChangeNotifier::ConnectionTypeObserver { public: // Observes measurements of round trip time. class NET_EXPORT_PRIVATE RTTObserver { public: RTTObserver(const RTTObserver&) = delete; RTTObserver& operator=(const RTTObserver&) = delete; // Will be called when a new RTT observation is available. The round trip // time is specified in milliseconds. The time when the observation was // taken and the source of the observation are provided. virtual void OnRTTObservation(int32_t rtt_ms, const base::TimeTicks& timestamp, NetworkQualityObservationSource source) = 0; protected: RTTObserver() = default; virtual ~RTTObserver() = default; }; // Observes measurements of throughput. class NET_EXPORT_PRIVATE ThroughputObserver { public: ThroughputObserver(const ThroughputObserver&) = delete; ThroughputObserver& operator=(const ThroughputObserver&) = delete; // Will be called when a new throughput observation is available. // Throughput is specified in kilobits per second. virtual void OnThroughputObservation( int32_t throughput_kbps, const base::TimeTicks& timestamp, NetworkQualityObservationSource source) = 0; protected: ThroughputObserver() = default; virtual ~ThroughputObserver() = default; }; // Creates a new NetworkQualityEstimator. // |params| contains the // configuration parameters relevant to network quality estimator. The caller // must guarantee that |net_log| outlives |this|. NetworkQualityEstimator( std::unique_ptr params, NetLog* net_log); NetworkQualityEstimator(const NetworkQualityEstimator&) = delete; NetworkQualityEstimator& operator=(const NetworkQualityEstimator&) = delete; ~NetworkQualityEstimator() override; // Returns the current effective connection type. The effective connection // type is computed by the network quality estimator at regular intervals and // at certain events (e.g., connection change). Virtualized for testing. virtual EffectiveConnectionType GetEffectiveConnectionType() const; // Adds |observer| to a list of effective connection type observers. // The observer must register and unregister itself on the same thread. // |observer| would be notified on the thread on which it registered. // |observer| would be notified of the current effective connection // type in the next message pump. void AddEffectiveConnectionTypeObserver( EffectiveConnectionTypeObserver* observer); // Removes |observer| from a list of effective connection type observers. void RemoveEffectiveConnectionTypeObserver( EffectiveConnectionTypeObserver* observer); // Adds/Removes |observer| from the list of peer to peer connections count // observers. The observer must register and unregister itself on the same // thread. |observer| would be notified on the thread on which it registered. // |observer| would be notified of the current count of peer to peer // connections in the next message pump. void AddPeerToPeerConnectionsCountObserver( PeerToPeerConnectionsCountObserver* observer); void RemovePeerToPeerConnectionsCountObserver( PeerToPeerConnectionsCountObserver* observer); // Returns the current HTTP RTT estimate. If the estimate is unavailable, // the returned optional value is null. The RTT at the HTTP layer measures the // time from when the request was sent (this happens after the connection is // established) to the time when the response headers were received. // Virtualized for testing. virtual absl::optional GetHttpRTT() const; // Returns the current transport RTT estimate. If the estimate is // unavailable, the returned optional value is null. The RTT at the transport // layer provides an aggregate estimate of the transport RTT as computed by // various underlying TCP and QUIC connections. Virtualized for testing. virtual absl::optional GetTransportRTT() const; // Returns the current downstream throughput estimate (in kilobits per // second). If the estimate is unavailable, the returned optional value is // null. absl::optional GetDownstreamThroughputKbps() const; // Adds |observer| to the list of RTT and throughput estimate observers. // The observer must register and unregister itself on the same thread. // |observer| would be notified on the thread on which it registered. // |observer| would be notified of the current values in the next message // pump. void AddRTTAndThroughputEstimatesObserver( RTTAndThroughputEstimatesObserver* observer); // Removes |observer| from the list of RTT and throughput estimate // observers. void RemoveRTTAndThroughputEstimatesObserver( RTTAndThroughputEstimatesObserver* observer); // Notifies NetworkQualityEstimator that the response header of |request| has // been received. Reports the total prefilter network bytes that have been // read for the response of |request|. void NotifyHeadersReceived(const URLRequest& request, int64_t prefilter_total_bytes_read); // Notifies NetworkQualityEstimator that unfiltered bytes have been read for // |request|. Reports the total prefilter network bytes that have been read // for the response of |request|. void NotifyBytesRead(const URLRequest& request, int64_t prefilter_total_bytes_read); // Notifies NetworkQualityEstimator that the headers of |request| are about to // be sent. void NotifyStartTransaction(const URLRequest& request); // Notifies NetworkQualityEstimator that the response body of |request| has // been received. void NotifyRequestCompleted(const URLRequest& request); // Notifies NetworkQualityEstimator that |request| will be destroyed. void NotifyURLRequestDestroyed(const URLRequest& request); // Adds |rtt_observer| to the list of round trip time observers. Must be // called on the IO thread. void AddRTTObserver(RTTObserver* rtt_observer); // Removes |rtt_observer| from the list of round trip time observers if it // is on the list of observers. Must be called on the IO thread. void RemoveRTTObserver(RTTObserver* rtt_observer); // Adds |throughput_observer| to the list of throughput observers. Must be // called on the IO thread. void AddThroughputObserver(ThroughputObserver* throughput_observer); // Removes |throughput_observer| from the list of throughput observers if it // is on the list of observers. Must be called on the IO thread. void RemoveThroughputObserver(ThroughputObserver* throughput_observer); SocketPerformanceWatcherFactory* GetSocketPerformanceWatcherFactory(); // |use_localhost_requests| should only be true when testing against local // HTTP server and allows the requests to local host to be used for network // quality estimation. void SetUseLocalHostRequestsForTesting(bool use_localhost_requests); // |use_small_responses| should only be true when testing. // Allows the responses smaller than |kMinTransferSizeInBits| to be used for // network quality estimation. void SetUseSmallResponsesForTesting(bool use_small_responses); // If |disable_offline_check| is set to true, then the device offline check is // disabled when computing the effective connection type or when writing the // prefs. void DisableOfflineCheckForTesting(bool disable_offline_check); // Reports |effective_connection_type| to all // EffectiveConnectionTypeObservers. void ReportEffectiveConnectionTypeForTesting( EffectiveConnectionType effective_connection_type); // Reports the RTTs and throughput to all RTTAndThroughputEstimatesObservers. void ReportRTTsAndThroughputForTesting(base::TimeDelta http_rtt, base::TimeDelta transport_rtt, int32_t downstream_throughput_kbps); // Adds and removes |observer| from the list of cache observers. void AddNetworkQualitiesCacheObserver( nqe::internal::NetworkQualityStore::NetworkQualitiesCacheObserver* observer); void RemoveNetworkQualitiesCacheObserver( nqe::internal::NetworkQualityStore::NetworkQualitiesCacheObserver* observer); // Called when the persistent prefs have been read. |read_prefs| contains the // parsed prefs as a map between NetworkIDs and CachedNetworkQualities. void OnPrefsRead( const std::map read_prefs); const NetworkQualityEstimatorParams* params() { return params_.get(); } #if BUILDFLAG(IS_CHROMEOS_ASH) // Enables getting the network id asynchronously when // GatherEstimatesForNextConnectionType(). This should always be called in // production, because getting the network id involves a blocking call to // recv() in AddressTrackerLinux, and the IO thread should never be blocked. // TODO(https://crbug.com/821607): Remove after the bug is resolved. void EnableGetNetworkIdAsynchronously(); #endif // BUILDFLAG(IS_CHROMEOS_ASH) // Forces the effective connection type to be recomputed as |type|. Once // called, effective connection type would always be computed as |type|. // Calling this also notifies all the observers of the effective connection // type as |type|. void SimulateNetworkQualityChangeForTesting( net::EffectiveConnectionType type); // Notifies |this| of round trip ping latency reported by H2 connections. virtual void RecordSpdyPingLatency(const HostPortPair& host_port_pair, base::TimeDelta rtt); // Sets the current count of media connections that require low latency. void OnPeerToPeerConnectionsCountChange(uint32_t count); // Returns the current count of peer to peer connections that may require low // latency. uint32_t GetPeerToPeerConnectionsCountChange() const; // Forces NetworkQualityEstimator reports // NetworkChangeNotifier::CONNECTION_WIFI(2) as // EFFECTIVE_CONNECTION_TYPE_SLOW_2G(2) since EffectiveConnectionType and the // production receivers doesn't notice Wifi. void ForceReportWifiAsSlow2GForTesting(); typedef nqe::internal::Observation Observation; typedef nqe::internal::ObservationBuffer ObservationBuffer; protected: // NetworkChangeNotifier::ConnectionTypeObserver implementation: void OnConnectionTypeChanged( NetworkChangeNotifier::ConnectionType type) override; // Returns true if median RTT across all samples that belong to // |observation_category| is available and sets |rtt| to the median of RTT // observations since |start_time|. Virtualized for testing. |rtt| should not // be null. If |observations_count| is not null, then it is set to the number // of RTT observations that were used for computing the RTT estimate. [[nodiscard]] virtual bool GetRecentRTT( nqe::internal::ObservationCategory observation_category, const base::TimeTicks& start_time, base::TimeDelta* rtt, size_t* observations_count) const; // Returns true if median downstream throughput is available and sets |kbps| // to the median of downstream throughput (in kilobits per second) // observations since |start_time|. Virtualized for testing. |kbps| // should not be null. Virtualized for testing. // TODO(tbansal): Change it to return throughput as int32. [[nodiscard]] virtual bool GetRecentDownlinkThroughputKbps( const base::TimeTicks& start_time, int32_t* kbps) const; // Overrides the tick clock used by |this| for testing. void SetTickClockForTesting(const base::TickClock* tick_clock); // Returns the effective type of the current connection based on the // samples observed. May use HTTP RTT, transport RTT and // downstream throughput to compute the effective connection type based on // |http_rtt_metric|, |transport_rtt_metric| and // |downstream_throughput_kbps_metric|, respectively. |http_rtt|, // |transport_rtt| and |downstream_throughput_kbps| must be non-null. // |http_rtt|, |transport_rtt| and |downstream_throughput_kbps| are // set to the expected HTTP RTT, transport RTT and downstream throughput (in // kilobits per second) based on observations taken since |start_time|. // If |transport_rtt_observation_count| is not null, then it is set to the // number of transport RTT observations that were available when computing the // effective connection type. virtual EffectiveConnectionType GetRecentEffectiveConnectionTypeUsingMetrics( base::TimeDelta* http_rtt, base::TimeDelta* transport_rtt, base::TimeDelta* end_to_end_rtt, int32_t* downstream_throughput_kbps, size_t* transport_rtt_observation_count, size_t* end_to_end_rtt_observation_count) const; // Notifies |this| of a new transport layer RTT. Called by socket watchers. // Protected for testing. void OnUpdatedTransportRTTAvailable( SocketPerformanceWatcherFactory::Protocol protocol, const base::TimeDelta& rtt, const absl::optional& host); // Returns an estimate of network quality at the specified |percentile|. // Only the observations later than |start_time| are taken into account. // |percentile| must be between 0 and 100 (both inclusive) with higher // percentiles indicating less performant networks. For example, if // |percentile| is 90, then the network is expected to be faster than the // returned estimate with 0.9 probability. Similarly, network is expected to // be slower than the returned estimate with 0.1 probability. // Virtualized for testing. // |observation_category| is the category of observations which should be used // for computing the RTT estimate. // If |observations_count| is not null, then it is set to the number of RTT // observations that were available when computing the RTT estimate. virtual base::TimeDelta GetRTTEstimateInternal( base::TimeTicks start_time, nqe::internal::ObservationCategory observation_category, int percentile, size_t* observations_count) const; int32_t GetDownlinkThroughputKbpsEstimateInternal( const base::TimeTicks& start_time, int percentile) const; // Notifies the observers of RTT or throughput estimates computation. virtual void NotifyObserversOfRTTOrThroughputComputed() const; // Notifies |observer| of the current RTT and throughput if |observer| is // still registered as an observer. virtual void NotifyRTTAndThroughputEstimatesObserverIfPresent( RTTAndThroughputEstimatesObserver* observer) const; // Adds |observation| to the buffer of RTT observations, and notifies RTT // observers of |observation|. May also trigger recomputation of effective // connection type. void AddAndNotifyObserversOfRTT(const Observation& observation); // Adds |observation| to the buffer of throughput observations, and notifies // throughput observers of |observation|. May also trigger recomputation of // effective connection type. void AddAndNotifyObserversOfThroughput(const Observation& observation); // Returns true if the request with observed HTTP of |observed_http_rtt| is // expected to be a hanging request. The decision is made by comparing // |observed_http_rtt| with the expected HTTP and transport RTT. bool IsHangingRequest(base::TimeDelta observed_http_rtt) const; // Forces computation of effective connection type, and notifies observers // if there is a change in its value. void ComputeEffectiveConnectionType(); // Returns a non-null value if the value of the effective connection type has // been overridden for testing. virtual absl::optional GetOverrideECT() const; // Observer list for RTT or throughput estimates. Protected for testing. base::ObserverList::Unchecked rtt_and_throughput_estimates_observer_list_; // Observer list for changes in effective connection type. base::ObserverList::Unchecked effective_connection_type_observer_list_; // Observer list for changes in peer to peer connections count. base::ObserverList::Unchecked peer_to_peer_type_observer_list_; // Params to configure the network quality estimator. const std::unique_ptr params_; // Number of end to end RTT samples available when the ECT was last computed. size_t end_to_end_rtt_observation_count_at_last_ect_computation_ = 0; // Current count of active peer to peer connections. uint32_t p2p_connections_count_ = 0u; private: FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, AdaptiveRecomputationEffectiveConnectionType); FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, StoreObservations); FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, TestAddObservation); FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, DefaultObservationsOverridden); FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, ComputedPercentiles); FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, TestGetMetricsSince); FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, UnknownEffectiveConnectionType); FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, TypicalNetworkQualities); FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, OnPrefsReadWithReadingDisabled); FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, ForceEffectiveConnectionTypeThroughFieldTrial); FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, ObservationDiscardedIfCachedEstimateAvailable); FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, TestRttThroughputObservers); // Returns the RTT value to be used when the valid RTT is unavailable. Readers // should discard RTT if it is set to the value returned by |InvalidRTT()|. static const base::TimeDelta InvalidRTT(); // Records a downstream throughput observation to the observation buffer if // a valid observation is available. |downstream_kbps| is the downstream // throughput in kilobits per second. void OnNewThroughputObservationAvailable(int32_t downstream_kbps); // Adds the default median RTT and downstream throughput estimate for the // current connection type to the observation buffer. void AddDefaultEstimates(); // Returns the current network ID checking by calling the platform APIs. // Virtualized for testing. virtual nqe::internal::NetworkID GetCurrentNetworkID() const; // Returns true only if the |request| can be used for RTT estimation. bool RequestProvidesRTTObservation(const URLRequest& request) const; // Returns true if ECT should be recomputed. bool ShouldComputeEffectiveConnectionType() const; // Calls ShouldComputeEffectiveConnectionType() to determine if ECT needs to // be computed. If so, it recomputes effective connection type. void MaybeComputeEffectiveConnectionType(); // Notifies observers of a change in effective connection type. void NotifyObserversOfEffectiveConnectionTypeChanged(); // Notifies |observer| of the current effective connection type if |observer| // is still registered as an observer. void NotifyEffectiveConnectionTypeObserverIfPresent( MayBeDangling observer) const; // Notifies |observer| of the current count of peer to peer connections. void NotifyPeerToPeerConnectionsCountObserverIfPresent( MayBeDangling observer) const; // Records NQE accuracy metrics. |measuring_duration| should belong to the // vector returned by AccuracyRecordingIntervals(). // RecordAccuracyAfterMainFrame should be called |measuring_duration| after a // main frame request is observed. void RecordAccuracyAfterMainFrame(base::TimeDelta measuring_duration) const; // Updates the provided |http_rtt| based on all provided RTT values. void UpdateHttpRttUsingAllRttValues( base::TimeDelta* http_rtt, const base::TimeDelta transport_rtt, const base::TimeDelta end_to_end_rtt) const; // Returns true if the cached network quality estimate was successfully read. bool ReadCachedNetworkQualityEstimate(); // Gathers metrics for the next connection type. Called when there is a change // in the connection type. void GatherEstimatesForNextConnectionType(); // Invoked to continue GatherEstimatesForNextConnectionType work after getting // network id. If |get_network_id_asynchronously_| is set, the network id is // fetched on a worker thread. Otherwise, GatherEstimatesForNextConnectionType // calls this directly. This is a workaround for https://crbug.com/821607 // where net::GetWifiSSID() call gets stuck. void ContinueGatherEstimatesForNextConnectionType( const nqe::internal::NetworkID& network_id); // Updates the value of |cached_estimate_applied_| if |observation| is // computed from a cached estimate. |buffer| is the observation buffer to // which the cached estimate is being added to. void MaybeUpdateCachedEstimateApplied(const Observation& observation, ObservationBuffer* buffer); // Returns true if |observation| should be added to the observation buffer. bool ShouldAddObservation(const Observation& observation) const; // Returns true if the socket watcher can run the callback to notify the RTT // observations. bool ShouldSocketWatcherNotifyRTT(base::TimeTicks now); // When RTT counts are low, it may be impossible to predict accurate ECT. In // that case, we just give the highest value. void AdjustHttpRttBasedOnRTTCounts(base::TimeDelta* http_rtt) const; // Clamps the throughput estimate based on the current effective connection // type. void ClampKbpsBasedOnEct(); // Determines if the requests to local host can be used in estimating the // network quality. Set to true only for tests. bool use_localhost_requests_ = false; // When set to true, the device offline check is disabled when computing the // effective connection type or when writing the prefs. Set to true only for // testing. bool disable_offline_check_ = false; // Tick clock used by the network quality estimator. raw_ptr tick_clock_; // Time when last connection change was observed. base::TimeTicks last_connection_change_; // ID of the current network. nqe::internal::NetworkID current_network_id_; // Buffer that holds throughput observations from the HTTP layer (in kilobits // per second) sorted by timestamp. ObservationBuffer http_downstream_throughput_kbps_observations_; // Buffer that holds RTT observations with different observation categories. // The entries in |rtt_ms_observations_| are in the same order as the // entries in the nqe::internal:ObservationCategory enum. // Each observation buffer in |rtt_ms_observations_| stores RTT observations // in milliseconds. Within a buffer, the observations are sorted by timestamp. ObservationBuffer rtt_ms_observations_[nqe::internal::OBSERVATION_CATEGORY_COUNT]; // Observer lists for round trip times and throughput measurements. base::ObserverList::Unchecked rtt_observer_list_; base::ObserverList::Unchecked throughput_observer_list_; std::unique_ptr watcher_factory_; // Takes throughput measurements, and passes them back to |this| through the // provided callback. |this| stores the throughput observations in // |downstream_throughput_kbps_observations_|, which are later used for // estimating the throughput. std::unique_ptr throughput_analyzer_; // Minimum duration between two consecutive computations of effective // connection type. Set to non-zero value as a performance optimization. const base::TimeDelta effective_connection_type_recomputation_interval_ = base::Seconds(10); // Time when the effective connection type was last computed. base::TimeTicks last_effective_connection_type_computation_; // Number of RTT and bandwidth samples available when effective connection // type was last recomputed. size_t rtt_observations_size_at_last_ect_computation_ = 0; size_t throughput_observations_size_at_last_ect_computation_ = 0; // Number of transport RTT samples available when the ECT was last computed. size_t transport_rtt_observation_count_last_ect_computation_ = 0; // Number of RTT observations received since the effective connection type was // last computed. size_t new_rtt_observations_since_last_ect_computation_ = 0; // Number of throughput observations received since the effective connection // type was last computed. size_t new_throughput_observations_since_last_ect_computation_ = 0; // Current estimate of the network quality. nqe::internal::NetworkQuality network_quality_; absl::optional end_to_end_rtt_; // Current effective connection type. It is updated on connection change // events. It is also updated every time there is network traffic (provided // the last computation was more than // |effective_connection_type_recomputation_interval_| ago). EffectiveConnectionType effective_connection_type_ = EFFECTIVE_CONNECTION_TYPE_UNKNOWN; // Stores the qualities of different networks. std::unique_ptr network_quality_store_; // True if a cached RTT or throughput estimate was available and the // corresponding observation has been added on the current network. bool cached_estimate_applied_ = false; SEQUENCE_CHECKER(sequence_checker_); NetLogWithSource net_log_; // Manages the writing of events to the net log. nqe::internal::EventCreator event_creator_; // Time when the last RTT observation from a socket watcher was received. base::TimeTicks last_socket_watcher_rtt_notification_; absl::optional last_signal_strength_check_timestamp_; #if BUILDFLAG(IS_CHROMEOS_ASH) // Whether the network id should be obtained on a worker thread. bool get_network_id_asynchronously_ = false; #endif bool force_report_wifi_as_slow_2g_for_testing_ = false; base::WeakPtrFactory weak_ptr_factory_{this}; }; } // namespace net #endif // NET_NQE_NETWORK_QUALITY_ESTIMATOR_H_