| // Copyright 2014 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. | 
 |  | 
 | #include "components/metrics/net/network_metrics_provider.h" | 
 |  | 
 | #include <stdint.h> | 
 |  | 
 | #include <string> | 
 | #include <vector> | 
 |  | 
 | #include "base/bind_helpers.h" | 
 | #include "base/callback_forward.h" | 
 | #include "base/compiler_specific.h" | 
 | #include "base/metrics/histogram_macros.h" | 
 | #include "base/metrics/sparse_histogram.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/string_split.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/task_scheduler/post_task.h" | 
 | #include "base/threading/thread_task_runner_handle.h" | 
 | #include "build/build_config.h" | 
 | #include "net/base/net_errors.h" | 
 | #include "net/nqe/effective_connection_type_observer.h" | 
 | #include "net/nqe/network_quality_estimator.h" | 
 |  | 
 | #if defined(OS_CHROMEOS) | 
 | #include "components/metrics/net/wifi_access_point_info_provider_chromeos.h" | 
 | #endif  // OS_CHROMEOS | 
 |  | 
 | namespace metrics { | 
 |  | 
 | SystemProfileProto::Network::EffectiveConnectionType | 
 | ConvertEffectiveConnectionType( | 
 |     net::EffectiveConnectionType effective_connection_type) { | 
 |   switch (effective_connection_type) { | 
 |     case net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN: | 
 |       return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN; | 
 |     case net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G: | 
 |       return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_SLOW_2G; | 
 |     case net::EFFECTIVE_CONNECTION_TYPE_2G: | 
 |       return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G; | 
 |     case net::EFFECTIVE_CONNECTION_TYPE_3G: | 
 |       return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_3G; | 
 |     case net::EFFECTIVE_CONNECTION_TYPE_4G: | 
 |       return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_4G; | 
 |     case net::EFFECTIVE_CONNECTION_TYPE_OFFLINE: | 
 |       return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_OFFLINE; | 
 |     case net::EFFECTIVE_CONNECTION_TYPE_LAST: | 
 |       NOTREACHED(); | 
 |       return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN; | 
 |   } | 
 |   NOTREACHED(); | 
 |   return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN; | 
 | } | 
 |  | 
 | // Listens to the changes in the effective conection type. | 
 | class NetworkMetricsProvider::EffectiveConnectionTypeObserver | 
 |     : public net::EffectiveConnectionTypeObserver { | 
 |  public: | 
 |   // |network_quality_estimator| is used to provide the network quality | 
 |   // estimates. Guaranteed to be non-null. |callback| is run on | 
 |   // |callback_task_runner|, and provides notifications about the changes in the | 
 |   // effective connection type. | 
 |   EffectiveConnectionTypeObserver( | 
 |       base::Callback<void(net::EffectiveConnectionType)> callback, | 
 |       const scoped_refptr<base::SequencedTaskRunner>& callback_task_runner) | 
 |       : network_quality_estimator_(nullptr), | 
 |         callback_(callback), | 
 |         callback_task_runner_(callback_task_runner) { | 
 |     DCHECK(callback_); | 
 |     DCHECK(callback_task_runner_); | 
 |     // |this| is initialized and used on the IO thread using | 
 |     // |network_quality_task_runner_|. | 
 |     thread_checker_.DetachFromThread(); | 
 |   } | 
 |  | 
 |   ~EffectiveConnectionTypeObserver() override { | 
 |     DCHECK(thread_checker_.CalledOnValidThread()); | 
 |     if (network_quality_estimator_) | 
 |       network_quality_estimator_->RemoveEffectiveConnectionTypeObserver(this); | 
 |   } | 
 |  | 
 |   // Initializes |this| on IO thread using |network_quality_task_runner_|. This | 
 |   // is the same thread on which |network_quality_estimator| lives. | 
 |   void Init(net::NetworkQualityEstimator* network_quality_estimator) { | 
 |     network_quality_estimator_ = network_quality_estimator; | 
 |     if (network_quality_estimator_) | 
 |       network_quality_estimator_->AddEffectiveConnectionTypeObserver(this); | 
 |   } | 
 |  | 
 |  private: | 
 |   // net::EffectiveConnectionTypeObserver: | 
 |   void OnEffectiveConnectionTypeChanged( | 
 |       net::EffectiveConnectionType type) override { | 
 |     DCHECK(thread_checker_.CalledOnValidThread()); | 
 |     callback_task_runner_->PostTask(FROM_HERE, base::Bind(callback_, type)); | 
 |   } | 
 |  | 
 |   // Notifies |this| when there is a change in the effective connection type. | 
 |   net::NetworkQualityEstimator* network_quality_estimator_; | 
 |  | 
 |   // Called when the effective connection type is changed. | 
 |   base::Callback<void(net::EffectiveConnectionType)> callback_; | 
 |  | 
 |   // Task runner on which |callback_| is run. | 
 |   scoped_refptr<base::SequencedTaskRunner> callback_task_runner_; | 
 |  | 
 |   base::ThreadChecker thread_checker_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(EffectiveConnectionTypeObserver); | 
 | }; | 
 |  | 
 | NetworkMetricsProvider::NetworkMetricsProvider( | 
 |     std::unique_ptr<NetworkQualityEstimatorProvider> | 
 |         network_quality_estimator_provider) | 
 |     : connection_type_is_ambiguous_(false), | 
 |       network_change_notifier_initialized_(false), | 
 |       wifi_phy_layer_protocol_is_ambiguous_(false), | 
 |       wifi_phy_layer_protocol_(net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN), | 
 |       total_aborts_(0), | 
 |       total_codes_(0), | 
 |       network_quality_estimator_provider_( | 
 |           std::move(network_quality_estimator_provider)), | 
 |       effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN), | 
 |       min_effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN), | 
 |       max_effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN), | 
 |       weak_ptr_factory_(this) { | 
 |   net::NetworkChangeNotifier::AddConnectionTypeObserver(this); | 
 |   connection_type_ = net::NetworkChangeNotifier::GetConnectionType(); | 
 |   if (connection_type_ != net::NetworkChangeNotifier::CONNECTION_UNKNOWN) | 
 |     network_change_notifier_initialized_ = true; | 
 |  | 
 |   ProbeWifiPHYLayerProtocol(); | 
 |  | 
 |   if (network_quality_estimator_provider_) { | 
 |     effective_connection_type_observer_.reset( | 
 |         new EffectiveConnectionTypeObserver( | 
 |             base::Bind( | 
 |                 &NetworkMetricsProvider::OnEffectiveConnectionTypeChanged, | 
 |                 base::Unretained(this)), | 
 |             base::ThreadTaskRunnerHandle::Get())); | 
 |  | 
 |     // Get the network quality estimator and initialize | 
 |     // |effective_connection_type_observer_| on the same task runner on  which | 
 |     // the network quality estimator lives. It is safe to use base::Unretained | 
 |     // here since both |network_quality_estimator_provider_| and | 
 |     // |effective_connection_type_observer_| are owned by |this|, and | 
 |     // |network_quality_estimator_provider_| is deleted before | 
 |     // |effective_connection_type_observer_|. | 
 |     network_quality_estimator_provider_->PostReplyNetworkQualityEstimator( | 
 |         base::Bind( | 
 |             &EffectiveConnectionTypeObserver::Init, | 
 |             base::Unretained(effective_connection_type_observer_.get()))); | 
 |   } | 
 | } | 
 |  | 
 | NetworkMetricsProvider::~NetworkMetricsProvider() { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this); | 
 |  | 
 |   if (network_quality_estimator_provider_) { | 
 |     scoped_refptr<base::SequencedTaskRunner> network_quality_task_runner = | 
 |         network_quality_estimator_provider_->GetTaskRunner(); | 
 |  | 
 |     // |network_quality_estimator_provider_| must be deleted before | 
 |     // |effective_connection_type_observer_| since | 
 |     // |effective_connection_type_observer_| may callback into | 
 |     // |effective_connection_type_observer_|. | 
 |     network_quality_estimator_provider_.reset(); | 
 |  | 
 |     if (network_quality_task_runner && | 
 |         !network_quality_task_runner->DeleteSoon( | 
 |             FROM_HERE, effective_connection_type_observer_.release())) { | 
 |       NOTREACHED() << " ECT observer was not deleted successfully"; | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void NetworkMetricsProvider::ProvideCurrentSessionData( | 
 |     ChromeUserMetricsExtension*) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   // ProvideCurrentSessionData is called on the main thread, at the time a | 
 |   // metrics record is being finalized. | 
 |   net::NetworkChangeNotifier::FinalizingMetricsLogRecord(); | 
 |   LogAggregatedMetrics(); | 
 | } | 
 |  | 
 | void NetworkMetricsProvider::ProvideSystemProfileMetrics( | 
 |     SystemProfileProto* system_profile) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   DCHECK(!connection_type_is_ambiguous_ || | 
 |          network_change_notifier_initialized_); | 
 |   SystemProfileProto::Network* network = system_profile->mutable_network(); | 
 |   network->set_connection_type_is_ambiguous(connection_type_is_ambiguous_); | 
 |   network->set_connection_type(GetConnectionType()); | 
 |   network->set_wifi_phy_layer_protocol_is_ambiguous( | 
 |       wifi_phy_layer_protocol_is_ambiguous_); | 
 |   network->set_wifi_phy_layer_protocol(GetWifiPHYLayerProtocol()); | 
 |  | 
 |   network->set_min_effective_connection_type( | 
 |       ConvertEffectiveConnectionType(min_effective_connection_type_)); | 
 |   network->set_max_effective_connection_type( | 
 |       ConvertEffectiveConnectionType(max_effective_connection_type_)); | 
 |  | 
 |   // Update the connection type. Note that this is necessary to set the network | 
 |   // type to "none" if there is no network connection for an entire UMA logging | 
 |   // window, since OnConnectionTypeChanged() ignores transitions to the "none" | 
 |   // state. | 
 |   connection_type_ = net::NetworkChangeNotifier::GetConnectionType(); | 
 |   // Reset the "ambiguous" flags, since a new metrics log session has started. | 
 |   connection_type_is_ambiguous_ = false; | 
 |   wifi_phy_layer_protocol_is_ambiguous_ = false; | 
 |   min_effective_connection_type_ = effective_connection_type_; | 
 |   max_effective_connection_type_ = effective_connection_type_; | 
 |  | 
 |   if (!wifi_access_point_info_provider_.get()) { | 
 | #if defined(OS_CHROMEOS) | 
 |     wifi_access_point_info_provider_.reset( | 
 |         new WifiAccessPointInfoProviderChromeos()); | 
 | #else | 
 |     wifi_access_point_info_provider_.reset( | 
 |         new WifiAccessPointInfoProvider()); | 
 | #endif  // OS_CHROMEOS | 
 |   } | 
 |  | 
 |   // Connected wifi access point information. | 
 |   WifiAccessPointInfoProvider::WifiAccessPointInfo info; | 
 |   if (wifi_access_point_info_provider_->GetInfo(&info)) | 
 |     WriteWifiAccessPointProto(info, network); | 
 | } | 
 |  | 
 | void NetworkMetricsProvider::OnConnectionTypeChanged( | 
 |     net::NetworkChangeNotifier::ConnectionType type) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   // To avoid reporting an ambiguous connection type for users on flaky | 
 |   // connections, ignore transitions to the "none" state. Note that the | 
 |   // connection type is refreshed in ProvideSystemProfileMetrics() each time a | 
 |   // new UMA logging window begins, so users who genuinely transition to offline | 
 |   // mode for an extended duration will still be at least partially represented | 
 |   // in the metrics logs. | 
 |   if (type == net::NetworkChangeNotifier::CONNECTION_NONE) { | 
 |     network_change_notifier_initialized_ = true; | 
 |     return; | 
 |   } | 
 |  | 
 |   DCHECK(network_change_notifier_initialized_ || | 
 |          connection_type_ == net::NetworkChangeNotifier::CONNECTION_UNKNOWN); | 
 |  | 
 |   if (type != connection_type_ && | 
 |       connection_type_ != net::NetworkChangeNotifier::CONNECTION_NONE && | 
 |       network_change_notifier_initialized_) { | 
 |     // If |network_change_notifier_initialized_| is false, it implies that this | 
 |     // is the first connection change callback received from network change | 
 |     // notifier, and the previous connection type was CONNECTION_UNKNOWN. In | 
 |     // that case, connection type should not be marked as ambiguous since there | 
 |     // was no actual change in the connection type. | 
 |     connection_type_is_ambiguous_ = true; | 
 |   } | 
 |  | 
 |   network_change_notifier_initialized_ = true; | 
 |   connection_type_ = type; | 
 |  | 
 |   ProbeWifiPHYLayerProtocol(); | 
 | } | 
 |  | 
 | SystemProfileProto::Network::ConnectionType | 
 | NetworkMetricsProvider::GetConnectionType() const { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   switch (connection_type_) { | 
 |     case net::NetworkChangeNotifier::CONNECTION_NONE: | 
 |       return SystemProfileProto::Network::CONNECTION_NONE; | 
 |     case net::NetworkChangeNotifier::CONNECTION_UNKNOWN: | 
 |       return SystemProfileProto::Network::CONNECTION_UNKNOWN; | 
 |     case net::NetworkChangeNotifier::CONNECTION_ETHERNET: | 
 |       return SystemProfileProto::Network::CONNECTION_ETHERNET; | 
 |     case net::NetworkChangeNotifier::CONNECTION_WIFI: | 
 |       return SystemProfileProto::Network::CONNECTION_WIFI; | 
 |     case net::NetworkChangeNotifier::CONNECTION_2G: | 
 |       return SystemProfileProto::Network::CONNECTION_2G; | 
 |     case net::NetworkChangeNotifier::CONNECTION_3G: | 
 |       return SystemProfileProto::Network::CONNECTION_3G; | 
 |     case net::NetworkChangeNotifier::CONNECTION_4G: | 
 |       return SystemProfileProto::Network::CONNECTION_4G; | 
 |     case net::NetworkChangeNotifier::CONNECTION_BLUETOOTH: | 
 |       return SystemProfileProto::Network::CONNECTION_BLUETOOTH; | 
 |   } | 
 |   NOTREACHED(); | 
 |   return SystemProfileProto::Network::CONNECTION_UNKNOWN; | 
 | } | 
 |  | 
 | SystemProfileProto::Network::WifiPHYLayerProtocol | 
 | NetworkMetricsProvider::GetWifiPHYLayerProtocol() const { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   switch (wifi_phy_layer_protocol_) { | 
 |     case net::WIFI_PHY_LAYER_PROTOCOL_NONE: | 
 |       return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_NONE; | 
 |     case net::WIFI_PHY_LAYER_PROTOCOL_ANCIENT: | 
 |       return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_ANCIENT; | 
 |     case net::WIFI_PHY_LAYER_PROTOCOL_A: | 
 |       return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_A; | 
 |     case net::WIFI_PHY_LAYER_PROTOCOL_B: | 
 |       return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_B; | 
 |     case net::WIFI_PHY_LAYER_PROTOCOL_G: | 
 |       return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_G; | 
 |     case net::WIFI_PHY_LAYER_PROTOCOL_N: | 
 |       return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_N; | 
 |     case net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN: | 
 |       return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN; | 
 |   } | 
 |   NOTREACHED(); | 
 |   return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN; | 
 | } | 
 |  | 
 | void NetworkMetricsProvider::ProbeWifiPHYLayerProtocol() { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   base::PostTaskWithTraitsAndReplyWithResult( | 
 |       FROM_HERE, | 
 |       {base::MayBlock(), base::TaskPriority::BACKGROUND, | 
 |        base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, | 
 |       base::BindOnce(&net::GetWifiPHYLayerProtocol), | 
 |       base::BindOnce(&NetworkMetricsProvider::OnWifiPHYLayerProtocolResult, | 
 |                      weak_ptr_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void NetworkMetricsProvider::OnWifiPHYLayerProtocolResult( | 
 |     net::WifiPHYLayerProtocol mode) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   if (wifi_phy_layer_protocol_ != net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN && | 
 |       mode != wifi_phy_layer_protocol_) { | 
 |     wifi_phy_layer_protocol_is_ambiguous_ = true; | 
 |   } | 
 |   wifi_phy_layer_protocol_ = mode; | 
 | } | 
 |  | 
 | void NetworkMetricsProvider::WriteWifiAccessPointProto( | 
 |     const WifiAccessPointInfoProvider::WifiAccessPointInfo& info, | 
 |     SystemProfileProto::Network* network_proto) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   SystemProfileProto::Network::WifiAccessPoint* access_point_info = | 
 |       network_proto->mutable_access_point_info(); | 
 |   SystemProfileProto::Network::WifiAccessPoint::SecurityMode security = | 
 |       SystemProfileProto::Network::WifiAccessPoint::SECURITY_UNKNOWN; | 
 |   switch (info.security) { | 
 |     case WifiAccessPointInfoProvider::WIFI_SECURITY_NONE: | 
 |       security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_NONE; | 
 |       break; | 
 |     case WifiAccessPointInfoProvider::WIFI_SECURITY_WPA: | 
 |       security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_WPA; | 
 |       break; | 
 |     case WifiAccessPointInfoProvider::WIFI_SECURITY_WEP: | 
 |       security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_WEP; | 
 |       break; | 
 |     case WifiAccessPointInfoProvider::WIFI_SECURITY_RSN: | 
 |       security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_RSN; | 
 |       break; | 
 |     case WifiAccessPointInfoProvider::WIFI_SECURITY_802_1X: | 
 |       security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_802_1X; | 
 |       break; | 
 |     case WifiAccessPointInfoProvider::WIFI_SECURITY_PSK: | 
 |       security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_PSK; | 
 |       break; | 
 |     case WifiAccessPointInfoProvider::WIFI_SECURITY_UNKNOWN: | 
 |       security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_UNKNOWN; | 
 |       break; | 
 |   } | 
 |   access_point_info->set_security_mode(security); | 
 |  | 
 |   // |bssid| is xx:xx:xx:xx:xx:xx, extract the first three components and | 
 |   // pack into a uint32_t. | 
 |   std::string bssid = info.bssid; | 
 |   if (bssid.size() == 17 && bssid[2] == ':' && bssid[5] == ':' && | 
 |       bssid[8] == ':' && bssid[11] == ':' && bssid[14] == ':') { | 
 |     std::string vendor_prefix_str; | 
 |     uint32_t vendor_prefix; | 
 |  | 
 |     base::RemoveChars(bssid.substr(0, 9), ":", &vendor_prefix_str); | 
 |     DCHECK_EQ(6U, vendor_prefix_str.size()); | 
 |     if (base::HexStringToUInt(vendor_prefix_str, &vendor_prefix)) | 
 |       access_point_info->set_vendor_prefix(vendor_prefix); | 
 |     else | 
 |       NOTREACHED(); | 
 |   } | 
 |  | 
 |   // Return if vendor information is not provided. | 
 |   if (info.model_number.empty() && info.model_name.empty() && | 
 |       info.device_name.empty() && info.oui_list.empty()) | 
 |     return; | 
 |  | 
 |   SystemProfileProto::Network::WifiAccessPoint::VendorInformation* vendor = | 
 |       access_point_info->mutable_vendor_info(); | 
 |   if (!info.model_number.empty()) | 
 |     vendor->set_model_number(info.model_number); | 
 |   if (!info.model_name.empty()) | 
 |     vendor->set_model_name(info.model_name); | 
 |   if (!info.device_name.empty()) | 
 |     vendor->set_device_name(info.device_name); | 
 |  | 
 |   // Return if OUI list is not provided. | 
 |   if (info.oui_list.empty()) | 
 |     return; | 
 |  | 
 |   // Parse OUI list. | 
 |   for (const base::StringPiece& oui_str : base::SplitStringPiece( | 
 |            info.oui_list, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { | 
 |     uint32_t oui; | 
 |     if (base::HexStringToUInt(oui_str, &oui)) | 
 |       vendor->add_element_identifier(oui); | 
 |     else | 
 |       NOTREACHED(); | 
 |   } | 
 | } | 
 |  | 
 | void NetworkMetricsProvider::LogAggregatedMetrics() { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   base::HistogramBase* error_codes = base::SparseHistogram::FactoryGet( | 
 |       "Net.ErrorCodesForMainFrame3", | 
 |       base::HistogramBase::kUmaTargetedHistogramFlag); | 
 |   std::unique_ptr<base::HistogramSamples> samples = | 
 |       error_codes->SnapshotSamples(); | 
 |   base::HistogramBase::Count new_aborts = | 
 |       samples->GetCount(-net::ERR_ABORTED) - total_aborts_; | 
 |   base::HistogramBase::Count new_codes = samples->TotalCount() - total_codes_; | 
 |   if (new_codes > 0) { | 
 |     UMA_HISTOGRAM_CUSTOM_COUNTS("Net.ErrAborted.CountPerUpload2", new_aborts, 1, | 
 |                                 100000000, 50); | 
 |     UMA_HISTOGRAM_PERCENTAGE("Net.ErrAborted.ProportionPerUpload", | 
 |                              (100 * new_aborts) / new_codes); | 
 |     total_codes_ += new_codes; | 
 |     total_aborts_ += new_aborts; | 
 |   } | 
 | } | 
 |  | 
 | void NetworkMetricsProvider::OnEffectiveConnectionTypeChanged( | 
 |     net::EffectiveConnectionType type) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   effective_connection_type_ = type; | 
 |  | 
 |   if (effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN || | 
 |       effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_OFFLINE) { | 
 |     // The effective connection type may be reported as Unknown if there is a | 
 |     // change in the connection type. Disregard it since network requests can't | 
 |     // be send during the changes in connection type. Similarly, disregard | 
 |     // offline as the type since it may be reported as the effective connection | 
 |     // type for a short period when there is a change in the connection type. | 
 |     return; | 
 |   } | 
 |  | 
 |   if (min_effective_connection_type_ == | 
 |           net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN && | 
 |       max_effective_connection_type_ == | 
 |           net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN) { | 
 |     min_effective_connection_type_ = type; | 
 |     max_effective_connection_type_ = type; | 
 |     return; | 
 |   } | 
 |  | 
 |   if (min_effective_connection_type_ == | 
 |           net::EFFECTIVE_CONNECTION_TYPE_OFFLINE && | 
 |       max_effective_connection_type_ == | 
 |           net::EFFECTIVE_CONNECTION_TYPE_OFFLINE) { | 
 |     min_effective_connection_type_ = type; | 
 |     max_effective_connection_type_ = type; | 
 |     return; | 
 |   } | 
 |  | 
 |   min_effective_connection_type_ = | 
 |       std::min(min_effective_connection_type_, effective_connection_type_); | 
 |   max_effective_connection_type_ = | 
 |       std::max(max_effective_connection_type_, effective_connection_type_); | 
 |  | 
 |   DCHECK_EQ( | 
 |       min_effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN, | 
 |       max_effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN); | 
 |   DCHECK_EQ( | 
 |       min_effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_OFFLINE, | 
 |       max_effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_OFFLINE); | 
 | } | 
 |  | 
 | }  // namespace metrics |