blob: 1c47b73da76e6f07c1f0b4e461a8aa390fc951de [file] [log] [blame]
// Copyright 2014 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 "components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_stats.h"
#include "base/callback.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_util.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_type_info.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/proxy_server.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/proxy_resolution/proxy_resolution_service.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
namespace data_reduction_proxy {
namespace {
const int kMinFailedRequestsWhenUnavailable = 1;
const int kMaxSuccessfulRequestsWhenUnavailable = 0;
const int kMaxFailedRequestsBeforeReset = 3;
// Records a net error code that resulted in bypassing the data reduction
// proxy (|is_primary| is true) or the data reduction proxy fallback.
void RecordDataReductionProxyBypassOnNetworkError(
bool is_primary,
const net::ProxyServer& proxy_server,
int net_error) {
if (is_primary) {
base::UmaHistogramSparse("DataReductionProxy.BypassOnNetworkErrorPrimary",
std::abs(net_error));
return;
}
base::UmaHistogramSparse("DataReductionProxy.BypassOnNetworkErrorFallback",
std::abs(net_error));
}
} // namespace
// static
void DataReductionProxyBypassStats::RecordDataReductionProxyBypassInfo(
bool is_primary,
bool bypass_all,
const net::ProxyServer& proxy_server,
DataReductionProxyBypassType bypass_type) {
if (bypass_all) {
if (is_primary) {
UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.BlockTypePrimary",
bypass_type, BYPASS_EVENT_TYPE_MAX);
} else {
UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.BlockTypeFallback",
bypass_type, BYPASS_EVENT_TYPE_MAX);
}
} else {
if (is_primary) {
UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.BypassTypePrimary",
bypass_type, BYPASS_EVENT_TYPE_MAX);
} else {
UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.BypassTypeFallback",
bypass_type, BYPASS_EVENT_TYPE_MAX);
}
}
}
// static
void DataReductionProxyBypassStats::DetectAndRecordMissingViaHeaderResponseCode(
bool is_primary,
const net::HttpResponseHeaders& headers) {
if (HasDataReductionProxyViaHeader(headers, nullptr)) {
// The data reduction proxy via header is present, so don't record anything.
return;
}
if (is_primary) {
base::UmaHistogramSparse(
"DataReductionProxy.MissingViaHeader.ResponseCode.Primary",
headers.response_code());
} else {
base::UmaHistogramSparse(
"DataReductionProxy.MissingViaHeader.ResponseCode.Fallback",
headers.response_code());
}
}
DataReductionProxyBypassStats::DataReductionProxyBypassStats(
DataReductionProxyConfig* config,
UnreachableCallback unreachable_callback)
: data_reduction_proxy_config_(config),
unreachable_callback_(unreachable_callback),
last_bypass_type_(BYPASS_EVENT_TYPE_MAX),
triggering_request_(true),
successful_requests_through_proxy_count_(0),
proxy_net_errors_count_(0),
unavailable_(false) {
DCHECK(config);
// Constructed on the UI thread, but should be checked on the IO thread.
thread_checker_.DetachFromThread();
}
DataReductionProxyBypassStats::~DataReductionProxyBypassStats() {
net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
}
void DataReductionProxyBypassStats::InitializeOnIOThread() {
DCHECK(thread_checker_.CalledOnValidThread());
net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
}
void DataReductionProxyBypassStats::OnUrlRequestCompleted(
const net::URLRequest* request,
bool started,
int net_error) {
DCHECK(thread_checker_.CalledOnValidThread());
base::Optional<DataReductionProxyTypeInfo> proxy_info =
data_reduction_proxy_config_->FindConfiguredDataReductionProxy(
request->proxy_server());
// Ignore requests that did not use the data reduction proxy. The check for
// LOAD_BYPASS_PROXY is necessary because the proxy_server() in the |request|
// might still be set to the data reduction proxy if |request| was retried
// over direct and a network error occurred while retrying it.
if (!proxy_info || (request->load_flags() & net::LOAD_BYPASS_PROXY) != 0 ||
net_error != net::OK) {
return;
}
successful_requests_through_proxy_count_++;
NotifyUnavailabilityIfChanged();
// Report the success counts.
UMA_HISTOGRAM_COUNTS_100(
"DataReductionProxy.SuccessfulRequestCompletionCounts",
proxy_info->proxy_index);
// It is possible that the scheme of request->proxy_server() is different
// from the scheme of proxy_info.proxy_servers.front(). The former may be set
// to QUIC by the network stack, while the latter may be set to HTTPS.
UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.ProxySchemeUsed",
util::ConvertNetProxySchemeToProxyScheme(
request->proxy_server().scheme()),
PROXY_SCHEME_MAX);
if (request->load_flags() & net::LOAD_MAIN_FRAME_DEPRECATED) {
UMA_HISTOGRAM_COUNTS_100(
"DataReductionProxy.SuccessfulRequestCompletionCounts.MainFrame",
proxy_info->proxy_index);
}
}
void DataReductionProxyBypassStats::SetBypassType(
DataReductionProxyBypassType type) {
DCHECK(thread_checker_.CalledOnValidThread());
last_bypass_type_ = type;
triggering_request_ = true;
}
DataReductionProxyBypassType
DataReductionProxyBypassStats::GetBypassType() const {
DCHECK(thread_checker_.CalledOnValidThread());
return last_bypass_type_;
}
void DataReductionProxyBypassStats::OnProxyFallback(
const net::ProxyServer& bypassed_proxy,
int net_error) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!bypassed_proxy.is_valid() || bypassed_proxy.is_direct())
return;
base::Optional<DataReductionProxyTypeInfo> proxy_type_info =
data_reduction_proxy_config_->FindConfiguredDataReductionProxy(
bypassed_proxy);
if (!proxy_type_info)
return;
proxy_net_errors_count_++;
// To account for the case when the proxy is reachable for sometime, and
// then gets blocked, we reset counts when number of errors exceed
// the threshold.
if (proxy_net_errors_count_ >= kMaxFailedRequestsBeforeReset &&
successful_requests_through_proxy_count_ >
kMaxSuccessfulRequestsWhenUnavailable) {
ClearRequestCounts();
} else {
NotifyUnavailabilityIfChanged();
}
const bool is_first_proxy = proxy_type_info->proxy_index == 0U;
RecordDataReductionProxyBypassInfo(is_first_proxy, false, bypassed_proxy,
BYPASS_EVENT_TYPE_NETWORK_ERROR);
RecordDataReductionProxyBypassOnNetworkError(is_first_proxy, bypassed_proxy,
net_error);
}
void DataReductionProxyBypassStats::ClearRequestCounts() {
DCHECK(thread_checker_.CalledOnValidThread());
successful_requests_through_proxy_count_ = 0;
proxy_net_errors_count_ = 0;
}
void DataReductionProxyBypassStats::NotifyUnavailabilityIfChanged() {
DCHECK(thread_checker_.CalledOnValidThread());
bool prev_unavailable = unavailable_;
unavailable_ =
(proxy_net_errors_count_ >= kMinFailedRequestsWhenUnavailable &&
successful_requests_through_proxy_count_ <=
kMaxSuccessfulRequestsWhenUnavailable);
if (prev_unavailable != unavailable_)
unreachable_callback_.Run(unavailable_);
}
void DataReductionProxyBypassStats::RecordBypassedBytesHistograms(
const net::URLRequest& request,
bool data_reduction_proxy_enabled,
const net::ProxyConfig& data_reduction_proxy_config) {
DCHECK(thread_checker_.CalledOnValidThread());
int64_t content_length = request.received_response_content_length();
// Only record histograms when the data reduction proxy is enabled.
if (!data_reduction_proxy_enabled)
return;
// TODO(bengr): Add histogram(s) for byte counts of unsupported schemes, e.g.,
// ws and wss.
if (!request.url().SchemeIsHTTPOrHTTPS())
return;
if (data_reduction_proxy_config_->FindConfiguredDataReductionProxy(
request.proxy_server())) {
RecordBypassedBytes(last_bypass_type_,
DataReductionProxyBypassStats::NOT_BYPASSED,
content_length);
return;
}
if (request.url().SchemeIs(url::kHttpsScheme)) {
RecordBypassedBytes(last_bypass_type_,
DataReductionProxyBypassStats::SSL,
content_length);
return;
}
// Now that the data reduction proxy is a best effort proxy, if the effective
// proxy configuration resolves to anything other than direct:// for a URL,
// the data reduction proxy will not be used.
if (!request.proxy_server().is_valid() ||
!request.proxy_server().is_direct()) {
RecordBypassedBytes(last_bypass_type_,
DataReductionProxyBypassStats::PROXY_OVERRIDDEN,
content_length);
return;
}
if (data_reduction_proxy_config_->IsBypassedByDataReductionProxyLocalRules(
request, data_reduction_proxy_config)) {
RecordBypassedBytes(last_bypass_type_,
DataReductionProxyBypassStats::LOCAL_BYPASS_RULES,
content_length);
return;
}
std::string mime_type;
if (request.response_headers())
request.response_headers()->GetMimeType(&mime_type);
// MIME types are named by <media-type>/<subtype>. Check to see if the media
// type is audio or video in order to record audio/video bypasses separately
// for current bypasses and for the triggering requests of short bypasses.
if ((last_bypass_type_ == BYPASS_EVENT_TYPE_CURRENT ||
(triggering_request_ && last_bypass_type_ == BYPASS_EVENT_TYPE_SHORT)) &&
(mime_type.compare(0, 6, "audio/") == 0 ||
mime_type.compare(0, 6, "video/") == 0)) {
RecordBypassedBytes(last_bypass_type_,
DataReductionProxyBypassStats::AUDIO_VIDEO,
content_length);
triggering_request_ = false;
return;
}
// Report current bypasses of MIME type "application/octet-stream" separately.
if (last_bypass_type_ == BYPASS_EVENT_TYPE_CURRENT &&
mime_type.find("application/octet-stream") != std::string::npos) {
RecordBypassedBytes(last_bypass_type_,
DataReductionProxyBypassStats::APPLICATION_OCTET_STREAM,
content_length);
return;
}
// Only record separate triggering request UMA for short, medium, and long
// bypass events.
if (triggering_request_ &&
(last_bypass_type_ == BYPASS_EVENT_TYPE_SHORT ||
last_bypass_type_ == BYPASS_EVENT_TYPE_MEDIUM ||
last_bypass_type_ == BYPASS_EVENT_TYPE_LONG)) {
RecordBypassedBytes(last_bypass_type_,
DataReductionProxyBypassStats::TRIGGERING_REQUEST,
content_length);
triggering_request_ = false;
return;
}
if (last_bypass_type_ != BYPASS_EVENT_TYPE_MAX) {
RecordBypassedBytes(last_bypass_type_,
DataReductionProxyBypassStats::BYPASSED_BYTES_TYPE_MAX,
content_length);
return;
}
if (data_reduction_proxy_config_->AreDataReductionProxiesBypassed(
request, data_reduction_proxy_config, nullptr)) {
RecordBypassedBytes(last_bypass_type_,
DataReductionProxyBypassStats::NETWORK_ERROR,
content_length);
}
}
void DataReductionProxyBypassStats::OnNetworkChanged(
net::NetworkChangeNotifier::ConnectionType type) {
DCHECK(thread_checker_.CalledOnValidThread());
ClearRequestCounts();
}
void DataReductionProxyBypassStats::RecordBypassedBytes(
DataReductionProxyBypassType bypass_type,
DataReductionProxyBypassStats::BypassedBytesType bypassed_bytes_type,
int64_t content_length) {
DCHECK(thread_checker_.CalledOnValidThread());
// Individual histograms are needed to count the bypassed bytes for each
// bypass type so that we can see the size of requests. This helps us
// remove outliers that would skew the sum of bypassed bytes for each type.
switch (bypassed_bytes_type) {
case DataReductionProxyBypassStats::NOT_BYPASSED:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes.NotBypassed", content_length);
break;
case DataReductionProxyBypassStats::SSL:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes.SSL", content_length);
break;
case DataReductionProxyBypassStats::LOCAL_BYPASS_RULES:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes.LocalBypassRules",
content_length);
break;
case DataReductionProxyBypassStats::PROXY_OVERRIDDEN:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes.ProxyOverridden",
content_length);
break;
case DataReductionProxyBypassStats::AUDIO_VIDEO:
switch (bypass_type) {
case BYPASS_EVENT_TYPE_CURRENT:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes.CurrentAudioVideo",
content_length);
break;
case BYPASS_EVENT_TYPE_SHORT:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes.ShortAudioVideo",
content_length);
break;
default:
NOTREACHED();
}
break;
case DataReductionProxyBypassStats::APPLICATION_OCTET_STREAM:
switch (bypass_type) {
case BYPASS_EVENT_TYPE_CURRENT:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes.CurrentApplicationOctetStream",
content_length);
break;
default:
NOTREACHED();
}
break;
case DataReductionProxyBypassStats::TRIGGERING_REQUEST:
switch (bypass_type) {
case BYPASS_EVENT_TYPE_SHORT:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes.ShortTriggeringRequest",
content_length);
break;
case BYPASS_EVENT_TYPE_MEDIUM:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes.MediumTriggeringRequest",
content_length);
break;
case BYPASS_EVENT_TYPE_LONG:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes.LongTriggeringRequest",
content_length);
break;
default:
break;
}
break;
case DataReductionProxyBypassStats::NETWORK_ERROR:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes.NetworkErrorOther",
content_length);
break;
case DataReductionProxyBypassStats::BYPASSED_BYTES_TYPE_MAX:
switch (bypass_type) {
case BYPASS_EVENT_TYPE_CURRENT:
UMA_HISTOGRAM_COUNTS("DataReductionProxy.BypassedBytes.Current",
content_length);
break;
case BYPASS_EVENT_TYPE_SHORT:
UMA_HISTOGRAM_COUNTS("DataReductionProxy.BypassedBytes.ShortAll",
content_length);
break;
case BYPASS_EVENT_TYPE_MEDIUM:
UMA_HISTOGRAM_COUNTS("DataReductionProxy.BypassedBytes.MediumAll",
content_length);
break;
case BYPASS_EVENT_TYPE_LONG:
UMA_HISTOGRAM_COUNTS("DataReductionProxy.BypassedBytes.LongAll",
content_length);
break;
case BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_4XX:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes.MissingViaHeader4xx",
content_length);
break;
case BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_OTHER:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes.MissingViaHeaderOther",
content_length);
break;
case BYPASS_EVENT_TYPE_MALFORMED_407:
UMA_HISTOGRAM_COUNTS("DataReductionProxy.BypassedBytes.Malformed407",
content_length);
break;
case BYPASS_EVENT_TYPE_STATUS_500_HTTP_INTERNAL_SERVER_ERROR:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes."
"Status500HttpInternalServerError",
content_length);
break;
case BYPASS_EVENT_TYPE_STATUS_502_HTTP_BAD_GATEWAY:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes.Status502HttpBadGateway",
content_length);
break;
case BYPASS_EVENT_TYPE_STATUS_503_HTTP_SERVICE_UNAVAILABLE:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes."
"Status503HttpServiceUnavailable",
content_length);
break;
case BYPASS_EVENT_TYPE_URL_REDIRECT_CYCLE:
UMA_HISTOGRAM_COUNTS(
"DataReductionProxy.BypassedBytes.URLRedirectCycle",
content_length);
break;
default:
break;
}
break;
}
}
} // namespace data_reduction_proxy