// 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 #include "base/format_macros.h" #include "base/metrics/histogram.h" #include "base/strings/stringprintf.h" #include "chrome/browser/predictors/autocomplete_action_predictor.h" #include "chrome/browser/prerender/prerender_manager.h" #include "chrome/browser/prerender/prerender_util.h" using predictors::AutocompleteActionPredictor; namespace prerender { namespace { // 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, uint8 experiment_id, bool is_wash, const std::string& name) { if (is_wash) return ComposeHistogramName("wash", name); if (origin == ORIGIN_GWS_PRERENDER) { if (experiment_id == kNoExperiment) return ComposeHistogramName("gws", name); return ComposeHistogramName("exp" + std::string(1, experiment_id + '0'), name); } if (experiment_id != kNoExperiment) 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_LOCAL_PREDICTOR: return ComposeHistogramName("localpredictor", 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: // Handled above. 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 experiment-based and 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, GetCurrentExperimentId(), \ IsOriginExperimentWash(), HISTOGRAM, \ histogram_name) #define PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(histogram_name, origin, \ experiment, HISTOGRAM) \ PREFIXED_HISTOGRAM_INTERNAL(origin, experiment, false, HISTOGRAM, \ histogram_name) #define PREFIXED_HISTOGRAM_INTERNAL(origin, experiment, 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, experiment, wash, \ histogram_name); \ /* Usually, a browsing session should only have a single experiment. */ \ /* Therefore, when there is a second experiment ID other than the one */ \ /* being recorded, don't record anything. */ \ /* Furthermore, experiments only apply if the origin is GWS. Should there */ \ /* somehow be an experiment ID if the origin is not GWS, ignore the */ \ /* experiment ID. */ \ static uint8 recording_experiment = kNoExperiment; \ if (recording_experiment == kNoExperiment && experiment != kNoExperiment) \ recording_experiment = experiment; \ if (wash) { \ HISTOGRAM; \ } else if (experiment != kNoExperiment && \ (origin != ORIGIN_GWS_PRERENDER || \ experiment != recording_experiment)) { \ } 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_LOCAL_PREDICTOR) { \ HISTOGRAM; \ } else if (origin == ORIGIN_EXTERNAL_REQUEST) { \ HISTOGRAM; \ } else if (origin == ORIGIN_INSTANT) { \ HISTOGRAM; \ } else if (origin == ORIGIN_LINK_REL_NEXT) { \ HISTOGRAM; \ } else if (experiment != kNoExperiment) { \ HISTOGRAM; \ } else { \ HISTOGRAM; \ } \ } while (0) PrerenderHistograms::PrerenderHistograms() : last_experiment_id_(kNoExperiment), last_origin_(ORIGIN_MAX), origin_experiment_wash_(false), seen_any_pageload_(true), seen_pageload_started_after_prerender_(true) { } void PrerenderHistograms::RecordPrerender(Origin origin, const GURL& url) { // Check if we are doing an experiment. uint8 experiment = GetQueryStringBasedExperiment(url); // We need to update last_experiment_id_, last_origin_, and // origin_experiment_wash_. if (!WithinWindow()) { // If we are outside a window, this is a fresh start and we are fine, // and there is no mix. origin_experiment_wash_ = false; } else { // If we are inside the last window, there is a mish mash of origins // and experiments if either there was a mish mash before, or the current // experiment/origin does not match the previous one. if (experiment != last_experiment_id_ || origin != last_origin_) origin_experiment_wash_ = true; } last_origin_ = origin; last_experiment_id_ = experiment; // 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( base::StringPrintf("Prerender.OmniboxPrerenderCount%s", PrerenderManager::GetModeString()), 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( base::StringPrintf("Prerender.OmniboxNavigationsUsedPrerenderCount%s", PrerenderManager::GetModeString()), 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. // ...MatchedComplete -- A prerendered page that was swapped in + a few // that were not swapped in so that the set of pages lines up more closely with // the control group. // ...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(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, uint8 experiment_id, PrerenderContents::MatchCompleteStatus mc_status, FinalStatus final_status) const { DCHECK(final_status != FINAL_STATUS_MAX); if (mc_status == PrerenderContents::MATCH_COMPLETE_DEFAULT || mc_status == PrerenderContents::MATCH_COMPLETE_REPLACED) { PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT( "FinalStatus", origin, experiment_id, UMA_HISTOGRAM_ENUMERATION(name, final_status, FINAL_STATUS_MAX)); } if (mc_status == PrerenderContents::MATCH_COMPLETE_DEFAULT || mc_status == PrerenderContents::MATCH_COMPLETE_REPLACEMENT || mc_status == PrerenderContents::MATCH_COMPLETE_REPLACEMENT_PENDING) { PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT( "FinalStatusMatchComplete", origin, experiment_id, UMA_HISTOGRAM_ENUMERATION(name, final_status, FINAL_STATUS_MAX)); } } void PrerenderHistograms::RecordEvent(Origin origin, uint8 experiment_id, PrerenderEvent event) const { DCHECK_LT(event, PRERENDER_EVENT_MAX); PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT( "Event", origin, experiment_id, UMA_HISTOGRAM_ENUMERATION(name, event, PRERENDER_EVENT_MAX)); } void PrerenderHistograms::RecordCookieStatus(Origin origin, uint8 experiment_id, int cookie_status) const { DCHECK_GE(cookie_status, 0); DCHECK_LT(cookie_status, PrerenderContents::kNumCookieStatuses); PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT( "CookieStatus", origin, experiment_id, UMA_HISTOGRAM_ENUMERATION(name, cookie_status, PrerenderContents::kNumCookieStatuses)); } void PrerenderHistograms::RecordCookieSendType( Origin origin, uint8 experiment_id, int cookie_send_type) const { DCHECK_GE(cookie_send_type, 0); DCHECK_LT(cookie_send_type, PrerenderContents::kNumCookieSendTypes); PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT( "CookieSendType", origin, experiment_id, UMA_HISTOGRAM_ENUMERATION(name, cookie_send_type, PrerenderContents::kNumCookieSendTypes)); } void PrerenderHistograms::RecordPrerenderPageVisitedStatus( Origin origin, uint8 experiment_id, bool visited_before) const { PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT( "PageVisitedStatus", origin, experiment_id, UMA_HISTOGRAM_BOOLEAN(name, visited_before)); } void PrerenderHistograms::RecordNetworkBytes(Origin origin, bool used, int64 prerender_bytes, int64 profile_bytes) { 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)); } } uint8 PrerenderHistograms::GetCurrentExperimentId() const { if (!WithinWindow()) return kNoExperiment; return last_experiment_id_; } bool PrerenderHistograms::IsOriginExperimentWash() const { if (!WithinWindow()) return false; return origin_experiment_wash_; } } // namespace prerender