blob: 4e97663049a8d3e75b14b55e98a28ec60af7f2cc [file] [log] [blame]
// Copyright (c) 2012 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/prerender/prerender_histograms.h"
#include <string>
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/prerender/prerender_manager.h"
#include "chrome/browser/prerender/prerender_util.h"
#include "net/http/http_cache.h"
namespace prerender {
namespace {
// This enum is used to define the buckets for the
// "Prerender.NoStatePrefetchResourceCount" histogram family.
// Hence, existing enumerated constants should never be deleted or reordered,
// and new constants should only be appended at the end of the enumeration.
enum NoStatePrefetchResponseType {
NO_STORE = 1 << 0,
REDIRECT = 1 << 1,
MAIN_RESOURCE = 1 << 2,
NO_STATE_PREFETCH_RESPONSE_TYPE_COUNT = 1 << 3
};
int GetResourceType(bool is_main_resource, bool is_redirect, bool is_no_store) {
return (is_no_store * NO_STORE) + (is_redirect * REDIRECT) +
(is_main_resource * MAIN_RESOURCE);
}
// Similar to UMA_HISTOGRAM_ENUMERATION but allows a dynamic histogram name.
// Records a sample such as 0 <= sample < bucket_count, in a histogram with
// |bucket_count| buckets of width 1 each.
void RecordHistogramEnum(const std::string& histogram_name,
base::HistogramBase::Sample sample,
base::HistogramBase::Sample bucket_count) {
DCHECK_LT(sample, bucket_count);
base::HistogramBase* histogram_pointer = base::LinearHistogram::FactoryGet(
histogram_name, 1, bucket_count, bucket_count + 1,
base::HistogramBase::kUmaTargetedHistogramFlag);
histogram_pointer->Add(sample);
}
// Similar to UMA_HISTOGRAM_CUSTOM_TIMES but allows a dynamic histogram name.
void RecordHistogramTime(const std::string& histogram_name,
base::TimeDelta time_min,
base::TimeDelta time_max,
base::TimeDelta sample,
base::HistogramBase::Sample bucket_count) {
base::HistogramBase* histogram_pointer = base::Histogram::FactoryTimeGet(
histogram_name, time_min, time_max, bucket_count,
base::HistogramBase::kUmaTargetedHistogramFlag);
histogram_pointer->AddTime(sample);
}
// Time window for which we will record windowed PLTs from the last observed
// link rel=prefetch tag. This is not intended to be the same as the prerender
// ttl, it's just intended to be a window during which a prerender has likely
// affected performance.
const int kWindowDurationSeconds = 30;
std::string ComposeHistogramName(const std::string& prefix_type,
const std::string& name) {
if (prefix_type.empty())
return std::string("Prerender.") + name;
return std::string("Prerender.") + prefix_type + std::string("_") + name;
}
std::string GetHistogramName(Origin origin, bool is_wash,
const std::string& name) {
if (is_wash)
return ComposeHistogramName("wash", name);
switch (origin) {
case ORIGIN_OMNIBOX:
return ComposeHistogramName("omnibox", name);
case ORIGIN_NONE:
return ComposeHistogramName("none", name);
case ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN:
return ComposeHistogramName("websame", name);
case ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN:
return ComposeHistogramName("webcross", name);
case ORIGIN_EXTERNAL_REQUEST:
return ComposeHistogramName("externalrequest", name);
case ORIGIN_INSTANT:
return ComposeHistogramName("Instant", name);
case ORIGIN_LINK_REL_NEXT:
return ComposeHistogramName("webnext", name);
case ORIGIN_GWS_PRERENDER:
return ComposeHistogramName("gws", name);
case ORIGIN_EXTERNAL_REQUEST_FORCED_CELLULAR:
return ComposeHistogramName("externalrequestforced", name);
case ORIGIN_OFFLINE:
return ComposeHistogramName("offline", name);
default:
NOTREACHED();
break;
}
// Dummy return value to make the compiler happy.
NOTREACHED();
return ComposeHistogramName("wash", name);
}
bool OriginIsOmnibox(Origin origin) {
return origin == ORIGIN_OMNIBOX;
}
} // namespace
// Helper macros for origin-based histogram reporting. All HISTOGRAM arguments
// must be UMA_HISTOGRAM... macros that contain an argument "name" which these
// macros will eventually substitute for the actual name used.
#define PREFIXED_HISTOGRAM(histogram_name, origin, HISTOGRAM) \
PREFIXED_HISTOGRAM_INTERNAL(origin, IsOriginWash(), HISTOGRAM, histogram_name)
#define PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(histogram_name, origin, \
HISTOGRAM) \
PREFIXED_HISTOGRAM_INTERNAL(origin, false, HISTOGRAM, histogram_name)
#define PREFIXED_HISTOGRAM_INTERNAL(origin, wash, HISTOGRAM, histogram_name) \
do { \
{ \
/* Do not rename. HISTOGRAM expects a local variable "name". */ \
std::string name = ComposeHistogramName(std::string(), histogram_name); \
HISTOGRAM; \
} \
/* Do not rename. HISTOGRAM expects a local variable "name". */ \
std::string name = GetHistogramName(origin, wash, histogram_name); \
/* Branching because HISTOGRAM is caching the histogram into a static. */ \
if (wash) { \
HISTOGRAM; \
} else if (origin == ORIGIN_OMNIBOX) { \
HISTOGRAM; \
} else if (origin == ORIGIN_NONE) { \
HISTOGRAM; \
} else if (origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) { \
HISTOGRAM; \
} else if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN) { \
HISTOGRAM; \
} else if (origin == ORIGIN_EXTERNAL_REQUEST) { \
HISTOGRAM; \
} else if (origin == ORIGIN_INSTANT) { \
HISTOGRAM; \
} else if (origin == ORIGIN_LINK_REL_NEXT) { \
HISTOGRAM; \
} else if (origin == ORIGIN_EXTERNAL_REQUEST_FORCED_CELLULAR) { \
HISTOGRAM; \
} else if (origin == ORIGIN_OFFLINE) { \
HISTOGRAM; \
} else { \
HISTOGRAM; \
} \
} while (0)
PrerenderHistograms::PrerenderHistograms()
: last_origin_(ORIGIN_MAX),
origin_wash_(false),
seen_any_pageload_(true),
seen_pageload_started_after_prerender_(true) {
}
void PrerenderHistograms::RecordPrerender(Origin origin, const GURL& url) {
// We need to update last_origin_ and origin_wash_.
if (!WithinWindow()) {
// If we are outside a window, this is a fresh start and we are fine,
// and there is no mix.
origin_wash_ = false;
} else {
// If we are inside the last window, there is a mish mash of origins if
// either there was a mish mash before, or the current origin does not match
// the previous one.
if (origin != last_origin_)
origin_wash_ = true;
}
last_origin_ = origin;
// If we observe multiple tags within the 30 second window, we will still
// reset the window to begin at the most recent occurrence, so that we will
// always be in a window in the 30 seconds from each occurrence.
last_prerender_seen_time_ = GetCurrentTimeTicks();
seen_any_pageload_ = false;
seen_pageload_started_after_prerender_ = false;
}
void PrerenderHistograms::RecordPrerenderStarted(Origin origin) const {
if (OriginIsOmnibox(origin)) {
UMA_HISTOGRAM_ENUMERATION(
"Prerender.OmniboxPrerenderCount", 1, 2);
}
}
void PrerenderHistograms::RecordConcurrency(size_t prerender_count) const {
static const size_t kMaxRecordableConcurrency = 20;
DCHECK_GE(kMaxRecordableConcurrency, Config().max_link_concurrency);
UMA_HISTOGRAM_ENUMERATION(
base::StringPrintf("Prerender.PrerenderCountOf%" PRIuS "Max",
kMaxRecordableConcurrency),
prerender_count, kMaxRecordableConcurrency + 1);
}
void PrerenderHistograms::RecordUsedPrerender(Origin origin) const {
if (OriginIsOmnibox(origin)) {
UMA_HISTOGRAM_ENUMERATION(
"Prerender.OmniboxNavigationsUsedPrerenderCount", 1, 2);
}
}
void PrerenderHistograms::RecordTimeSinceLastRecentVisit(
Origin origin,
base::TimeDelta delta) const {
PREFIXED_HISTOGRAM(
"TimeSinceLastRecentVisit", origin,
UMA_HISTOGRAM_TIMES(name, delta));
}
base::TimeTicks PrerenderHistograms::GetCurrentTimeTicks() const {
return base::TimeTicks::Now();
}
// Helper macro for histograms.
#define RECORD_PLT(tag, perceived_page_load_time) \
PREFIXED_HISTOGRAM( \
tag, origin, \
UMA_HISTOGRAM_CUSTOM_TIMES( \
name, \
perceived_page_load_time, \
base::TimeDelta::FromMilliseconds(10), \
base::TimeDelta::FromSeconds(60), \
100))
// Summary of all histograms Perceived PLT histograms:
// (all prefixed PerceivedPLT)
// PerceivedPLT -- Perceived Pageloadtimes (PPLT) for all pages in the group.
// ...Windowed -- PPLT for pages in the 30s after a prerender is created.
// ...Matched -- A prerendered page that was swapped in. In the NoUse
// and Control group cases, while nothing ever gets swapped in, we do keep
// track of what would be prerendered and would be swapped in -- and those
// cases are what is classified as Match for these groups.
// ...FirstAfterMiss -- First page to finish loading after a prerender, which
// is different from the page that was prerendered.
// ...FirstAfterMissNonOverlapping -- Same as FirstAfterMiss, but only
// triggering for the first page to finish after the prerender that also started
// after the prerender started.
// ...FirstAfterMissBoth -- pages meeting
// FirstAfterMiss AND FirstAfterMissNonOverlapping
// ...FirstAfterMissAnyOnly -- pages meeting
// FirstAfterMiss but NOT FirstAfterMissNonOverlapping
// ..FirstAfterMissNonOverlappingOnly -- pages meeting
// FirstAfterMissNonOverlapping but NOT FirstAfterMiss
void PrerenderHistograms::RecordPerceivedPageLoadTime(
Origin origin,
base::TimeDelta perceived_page_load_time,
NavigationType navigation_type,
const GURL& url) {
if (!url.SchemeIsHTTPOrHTTPS())
return;
bool within_window = WithinWindow();
bool is_google_url = IsGoogleDomain(url);
RECORD_PLT("PerceivedPLT", perceived_page_load_time);
if (within_window)
RECORD_PLT("PerceivedPLTWindowed", perceived_page_load_time);
if (navigation_type != NAVIGATION_TYPE_NORMAL) {
DCHECK(navigation_type == NAVIGATION_TYPE_WOULD_HAVE_BEEN_PRERENDERED ||
navigation_type == NAVIGATION_TYPE_PRERENDERED);
RECORD_PLT("PerceivedPLTMatchedComplete", perceived_page_load_time);
if (navigation_type == NAVIGATION_TYPE_PRERENDERED)
RECORD_PLT("PerceivedPLTMatched", perceived_page_load_time);
seen_any_pageload_ = true;
seen_pageload_started_after_prerender_ = true;
} else if (within_window) {
RECORD_PLT("PerceivedPLTWindowNotMatched", perceived_page_load_time);
if (!is_google_url) {
bool recorded_any = false;
bool recorded_non_overlapping = false;
if (!seen_any_pageload_) {
seen_any_pageload_ = true;
RECORD_PLT("PerceivedPLTFirstAfterMiss", perceived_page_load_time);
recorded_any = true;
}
if (!seen_pageload_started_after_prerender_ &&
perceived_page_load_time <= GetTimeSinceLastPrerender()) {
seen_pageload_started_after_prerender_ = true;
RECORD_PLT("PerceivedPLTFirstAfterMissNonOverlapping",
perceived_page_load_time);
recorded_non_overlapping = true;
}
if (recorded_any || recorded_non_overlapping) {
if (recorded_any && recorded_non_overlapping) {
RECORD_PLT("PerceivedPLTFirstAfterMissBoth",
perceived_page_load_time);
} else if (recorded_any) {
RECORD_PLT("PerceivedPLTFirstAfterMissAnyOnly",
perceived_page_load_time);
} else if (recorded_non_overlapping) {
RECORD_PLT("PerceivedPLTFirstAfterMissNonOverlappingOnly",
perceived_page_load_time);
}
}
}
}
}
void PrerenderHistograms::RecordPageLoadTimeNotSwappedIn(
Origin origin,
base::TimeDelta page_load_time,
const GURL& url) const {
// If the URL to be prerendered is not a http[s] URL, or is a Google URL,
// do not record.
if (!url.SchemeIsHTTPOrHTTPS() || IsGoogleDomain(url))
return;
RECORD_PLT("PrerenderNotSwappedInPLT", page_load_time);
}
void PrerenderHistograms::RecordPercentLoadDoneAtSwapin(Origin origin,
double fraction) const {
if (fraction < 0.0 || fraction > 1.0)
return;
int percentage = static_cast<int>(fraction * 100);
if (percentage < 0 || percentage > 100)
return;
PREFIXED_HISTOGRAM("PercentLoadDoneAtSwapin",
origin, UMA_HISTOGRAM_PERCENTAGE(name, percentage));
}
base::TimeDelta PrerenderHistograms::GetTimeSinceLastPrerender() const {
return base::TimeTicks::Now() - last_prerender_seen_time_;
}
bool PrerenderHistograms::WithinWindow() const {
if (last_prerender_seen_time_.is_null())
return false;
return GetTimeSinceLastPrerender() <=
base::TimeDelta::FromSeconds(kWindowDurationSeconds);
}
void PrerenderHistograms::RecordTimeUntilUsed(
Origin origin,
base::TimeDelta time_until_used) const {
PREFIXED_HISTOGRAM(
"TimeUntilUsed2", origin,
UMA_HISTOGRAM_CUSTOM_TIMES(
name,
time_until_used,
base::TimeDelta::FromMilliseconds(10),
base::TimeDelta::FromMinutes(30),
50));
}
void PrerenderHistograms::RecordAbandonTimeUntilUsed(
Origin origin,
base::TimeDelta time_until_used) const {
PREFIXED_HISTOGRAM(
"AbandonTimeUntilUsed", origin,
UMA_HISTOGRAM_CUSTOM_TIMES(
name,
time_until_used,
base::TimeDelta::FromMilliseconds(10),
base::TimeDelta::FromSeconds(30),
50));
}
void PrerenderHistograms::RecordPerSessionCount(Origin origin,
int count) const {
PREFIXED_HISTOGRAM(
"PrerendersPerSessionCount", origin,
UMA_HISTOGRAM_COUNTS(name, count));
}
void PrerenderHistograms::RecordTimeBetweenPrerenderRequests(
Origin origin, base::TimeDelta time) const {
PREFIXED_HISTOGRAM(
"TimeBetweenPrerenderRequests", origin,
UMA_HISTOGRAM_TIMES(name, time));
}
void PrerenderHistograms::RecordFinalStatus(
Origin origin,
FinalStatus final_status) const {
DCHECK(final_status != FINAL_STATUS_MAX);
PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
"FinalStatus", origin,
UMA_HISTOGRAM_ENUMERATION(name, final_status, FINAL_STATUS_MAX));
}
void PrerenderHistograms::RecordNetworkBytes(Origin origin,
bool used,
int64_t prerender_bytes,
int64_t profile_bytes) const {
const int kHistogramMin = 1;
const int kHistogramMax = 100000000; // 100M.
const int kBucketCount = 50;
UMA_HISTOGRAM_CUSTOM_COUNTS("Prerender.NetworkBytesTotalForProfile",
profile_bytes,
kHistogramMin,
1000000000, // 1G
kBucketCount);
if (prerender_bytes == 0)
return;
if (used) {
PREFIXED_HISTOGRAM(
"NetworkBytesUsed",
origin,
UMA_HISTOGRAM_CUSTOM_COUNTS(
name, prerender_bytes, kHistogramMin, kHistogramMax, kBucketCount));
} else {
PREFIXED_HISTOGRAM(
"NetworkBytesWasted",
origin,
UMA_HISTOGRAM_CUSTOM_COUNTS(
name, prerender_bytes, kHistogramMin, kHistogramMax, kBucketCount));
}
}
void PrerenderHistograms::RecordPrefetchResponseReceived(
Origin origin,
bool is_main_resource,
bool is_redirect,
bool is_no_store) const {
DCHECK(thread_checker_.CalledOnValidThread());
int sample = GetResourceType(is_main_resource, is_redirect, is_no_store);
std::string histogram_name =
GetHistogramName(origin, IsOriginWash(), "NoStatePrefetchResponseTypes");
RecordHistogramEnum(histogram_name, sample,
NO_STATE_PREFETCH_RESPONSE_TYPE_COUNT);
}
void PrerenderHistograms::RecordPrefetchRedirectCount(
Origin origin,
bool is_main_resource,
int redirect_count) const {
DCHECK(thread_checker_.CalledOnValidThread());
const int kMaxRedirectCount = 10;
std::string histogram_base_name = base::StringPrintf(
"NoStatePrefetch%sResourceRedirects", is_main_resource ? "Main" : "Sub");
std::string histogram_name =
GetHistogramName(origin, IsOriginWash(), histogram_base_name);
RecordHistogramEnum(histogram_name, redirect_count, kMaxRedirectCount);
}
void PrerenderHistograms::RecordFirstContentfulPaint(
Origin origin,
bool is_no_store,
base::TimeDelta time,
base::TimeDelta prefetch_age) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!prefetch_age.is_zero()) {
DCHECK_NE(origin, ORIGIN_NONE);
RecordHistogramTime(GetHistogramName(origin, IsOriginWash(),
"Prerender.NoStatePrefetchAge"),
base::TimeDelta::FromMilliseconds(10),
base::TimeDelta::FromMinutes(30), prefetch_age, 50);
}
RecordHistogramTime(GetFirstContentfulPaintHistogramName(
origin, IsOriginWash(), is_no_store, prefetch_age),
base::TimeDelta::FromMilliseconds(10),
base::TimeDelta::FromMinutes(2), time, 50);
}
bool PrerenderHistograms::IsOriginWash() const {
if (!WithinWindow())
return false;
return origin_wash_;
}
// static
std::string PrerenderHistograms::GetFirstContentfulPaintHistogramName(
Origin origin,
bool is_wash,
bool is_no_store,
base::TimeDelta prefetch_age) {
std::string histogram_base_name;
if (prefetch_age.is_zero()) {
histogram_base_name = "NoStatePrefetchTTFCP.Reference";
} else {
histogram_base_name = prefetch_age < base::TimeDelta::FromMinutes(
net::HttpCache::kPrefetchReuseMins)
? "NoStatePrefetchTTFCP.Warm"
: "NoStatePrefetchTTFCP.Cold";
}
histogram_base_name += is_no_store ? ".NoStore" : ".Cacheable";
return GetHistogramName(origin, is_wash, histogram_base_name);
}
} // namespace prerender