| // 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/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_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 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); \ |
| 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 (experiment != kNoExperiment) { \ |
| HISTOGRAM; \ |
| } else { \ |
| HISTOGRAM; \ |
| } \ |
| } |
| |
| 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)); |
| } |
| |
| void PrerenderHistograms::RecordFractionPixelsFinalAtSwapin( |
| 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("FractionPixelsFinalAtSwapin", |
| origin, UMA_HISTOGRAM_PERCENTAGE(name, percentage)); |
| } |
| |
| 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, |
| bool was_prerender, |
| bool was_complete_prerender, 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 (was_prerender || was_complete_prerender) { |
| if (was_prerender) |
| RECORD_PLT("PerceivedPLTMatched", perceived_page_load_time); |
| if (was_complete_prerender) |
| RECORD_PLT("PerceivedPLTMatchedComplete", 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::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(PrerenderEvent event) const { |
| DCHECK_LT(event, PRERENDER_EVENT_MAX); |
| UMA_HISTOGRAM_ENUMERATION("Prerender.Event", event, PRERENDER_EVENT_MAX); |
| } |
| |
| 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 |