blob: ca94052239c652ed7620ae38b165340d8d9d2dfa [file] [log] [blame]
// Copyright 2019 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 "chrome/browser/availability/availability_prober.h"
#include <math.h>
#include <cmath>
#include "base/base64.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/guid.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/time/default_clock.h"
#include "base/time/default_tick_clock.h"
#include "build/build_config.h"
#include "chrome/browser/availability/proto/availability_prober_cache_entry.pb.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/network_service_instance.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/http/http_status_code.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#if defined(OS_ANDROID)
#include "net/android/network_library.h"
#include "net/base/network_interfaces.h"
#endif
namespace {
const char kCachePrefKeyPrefix[] = "Availability.Prober.cache";
const char kSuccessHistogram[] = "Availability.Prober.DidSucceed";
const char kSuccessAfterReportedFailure[] =
"Availability.Prober.DidSucceed.AfterReportedFailure";
const char kFinalResultHistogram[] = "Availability.Prober.FinalState";
const char kTimeUntilSuccess[] = "Availability.Prober.TimeUntilSuccess2";
const char kTimeUntilFailure[] = "Availability.Prober.TimeUntilFailure2";
const char kAttemptsBeforeSuccessHistogram[] =
"Availability.Prober.NumAttemptsBeforeSuccess";
const char kHttpRespCodeHistogram[] = "Availability.Prober.ResponseCode";
const char kNetErrorHistogram[] = "Availability.Prober.NetError";
const char kCacheEntryAgeHistogram[] = "Availability.Prober.CacheEntryAge";
// Please keep this up to date with logged histogram suffix
// |Availability.Prober.Clients| in tools/metrics/histograms/histograms.xml.
// These names are also used in prefs so they should not be changed without
// consideration for removing the old value.
std::string NameForClient(AvailabilityProber::ClientName name) {
switch (name) {
case AvailabilityProber::ClientName::kIsolatedPrerenderOriginCheck:
return "IsolatedPrerenderOriginCheck";
case AvailabilityProber::ClientName::kIsolatedPrerenderTLSCanaryCheck:
return "IsolatedPrerenderTLSCanaryCheck";
case AvailabilityProber::ClientName::kIsolatedPrerenderDNSCanaryCheck:
return "IsolatedPrerenderDNSCanaryCheck";
default:
NOTREACHED() << static_cast<int>(name);
return std::string();
}
NOTREACHED();
return std::string();
}
std::string PrefKeyForName(const std::string& name) {
return base::StringPrintf("%s.%s", kCachePrefKeyPrefix, name.c_str());
}
std::string HttpMethodToString(AvailabilityProber::HttpMethod http_method) {
switch (http_method) {
case AvailabilityProber::HttpMethod::kGet:
return "GET";
case AvailabilityProber::HttpMethod::kHead:
return "HEAD";
}
}
// Computes the time delta for a given Backoff algorithm, a base interval, and
// the count of how many attempts have been made thus far.
base::TimeDelta ComputeNextTimeDeltaForBackoff(
AvailabilityProber::Backoff backoff,
base::TimeDelta base_interval,
size_t attempts_so_far) {
switch (backoff) {
case AvailabilityProber::Backoff::kLinear:
return base_interval;
case AvailabilityProber::Backoff::kExponential:
return base_interval * pow(2, attempts_so_far);
}
}
std::string GenerateNetworkID(
network::NetworkConnectionTracker* network_connection_tracker) {
network::mojom::ConnectionType connection_type =
network::mojom::ConnectionType::CONNECTION_UNKNOWN;
if (network_connection_tracker) {
network_connection_tracker->GetConnectionType(&connection_type,
base::DoNothing());
}
std::string id = base::NumberToString(static_cast<int>(connection_type));
bool is_cellular =
network::NetworkConnectionTracker::IsConnectionCellular(connection_type);
if (is_cellular) {
// Don't care about cell connection type.
id = "cell";
}
// Further identify WiFi and cell connections. These calls are only supported
// for Android devices.
#if defined(OS_ANDROID)
if (connection_type == network::mojom::ConnectionType::CONNECTION_WIFI) {
return base::StringPrintf("%s,%s", id.c_str(), net::GetWifiSSID().c_str());
}
if (is_cellular) {
return base::StringPrintf(
"%s,%s", id.c_str(),
net::android::GetTelephonyNetworkOperator().c_str());
}
#endif
return id;
}
absl::optional<base::Value> EncodeCacheEntryValue(
const AvailabilityProberCacheEntry& entry) {
std::string serialized_entry;
bool serialize_to_string_ok = entry.SerializeToString(&serialized_entry);
if (!serialize_to_string_ok)
return absl::nullopt;
std::string base64_encoded;
base::Base64Encode(serialized_entry, &base64_encoded);
return base::Value(base64_encoded);
}
absl::optional<AvailabilityProberCacheEntry> DecodeCacheEntryValue(
const base::Value& value) {
if (!value.is_string())
return absl::nullopt;
std::string base64_decoded;
if (!base::Base64Decode(value.GetString(), &base64_decoded))
return absl::nullopt;
AvailabilityProberCacheEntry entry;
if (!entry.ParseFromString(base64_decoded))
return absl::nullopt;
return entry;
}
base::Time LastModifiedTimeFromCacheEntry(
const AvailabilityProberCacheEntry& entry) {
return base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMicroseconds(entry.last_modified()));
}
void RemoveOldestDictionaryEntry(base::DictionaryValue* dict) {
std::vector<std::string> keys_to_remove;
std::string oldest_key;
base::Time oldest_mod_time = base::Time::Max();
for (auto iter : dict->DictItems()) {
absl::optional<AvailabilityProberCacheEntry> entry =
DecodeCacheEntryValue(iter.second);
if (!entry.has_value()) {
// Also remove anything that can't be decoded.
keys_to_remove.push_back(iter.first);
continue;
}
base::Time mod_time = LastModifiedTimeFromCacheEntry(entry.value());
if (mod_time < oldest_mod_time) {
oldest_key = iter.first;
oldest_mod_time = mod_time;
}
}
if (!oldest_key.empty()) {
keys_to_remove.push_back(oldest_key);
}
for (const std::string& key : keys_to_remove) {
dict->RemoveKey(key);
}
}
#if defined(OS_ANDROID)
bool IsInForeground(base::android::ApplicationState state) {
switch (state) {
case base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES:
return true;
case base::android::APPLICATION_STATE_UNKNOWN:
case base::android::APPLICATION_STATE_HAS_PAUSED_ACTIVITIES:
case base::android::APPLICATION_STATE_HAS_STOPPED_ACTIVITIES:
case base::android::APPLICATION_STATE_HAS_DESTROYED_ACTIVITIES:
return false;
}
}
#endif
} // namespace
AvailabilityProber::RetryPolicy::RetryPolicy() = default;
AvailabilityProber::RetryPolicy::~RetryPolicy() = default;
AvailabilityProber::RetryPolicy::RetryPolicy(
AvailabilityProber::RetryPolicy const&) = default;
AvailabilityProber::TimeoutPolicy::TimeoutPolicy() = default;
AvailabilityProber::TimeoutPolicy::~TimeoutPolicy() = default;
AvailabilityProber::TimeoutPolicy::TimeoutPolicy(
AvailabilityProber::TimeoutPolicy const&) = default;
AvailabilityProber::AvailabilityProber(
Delegate* delegate,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
PrefService* pref_service,
const ClientName name,
const GURL& url,
const HttpMethod http_method,
const net::HttpRequestHeaders headers,
const RetryPolicy& retry_policy,
const TimeoutPolicy& timeout_policy,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
const size_t max_cache_entries,
base::TimeDelta revalidate_cache_after)
: AvailabilityProber(delegate,
url_loader_factory,
pref_service,
name,
url,
http_method,
headers,
retry_policy,
timeout_policy,
traffic_annotation,
max_cache_entries,
revalidate_cache_after,
base::DefaultTickClock::GetInstance(),
base::DefaultClock::GetInstance()) {}
AvailabilityProber::AvailabilityProber(
Delegate* delegate,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
PrefService* pref_service,
const ClientName name,
const GURL& url,
const HttpMethod http_method,
const net::HttpRequestHeaders headers,
const RetryPolicy& retry_policy,
const TimeoutPolicy& timeout_policy,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
const size_t max_cache_entries,
base::TimeDelta revalidate_cache_after,
const base::TickClock* tick_clock,
const base::Clock* clock)
: delegate_(delegate),
name_(NameForClient(name)),
pref_key_(PrefKeyForName(NameForClient(name))),
url_(url),
http_method_(http_method),
headers_(headers),
retry_policy_(retry_policy),
timeout_policy_(timeout_policy),
max_cache_entries_(max_cache_entries),
revalidate_cache_after_(revalidate_cache_after),
traffic_annotation_(traffic_annotation),
successive_retry_count_(0),
successive_timeout_count_(0),
cached_probe_results_(std::make_unique<base::DictionaryValue>()),
tick_clock_(tick_clock),
clock_(clock),
network_connection_tracker_(nullptr),
pref_service_(pref_service),
url_loader_factory_(url_loader_factory),
reported_external_failure_(false),
weak_factory_(this) {
DCHECK(delegate_);
// The NetworkConnectionTracker can only be used directly on the UI thread.
// Otherwise we use the cross-thread call.
if (content::BrowserThread::CurrentlyOn(content::BrowserThread::UI) &&
content::GetNetworkConnectionTracker()) {
AddSelfAsNetworkConnectionObserver(content::GetNetworkConnectionTracker());
} else {
content::GetNetworkConnectionTrackerFromUIThread(
base::BindOnce(&AvailabilityProber::AddSelfAsNetworkConnectionObserver,
weak_factory_.GetWeakPtr()));
}
if (pref_service_) {
cached_probe_results_ =
pref_service_->GetDictionary(pref_key_)->CreateDeepCopy();
}
}
AvailabilityProber::~AvailabilityProber() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (network_connection_tracker_)
network_connection_tracker_->RemoveNetworkConnectionObserver(this);
}
base::WeakPtr<AvailabilityProber> AvailabilityProber::AsWeakPtr() const {
return weak_factory_.GetWeakPtr();
}
// static
void AvailabilityProber::RegisterProfilePrefs(PrefRegistrySimple* registry) {
for (int i = static_cast<int>(
AvailabilityProber::ClientName::kIsolatedPrerenderOriginCheck);
i <= static_cast<int>(AvailabilityProber::ClientName::kMaxValue); i++) {
if (i == static_cast<int>(AvailabilityProber::ClientName::
kIsolatedPrerenderCanaryCheck_DEPRECATED)) {
continue;
}
registry->RegisterDictionaryPref(PrefKeyForName(
NameForClient(static_cast<AvailabilityProber::ClientName>(i))));
}
}
// static
void AvailabilityProber::ClearData(PrefService* pref_service) {
for (int i = static_cast<int>(
AvailabilityProber::ClientName::kIsolatedPrerenderOriginCheck);
i <= static_cast<int>(AvailabilityProber::ClientName::kMaxValue); i++) {
if (i == static_cast<int>(AvailabilityProber::ClientName::
kIsolatedPrerenderCanaryCheck_DEPRECATED)) {
continue;
}
std::string key = PrefKeyForName(
NameForClient(static_cast<AvailabilityProber::ClientName>(i)));
DictionaryPrefUpdate update(pref_service, key);
update.Get()->Clear();
}
}
void AvailabilityProber::AddSelfAsNetworkConnectionObserver(
network::NetworkConnectionTracker* network_connection_tracker) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
network_connection_tracker_ = network_connection_tracker;
network_connection_tracker_->AddNetworkConnectionObserver(this);
}
void AvailabilityProber::OnProbingEnd() {
base::Value* cache_entry =
cached_probe_results_->FindKey(GetCacheKeyForCurrentNetwork());
if (cache_entry) {
absl::optional<AvailabilityProberCacheEntry> entry =
DecodeCacheEntryValue(*cache_entry);
if (entry.has_value()) {
base::BooleanHistogram::FactoryGet(
AppendNameToHistogram(kFinalResultHistogram),
base::HistogramBase::kUmaTargetedHistogramFlag)
->Add(entry.value().is_success());
}
}
ResetState();
}
void AvailabilityProber::ResetState() {
time_when_set_active_ = absl::nullopt;
successive_retry_count_ = 0;
successive_timeout_count_ = 0;
retry_timer_.reset();
timeout_timer_.reset();
url_loader_.reset();
reported_external_failure_ = false;
#if defined(OS_ANDROID)
application_status_listener_.reset();
#endif
}
void AvailabilityProber::SendNowIfInactive(bool send_only_in_foreground) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (time_when_set_active_.has_value())
return;
#if defined(OS_ANDROID)
if (send_only_in_foreground &&
!IsInForeground(base::android::ApplicationStatusListener::GetState())) {
// base::Unretained is safe here because the callback is owned by
// |application_status_listener_| which is owned by |this|.
application_status_listener_ =
base::android::ApplicationStatusListener::New(
base::BindRepeating(&AvailabilityProber::OnApplicationStateChange,
base::Unretained(this)));
return;
}
#endif
CreateAndStartURLLoader();
}
void AvailabilityProber::RepeatedlyProbe(base::TimeDelta interval,
bool send_only_in_foreground) {
repeating_timer_ = std::make_unique<base::RepeatingTimer>(tick_clock_);
// base::Unretained is safe here because |repeating_timer_| is owned by
// |this|.
repeating_timer_->Start(
FROM_HERE, interval,
base::BindRepeating(&AvailabilityProber::SendNowIfInactive,
base::Unretained(this), send_only_in_foreground));
SendNowIfInactive(send_only_in_foreground);
}
#if defined(OS_ANDROID)
void AvailabilityProber::OnApplicationStateChange(
base::android::ApplicationState new_state) {
DCHECK(application_status_listener_);
if (!IsInForeground(new_state))
return;
SendNowIfInactive(false);
application_status_listener_.reset();
}
#endif
void AvailabilityProber::OnConnectionChanged(
network::mojom::ConnectionType type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If a probe is already in flight we don't want to continue to use it since
// the network has just changed. Reset all state and start again.
ResetState();
CreateAndStartURLLoader();
}
void AvailabilityProber::CreateAndStartURLLoader() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!time_when_set_active_.has_value() || successive_retry_count_ > 0);
DCHECK(!retry_timer_ || !retry_timer_->IsRunning());
DCHECK(!url_loader_);
if (!delegate_->ShouldSendNextProbe()) {
OnProbingEnd();
return;
}
time_when_set_active_ = clock_->Now();
GURL url = url_;
if (retry_policy_.use_random_urls) {
std::string query = "guid=" + base::GenerateGUID();
GURL::Replacements replacements;
replacements.SetQuery(query.c_str(), url::Component(0, query.length()));
url = url.ReplaceComponents(replacements);
}
auto request = std::make_unique<network::ResourceRequest>();
request->url = url;
request->method = HttpMethodToString(http_method_);
request->headers = headers_;
request->load_flags = net::LOAD_DISABLE_CACHE;
request->credentials_mode = network::mojom::CredentialsMode::kOmit;
url_loader_ =
network::SimpleURLLoader::Create(std::move(request), traffic_annotation_);
url_loader_->SetAllowHttpErrorResults(true);
url_loader_->DownloadToString(
url_loader_factory_.get(),
base::BindOnce(&AvailabilityProber::OnURLLoadComplete,
base::Unretained(this)),
1024);
// We don't use SimpleURLLoader's timeout functionality because it is not
// possible to test by AvailabilityProberTest.
base::TimeDelta ttl = ComputeNextTimeDeltaForBackoff(
timeout_policy_.backoff, timeout_policy_.base_timeout,
successive_timeout_count_);
timeout_timer_ = std::make_unique<base::OneShotTimer>(tick_clock_);
// base::Unretained is safe because |timeout_timer_| is owned by this.
timeout_timer_->Start(FROM_HERE, ttl,
base::BindOnce(&AvailabilityProber::ProcessProbeTimeout,
base::Unretained(this)));
}
void AvailabilityProber::OnURLLoadComplete(
std::unique_ptr<std::string> response_body) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
int response_code = -1;
if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers) {
response_code = url_loader_->ResponseInfo()->headers->response_code();
base::UmaHistogramSparse(AppendNameToHistogram(kHttpRespCodeHistogram),
std::abs(response_code));
}
base::UmaHistogramSparse(AppendNameToHistogram(kNetErrorHistogram),
std::abs(url_loader_->NetError()));
bool was_successful = delegate_->IsResponseSuccess(
static_cast<net::Error>(url_loader_->NetError()),
url_loader_->ResponseInfo(), std::move(response_body));
timeout_timer_.reset();
url_loader_.reset();
successive_timeout_count_ = 0;
if (was_successful) {
ProcessProbeSuccess();
return;
}
ProcessProbeFailure();
}
void AvailabilityProber::ProcessProbeTimeout() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(url_loader_);
// Since we manually set the timeout handling of the probe, record the net
// error here as well for simplicity.
base::UmaHistogramSparse(AppendNameToHistogram(kNetErrorHistogram),
std::abs(net::ERR_TIMED_OUT));
url_loader_.reset();
successive_timeout_count_++;
ProcessProbeFailure();
}
void AvailabilityProber::ProcessProbeFailure() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!retry_timer_ || !retry_timer_->IsRunning());
DCHECK(!timeout_timer_ || !timeout_timer_->IsRunning());
DCHECK(!url_loader_);
DCHECK(time_when_set_active_.has_value());
RecordProbeResult(false);
DCHECK(time_when_set_active_.has_value());
if (time_when_set_active_.has_value()) {
base::TimeDelta active_time = clock_->Now() - time_when_set_active_.value();
base::Histogram::FactoryTimeGet(
AppendNameToHistogram(kTimeUntilFailure),
base::TimeDelta::FromMilliseconds(0) /* minimum */,
base::TimeDelta::FromMilliseconds(60000) /* maximum */,
50 /* bucket_count */, base::HistogramBase::kUmaTargetedHistogramFlag)
->Add(active_time.InMilliseconds());
}
if (retry_policy_.max_retries > successive_retry_count_) {
base::TimeDelta interval = ComputeNextTimeDeltaForBackoff(
retry_policy_.backoff, retry_policy_.base_interval,
successive_retry_count_);
retry_timer_ = std::make_unique<base::OneShotTimer>(tick_clock_);
// base::Unretained is safe because |retry_timer_| is owned by this.
retry_timer_->Start(
FROM_HERE, interval,
base::BindOnce(&AvailabilityProber::CreateAndStartURLLoader,
base::Unretained(this)));
successive_retry_count_++;
return;
}
OnProbingEnd();
}
void AvailabilityProber::ProcessProbeSuccess() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!retry_timer_ || !retry_timer_->IsRunning());
DCHECK(!timeout_timer_ || !timeout_timer_->IsRunning());
DCHECK(!url_loader_);
DCHECK(time_when_set_active_.has_value());
base::LinearHistogram::FactoryGet(
AppendNameToHistogram(kAttemptsBeforeSuccessHistogram), 1 /* minimum */,
25 /* maximum */, 25 /* bucket_count */,
base::HistogramBase::kUmaTargetedHistogramFlag)
// |successive_retry_count_| is zero when the first attempt is successful.
// Increase by one for more intuitive metrics.
->Add(successive_retry_count_ + 1);
DCHECK(time_when_set_active_.has_value());
if (time_when_set_active_.has_value()) {
base::TimeDelta active_time = clock_->Now() - time_when_set_active_.value();
base::Histogram::FactoryTimeGet(
AppendNameToHistogram(kTimeUntilSuccess),
base::TimeDelta::FromMilliseconds(0) /* minimum */,
base::TimeDelta::FromMilliseconds(30000) /* maximum */,
50 /* bucket_count */, base::HistogramBase::kUmaTargetedHistogramFlag)
->Add(active_time.InMilliseconds());
}
RecordProbeResult(true);
OnProbingEnd();
}
absl::optional<bool> AvailabilityProber::LastProbeWasSuccessful() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::Value* cache_entry =
cached_probe_results_->FindKey(GetCacheKeyForCurrentNetwork());
if (!cache_entry)
return absl::nullopt;
absl::optional<AvailabilityProberCacheEntry> entry =
DecodeCacheEntryValue(*cache_entry);
if (!entry.has_value())
return absl::nullopt;
base::TimeDelta cache_entry_age =
clock_->Now() - LastModifiedTimeFromCacheEntry(entry.value());
base::LinearHistogram::FactoryTimeGet(
AppendNameToHistogram(kCacheEntryAgeHistogram),
base::TimeDelta::FromHours(0) /* minimum */,
base::TimeDelta::FromHours(72) /* maximum */, 50 /* bucket_count */,
base::HistogramBase::kUmaTargetedHistogramFlag)
->Add(cache_entry_age.InHours());
// Check if the cache entry should be revalidated because it has expired or
// cache_entry_age is negative because the clock was moved back.
if (cache_entry_age >= revalidate_cache_after_ ||
cache_entry_age < base::TimeDelta()) {
SendNowIfInactive(false);
}
return entry.value().is_success();
}
void AvailabilityProber::ReportExternalFailureAndRetry() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
RecordProbeResult(false);
reported_external_failure_ = true;
SendNowIfInactive(false);
}
void AvailabilityProber::SetOnCompleteCallback(
AvailabilityProberOnCompleteCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
on_complete_callback_ = std::move(callback);
}
void AvailabilityProber::RecordProbeResult(bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AvailabilityProberCacheEntry entry;
entry.set_is_success(success);
entry.set_last_modified(
clock_->Now().ToDeltaSinceWindowsEpoch().InMicroseconds());
absl::optional<base::Value> encoded = EncodeCacheEntryValue(entry);
if (!encoded.has_value()) {
NOTREACHED();
return;
}
base::DictionaryValue* update_dict = cached_probe_results_.get();
if (pref_service_) {
DictionaryPrefUpdate update(pref_service_, pref_key_);
update_dict = update.Get();
}
update_dict->SetKey(GetCacheKeyForCurrentNetwork(),
std::move(encoded.value()));
if (update_dict->DictSize() > max_cache_entries_)
RemoveOldestDictionaryEntry(update_dict);
cached_probe_results_ = update_dict->CreateDeepCopy();
base::BooleanHistogram::FactoryGet(
AppendNameToHistogram(kSuccessHistogram),
base::HistogramBase::kUmaTargetedHistogramFlag)
->Add(success);
if (reported_external_failure_) {
base::BooleanHistogram::FactoryGet(
AppendNameToHistogram(kSuccessAfterReportedFailure),
base::HistogramBase::kUmaTargetedHistogramFlag)
->Add(success);
}
// The callback may delete |this| so run it in a post task.
if (on_complete_callback_) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&AvailabilityProber::RunCallback,
weak_factory_.GetWeakPtr(), success));
}
}
void AvailabilityProber::RunCallback(bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::move(on_complete_callback_).Run(success);
}
std::string AvailabilityProber::GetCacheKeyForCurrentNetwork() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return base::StringPrintf(
"%s;%s:%d", GenerateNetworkID(network_connection_tracker_).c_str(),
url_.host().c_str(), url_.EffectiveIntPort());
}
std::string AvailabilityProber::AppendNameToHistogram(
const std::string& histogram) const {
return base::StringPrintf("%s.%s", histogram.c_str(), name_.c_str());
}