| // Copyright 2013 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 "chrome/browser/net/chrome_network_data_saving_metrics.h" |
| |
| #include "base/metrics/histogram.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/prefs/scoped_user_pref_update.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "chrome/browser/net/spdyproxy/data_reduction_proxy_settings.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/pref_names.h" |
| #include "content/public/common/url_constants.h" |
| #include "net/base/host_port_pair.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/proxy/proxy_retry_info.h" |
| #include "net/proxy/proxy_service.h" |
| #include "net/url_request/url_request_context.h" |
| |
| namespace { |
| |
| // A bypass delay more than this is treated as a long delay. |
| const int kLongBypassDelayInSeconds = 30 * 60; |
| |
| #if defined(OS_ANDROID) || defined(OS_IOS) |
| |
| // The number of days of history stored in the content lengths prefs. |
| const size_t kNumDaysInHistory = 60; |
| |
| // Increments an int64, stored as a string, in a ListPref at the specified |
| // index. The value must already exist and be a string representation of a |
| // number. |
| void AddInt64ToListPref(size_t index, |
| int64 length, |
| base::ListValue* list_update) { |
| int64 value = 0; |
| std::string old_string_value; |
| bool rv = list_update->GetString(index, &old_string_value); |
| DCHECK(rv); |
| if (rv) { |
| rv = base::StringToInt64(old_string_value, &value); |
| DCHECK(rv); |
| } |
| value += length; |
| list_update->Set(index, Value::CreateStringValue(base::Int64ToString(value))); |
| } |
| |
| int64 ListPrefInt64Value(const base::ListValue& list_update, size_t index) { |
| std::string string_value; |
| if (!list_update.GetString(index, &string_value)) { |
| NOTREACHED(); |
| return 0; |
| } |
| |
| int64 value = 0; |
| bool rv = base::StringToInt64(string_value, &value); |
| DCHECK(rv); |
| return value; |
| } |
| |
| // Report UMA metrics for daily data reductions. |
| void RecordDailyContentLengthHistograms( |
| int64 original_length, |
| int64 received_length, |
| int64 original_length_with_data_reduction_enabled, |
| int64 received_length_with_data_reduction_enabled, |
| int64 original_length_via_data_reduction_proxy, |
| int64 received_length_via_data_reduction_proxy, |
| int64 https_length_with_data_reduction_enabled, |
| int64 short_bypass_length_with_data_reduction_enabled, |
| int64 long_bypass_length_with_data_reduction_enabled, |
| int64 unknown_length_with_data_reduction_enabled) { |
| // Report daily UMA only for days having received content. |
| if (original_length <= 0 || received_length <= 0) |
| return; |
| |
| // Record metrics in KB. |
| UMA_HISTOGRAM_COUNTS( |
| "Net.DailyOriginalContentLength", original_length >> 10); |
| UMA_HISTOGRAM_COUNTS( |
| "Net.DailyContentLength", received_length >> 10); |
| int percent = 0; |
| // UMA percentage cannot be negative. |
| if (original_length > received_length) { |
| percent = (100 * (original_length - received_length)) / original_length; |
| } |
| UMA_HISTOGRAM_PERCENTAGE("Net.DailyContentSavingPercent", percent); |
| |
| if (original_length_with_data_reduction_enabled <= 0 || |
| received_length_with_data_reduction_enabled <= 0) { |
| return; |
| } |
| |
| UMA_HISTOGRAM_COUNTS( |
| "Net.DailyOriginalContentLength_DataReductionProxyEnabled", |
| original_length_with_data_reduction_enabled >> 10); |
| UMA_HISTOGRAM_COUNTS( |
| "Net.DailyContentLength_DataReductionProxyEnabled", |
| received_length_with_data_reduction_enabled >> 10); |
| |
| int percent_data_reduction_proxy_enabled = 0; |
| // UMA percentage cannot be negative. |
| if (original_length_with_data_reduction_enabled > |
| received_length_with_data_reduction_enabled) { |
| percent_data_reduction_proxy_enabled = |
| 100 * (original_length_with_data_reduction_enabled - |
| received_length_with_data_reduction_enabled) / |
| original_length_with_data_reduction_enabled; |
| } |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Net.DailyContentSavingPercent_DataReductionProxyEnabled", |
| percent_data_reduction_proxy_enabled); |
| |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Net.DailyContentPercent_DataReductionProxyEnabled", |
| (100 * received_length_with_data_reduction_enabled) / received_length); |
| |
| if (https_length_with_data_reduction_enabled > 0) { |
| UMA_HISTOGRAM_COUNTS( |
| "Net.DailyContentLength_DataReductionProxyEnabled_Https", |
| https_length_with_data_reduction_enabled >> 10); |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Net.DailyContentPercent_DataReductionProxyEnabled_Https", |
| (100 * https_length_with_data_reduction_enabled) / received_length); |
| } |
| |
| if (short_bypass_length_with_data_reduction_enabled > 0) { |
| UMA_HISTOGRAM_COUNTS( |
| "Net.DailyContentLength_DataReductionProxyEnabled_ShortBypass", |
| short_bypass_length_with_data_reduction_enabled >> 10); |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Net.DailyContentPercent_DataReductionProxyEnabled_ShortBypass", |
| ((100 * short_bypass_length_with_data_reduction_enabled) / |
| received_length)); |
| } |
| |
| if (long_bypass_length_with_data_reduction_enabled > 0) { |
| UMA_HISTOGRAM_COUNTS( |
| "Net.DailyContentLength_DataReductionProxyEnabled_LongBypass", |
| long_bypass_length_with_data_reduction_enabled >> 10); |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Net.DailyContentPercent_DataReductionProxyEnabled_LongBypass", |
| ((100 * long_bypass_length_with_data_reduction_enabled) / |
| received_length)); |
| } |
| |
| if (unknown_length_with_data_reduction_enabled > 0) { |
| UMA_HISTOGRAM_COUNTS( |
| "Net.DailyContentLength_DataReductionProxyEnabled_Unknown", |
| unknown_length_with_data_reduction_enabled >> 10); |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Net.DailyContentPercent_DataReductionProxyEnabled_Unknown", |
| ((100 * unknown_length_with_data_reduction_enabled) / |
| received_length)); |
| } |
| |
| if (original_length_via_data_reduction_proxy <= 0 || |
| received_length_via_data_reduction_proxy <= 0) { |
| return; |
| } |
| |
| UMA_HISTOGRAM_COUNTS( |
| "Net.DailyOriginalContentLength_ViaDataReductionProxy", |
| original_length_via_data_reduction_proxy >> 10); |
| UMA_HISTOGRAM_COUNTS( |
| "Net.DailyContentLength_ViaDataReductionProxy", |
| received_length_via_data_reduction_proxy >> 10); |
| int percent_via_data_reduction_proxy = 0; |
| if (original_length_via_data_reduction_proxy > |
| received_length_via_data_reduction_proxy) { |
| percent_via_data_reduction_proxy = |
| 100 * (original_length_via_data_reduction_proxy - |
| received_length_via_data_reduction_proxy) / |
| original_length_via_data_reduction_proxy; |
| } |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Net.DailyContentSavingPercent_ViaDataReductionProxy", |
| percent_via_data_reduction_proxy); |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Net.DailyContentPercent_ViaDataReductionProxy", |
| (100 * received_length_via_data_reduction_proxy) / received_length); |
| } |
| |
| // Ensure list has exactly |length| elements, either by truncating at the |
| // front, or appending "0"'s to the back. |
| void MaintainContentLengthPrefsWindow(base::ListValue* list, size_t length) { |
| // Remove data for old days from the front. |
| while (list->GetSize() > length) |
| list->Remove(0, NULL); |
| // Newly added lists are empty. Add entries to back to fill the window, |
| // each initialized to zero. |
| while (list->GetSize() < length) |
| list->AppendString(base::Int64ToString(0)); |
| DCHECK_EQ(length, list->GetSize()); |
| } |
| |
| // DailyContentLengthUpdate maintains a data saving pref. The pref is a list |
| // of |kNumDaysInHistory| elements of daily total content lengths for the past |
| // |kNumDaysInHistory| days. |
| class DailyContentLengthUpdate { |
| public: |
| DailyContentLengthUpdate( |
| const char* pref, |
| PrefService* pref_service) |
| : update_(pref_service, pref) { |
| } |
| |
| void UpdateForDataChange(int days_since_last_update) { |
| // New empty lists may have been created. Maintain the invariant that |
| // there should be exactly |kNumDaysInHistory| days in the histories. |
| MaintainContentLengthPrefsWindow(update_.Get(), kNumDaysInHistory); |
| if (days_since_last_update) { |
| MaintainContentLengthPrefForDateChange(days_since_last_update); |
| } |
| } |
| |
| // Update the lengths for the current day. |
| void Add(int content_length) { |
| AddInt64ToListPref(kNumDaysInHistory - 1, content_length, update_.Get()); |
| } |
| |
| int64 GetListPrefValue(size_t index) { |
| return ListPrefInt64Value(*update_, index); |
| } |
| |
| private: |
| // Update the list for date change and ensure the list has exactly |length| |
| // elements. The last entry in the list will be for the current day after |
| // the update. |
| void MaintainContentLengthPrefForDateChange(int days_since_last_update) { |
| if (days_since_last_update == -1) { |
| // The system may go backwards in time by up to a day for legitimate |
| // reasons, such as with changes to the time zone. In such cases, we |
| // keep adding to the current day. |
| // Note: we accept the fact that some reported data is shifted to |
| // the adjacent day if users travel back and forth across time zones. |
| days_since_last_update = 0; |
| } else if (days_since_last_update < -1) { |
| // Erase all entries if the system went backwards in time by more than |
| // a day. |
| update_->Clear(); |
| |
| days_since_last_update = kNumDaysInHistory; |
| } |
| DCHECK_GE(days_since_last_update, 0); |
| |
| // Add entries for days since last update event. This will make the |
| // lists longer than kNumDaysInHistory. The additional items will be cut off |
| // from the head of the lists by |MaintainContentLengthPrefsWindow|, below. |
| for (int i = 0; |
| i < days_since_last_update && i < static_cast<int>(kNumDaysInHistory); |
| ++i) { |
| update_->AppendString(base::Int64ToString(0)); |
| } |
| |
| // Entries for new days may have been appended. Maintain the invariant that |
| // there should be exactly |kNumDaysInHistory| days in the histories. |
| MaintainContentLengthPrefsWindow(update_.Get(), kNumDaysInHistory); |
| } |
| |
| ListPrefUpdate update_; |
| }; |
| |
| // DailyDataSavingUpdate maintains a pair of data saving prefs, original_update_ |
| // and received_update_. pref_original is a list of |kNumDaysInHistory| elements |
| // of daily total original content lengths for the past |kNumDaysInHistory| |
| // days. pref_received is the corresponding list of the daily total received |
| // content lengths. |
| class DailyDataSavingUpdate { |
| public: |
| DailyDataSavingUpdate( |
| const char* pref_original, |
| const char* pref_received, |
| PrefService* pref_service) |
| : original_(pref_original, pref_service), |
| received_(pref_received, pref_service) { |
| } |
| |
| void UpdateForDataChange(int days_since_last_update) { |
| original_.UpdateForDataChange(days_since_last_update); |
| received_.UpdateForDataChange(days_since_last_update); |
| } |
| |
| // Update the lengths for the current day. |
| void Add(int original_content_length, int received_content_length) { |
| original_.Add(original_content_length); |
| received_.Add(received_content_length); |
| } |
| |
| int64 GetOriginalListPrefValue(size_t index) { |
| return original_.GetListPrefValue(index); |
| } |
| int64 GetReceivedListPrefValue(size_t index) { |
| return received_.GetListPrefValue(index); |
| } |
| |
| private: |
| DailyContentLengthUpdate original_; |
| DailyContentLengthUpdate received_; |
| }; |
| |
| #endif // defined(OS_ANDROID) || defined(OS_IOS) |
| |
| // Returns true if the request is bypassed by all configured data reduction |
| // proxies. It returns the bypass delay in delay_seconds (if not NULL). If |
| // the request is bypassed by more than one proxy, delay_seconds returns |
| // shortest delay. |
| bool IsBypassRequest(const net::URLRequest* request, int64* delay_seconds) { |
| #if defined(OS_ANDROID) || defined(OS_IOS) |
| DataReductionProxySettings::DataReductionProxyList proxies = |
| DataReductionProxySettings::GetDataReductionProxies(); |
| if (proxies.size() == 0) |
| return false; |
| |
| if (request == NULL || request->context() == NULL || |
| request->context()->proxy_service() == NULL) { |
| return false; |
| } |
| |
| const net::ProxyRetryInfoMap& retry_map = |
| request->context()->proxy_service()->proxy_retry_info(); |
| if (retry_map.size() == 0) |
| return false; |
| |
| int64 shortest_delay = 0; |
| // The request is bypassed if all configured proxies are in the retry map. |
| for (size_t i = 0; i < proxies.size(); ++i) { |
| std::string proxy = net::HostPortPair::FromURL(proxies[i]).ToString(); |
| // The retry list has the scheme prefix for https but not for http. |
| if (proxies[i].SchemeIs(content::kHttpsScheme)) |
| proxy = std::string(content::kHttpsScheme) + "://" + proxy; |
| |
| net::ProxyRetryInfoMap::const_iterator found = retry_map.find(proxy); |
| if (found == retry_map.end()) |
| return false; |
| if (shortest_delay == 0 || |
| shortest_delay > found->second.current_delay.InSeconds()) { |
| shortest_delay = found->second.current_delay.InSeconds(); |
| } |
| } |
| if (delay_seconds != NULL) |
| *delay_seconds = shortest_delay; |
| return true; |
| #else |
| return false; |
| #endif // defined(OS_ANDROID) || defined(OS_IOS) |
| } |
| |
| // IsDataReductionProxyReponse returns true if response_headers contains the |
| // data reduction proxy Via header value. |
| bool IsDataReductionProxyReponse( |
| const net::HttpResponseHeaders* response_headers) { |
| const char kDatReductionProxyViaValue[] = "1.1 Chrome Compression Proxy"; |
| size_t value_len = strlen(kDatReductionProxyViaValue); |
| void* iter = NULL; |
| std::string temp; |
| while (response_headers->EnumerateHeader(&iter, "Via", &temp)) { |
| string::const_iterator it = |
| std::search(temp.begin(), |
| temp.end(), |
| kDatReductionProxyViaValue, |
| kDatReductionProxyViaValue + value_len, |
| base::CaseInsensitiveCompareASCII<char>()); |
| if (it != temp.end()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| namespace chrome_browser_net { |
| |
| DataReductionRequestType GetDataReductionRequestType( |
| const Profile* profile, const net::URLRequest* request) { |
| if (profile != NULL && profile->IsOffTheRecord()) |
| return OFF_THE_RECORD; |
| if (request->url().SchemeIs(content::kHttpsScheme)) |
| return HTTPS; |
| if (!request->url().SchemeIs(content::kHttpScheme)) { |
| NOTREACHED(); |
| return UNKNOWN_TYPE; |
| } |
| int64 bypass_delay = 0; |
| if (IsBypassRequest(request, &bypass_delay)) { |
| return (bypass_delay > kLongBypassDelayInSeconds) ? |
| LONG_BYPASS : SHORT_BYPASS; |
| } |
| return IsDataReductionProxyReponse(request->response_info().headers) ? |
| VIA_DATA_REDUCTION_PROXY: UNKNOWN_TYPE; |
| } |
| |
| int64 GetAdjustedOriginalContentLength( |
| DataReductionRequestType data_reduction_type, |
| int64 original_content_length, |
| int64 received_content_length) { |
| // Since there was no indication of the original content length, presume |
| // it is no different from the number of bytes read. |
| if (original_content_length == -1 || |
| data_reduction_type != chrome_browser_net::VIA_DATA_REDUCTION_PROXY) { |
| return received_content_length; |
| } |
| return original_content_length; |
| } |
| |
| #if defined(OS_ANDROID) || defined(OS_IOS) |
| void UpdateContentLengthPrefsForDataReductionProxy( |
| int received_content_length, |
| int original_content_length, |
| bool with_data_reduction_proxy_enabled, |
| DataReductionRequestType data_reduction_type, |
| base::Time now, PrefService* prefs) { |
| // TODO(bengr): Remove this check once the underlying cause of |
| // http://crbug.com/287821 is fixed. For now, only continue if the current |
| // year is reported as being between 1972 and 2970. |
| base::TimeDelta time_since_unix_epoch = now - base::Time::UnixEpoch(); |
| const int kMinDaysSinceUnixEpoch = 365 * 2; // 2 years. |
| const int kMaxDaysSinceUnixEpoch = 365 * 1000; // 1000 years. |
| if (time_since_unix_epoch.InDays() < kMinDaysSinceUnixEpoch || |
| time_since_unix_epoch.InDays() > kMaxDaysSinceUnixEpoch) { |
| return; |
| } |
| |
| // Determine how many days it has been since the last update. |
| int64 then_internal = prefs->GetInt64( |
| prefs::kDailyHttpContentLengthLastUpdateDate); |
| // Local midnight could have been shifted due to time zone change. |
| base::Time then_midnight = |
| base::Time::FromInternalValue(then_internal).LocalMidnight(); |
| base::Time midnight = now.LocalMidnight(); |
| int days_since_last_update = (midnight - then_midnight).InDays(); |
| |
| // Each day, we calculate the total number of bytes received and the total |
| // size of all corresponding resources before any data-reducing recompression |
| // is applied. These values are used to compute the data savings realized |
| // by applying our compression techniques. Totals for the last |
| // |kNumDaysInHistory| days are maintained. |
| DailyDataSavingUpdate total( |
| prefs::kDailyHttpOriginalContentLength, |
| prefs::kDailyHttpReceivedContentLength, |
| prefs); |
| total.UpdateForDataChange(days_since_last_update); |
| |
| DailyDataSavingUpdate proxy_enabled( |
| prefs::kDailyOriginalContentLengthWithDataReductionProxyEnabled, |
| prefs::kDailyContentLengthWithDataReductionProxyEnabled, |
| prefs); |
| proxy_enabled.UpdateForDataChange(days_since_last_update); |
| |
| DailyDataSavingUpdate via_proxy( |
| prefs::kDailyOriginalContentLengthViaDataReductionProxy, |
| prefs::kDailyContentLengthViaDataReductionProxy, |
| prefs); |
| via_proxy.UpdateForDataChange(days_since_last_update); |
| |
| DailyContentLengthUpdate https( |
| prefs::kDailyContentLengthHttpsWithDataReductionProxyEnabled, prefs); |
| https.UpdateForDataChange(days_since_last_update); |
| |
| DailyContentLengthUpdate short_bypass( |
| prefs::kDailyContentLengthShortBypassWithDataReductionProxyEnabled, |
| prefs); |
| short_bypass.UpdateForDataChange(days_since_last_update); |
| |
| DailyContentLengthUpdate long_bypass( |
| prefs::kDailyContentLengthLongBypassWithDataReductionProxyEnabled, prefs); |
| long_bypass.UpdateForDataChange(days_since_last_update); |
| |
| DailyContentLengthUpdate unknown( |
| prefs::kDailyContentLengthUnknownWithDataReductionProxyEnabled, prefs); |
| unknown.UpdateForDataChange(days_since_last_update); |
| |
| total.Add(original_content_length, received_content_length); |
| if (with_data_reduction_proxy_enabled) { |
| proxy_enabled.Add(original_content_length, received_content_length); |
| // Ignore data source cases, if exist, when |
| // "with_data_reduction_proxy_enabled == false" |
| switch (data_reduction_type) { |
| case VIA_DATA_REDUCTION_PROXY: |
| via_proxy.Add(original_content_length, received_content_length); |
| break; |
| case OFF_THE_RECORD: |
| // We don't measure off-the-record data. |
| break; |
| case HTTPS: |
| https.Add(received_content_length); |
| break; |
| case SHORT_BYPASS: |
| short_bypass.Add(received_content_length); |
| break; |
| case LONG_BYPASS: |
| long_bypass.Add(received_content_length); |
| break; |
| case UNKNOWN_TYPE: |
| unknown.Add(received_content_length); |
| break; |
| } |
| } |
| |
| if (days_since_last_update) { |
| // Record the last update time in microseconds in UTC. |
| prefs->SetInt64(prefs::kDailyHttpContentLengthLastUpdateDate, |
| midnight.ToInternalValue()); |
| |
| // A new day. Report the previous day's data if exists. We'll lose usage |
| // data if the last time Chrome was run was more than a day ago. |
| // Here, we prefer collecting less data but the collected data is |
| // associated with an accurate date. |
| if (days_since_last_update == 1) { |
| // The previous day's data point is the second one from the tail. |
| // Therefore (kNumDaysInHistory - 2) below. |
| RecordDailyContentLengthHistograms( |
| total.GetOriginalListPrefValue(kNumDaysInHistory - 2), |
| total.GetReceivedListPrefValue(kNumDaysInHistory - 2), |
| proxy_enabled.GetOriginalListPrefValue(kNumDaysInHistory - 2), |
| proxy_enabled.GetReceivedListPrefValue(kNumDaysInHistory - 2), |
| via_proxy.GetOriginalListPrefValue(kNumDaysInHistory - 2), |
| via_proxy.GetReceivedListPrefValue(kNumDaysInHistory - 2), |
| https.GetListPrefValue(kNumDaysInHistory - 2), |
| short_bypass.GetListPrefValue(kNumDaysInHistory - 2), |
| long_bypass.GetListPrefValue(kNumDaysInHistory - 2), |
| unknown.GetListPrefValue(kNumDaysInHistory - 2)); |
| } |
| } |
| } |
| #endif // defined(OS_ANDROID) || defined(OS_IOS) |
| |
| void UpdateContentLengthPrefs( |
| int received_content_length, |
| int original_content_length, |
| bool with_data_reduction_proxy_enabled, |
| DataReductionRequestType data_reduction_type, |
| PrefService* prefs) { |
| int64 total_received = prefs->GetInt64(prefs::kHttpReceivedContentLength); |
| int64 total_original = prefs->GetInt64(prefs::kHttpOriginalContentLength); |
| total_received += received_content_length; |
| total_original += original_content_length; |
| prefs->SetInt64(prefs::kHttpReceivedContentLength, total_received); |
| prefs->SetInt64(prefs::kHttpOriginalContentLength, total_original); |
| |
| #if defined(OS_ANDROID) || defined(OS_IOS) |
| UpdateContentLengthPrefsForDataReductionProxy( |
| received_content_length, |
| original_content_length, |
| with_data_reduction_proxy_enabled, |
| data_reduction_type, |
| base::Time::Now(), |
| prefs); |
| #endif // defined(OS_ANDROID) || defined(OS_IOS) |
| |
| } |
| |
| } // namespace chrome_browser_net |