Home | History | Annotate | Download | only in browser
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "components/data_reduction_proxy/browser/data_reduction_proxy_usage_stats.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/callback.h"
      9 #include "base/message_loop/message_loop_proxy.h"
     10 #include "base/metrics/histogram.h"
     11 #include "base/metrics/sparse_histogram.h"
     12 #include "base/prefs/pref_member.h"
     13 #include "components/data_reduction_proxy/common/data_reduction_proxy_headers.h"
     14 #include "net/base/net_errors.h"
     15 #include "net/http/http_response_headers.h"
     16 #include "net/http/http_status_code.h"
     17 #include "net/proxy/proxy_retry_info.h"
     18 #include "net/proxy/proxy_server.h"
     19 #include "net/proxy/proxy_service.h"
     20 #include "net/url_request/url_request.h"
     21 #include "net/url_request/url_request_context.h"
     22 
     23 using base::MessageLoopProxy;
     24 using net::HostPortPair;
     25 using net::ProxyServer;
     26 using net::ProxyService;
     27 using net::NetworkChangeNotifier;
     28 using net::URLRequest;
     29 
     30 namespace data_reduction_proxy {
     31 
     32 namespace {
     33 
     34 const int kMinFailedRequestsWhenUnavailable = 1;
     35 const int kMaxSuccessfulRequestsWhenUnavailable = 0;
     36 const int kMaxFailedRequestsBeforeReset = 3;
     37 
     38 // Records a net error code that resulted in bypassing the data reduction
     39 // proxy (|is_primary| is true) or the data reduction proxy fallback.
     40 void RecordDataReductionProxyBypassOnNetworkError(
     41     bool is_primary,
     42     const ProxyServer& proxy_server,
     43     int net_error) {
     44   if (is_primary) {
     45     UMA_HISTOGRAM_SPARSE_SLOWLY(
     46         "DataReductionProxy.BypassOnNetworkErrorPrimary",
     47         std::abs(net_error));
     48     return;
     49   }
     50   UMA_HISTOGRAM_SPARSE_SLOWLY(
     51       "DataReductionProxy.BypassOnNetworkErrorFallback",
     52       std::abs(net_error));
     53 }
     54 
     55 }  // namespace
     56 
     57 // static
     58 void DataReductionProxyUsageStats::RecordDataReductionProxyBypassInfo(
     59     bool is_primary,
     60     bool bypass_all,
     61     const net::ProxyServer& proxy_server,
     62     DataReductionProxyBypassType bypass_type) {
     63   if (bypass_all) {
     64     if (is_primary) {
     65       UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.BlockTypePrimary",
     66                                 bypass_type, BYPASS_EVENT_TYPE_MAX);
     67     } else {
     68       UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.BlockTypeFallback",
     69                                 bypass_type, BYPASS_EVENT_TYPE_MAX);
     70     }
     71   } else {
     72     if (is_primary) {
     73       UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.BypassTypePrimary",
     74                                 bypass_type, BYPASS_EVENT_TYPE_MAX);
     75     } else {
     76       UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.BypassTypeFallback",
     77                                 bypass_type, BYPASS_EVENT_TYPE_MAX);
     78     }
     79   }
     80 }
     81 
     82 // static
     83 void DataReductionProxyUsageStats::DetectAndRecordMissingViaHeaderResponseCode(
     84       bool is_primary,
     85       const net::HttpResponseHeaders* headers) {
     86   if (HasDataReductionProxyViaHeader(headers, NULL)) {
     87     // The data reduction proxy via header is present, so don't record anything.
     88     return;
     89   }
     90 
     91   if (is_primary) {
     92     UMA_HISTOGRAM_SPARSE_SLOWLY(
     93         "DataReductionProxy.MissingViaHeader.ResponseCode.Primary",
     94         headers->response_code());
     95   } else {
     96     UMA_HISTOGRAM_SPARSE_SLOWLY(
     97         "DataReductionProxy.MissingViaHeader.ResponseCode.Fallback",
     98         headers->response_code());
     99   }
    100 }
    101 
    102 DataReductionProxyUsageStats::DataReductionProxyUsageStats(
    103     DataReductionProxyParams* params,
    104     const scoped_refptr<MessageLoopProxy>& ui_thread_proxy)
    105     : data_reduction_proxy_params_(params),
    106       last_bypass_type_(BYPASS_EVENT_TYPE_MAX),
    107       triggering_request_(true),
    108       ui_thread_proxy_(ui_thread_proxy),
    109       successful_requests_through_proxy_count_(0),
    110       proxy_net_errors_count_(0),
    111       unavailable_(false) {
    112   DCHECK(params);
    113 
    114   NetworkChangeNotifier::AddNetworkChangeObserver(this);
    115 };
    116 
    117 DataReductionProxyUsageStats::~DataReductionProxyUsageStats() {
    118   NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
    119 };
    120 
    121 void DataReductionProxyUsageStats::OnUrlRequestCompleted(
    122     const net::URLRequest* request, bool started) {
    123   DCHECK(thread_checker_.CalledOnValidThread());
    124 
    125   if (request->status().status() == net::URLRequestStatus::SUCCESS &&
    126       data_reduction_proxy_params_->WasDataReductionProxyUsed(request, NULL)) {
    127     successful_requests_through_proxy_count_++;
    128     NotifyUnavailabilityIfChanged();
    129   }
    130 }
    131 
    132 void DataReductionProxyUsageStats::OnNetworkChanged(
    133     NetworkChangeNotifier::ConnectionType type) {
    134   DCHECK(thread_checker_.CalledOnValidThread());
    135   ClearRequestCounts();
    136 }
    137 
    138 void DataReductionProxyUsageStats::ClearRequestCounts() {
    139   DCHECK(thread_checker_.CalledOnValidThread());
    140   successful_requests_through_proxy_count_ = 0;
    141   proxy_net_errors_count_ = 0;
    142 }
    143 
    144 void DataReductionProxyUsageStats::NotifyUnavailabilityIfChanged() {
    145   bool prev_unavailable = unavailable_;
    146   unavailable_ =
    147       (proxy_net_errors_count_ >= kMinFailedRequestsWhenUnavailable &&
    148           successful_requests_through_proxy_count_ <=
    149               kMaxSuccessfulRequestsWhenUnavailable);
    150   if (prev_unavailable != unavailable_) {
    151     ui_thread_proxy_->PostTask(FROM_HERE, base::Bind(
    152         &DataReductionProxyUsageStats::NotifyUnavailabilityOnUIThread,
    153         base::Unretained(this),
    154         unavailable_));
    155   }
    156 }
    157 
    158 void DataReductionProxyUsageStats::NotifyUnavailabilityOnUIThread(
    159     bool unavailable) {
    160   DCHECK(ui_thread_proxy_->BelongsToCurrentThread());
    161   if (!unavailable_callback_.is_null())
    162     unavailable_callback_.Run(unavailable);
    163 }
    164 
    165 void DataReductionProxyUsageStats::SetBypassType(
    166     DataReductionProxyBypassType type) {
    167   last_bypass_type_ = type;
    168   triggering_request_ = true;
    169 }
    170 
    171 void DataReductionProxyUsageStats::RecordBytesHistograms(
    172     net::URLRequest* request,
    173     const BooleanPrefMember& data_reduction_proxy_enabled,
    174     const net::ProxyConfig& data_reduction_proxy_config) {
    175   RecordBypassedBytesHistograms(request, data_reduction_proxy_enabled,
    176                                 data_reduction_proxy_config);
    177   RecordMissingViaHeaderBytes(request);
    178 }
    179 
    180 void DataReductionProxyUsageStats::RecordBypassedBytesHistograms(
    181     net::URLRequest* request,
    182     const BooleanPrefMember& data_reduction_proxy_enabled,
    183     const net::ProxyConfig& data_reduction_proxy_config) {
    184   int64 content_length = request->received_response_content_length();
    185 
    186   if (data_reduction_proxy_enabled.GetValue() &&
    187       !data_reduction_proxy_config.Equals(
    188           request->context()->proxy_service()->config())) {
    189     RecordBypassedBytes(last_bypass_type_,
    190                         DataReductionProxyUsageStats::MANAGED_PROXY_CONFIG,
    191                         content_length);
    192     return;
    193   }
    194 
    195   if (data_reduction_proxy_params_->WasDataReductionProxyUsed(request, NULL)) {
    196     RecordBypassedBytes(last_bypass_type_,
    197                         DataReductionProxyUsageStats::NOT_BYPASSED,
    198                         content_length);
    199     return;
    200   }
    201 
    202   if (data_reduction_proxy_enabled.GetValue() &&
    203       request->url().SchemeIs(url::kHttpsScheme)) {
    204     RecordBypassedBytes(last_bypass_type_,
    205                         DataReductionProxyUsageStats::SSL,
    206                         content_length);
    207     return;
    208   }
    209 
    210   if (data_reduction_proxy_enabled.GetValue() &&
    211       data_reduction_proxy_params_->IsBypassedByDataReductionProxyLocalRules(
    212           *request, data_reduction_proxy_config)) {
    213     RecordBypassedBytes(last_bypass_type_,
    214                         DataReductionProxyUsageStats::LOCAL_BYPASS_RULES,
    215                         content_length);
    216     return;
    217   }
    218 
    219   // Only record separate triggering request UMA for short, medium, and long
    220   // bypass events.
    221   if (triggering_request_ &&
    222      (last_bypass_type_ ==  BYPASS_EVENT_TYPE_SHORT ||
    223       last_bypass_type_ ==  BYPASS_EVENT_TYPE_MEDIUM ||
    224       last_bypass_type_ ==  BYPASS_EVENT_TYPE_LONG)) {
    225     std::string mime_type;
    226     request->GetMimeType(&mime_type);
    227     // MIME types are named by <media-type>/<subtype>. Check to see if the
    228     // media type is audio or video. Only record when triggered by short bypass,
    229     // there isn't an audio or video bucket for medium or long bypasses.
    230     if (last_bypass_type_ ==  BYPASS_EVENT_TYPE_SHORT &&
    231        (mime_type.compare(0, 6, "audio/") == 0  ||
    232         mime_type.compare(0, 6, "video/") == 0)) {
    233       RecordBypassedBytes(last_bypass_type_,
    234                           DataReductionProxyUsageStats::AUDIO_VIDEO,
    235                           content_length);
    236       return;
    237     }
    238 
    239     RecordBypassedBytes(last_bypass_type_,
    240                         DataReductionProxyUsageStats::TRIGGERING_REQUEST,
    241                         content_length);
    242     triggering_request_ = false;
    243     return;
    244   }
    245 
    246   if (last_bypass_type_ != BYPASS_EVENT_TYPE_MAX) {
    247     RecordBypassedBytes(last_bypass_type_,
    248                         DataReductionProxyUsageStats::BYPASSED_BYTES_TYPE_MAX,
    249                         content_length);
    250     return;
    251   }
    252 
    253   if (data_reduction_proxy_params_->AreDataReductionProxiesBypassed(*request,
    254                                                                     NULL)) {
    255     RecordBypassedBytes(last_bypass_type_,
    256                         DataReductionProxyUsageStats::NETWORK_ERROR,
    257                         content_length);
    258   }
    259 }
    260 
    261 void DataReductionProxyUsageStats::OnProxyFallback(
    262     const net::ProxyServer& bypassed_proxy,
    263     int net_error) {
    264   DataReductionProxyTypeInfo data_reduction_proxy_info;
    265   if (bypassed_proxy.is_valid() && !bypassed_proxy.is_direct() &&
    266       data_reduction_proxy_params_->IsDataReductionProxy(
    267       bypassed_proxy.host_port_pair(), &data_reduction_proxy_info)) {
    268     if (data_reduction_proxy_info.is_ssl)
    269       return;
    270 
    271     proxy_net_errors_count_++;
    272 
    273     // To account for the case when the proxy is reachable for sometime, and
    274     // then gets blocked, we reset counts when number of errors exceed
    275     // the threshold.
    276     if (proxy_net_errors_count_ >= kMaxFailedRequestsBeforeReset &&
    277         successful_requests_through_proxy_count_ >
    278             kMaxSuccessfulRequestsWhenUnavailable) {
    279       ClearRequestCounts();
    280     } else {
    281       NotifyUnavailabilityIfChanged();
    282     }
    283 
    284     if (!data_reduction_proxy_info.is_fallback) {
    285       RecordDataReductionProxyBypassInfo(
    286           true, false, bypassed_proxy, BYPASS_EVENT_TYPE_NETWORK_ERROR);
    287       RecordDataReductionProxyBypassOnNetworkError(
    288           true, bypassed_proxy, net_error);
    289     } else {
    290       RecordDataReductionProxyBypassInfo(
    291           false, false, bypassed_proxy, BYPASS_EVENT_TYPE_NETWORK_ERROR);
    292       RecordDataReductionProxyBypassOnNetworkError(
    293           false, bypassed_proxy, net_error);
    294     }
    295   }
    296 }
    297 
    298 void DataReductionProxyUsageStats::RecordBypassedBytes(
    299     DataReductionProxyBypassType bypass_type,
    300     DataReductionProxyUsageStats::BypassedBytesType bypassed_bytes_type,
    301     int64 content_length) {
    302   // Individual histograms are needed to count the bypassed bytes for each
    303   // bypass type so that we can see the size of requests. This helps us
    304   // remove outliers that would skew the sum of bypassed bytes for each type.
    305   switch (bypassed_bytes_type) {
    306     case DataReductionProxyUsageStats::NOT_BYPASSED:
    307       UMA_HISTOGRAM_COUNTS(
    308           "DataReductionProxy.BypassedBytes.NotBypassed", content_length);
    309       break;
    310     case DataReductionProxyUsageStats::SSL:
    311       UMA_HISTOGRAM_COUNTS(
    312           "DataReductionProxy.BypassedBytes.SSL", content_length);
    313       break;
    314     case DataReductionProxyUsageStats::LOCAL_BYPASS_RULES:
    315       UMA_HISTOGRAM_COUNTS(
    316           "DataReductionProxy.BypassedBytes.LocalBypassRules",
    317           content_length);
    318       break;
    319     case DataReductionProxyUsageStats::MANAGED_PROXY_CONFIG:
    320       UMA_HISTOGRAM_COUNTS(
    321           "DataReductionProxy.BypassedBytes.ManagedProxyConfig",
    322           content_length);
    323       break;
    324     case DataReductionProxyUsageStats::AUDIO_VIDEO:
    325       if (last_bypass_type_ == BYPASS_EVENT_TYPE_SHORT) {
    326         UMA_HISTOGRAM_COUNTS(
    327             "DataReductionProxy.BypassedBytes.ShortAudioVideo",
    328             content_length);
    329       }
    330       break;
    331     case DataReductionProxyUsageStats::TRIGGERING_REQUEST:
    332       switch (bypass_type) {
    333         case BYPASS_EVENT_TYPE_SHORT:
    334           UMA_HISTOGRAM_COUNTS(
    335               "DataReductionProxy.BypassedBytes.ShortTriggeringRequest",
    336               content_length);
    337           break;
    338         case BYPASS_EVENT_TYPE_MEDIUM:
    339           UMA_HISTOGRAM_COUNTS(
    340               "DataReductionProxy.BypassedBytes.MediumTriggeringRequest",
    341               content_length);
    342           break;
    343         case BYPASS_EVENT_TYPE_LONG:
    344           UMA_HISTOGRAM_COUNTS(
    345               "DataReductionProxy.BypassedBytes.LongTriggeringRequest",
    346               content_length);
    347           break;
    348         default:
    349           break;
    350       }
    351       break;
    352     case DataReductionProxyUsageStats::NETWORK_ERROR:
    353       UMA_HISTOGRAM_COUNTS(
    354           "DataReductionProxy.BypassedBytes.NetworkErrorOther",
    355           content_length);
    356       break;
    357     case DataReductionProxyUsageStats::BYPASSED_BYTES_TYPE_MAX:
    358       switch (bypass_type) {
    359         case BYPASS_EVENT_TYPE_CURRENT:
    360           UMA_HISTOGRAM_COUNTS("DataReductionProxy.BypassedBytes.Current",
    361                                content_length);
    362           break;
    363         case BYPASS_EVENT_TYPE_SHORT:
    364           UMA_HISTOGRAM_COUNTS("DataReductionProxy.BypassedBytes.ShortAll",
    365                                content_length);
    366           break;
    367         case BYPASS_EVENT_TYPE_MEDIUM:
    368           UMA_HISTOGRAM_COUNTS("DataReductionProxy.BypassedBytes.MediumAll",
    369                                content_length);
    370           break;
    371         case BYPASS_EVENT_TYPE_LONG:
    372           UMA_HISTOGRAM_COUNTS("DataReductionProxy.BypassedBytes.LongAll",
    373                                content_length);
    374           break;
    375         case BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_4XX:
    376           UMA_HISTOGRAM_COUNTS(
    377               "DataReductionProxy.BypassedBytes.MissingViaHeader4xx",
    378               content_length);
    379           break;
    380         case BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_OTHER:
    381           UMA_HISTOGRAM_COUNTS(
    382               "DataReductionProxy.BypassedBytes.MissingViaHeaderOther",
    383               content_length);
    384           break;
    385         case BYPASS_EVENT_TYPE_MALFORMED_407:
    386           UMA_HISTOGRAM_COUNTS("DataReductionProxy.BypassedBytes.Malformed407",
    387                                content_length);
    388          break;
    389         case BYPASS_EVENT_TYPE_STATUS_500_HTTP_INTERNAL_SERVER_ERROR:
    390           UMA_HISTOGRAM_COUNTS(
    391               "DataReductionProxy.BypassedBytes."
    392               "Status500HttpInternalServerError",
    393               content_length);
    394           break;
    395         case BYPASS_EVENT_TYPE_STATUS_502_HTTP_BAD_GATEWAY:
    396           UMA_HISTOGRAM_COUNTS(
    397               "DataReductionProxy.BypassedBytes.Status502HttpBadGateway",
    398               content_length);
    399           break;
    400         case BYPASS_EVENT_TYPE_STATUS_503_HTTP_SERVICE_UNAVAILABLE:
    401           UMA_HISTOGRAM_COUNTS(
    402               "DataReductionProxy.BypassedBytes."
    403               "Status503HttpServiceUnavailable",
    404               content_length);
    405           break;
    406         default:
    407           break;
    408       }
    409       break;
    410   }
    411 }
    412 
    413 void DataReductionProxyUsageStats::RecordMissingViaHeaderBytes(
    414     URLRequest* request) {
    415   // Responses that were served from cache should have been filtered out
    416   // already.
    417   DCHECK(!request->was_cached());
    418 
    419   if (!data_reduction_proxy_params_->WasDataReductionProxyUsed(request, NULL) ||
    420       HasDataReductionProxyViaHeader(request->response_headers(), NULL)) {
    421     // Only track requests that used the data reduction proxy and had responses
    422     // that were missing the data reduction proxy via header.
    423     return;
    424   }
    425 
    426   if (request->GetResponseCode() >= net::HTTP_BAD_REQUEST &&
    427       request->GetResponseCode() < net::HTTP_INTERNAL_SERVER_ERROR) {
    428     // Track 4xx responses that are missing via headers separately.
    429     UMA_HISTOGRAM_COUNTS("DataReductionProxy.MissingViaHeader.Bytes.4xx",
    430                          request->received_response_content_length());
    431   } else {
    432     UMA_HISTOGRAM_COUNTS("DataReductionProxy.MissingViaHeader.Bytes.Other",
    433                          request->received_response_content_length());
    434   }
    435 }
    436 
    437 }  // namespace data_reduction_proxy
    438 
    439 
    440