1 // Copyright 2013 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.
5 #include "chrome/browser/net/spdyproxy/data_saving_metrics.h"
7 #include "base/metrics/histogram.h"
8 #include "base/prefs/pref_service.h"
9 #include "base/prefs/scoped_user_pref_update.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "chrome/browser/net/spdyproxy/data_reduction_proxy_settings.h"
13 #include "chrome/common/pref_names.h"
14 #include "content/public/common/url_constants.h"
15 #include "net/base/host_port_pair.h"
16 #include "net/http/http_response_headers.h"
17 #include "net/proxy/proxy_retry_info.h"
18 #include "net/proxy/proxy_service.h"
19 #include "net/url_request/url_request_context.h"
23 // A bypass delay more than this is treated as a long delay.
24 const int kLongBypassDelayInSeconds
= 30 * 60;
26 #if defined(OS_ANDROID) || defined(OS_IOS)
28 // The number of days of history stored in the content lengths prefs.
29 const size_t kNumDaysInHistory
= 60;
31 // Increments an int64, stored as a string, in a ListPref at the specified
32 // index. The value must already exist and be a string representation of a
34 void AddInt64ToListPref(size_t index
,
36 base::ListValue
* list_update
) {
38 std::string old_string_value
;
39 bool rv
= list_update
->GetString(index
, &old_string_value
);
42 rv
= base::StringToInt64(old_string_value
, &value
);
46 list_update
->Set(index
,
47 base::Value::CreateStringValue(base::Int64ToString(value
)));
50 int64
ListPrefInt64Value(const base::ListValue
& list_update
, size_t index
) {
51 std::string string_value
;
52 if (!list_update
.GetString(index
, &string_value
)) {
58 bool rv
= base::StringToInt64(string_value
, &value
);
63 // Report UMA metrics for daily data reductions.
64 void RecordDailyContentLengthHistograms(
65 int64 original_length
,
66 int64 received_length
,
67 int64 original_length_with_data_reduction_enabled
,
68 int64 received_length_with_data_reduction_enabled
,
69 int64 original_length_via_data_reduction_proxy
,
70 int64 received_length_via_data_reduction_proxy
,
71 int64 https_length_with_data_reduction_enabled
,
72 int64 short_bypass_length_with_data_reduction_enabled
,
73 int64 long_bypass_length_with_data_reduction_enabled
,
74 int64 unknown_length_with_data_reduction_enabled
) {
75 // Report daily UMA only for days having received content.
76 if (original_length
<= 0 || received_length
<= 0)
79 // Record metrics in KB.
81 "Net.DailyOriginalContentLength", original_length
>> 10);
83 "Net.DailyContentLength", received_length
>> 10);
85 // UMA percentage cannot be negative.
86 if (original_length
> received_length
) {
87 percent
= (100 * (original_length
- received_length
)) / original_length
;
89 UMA_HISTOGRAM_PERCENTAGE("Net.DailyContentSavingPercent", percent
);
91 if (original_length_with_data_reduction_enabled
<= 0 ||
92 received_length_with_data_reduction_enabled
<= 0) {
97 "Net.DailyOriginalContentLength_DataReductionProxyEnabled",
98 original_length_with_data_reduction_enabled
>> 10);
100 "Net.DailyContentLength_DataReductionProxyEnabled",
101 received_length_with_data_reduction_enabled
>> 10);
103 int percent_data_reduction_proxy_enabled
= 0;
104 // UMA percentage cannot be negative.
105 if (original_length_with_data_reduction_enabled
>
106 received_length_with_data_reduction_enabled
) {
107 percent_data_reduction_proxy_enabled
=
108 100 * (original_length_with_data_reduction_enabled
-
109 received_length_with_data_reduction_enabled
) /
110 original_length_with_data_reduction_enabled
;
112 UMA_HISTOGRAM_PERCENTAGE(
113 "Net.DailyContentSavingPercent_DataReductionProxyEnabled",
114 percent_data_reduction_proxy_enabled
);
116 UMA_HISTOGRAM_PERCENTAGE(
117 "Net.DailyContentPercent_DataReductionProxyEnabled",
118 (100 * received_length_with_data_reduction_enabled
) / received_length
);
120 if (https_length_with_data_reduction_enabled
> 0) {
121 UMA_HISTOGRAM_COUNTS(
122 "Net.DailyContentLength_DataReductionProxyEnabled_Https",
123 https_length_with_data_reduction_enabled
>> 10);
124 UMA_HISTOGRAM_PERCENTAGE(
125 "Net.DailyContentPercent_DataReductionProxyEnabled_Https",
126 (100 * https_length_with_data_reduction_enabled
) / received_length
);
129 if (short_bypass_length_with_data_reduction_enabled
> 0) {
130 UMA_HISTOGRAM_COUNTS(
131 "Net.DailyContentLength_DataReductionProxyEnabled_ShortBypass",
132 short_bypass_length_with_data_reduction_enabled
>> 10);
133 UMA_HISTOGRAM_PERCENTAGE(
134 "Net.DailyContentPercent_DataReductionProxyEnabled_ShortBypass",
135 ((100 * short_bypass_length_with_data_reduction_enabled
) /
139 if (long_bypass_length_with_data_reduction_enabled
> 0) {
140 UMA_HISTOGRAM_COUNTS(
141 "Net.DailyContentLength_DataReductionProxyEnabled_LongBypass",
142 long_bypass_length_with_data_reduction_enabled
>> 10);
143 UMA_HISTOGRAM_PERCENTAGE(
144 "Net.DailyContentPercent_DataReductionProxyEnabled_LongBypass",
145 ((100 * long_bypass_length_with_data_reduction_enabled
) /
149 if (unknown_length_with_data_reduction_enabled
> 0) {
150 UMA_HISTOGRAM_COUNTS(
151 "Net.DailyContentLength_DataReductionProxyEnabled_Unknown",
152 unknown_length_with_data_reduction_enabled
>> 10);
153 UMA_HISTOGRAM_PERCENTAGE(
154 "Net.DailyContentPercent_DataReductionProxyEnabled_Unknown",
155 ((100 * unknown_length_with_data_reduction_enabled
) /
159 if (original_length_via_data_reduction_proxy
<= 0 ||
160 received_length_via_data_reduction_proxy
<= 0) {
164 UMA_HISTOGRAM_COUNTS(
165 "Net.DailyOriginalContentLength_ViaDataReductionProxy",
166 original_length_via_data_reduction_proxy
>> 10);
167 UMA_HISTOGRAM_COUNTS(
168 "Net.DailyContentLength_ViaDataReductionProxy",
169 received_length_via_data_reduction_proxy
>> 10);
170 int percent_via_data_reduction_proxy
= 0;
171 if (original_length_via_data_reduction_proxy
>
172 received_length_via_data_reduction_proxy
) {
173 percent_via_data_reduction_proxy
=
174 100 * (original_length_via_data_reduction_proxy
-
175 received_length_via_data_reduction_proxy
) /
176 original_length_via_data_reduction_proxy
;
178 UMA_HISTOGRAM_PERCENTAGE(
179 "Net.DailyContentSavingPercent_ViaDataReductionProxy",
180 percent_via_data_reduction_proxy
);
181 UMA_HISTOGRAM_PERCENTAGE(
182 "Net.DailyContentPercent_ViaDataReductionProxy",
183 (100 * received_length_via_data_reduction_proxy
) / received_length
);
186 // Ensure list has exactly |length| elements, either by truncating at the
187 // front, or appending "0"'s to the back.
188 void MaintainContentLengthPrefsWindow(base::ListValue
* list
, size_t length
) {
189 // Remove data for old days from the front.
190 while (list
->GetSize() > length
)
191 list
->Remove(0, NULL
);
192 // Newly added lists are empty. Add entries to back to fill the window,
193 // each initialized to zero.
194 while (list
->GetSize() < length
)
195 list
->AppendString(base::Int64ToString(0));
196 DCHECK_EQ(length
, list
->GetSize());
199 // DailyContentLengthUpdate maintains a data saving pref. The pref is a list
200 // of |kNumDaysInHistory| elements of daily total content lengths for the past
201 // |kNumDaysInHistory| days.
202 class DailyContentLengthUpdate
{
204 DailyContentLengthUpdate(
206 PrefService
* pref_service
)
207 : update_(pref_service
, pref
) {
210 void UpdateForDataChange(int days_since_last_update
) {
211 // New empty lists may have been created. Maintain the invariant that
212 // there should be exactly |kNumDaysInHistory| days in the histories.
213 MaintainContentLengthPrefsWindow(update_
.Get(), kNumDaysInHistory
);
214 if (days_since_last_update
) {
215 MaintainContentLengthPrefForDateChange(days_since_last_update
);
219 // Update the lengths for the current day.
220 void Add(int content_length
) {
221 AddInt64ToListPref(kNumDaysInHistory
- 1, content_length
, update_
.Get());
224 int64
GetListPrefValue(size_t index
) {
225 return ListPrefInt64Value(*update_
, index
);
229 // Update the list for date change and ensure the list has exactly |length|
230 // elements. The last entry in the list will be for the current day after
232 void MaintainContentLengthPrefForDateChange(int days_since_last_update
) {
233 if (days_since_last_update
== -1) {
234 // The system may go backwards in time by up to a day for legitimate
235 // reasons, such as with changes to the time zone. In such cases, we
236 // keep adding to the current day.
237 // Note: we accept the fact that some reported data is shifted to
238 // the adjacent day if users travel back and forth across time zones.
239 days_since_last_update
= 0;
240 } else if (days_since_last_update
< -1) {
241 // Erase all entries if the system went backwards in time by more than
245 days_since_last_update
= kNumDaysInHistory
;
247 DCHECK_GE(days_since_last_update
, 0);
249 // Add entries for days since last update event. This will make the
250 // lists longer than kNumDaysInHistory. The additional items will be cut off
251 // from the head of the lists by |MaintainContentLengthPrefsWindow|, below.
253 i
< days_since_last_update
&& i
< static_cast<int>(kNumDaysInHistory
);
255 update_
->AppendString(base::Int64ToString(0));
258 // Entries for new days may have been appended. Maintain the invariant that
259 // there should be exactly |kNumDaysInHistory| days in the histories.
260 MaintainContentLengthPrefsWindow(update_
.Get(), kNumDaysInHistory
);
263 ListPrefUpdate update_
;
266 // DailyDataSavingUpdate maintains a pair of data saving prefs, original_update_
267 // and received_update_. pref_original is a list of |kNumDaysInHistory| elements
268 // of daily total original content lengths for the past |kNumDaysInHistory|
269 // days. pref_received is the corresponding list of the daily total received
271 class DailyDataSavingUpdate
{
273 DailyDataSavingUpdate(
274 const char* pref_original
,
275 const char* pref_received
,
276 PrefService
* pref_service
)
277 : original_(pref_original
, pref_service
),
278 received_(pref_received
, pref_service
) {
281 void UpdateForDataChange(int days_since_last_update
) {
282 original_
.UpdateForDataChange(days_since_last_update
);
283 received_
.UpdateForDataChange(days_since_last_update
);
286 // Update the lengths for the current day.
287 void Add(int original_content_length
, int received_content_length
) {
288 original_
.Add(original_content_length
);
289 received_
.Add(received_content_length
);
292 int64
GetOriginalListPrefValue(size_t index
) {
293 return original_
.GetListPrefValue(index
);
295 int64
GetReceivedListPrefValue(size_t index
) {
296 return received_
.GetListPrefValue(index
);
300 DailyContentLengthUpdate original_
;
301 DailyContentLengthUpdate received_
;
304 #endif // defined(OS_ANDROID) || defined(OS_IOS)
306 // Returns true if the request is bypassed by all configured data reduction
307 // proxies. It returns the bypass delay in delay_seconds (if not NULL). If
308 // the request is bypassed by more than one proxy, delay_seconds returns
310 bool IsBypassRequest(const net::URLRequest
* request
, int64
* delay_seconds
) {
311 #if defined(OS_ANDROID) || defined(OS_IOS)
312 DataReductionProxySettings::DataReductionProxyList proxies
=
313 DataReductionProxySettings::GetDataReductionProxies();
314 if (proxies
.size() == 0)
317 if (request
== NULL
|| request
->context() == NULL
||
318 request
->context()->proxy_service() == NULL
) {
322 const net::ProxyRetryInfoMap
& retry_map
=
323 request
->context()->proxy_service()->proxy_retry_info();
324 if (retry_map
.size() == 0)
327 int64 shortest_delay
= 0;
328 // The request is bypassed if all configured proxies are in the retry map.
329 for (size_t i
= 0; i
< proxies
.size(); ++i
) {
330 std::string proxy
= net::HostPortPair::FromURL(proxies
[i
]).ToString();
331 // The retry list has the scheme prefix for https but not for http.
332 if (proxies
[i
].SchemeIs(content::kHttpsScheme
))
333 proxy
= std::string(content::kHttpsScheme
) + "://" + proxy
;
335 net::ProxyRetryInfoMap::const_iterator found
= retry_map
.find(proxy
);
336 if (found
== retry_map
.end())
338 if (shortest_delay
== 0 ||
339 shortest_delay
> found
->second
.current_delay
.InSeconds()) {
340 shortest_delay
= found
->second
.current_delay
.InSeconds();
343 if (delay_seconds
!= NULL
)
344 *delay_seconds
= shortest_delay
;
348 #endif // defined(OS_ANDROID) || defined(OS_IOS)
353 namespace spdyproxy
{
355 DataReductionRequestType
GetDataReductionRequestType(
356 const net::URLRequest
* request
) {
357 if (request
->url().SchemeIs(content::kHttpsScheme
))
359 if (!request
->url().SchemeIs(content::kHttpScheme
)) {
363 int64 bypass_delay
= 0;
364 if (IsBypassRequest(request
, &bypass_delay
)) {
365 return (bypass_delay
> kLongBypassDelayInSeconds
) ?
366 LONG_BYPASS
: SHORT_BYPASS
;
368 #if defined(SPDY_PROXY_AUTH_ORIGIN)
369 if (request
->response_info().headers
&&
370 request
->response_info().headers
->IsChromeProxyResponse()) {
371 return VIA_DATA_REDUCTION_PROXY
;
377 int64
GetAdjustedOriginalContentLength(
378 DataReductionRequestType data_reduction_type
,
379 int64 original_content_length
,
380 int64 received_content_length
) {
381 // Since there was no indication of the original content length, presume
382 // it is no different from the number of bytes read.
383 if (original_content_length
== -1 ||
384 data_reduction_type
!= spdyproxy::VIA_DATA_REDUCTION_PROXY
) {
385 return received_content_length
;
387 return original_content_length
;
390 #if defined(OS_ANDROID) || defined(OS_IOS)
391 void UpdateContentLengthPrefsForDataReductionProxy(
392 int received_content_length
,
393 int original_content_length
,
394 bool with_data_reduction_proxy_enabled
,
395 DataReductionRequestType data_reduction_type
,
396 base::Time now
, PrefService
* prefs
) {
397 // TODO(bengr): Remove this check once the underlying cause of
398 // http://crbug.com/287821 is fixed. For now, only continue if the current
399 // year is reported as being between 1972 and 2970.
400 base::TimeDelta time_since_unix_epoch
= now
- base::Time::UnixEpoch();
401 const int kMinDaysSinceUnixEpoch
= 365 * 2; // 2 years.
402 const int kMaxDaysSinceUnixEpoch
= 365 * 1000; // 1000 years.
403 if (time_since_unix_epoch
.InDays() < kMinDaysSinceUnixEpoch
||
404 time_since_unix_epoch
.InDays() > kMaxDaysSinceUnixEpoch
) {
408 // Determine how many days it has been since the last update.
409 int64 then_internal
= prefs
->GetInt64(
410 prefs::kDailyHttpContentLengthLastUpdateDate
);
411 // Local midnight could have been shifted due to time zone change.
412 base::Time then_midnight
=
413 base::Time::FromInternalValue(then_internal
).LocalMidnight();
414 base::Time midnight
= now
.LocalMidnight();
415 int days_since_last_update
= (midnight
- then_midnight
).InDays();
417 // Each day, we calculate the total number of bytes received and the total
418 // size of all corresponding resources before any data-reducing recompression
419 // is applied. These values are used to compute the data savings realized
420 // by applying our compression techniques. Totals for the last
421 // |kNumDaysInHistory| days are maintained.
422 DailyDataSavingUpdate
total(
423 prefs::kDailyHttpOriginalContentLength
,
424 prefs::kDailyHttpReceivedContentLength
,
426 total
.UpdateForDataChange(days_since_last_update
);
428 DailyDataSavingUpdate
proxy_enabled(
429 prefs::kDailyOriginalContentLengthWithDataReductionProxyEnabled
,
430 prefs::kDailyContentLengthWithDataReductionProxyEnabled
,
432 proxy_enabled
.UpdateForDataChange(days_since_last_update
);
434 DailyDataSavingUpdate
via_proxy(
435 prefs::kDailyOriginalContentLengthViaDataReductionProxy
,
436 prefs::kDailyContentLengthViaDataReductionProxy
,
438 via_proxy
.UpdateForDataChange(days_since_last_update
);
440 DailyContentLengthUpdate
https(
441 prefs::kDailyContentLengthHttpsWithDataReductionProxyEnabled
, prefs
);
442 https
.UpdateForDataChange(days_since_last_update
);
444 DailyContentLengthUpdate
short_bypass(
445 prefs::kDailyContentLengthShortBypassWithDataReductionProxyEnabled
,
447 short_bypass
.UpdateForDataChange(days_since_last_update
);
449 DailyContentLengthUpdate
long_bypass(
450 prefs::kDailyContentLengthLongBypassWithDataReductionProxyEnabled
, prefs
);
451 long_bypass
.UpdateForDataChange(days_since_last_update
);
453 DailyContentLengthUpdate
unknown(
454 prefs::kDailyContentLengthUnknownWithDataReductionProxyEnabled
, prefs
);
455 unknown
.UpdateForDataChange(days_since_last_update
);
457 total
.Add(original_content_length
, received_content_length
);
458 if (with_data_reduction_proxy_enabled
) {
459 proxy_enabled
.Add(original_content_length
, received_content_length
);
460 // Ignore data source cases, if exist, when
461 // "with_data_reduction_proxy_enabled == false"
462 switch (data_reduction_type
) {
463 case VIA_DATA_REDUCTION_PROXY
:
464 via_proxy
.Add(original_content_length
, received_content_length
);
467 https
.Add(received_content_length
);
470 short_bypass
.Add(received_content_length
);
473 long_bypass
.Add(received_content_length
);
476 unknown
.Add(received_content_length
);
481 if (days_since_last_update
) {
482 // Record the last update time in microseconds in UTC.
483 prefs
->SetInt64(prefs::kDailyHttpContentLengthLastUpdateDate
,
484 midnight
.ToInternalValue());
486 // A new day. Report the previous day's data if exists. We'll lose usage
487 // data if the last time Chrome was run was more than a day ago.
488 // Here, we prefer collecting less data but the collected data is
489 // associated with an accurate date.
490 if (days_since_last_update
== 1) {
491 // The previous day's data point is the second one from the tail.
492 // Therefore (kNumDaysInHistory - 2) below.
493 RecordDailyContentLengthHistograms(
494 total
.GetOriginalListPrefValue(kNumDaysInHistory
- 2),
495 total
.GetReceivedListPrefValue(kNumDaysInHistory
- 2),
496 proxy_enabled
.GetOriginalListPrefValue(kNumDaysInHistory
- 2),
497 proxy_enabled
.GetReceivedListPrefValue(kNumDaysInHistory
- 2),
498 via_proxy
.GetOriginalListPrefValue(kNumDaysInHistory
- 2),
499 via_proxy
.GetReceivedListPrefValue(kNumDaysInHistory
- 2),
500 https
.GetListPrefValue(kNumDaysInHistory
- 2),
501 short_bypass
.GetListPrefValue(kNumDaysInHistory
- 2),
502 long_bypass
.GetListPrefValue(kNumDaysInHistory
- 2),
503 unknown
.GetListPrefValue(kNumDaysInHistory
- 2));
507 #endif // defined(OS_ANDROID) || defined(OS_IOS)
509 void UpdateContentLengthPrefs(
510 int received_content_length
,
511 int original_content_length
,
512 bool with_data_reduction_proxy_enabled
,
513 DataReductionRequestType data_reduction_type
,
514 PrefService
* prefs
) {
515 int64 total_received
= prefs
->GetInt64(prefs::kHttpReceivedContentLength
);
516 int64 total_original
= prefs
->GetInt64(prefs::kHttpOriginalContentLength
);
517 total_received
+= received_content_length
;
518 total_original
+= original_content_length
;
519 prefs
->SetInt64(prefs::kHttpReceivedContentLength
, total_received
);
520 prefs
->SetInt64(prefs::kHttpOriginalContentLength
, total_original
);
522 #if defined(OS_ANDROID) || defined(OS_IOS)
523 UpdateContentLengthPrefsForDataReductionProxy(
524 received_content_length
,
525 original_content_length
,
526 with_data_reduction_proxy_enabled
,
530 #endif // defined(OS_ANDROID) || defined(OS_IOS)
534 } // namespace spdyproxy