| // 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 |
| 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 |
| 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<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_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 |