// 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: HistogramWatcher() : last_ip_address_change_(base::TimeTicks::Now()), last_connection_change_(base::TimeTicks::Now()), last_dns_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); } virtual ~HistogramWatcher() {} // NetworkChangeNotifier::IPAddressObserver implementation. virtual void OnIPAddressChanged() OVERRIDE { UMA_HISTOGRAM_MEDIUM_TIMES("NCN.IPAddressChange", SinceLast(&last_ip_address_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_)); } 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_)); } // 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_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::~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::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); } } NetworkChangeNotifier::NetworkChangeNotifier() : ip_address_observer_list_( new ObserverListThreadSafe( ObserverListBase::NOTIFY_EXISTING_ONLY)), connection_type_observer_list_( new ObserverListThreadSafe( ObserverListBase::NOTIFY_EXISTING_ONLY)), resolver_state_observer_list_( new ObserverListThreadSafe( ObserverListBase::NOTIFY_EXISTING_ONLY)), network_state_(new NetworkState()), histogram_watcher_(new HistogramWatcher()) { DCHECK(!g_network_change_notifier); g_network_change_notifier = this; } #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()); } } 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