| // 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/browser/data_reduction_proxy_usage_stats.h" |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/message_loop/message_loop_proxy.h" |
| #include "base/metrics/histogram.h" |
| #include "base/metrics/sparse_histogram.h" |
| #include "components/data_reduction_proxy/common/data_reduction_proxy_headers.h" |
| #include "net/base/net_errors.h" |
| #include "net/proxy/proxy_retry_info.h" |
| #include "net/proxy/proxy_server.h" |
| #include "net/proxy/proxy_service.h" |
| #include "net/url_request/url_request.h" |
| #include "net/url_request/url_request_context.h" |
| |
| using base::MessageLoopProxy; |
| using net::HostPortPair; |
| using net::ProxyServer; |
| using net::ProxyService; |
| using net::NetworkChangeNotifier; |
| using net::URLRequest; |
| |
| namespace data_reduction_proxy { |
| |
| namespace { |
| |
| // 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 ProxyServer& proxy_server, |
| int net_error) { |
| if (is_primary) { |
| UMA_HISTOGRAM_SPARSE_SLOWLY( |
| "DataReductionProxy.BypassOnNetworkErrorPrimary", |
| std::abs(net_error)); |
| return; |
| } |
| UMA_HISTOGRAM_SPARSE_SLOWLY( |
| "DataReductionProxy.BypassOnNetworkErrorFallback", |
| std::abs(net_error)); |
| } |
| |
| } // namespace |
| |
| // static |
| void DataReductionProxyUsageStats::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); |
| } |
| } |
| } |
| |
| DataReductionProxyUsageStats::DataReductionProxyUsageStats( |
| DataReductionProxyParams* params, |
| MessageLoopProxy* ui_thread_proxy) |
| : data_reduction_proxy_params_(params), |
| last_bypass_type_(BYPASS_EVENT_TYPE_MAX), |
| triggering_request_(true), |
| ui_thread_proxy_(ui_thread_proxy), |
| eligible_num_requests_through_proxy_(0), |
| actual_num_requests_through_proxy_(0), |
| unavailable_(false) { |
| NetworkChangeNotifier::AddNetworkChangeObserver(this); |
| }; |
| |
| DataReductionProxyUsageStats::~DataReductionProxyUsageStats() { |
| NetworkChangeNotifier::RemoveNetworkChangeObserver(this); |
| }; |
| |
| void DataReductionProxyUsageStats::OnUrlRequestCompleted( |
| const net::URLRequest* request, bool started) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (request->status().status() == net::URLRequestStatus::SUCCESS) { |
| if (data_reduction_proxy_params_->IsDataReductionProxyEligible(request)) { |
| bool was_received_via_proxy = |
| data_reduction_proxy_params_->WasDataReductionProxyUsed( |
| request, NULL); |
| IncrementRequestCounts(was_received_via_proxy); |
| } |
| } |
| } |
| |
| void DataReductionProxyUsageStats::OnNetworkChanged( |
| NetworkChangeNotifier::ConnectionType type) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| ClearRequestCounts(); |
| } |
| |
| void DataReductionProxyUsageStats::IncrementRequestCounts( |
| bool was_received_via_proxy) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (was_received_via_proxy) { |
| actual_num_requests_through_proxy_++; |
| } |
| eligible_num_requests_through_proxy_++; |
| |
| // To account for the case when the proxy works for a little while and then |
| // gets blocked, we reset the counts occasionally. |
| if (eligible_num_requests_through_proxy_ > 50 |
| && actual_num_requests_through_proxy_ > 0) { |
| ClearRequestCounts(); |
| } else { |
| MaybeNotifyUnavailability(); |
| } |
| } |
| |
| void DataReductionProxyUsageStats::ClearRequestCounts() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| eligible_num_requests_through_proxy_ = 0; |
| actual_num_requests_through_proxy_ = 0; |
| MaybeNotifyUnavailability(); |
| } |
| |
| void DataReductionProxyUsageStats::MaybeNotifyUnavailability() { |
| bool prev_unavailable = unavailable_; |
| unavailable_ = (eligible_num_requests_through_proxy_ > 0 && |
| actual_num_requests_through_proxy_ == 0); |
| if (prev_unavailable != unavailable_) { |
| ui_thread_proxy_->PostTask(FROM_HERE, base::Bind( |
| &DataReductionProxyUsageStats::NotifyUnavailabilityOnUIThread, |
| base::Unretained(this), |
| unavailable_)); |
| } |
| } |
| |
| void DataReductionProxyUsageStats::NotifyUnavailabilityOnUIThread( |
| bool unavailable) { |
| DCHECK(ui_thread_proxy_->BelongsToCurrentThread()); |
| if (!unavailable_callback_.is_null()) |
| unavailable_callback_.Run(unavailable); |
| } |
| |
| void DataReductionProxyUsageStats::SetBypassType( |
| DataReductionProxyBypassType type) { |
| last_bypass_type_ = type; |
| triggering_request_ = true; |
| } |
| |
| void DataReductionProxyUsageStats::RecordBypassedBytesHistograms( |
| net::URLRequest& request, |
| const BooleanPrefMember& data_reduction_proxy_enabled, |
| const net::ProxyConfig& data_reduction_proxy_config) { |
| int64 content_length = request.received_response_content_length(); |
| |
| if (data_reduction_proxy_enabled.GetValue() && |
| !data_reduction_proxy_config.Equals( |
| request.context()->proxy_service()->config())) { |
| RecordBypassedBytes(last_bypass_type_, |
| DataReductionProxyUsageStats::MANAGED_PROXY_CONFIG, |
| content_length); |
| return; |
| } |
| |
| if (data_reduction_proxy_params_->WasDataReductionProxyUsed(&request, NULL)) { |
| RecordBypassedBytes(last_bypass_type_, |
| DataReductionProxyUsageStats::NOT_BYPASSED, |
| content_length); |
| return; |
| } |
| |
| if (data_reduction_proxy_enabled.GetValue() && |
| request.url().SchemeIs(url::kHttpsScheme)) { |
| RecordBypassedBytes(last_bypass_type_, |
| DataReductionProxyUsageStats::SSL, |
| content_length); |
| return; |
| } |
| |
| if (data_reduction_proxy_enabled.GetValue() && |
| data_reduction_proxy_params_->IsBypassedByDataReductionProxyLocalRules( |
| request, data_reduction_proxy_config)) { |
| RecordBypassedBytes(last_bypass_type_, |
| DataReductionProxyUsageStats::LOCAL_BYPASS_RULES, |
| content_length); |
| return; |
| } |
| |
| if (triggering_request_) { |
| // We only record when audio or video triggers a bypass. We don't care |
| // about audio and video bypassed as collateral damage. |
| std::string mime_type; |
| request.GetMimeType(&mime_type); |
| // MIME types are named by <media-type>/<subtype>. We check to see if the |
| // media type is audio or video. |
| if (mime_type.compare(0, 6, "audio/") == 0 || |
| mime_type.compare(0, 6, "video/") == 0) { |
| RecordBypassedBytes(last_bypass_type_, |
| DataReductionProxyUsageStats::AUDIO_VIDEO, |
| content_length); |
| return; |
| } |
| |
| RecordBypassedBytes(last_bypass_type_, |
| DataReductionProxyUsageStats::TRIGGERING_REQUEST, |
| content_length); |
| triggering_request_ = false; |
| return; |
| } |
| |
| if (last_bypass_type_ != BYPASS_EVENT_TYPE_MAX) { |
| RecordBypassedBytes(last_bypass_type_, |
| DataReductionProxyUsageStats::BYPASSED_BYTES_TYPE_MAX, |
| content_length); |
| return; |
| } |
| |
| if (data_reduction_proxy_params_-> |
| AreDataReductionProxiesBypassed(request, NULL)) { |
| RecordBypassedBytes(last_bypass_type_, |
| DataReductionProxyUsageStats::NETWORK_ERROR, |
| content_length); |
| } |
| } |
| |
| void DataReductionProxyUsageStats::RecordBypassEventHistograms( |
| const net::ProxyServer& bypassed_proxy, |
| int net_error) const { |
| DataReductionProxyTypeInfo data_reduction_proxy_info; |
| if (bypassed_proxy.is_valid() && !bypassed_proxy.is_direct() && |
| data_reduction_proxy_params_->IsDataReductionProxy( |
| bypassed_proxy.host_port_pair(), &data_reduction_proxy_info)) { |
| if (data_reduction_proxy_info.is_ssl) |
| return; |
| if (!data_reduction_proxy_info.is_fallback) { |
| RecordDataReductionProxyBypassInfo( |
| true, false, bypassed_proxy, BYPASS_EVENT_TYPE_NETWORK_ERROR); |
| RecordDataReductionProxyBypassOnNetworkError( |
| true, bypassed_proxy, net_error); |
| } else { |
| RecordDataReductionProxyBypassInfo( |
| false, false, bypassed_proxy, BYPASS_EVENT_TYPE_NETWORK_ERROR); |
| RecordDataReductionProxyBypassOnNetworkError( |
| false, bypassed_proxy, net_error); |
| } |
| } |
| } |
| |
| void DataReductionProxyUsageStats::RecordBypassedBytes( |
| DataReductionProxyBypassType bypass_type, |
| DataReductionProxyUsageStats::BypassedBytesType bypassed_bytes_type, |
| int64 content_length) { |
| // 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 DataReductionProxyUsageStats::NOT_BYPASSED: |
| UMA_HISTOGRAM_COUNTS( |
| "DataReductionProxy.BypassedBytes.NotBypassed", content_length); |
| break; |
| case DataReductionProxyUsageStats::SSL: |
| UMA_HISTOGRAM_COUNTS( |
| "DataReductionProxy.BypassedBytes.SSL", content_length); |
| break; |
| case DataReductionProxyUsageStats::LOCAL_BYPASS_RULES: |
| UMA_HISTOGRAM_COUNTS( |
| "DataReductionProxy.BypassedBytes.LocalBypassRules", |
| content_length); |
| break; |
| case DataReductionProxyUsageStats::MANAGED_PROXY_CONFIG: |
| UMA_HISTOGRAM_COUNTS( |
| "DataReductionProxy.BypassedBytes.ManagedProxyConfig", |
| content_length); |
| break; |
| case DataReductionProxyUsageStats::AUDIO_VIDEO: |
| if (last_bypass_type_ == BYPASS_EVENT_TYPE_SHORT) { |
| UMA_HISTOGRAM_COUNTS( |
| "DataReductionProxy.BypassedBytes.ShortAudioVideo", |
| content_length); |
| } |
| break; |
| case DataReductionProxyUsageStats::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 DataReductionProxyUsageStats::NETWORK_ERROR: |
| UMA_HISTOGRAM_COUNTS( |
| "DataReductionProxy.BypassedBytes.NetworkErrorOther", |
| content_length); |
| break; |
| case DataReductionProxyUsageStats::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; |
| default: |
| break; |
| } |
| break; |
| } |
| } |
| |
| } // namespace data_reduction_proxy |
| |
| |