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