root/chrome/browser/net/spdyproxy/data_saving_metrics.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. AddInt64ToListPref
  2. ListPrefInt64Value
  3. RecordDailyContentLengthHistograms
  4. MaintainContentLengthPrefsWindow
  5. UpdateForDataChange
  6. Add
  7. GetListPrefValue
  8. MaintainContentLengthPrefForDateChange
  9. received_
  10. UpdateForDataChange
  11. Add
  12. GetOriginalListPrefValue
  13. GetReceivedListPrefValue
  14. IsBypassRequest
  15. GetDataReductionRequestType
  16. GetAdjustedOriginalContentLength
  17. UpdateContentLengthPrefsForDataReductionProxy
  18. UpdateContentLengthPrefs

// 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/spdyproxy/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/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, new base::StringValue(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)
}

}  // namespace

namespace spdyproxy {

DataReductionRequestType GetDataReductionRequestType(
    const net::URLRequest* request) {
  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;
  }
#if defined(SPDY_PROXY_AUTH_ORIGIN)
  if (request->response_info().headers &&
      request->response_info().headers->IsChromeProxyResponse()) {
    return VIA_DATA_REDUCTION_PROXY;
  }
#endif
  return 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 != spdyproxy::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 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 spdyproxy

/* [<][>][^][v][top][bottom][index][help] */