| // Copyright (c) 2012 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 "net/base/network_change_notifier.h" |
| |
| #include "base/metrics/histogram.h" |
| #include "base/synchronization/lock.h" |
| #include "build/build_config.h" |
| #include "googleurl/src/gurl.h" |
| #include "net/base/net_util.h" |
| #include "net/base/network_change_notifier_factory.h" |
| #include "net/dns/dns_config_service.h" |
| |
| #if defined(OS_WIN) |
| #include "net/base/network_change_notifier_win.h" |
| #elif defined(OS_LINUX) && !defined(OS_CHROMEOS) |
| #include "net/base/network_change_notifier_linux.h" |
| #elif defined(OS_MACOSX) |
| #include "net/base/network_change_notifier_mac.h" |
| #endif |
| |
| namespace net { |
| |
| namespace { |
| |
| // The actual singleton notifier. The class contract forbids usage of the API |
| // in ways that would require us to place locks around access to this object. |
| // (The prohibition on global non-POD objects makes it tricky to do such a thing |
| // anyway.) |
| NetworkChangeNotifier* g_network_change_notifier = NULL; |
| |
| // Class factory singleton. |
| NetworkChangeNotifierFactory* g_network_change_notifier_factory = NULL; |
| |
| class MockNetworkChangeNotifier : public NetworkChangeNotifier { |
| public: |
| virtual ConnectionType GetCurrentConnectionType() const OVERRIDE { |
| return CONNECTION_UNKNOWN; |
| } |
| }; |
| |
| } // namespace |
| |
| // The main observer class that records UMAs for network events. |
| class HistogramWatcher |
| : public NetworkChangeNotifier::ConnectionTypeObserver, |
| public NetworkChangeNotifier::IPAddressObserver, |
| public NetworkChangeNotifier::DNSObserver, |
| public NetworkChangeNotifier::NetworkChangeObserver { |
| public: |
| HistogramWatcher() |
| : last_ip_address_change_(base::TimeTicks::Now()), |
| last_connection_change_(base::TimeTicks::Now()), |
| last_dns_change_(base::TimeTicks::Now()), |
| last_network_change_(base::TimeTicks::Now()), |
| last_connection_type_(NetworkChangeNotifier::CONNECTION_UNKNOWN), |
| offline_packets_received_(0) {} |
| |
| // Registers our three Observer implementations. This is called from the |
| // network thread so that our Observer implementations are also called |
| // from the network thread. This avoids multi-threaded race conditions |
| // because the only other interface, |NotifyDataReceived| is also |
| // only called from the network thread. |
| void Init() { |
| NetworkChangeNotifier::AddConnectionTypeObserver(this); |
| NetworkChangeNotifier::AddIPAddressObserver(this); |
| NetworkChangeNotifier::AddDNSObserver(this); |
| NetworkChangeNotifier::AddNetworkChangeObserver(this); |
| } |
| |
| virtual ~HistogramWatcher() {} |
| |
| // NetworkChangeNotifier::IPAddressObserver implementation. |
| virtual void OnIPAddressChanged() OVERRIDE { |
| UMA_HISTOGRAM_MEDIUM_TIMES("NCN.IPAddressChange", |
| SinceLast(&last_ip_address_change_)); |
| UMA_HISTOGRAM_MEDIUM_TIMES( |
| "NCN.ConnectionTypeChangeToIPAddressChange", |
| last_ip_address_change_ - last_connection_change_); |
| } |
| |
| // NetworkChangeNotifier::ConnectionTypeObserver implementation. |
| virtual void OnConnectionTypeChanged( |
| NetworkChangeNotifier::ConnectionType type) OVERRIDE { |
| if (type != NetworkChangeNotifier::CONNECTION_NONE) { |
| UMA_HISTOGRAM_MEDIUM_TIMES("NCN.OnlineChange", |
| SinceLast(&last_connection_change_)); |
| |
| if (offline_packets_received_) { |
| if ((last_connection_change_ - last_offline_packet_received_) < |
| base::TimeDelta::FromSeconds(5)) { |
| // We can compare this sum with the sum of NCN.OfflineDataRecv. |
| UMA_HISTOGRAM_COUNTS_10000( |
| "NCN.OfflineDataRecvAny5sBeforeOnline", |
| offline_packets_received_); |
| } |
| |
| UMA_HISTOGRAM_MEDIUM_TIMES("NCN.OfflineDataRecvUntilOnline", |
| last_connection_change_ - |
| last_offline_packet_received_); |
| } |
| } else { |
| UMA_HISTOGRAM_MEDIUM_TIMES("NCN.OfflineChange", |
| SinceLast(&last_connection_change_)); |
| } |
| UMA_HISTOGRAM_MEDIUM_TIMES( |
| "NCN.IPAddressChangeToConnectionTypeChange", |
| last_connection_change_ - last_ip_address_change_); |
| |
| offline_packets_received_ = 0; |
| last_connection_type_ = type; |
| polling_interval_ = base::TimeDelta::FromSeconds(1); |
| } |
| |
| // NetworkChangeNotifier::DNSObserver implementation. |
| virtual void OnDNSChanged() OVERRIDE { |
| UMA_HISTOGRAM_MEDIUM_TIMES("NCN.DNSConfigChange", |
| SinceLast(&last_dns_change_)); |
| } |
| |
| // NetworkChangeNotifier::NetworkChangeObserver implementation. |
| virtual void OnNetworkChanged( |
| NetworkChangeNotifier::ConnectionType type) OVERRIDE { |
| if (type != NetworkChangeNotifier::CONNECTION_NONE) { |
| UMA_HISTOGRAM_MEDIUM_TIMES("NCN.NetworkOnlineChange", |
| SinceLast(&last_network_change_)); |
| } else { |
| UMA_HISTOGRAM_MEDIUM_TIMES("NCN.NetworkOfflineChange", |
| SinceLast(&last_network_change_)); |
| } |
| } |
| |
| // Record histogram data whenever we receive a packet but think we're |
| // offline. Should only be called from the network thread. |
| void NotifyDataReceived(const GURL& source) { |
| if (last_connection_type_ != NetworkChangeNotifier::CONNECTION_NONE || |
| IsLocalhost(source.host()) || |
| !(source.SchemeIs("http") || source.SchemeIs("https"))) { |
| return; |
| } |
| |
| base::TimeTicks current_time = base::TimeTicks::Now(); |
| UMA_HISTOGRAM_MEDIUM_TIMES("NCN.OfflineDataRecv", |
| current_time - last_connection_change_); |
| offline_packets_received_++; |
| last_offline_packet_received_ = current_time; |
| |
| if ((current_time - last_polled_connection_) > polling_interval_) { |
| polling_interval_ *= 2; |
| last_polled_connection_ = current_time; |
| base::TimeTicks started_get_connection_type = base::TimeTicks::Now(); |
| last_polled_connection_type_ = |
| NetworkChangeNotifier::GetConnectionType(); |
| UMA_HISTOGRAM_TIMES("NCN.GetConnectionTypeTime", |
| base::TimeTicks::Now() - |
| started_get_connection_type); |
| } |
| if (last_polled_connection_type_ == |
| NetworkChangeNotifier::CONNECTION_NONE) { |
| UMA_HISTOGRAM_MEDIUM_TIMES("NCN.PollingOfflineDataRecv", |
| current_time - last_connection_change_); |
| } |
| } |
| |
| private: |
| static base::TimeDelta SinceLast(base::TimeTicks *last_time) { |
| base::TimeTicks current_time = base::TimeTicks::Now(); |
| base::TimeDelta delta = current_time - *last_time; |
| *last_time = current_time; |
| return delta; |
| } |
| |
| base::TimeTicks last_ip_address_change_; |
| base::TimeTicks last_connection_change_; |
| base::TimeTicks last_dns_change_; |
| base::TimeTicks last_network_change_; |
| base::TimeTicks last_offline_packet_received_; |
| base::TimeTicks last_polled_connection_; |
| // |polling_interval_| is initialized by |OnConnectionTypeChanged| on our |
| // first transition to offline and on subsequent transitions. Once offline, |
| // |polling_interval_| doubles as offline data is received and we poll |
| // with |NetworkChangeNotifier::GetConnectionType| to verify the connection |
| // state. |
| base::TimeDelta polling_interval_; |
| // |last_connection_type_| is the last value passed to |
| // |OnConnectionTypeChanged|. |
| NetworkChangeNotifier::ConnectionType last_connection_type_; |
| // |last_polled_connection_type_| is last result from calling |
| // |NetworkChangeNotifier::GetConnectionType| in |NotifyDataReceived|. |
| NetworkChangeNotifier::ConnectionType last_polled_connection_type_; |
| int32 offline_packets_received_; |
| |
| DISALLOW_COPY_AND_ASSIGN(HistogramWatcher); |
| }; |
| |
| // NetworkState is thread safe. |
| class NetworkChangeNotifier::NetworkState { |
| public: |
| NetworkState() {} |
| ~NetworkState() {} |
| |
| void GetDnsConfig(DnsConfig* config) const { |
| base::AutoLock lock(lock_); |
| *config = dns_config_; |
| } |
| |
| void SetDnsConfig(const DnsConfig& dns_config) { |
| base::AutoLock lock(lock_); |
| dns_config_ = dns_config; |
| } |
| |
| private: |
| mutable base::Lock lock_; |
| DnsConfig dns_config_; |
| }; |
| |
| NetworkChangeNotifier::NetworkChangeCalculatorParams:: |
| NetworkChangeCalculatorParams() { |
| } |
| |
| // Calculates NetworkChange signal from IPAddress and ConnectionType signals. |
| class NetworkChangeNotifier::NetworkChangeCalculator |
| : public ConnectionTypeObserver, |
| public IPAddressObserver { |
| public: |
| NetworkChangeCalculator(const NetworkChangeCalculatorParams& params) |
| : params_(params), |
| have_announced_(false), |
| last_announced_connection_type_(CONNECTION_NONE), |
| pending_connection_type_(CONNECTION_NONE) {} |
| |
| void Init() { |
| AddConnectionTypeObserver(this); |
| AddIPAddressObserver(this); |
| } |
| |
| virtual ~NetworkChangeCalculator() { |
| RemoveConnectionTypeObserver(this); |
| RemoveIPAddressObserver(this); |
| } |
| |
| // NetworkChangeNotifier::IPAddressObserver implementation. |
| virtual void OnIPAddressChanged() OVERRIDE { |
| base::TimeDelta delay = last_announced_connection_type_ == CONNECTION_NONE |
| ? params_.ip_address_offline_delay_ : params_.ip_address_online_delay_; |
| // Cancels any previous timer. |
| timer_.Start(FROM_HERE, delay, this, &NetworkChangeCalculator::Notify); |
| } |
| |
| // NetworkChangeNotifier::ConnectionTypeObserver implementation. |
| virtual void OnConnectionTypeChanged(ConnectionType type) OVERRIDE { |
| pending_connection_type_ = type; |
| base::TimeDelta delay = last_announced_connection_type_ == CONNECTION_NONE |
| ? params_.connection_type_offline_delay_ |
| : params_.connection_type_online_delay_; |
| // Cancels any previous timer. |
| timer_.Start(FROM_HERE, delay, this, &NetworkChangeCalculator::Notify); |
| } |
| |
| private: |
| void Notify() { |
| // Don't bother signaling about dead connections. |
| if (have_announced_ && |
| (last_announced_connection_type_ == CONNECTION_NONE) && |
| (pending_connection_type_ == CONNECTION_NONE)) { |
| return; |
| } |
| have_announced_ = true; |
| last_announced_connection_type_ = pending_connection_type_; |
| // Immediately before sending out an online signal, send out an offline |
| // signal to perform any destructive actions before constructive actions. |
| if (pending_connection_type_ != CONNECTION_NONE) |
| NetworkChangeNotifier::NotifyObserversOfNetworkChange(CONNECTION_NONE); |
| NetworkChangeNotifier::NotifyObserversOfNetworkChange( |
| pending_connection_type_); |
| } |
| |
| const NetworkChangeCalculatorParams params_; |
| |
| // Indicates if NotifyObserversOfNetworkChange has been called yet. |
| bool have_announced_; |
| // Last value passed to NotifyObserversOfNetworkChange. |
| ConnectionType last_announced_connection_type_; |
| // Value to pass to NotifyObserversOfNetworkChange when Notify is called. |
| ConnectionType pending_connection_type_; |
| // Used to delay notifications so duplicates can be combined. |
| base::OneShotTimer<NetworkChangeCalculator> timer_; |
| }; |
| |
| NetworkChangeNotifier::~NetworkChangeNotifier() { |
| DCHECK_EQ(this, g_network_change_notifier); |
| g_network_change_notifier = NULL; |
| } |
| |
| // static |
| void NetworkChangeNotifier::SetFactory( |
| NetworkChangeNotifierFactory* factory) { |
| CHECK(!g_network_change_notifier_factory); |
| g_network_change_notifier_factory = factory; |
| } |
| |
| // static |
| NetworkChangeNotifier* NetworkChangeNotifier::Create() { |
| if (g_network_change_notifier_factory) |
| return g_network_change_notifier_factory->CreateInstance(); |
| |
| #if defined(OS_WIN) |
| NetworkChangeNotifierWin* network_change_notifier = |
| new NetworkChangeNotifierWin(); |
| network_change_notifier->WatchForAddressChange(); |
| return network_change_notifier; |
| #elif defined(OS_CHROMEOS) || defined(OS_ANDROID) |
| // ChromeOS and Android builds MUST use their own class factory. |
| #if !defined(OS_CHROMEOS) |
| // TODO(oshima): ash_shell do not have access to chromeos'es |
| // notifier yet. Re-enable this when chromeos'es notifier moved to |
| // chromeos root directory. crbug.com/119298. |
| CHECK(false); |
| #endif |
| return NULL; |
| #elif defined(OS_LINUX) |
| return NetworkChangeNotifierLinux::Create(); |
| #elif defined(OS_MACOSX) |
| return new NetworkChangeNotifierMac(); |
| #else |
| NOTIMPLEMENTED(); |
| return NULL; |
| #endif |
| } |
| |
| // static |
| NetworkChangeNotifier::ConnectionType |
| NetworkChangeNotifier::GetConnectionType() { |
| return g_network_change_notifier ? |
| g_network_change_notifier->GetCurrentConnectionType() : |
| CONNECTION_UNKNOWN; |
| } |
| |
| // static |
| void NetworkChangeNotifier::GetDnsConfig(DnsConfig* config) { |
| if (!g_network_change_notifier) { |
| *config = DnsConfig(); |
| } else { |
| g_network_change_notifier->network_state_->GetDnsConfig(config); |
| } |
| } |
| |
| // static |
| const char* NetworkChangeNotifier::ConnectionTypeToString( |
| ConnectionType type) { |
| static const char* kConnectionTypeNames[] = { |
| "CONNECTION_UNKNOWN", |
| "CONNECTION_ETHERNET", |
| "CONNECTION_WIFI", |
| "CONNECTION_2G", |
| "CONNECTION_3G", |
| "CONNECTION_4G", |
| "CONNECTION_NONE" |
| }; |
| COMPILE_ASSERT( |
| arraysize(kConnectionTypeNames) == |
| NetworkChangeNotifier::CONNECTION_NONE + 1, |
| ConnectionType_name_count_mismatch); |
| if (type < CONNECTION_UNKNOWN || type > CONNECTION_NONE) { |
| NOTREACHED(); |
| return "CONNECTION_INVALID"; |
| } |
| return kConnectionTypeNames[type]; |
| } |
| |
| // static |
| void NetworkChangeNotifier::NotifyDataReceived(const GURL& source) { |
| if (!g_network_change_notifier) |
| return; |
| g_network_change_notifier->histogram_watcher_->NotifyDataReceived(source); |
| } |
| |
| // static |
| void NetworkChangeNotifier::InitHistogramWatcher() { |
| if (!g_network_change_notifier) |
| return; |
| g_network_change_notifier->histogram_watcher_->Init(); |
| } |
| |
| #if defined(OS_LINUX) |
| // static |
| const internal::AddressTrackerLinux* |
| NetworkChangeNotifier::GetAddressTracker() { |
| return g_network_change_notifier ? |
| g_network_change_notifier->GetAddressTrackerInternal() : NULL; |
| } |
| #endif |
| |
| // static |
| bool NetworkChangeNotifier::IsOffline() { |
| return GetConnectionType() == CONNECTION_NONE; |
| } |
| |
| // static |
| bool NetworkChangeNotifier::IsConnectionCellular(ConnectionType type) { |
| bool is_cellular = false; |
| switch (type) { |
| case CONNECTION_2G: |
| case CONNECTION_3G: |
| case CONNECTION_4G: |
| is_cellular = true; |
| break; |
| case CONNECTION_UNKNOWN: |
| case CONNECTION_ETHERNET: |
| case CONNECTION_WIFI: |
| case CONNECTION_NONE: |
| is_cellular = false; |
| break; |
| } |
| return is_cellular; |
| } |
| |
| // static |
| NetworkChangeNotifier* NetworkChangeNotifier::CreateMock() { |
| return new MockNetworkChangeNotifier(); |
| } |
| |
| void NetworkChangeNotifier::AddIPAddressObserver(IPAddressObserver* observer) { |
| if (g_network_change_notifier) |
| g_network_change_notifier->ip_address_observer_list_->AddObserver(observer); |
| } |
| |
| void NetworkChangeNotifier::AddConnectionTypeObserver( |
| ConnectionTypeObserver* observer) { |
| if (g_network_change_notifier) { |
| g_network_change_notifier->connection_type_observer_list_->AddObserver( |
| observer); |
| } |
| } |
| |
| void NetworkChangeNotifier::AddDNSObserver(DNSObserver* observer) { |
| if (g_network_change_notifier) { |
| g_network_change_notifier->resolver_state_observer_list_->AddObserver( |
| observer); |
| } |
| } |
| |
| void NetworkChangeNotifier::AddNetworkChangeObserver( |
| NetworkChangeObserver* observer) { |
| if (g_network_change_notifier) { |
| g_network_change_notifier->network_change_observer_list_->AddObserver( |
| observer); |
| } |
| } |
| |
| void NetworkChangeNotifier::RemoveIPAddressObserver( |
| IPAddressObserver* observer) { |
| if (g_network_change_notifier) { |
| g_network_change_notifier->ip_address_observer_list_->RemoveObserver( |
| observer); |
| } |
| } |
| |
| void NetworkChangeNotifier::RemoveConnectionTypeObserver( |
| ConnectionTypeObserver* observer) { |
| if (g_network_change_notifier) { |
| g_network_change_notifier->connection_type_observer_list_->RemoveObserver( |
| observer); |
| } |
| } |
| |
| void NetworkChangeNotifier::RemoveDNSObserver(DNSObserver* observer) { |
| if (g_network_change_notifier) { |
| g_network_change_notifier->resolver_state_observer_list_->RemoveObserver( |
| observer); |
| } |
| } |
| |
| void NetworkChangeNotifier::RemoveNetworkChangeObserver( |
| NetworkChangeObserver* observer) { |
| if (g_network_change_notifier) { |
| g_network_change_notifier->network_change_observer_list_->RemoveObserver( |
| observer); |
| } |
| } |
| |
| NetworkChangeNotifier::NetworkChangeNotifier( |
| const NetworkChangeCalculatorParams& params |
| /*= NetworkChangeCalculatorParams()*/) |
| : ip_address_observer_list_( |
| new ObserverListThreadSafe<IPAddressObserver>( |
| ObserverListBase<IPAddressObserver>::NOTIFY_EXISTING_ONLY)), |
| connection_type_observer_list_( |
| new ObserverListThreadSafe<ConnectionTypeObserver>( |
| ObserverListBase<ConnectionTypeObserver>::NOTIFY_EXISTING_ONLY)), |
| resolver_state_observer_list_( |
| new ObserverListThreadSafe<DNSObserver>( |
| ObserverListBase<DNSObserver>::NOTIFY_EXISTING_ONLY)), |
| network_change_observer_list_( |
| new ObserverListThreadSafe<NetworkChangeObserver>( |
| ObserverListBase<NetworkChangeObserver>::NOTIFY_EXISTING_ONLY)), |
| network_state_(new NetworkState()), |
| histogram_watcher_(new HistogramWatcher()), |
| network_change_calculator_(new NetworkChangeCalculator(params)) { |
| DCHECK(!g_network_change_notifier); |
| g_network_change_notifier = this; |
| network_change_calculator_->Init(); |
| } |
| |
| #if defined(OS_LINUX) |
| const internal::AddressTrackerLinux* |
| NetworkChangeNotifier::GetAddressTrackerInternal() const { |
| return NULL; |
| } |
| #endif |
| |
| // static |
| void NetworkChangeNotifier::NotifyObserversOfIPAddressChange() { |
| if (g_network_change_notifier) { |
| g_network_change_notifier->ip_address_observer_list_->Notify( |
| &IPAddressObserver::OnIPAddressChanged); |
| } |
| } |
| |
| // static |
| void NetworkChangeNotifier::NotifyObserversOfDNSChange() { |
| if (g_network_change_notifier) { |
| g_network_change_notifier->resolver_state_observer_list_->Notify( |
| &DNSObserver::OnDNSChanged); |
| } |
| } |
| |
| // static |
| void NetworkChangeNotifier::SetDnsConfig(const DnsConfig& config) { |
| if (!g_network_change_notifier) |
| return; |
| g_network_change_notifier->network_state_->SetDnsConfig(config); |
| NotifyObserversOfDNSChange(); |
| } |
| |
| void NetworkChangeNotifier::NotifyObserversOfConnectionTypeChange() { |
| if (g_network_change_notifier) { |
| g_network_change_notifier->connection_type_observer_list_->Notify( |
| &ConnectionTypeObserver::OnConnectionTypeChanged, |
| GetConnectionType()); |
| } |
| } |
| |
| void NetworkChangeNotifier::NotifyObserversOfNetworkChange( |
| ConnectionType type) { |
| if (g_network_change_notifier) { |
| g_network_change_notifier->network_change_observer_list_->Notify( |
| &NetworkChangeObserver::OnNetworkChanged, |
| type); |
| } |
| } |
| |
| NetworkChangeNotifier::DisableForTest::DisableForTest() |
| : network_change_notifier_(g_network_change_notifier) { |
| DCHECK(g_network_change_notifier); |
| g_network_change_notifier = NULL; |
| } |
| |
| NetworkChangeNotifier::DisableForTest::~DisableForTest() { |
| DCHECK(!g_network_change_notifier); |
| g_network_change_notifier = network_change_notifier_; |
| } |
| |
| } // namespace net |