blob: fa0ad775afa4166642802596a16a938bb3d94e43 [file] [log] [blame]
// 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 "net/base/network_change_notifier_factory.h"
#include "base/metrics/histogram.h"
#include "build/build_config.h"
#include "googleurl/src/gurl.h"
#include "net/base/net_util.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 {
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 InitHistogramWatcher() {
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;
}
// NetworkChangeNotifier::DNSObserver implementation.
virtual void OnDNSChanged(unsigned detail) OVERRIDE {
if (detail == NetworkChangeNotifier::CHANGE_DNS_SETTINGS) {
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;
}
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_;
NetworkChangeNotifier::ConnectionType last_connection_type_;
int32 offline_packets_received_;
DISALLOW_COPY_AND_ASSIGN(HistogramWatcher);
};
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::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_->InitHistogramWatcher();
}
#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::IsWatchingDNS() {
if (!g_network_change_notifier)
return false;
base::AutoLock lock(g_network_change_notifier->watching_dns_lock_);
return g_network_change_notifier->watching_dns_;
}
// 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)),
watching_dns_(false) {
DCHECK(!g_network_change_notifier);
g_network_change_notifier = this;
histogram_watcher_.reset(new HistogramWatcher());
}
#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(unsigned detail) {
if (g_network_change_notifier) {
{
base::AutoLock lock(g_network_change_notifier->watching_dns_lock_);
if (detail & NetworkChangeNotifier::CHANGE_DNS_WATCH_STARTED) {
g_network_change_notifier->watching_dns_ = true;
} else if (detail & NetworkChangeNotifier::CHANGE_DNS_WATCH_FAILED) {
g_network_change_notifier->watching_dns_ = false;
}
// Include detail that watch is off to spare the call to IsWatchingDNS.
if (!g_network_change_notifier->watching_dns_)
detail |= NetworkChangeNotifier::CHANGE_DNS_WATCH_FAILED;
}
DCHECK(!(detail & NetworkChangeNotifier::CHANGE_DNS_WATCH_FAILED) ||
!(detail & NetworkChangeNotifier::CHANGE_DNS_WATCH_STARTED));
g_network_change_notifier->resolver_state_observer_list_->Notify(
&DNSObserver::OnDNSChanged, detail);
}
}
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