Home | History | Annotate | Download | only in prerender
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/prerender/prerender_histograms.h"
      6 
      7 #include <string>
      8 
      9 #include "base/format_macros.h"
     10 #include "base/metrics/histogram.h"
     11 #include "base/strings/stringprintf.h"
     12 #include "chrome/browser/predictors/autocomplete_action_predictor.h"
     13 #include "chrome/browser/prerender/prerender_manager.h"
     14 #include "chrome/browser/prerender/prerender_util.h"
     15 
     16 using predictors::AutocompleteActionPredictor;
     17 
     18 namespace prerender {
     19 
     20 namespace {
     21 
     22 // Time window for which we will record windowed PLTs from the last observed
     23 // link rel=prefetch tag. This is not intended to be the same as the prerender
     24 // ttl, it's just intended to be a window during which a prerender has likely
     25 // affected performance.
     26 const int kWindowDurationSeconds = 30;
     27 
     28 std::string ComposeHistogramName(const std::string& prefix_type,
     29                                  const std::string& name) {
     30   if (prefix_type.empty())
     31     return std::string("Prerender.") + name;
     32   return std::string("Prerender.") + prefix_type + std::string("_") + name;
     33 }
     34 
     35 std::string GetHistogramName(Origin origin, uint8 experiment_id,
     36                              bool is_wash, const std::string& name) {
     37   if (is_wash)
     38     return ComposeHistogramName("wash", name);
     39 
     40   if (origin == ORIGIN_GWS_PRERENDER) {
     41     if (experiment_id == kNoExperiment)
     42       return ComposeHistogramName("gws", name);
     43     return ComposeHistogramName("exp" + std::string(1, experiment_id + '0'),
     44                                 name);
     45   }
     46 
     47   if (experiment_id != kNoExperiment)
     48     return ComposeHistogramName("wash", name);
     49 
     50   switch (origin) {
     51     case ORIGIN_OMNIBOX:
     52       return ComposeHistogramName("omnibox", name);
     53     case ORIGIN_NONE:
     54       return ComposeHistogramName("none", name);
     55     case ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN:
     56       return ComposeHistogramName("websame", name);
     57     case ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN:
     58       return ComposeHistogramName("webcross", name);
     59     case ORIGIN_LOCAL_PREDICTOR:
     60       return ComposeHistogramName("localpredictor", name);
     61     case ORIGIN_EXTERNAL_REQUEST:
     62         return ComposeHistogramName("externalrequest", name);
     63     case ORIGIN_INSTANT:
     64       return ComposeHistogramName("Instant", name);
     65     case ORIGIN_GWS_PRERENDER:  // Handled above.
     66     default:
     67       NOTREACHED();
     68       break;
     69   };
     70 
     71   // Dummy return value to make the compiler happy.
     72   NOTREACHED();
     73   return ComposeHistogramName("wash", name);
     74 }
     75 
     76 bool OriginIsOmnibox(Origin origin) {
     77   return origin == ORIGIN_OMNIBOX;
     78 }
     79 
     80 }  // namespace
     81 
     82 // Helper macros for experiment-based and origin-based histogram reporting.
     83 // All HISTOGRAM arguments must be UMA_HISTOGRAM... macros that contain an
     84 // argument "name" which these macros will eventually substitute for the
     85 // actual name used.
     86 #define PREFIXED_HISTOGRAM(histogram_name, origin, HISTOGRAM)           \
     87   PREFIXED_HISTOGRAM_INTERNAL(origin, GetCurrentExperimentId(),         \
     88                               IsOriginExperimentWash(), HISTOGRAM, \
     89                               histogram_name)
     90 
     91 #define PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(histogram_name, origin, \
     92                                              experiment, HISTOGRAM) \
     93   PREFIXED_HISTOGRAM_INTERNAL(origin, experiment, false, HISTOGRAM, \
     94                               histogram_name)
     95 
     96 #define PREFIXED_HISTOGRAM_INTERNAL(origin, experiment, wash, HISTOGRAM, \
     97                                     histogram_name) { \
     98   { \
     99     /* Do not rename.  HISTOGRAM expects a local variable "name". */           \
    100     std::string name = ComposeHistogramName(std::string(), histogram_name);    \
    101     HISTOGRAM;                                                                 \
    102   } \
    103   /* Do not rename.  HISTOGRAM expects a local variable "name". */ \
    104   std::string name = GetHistogramName(origin, experiment, wash, \
    105                                       histogram_name); \
    106   /* Usually, a browsing session should only have a single experiment. */ \
    107   /* Therefore, when there is a second experiment ID other than the one */ \
    108   /* being recorded, don't record anything. */ \
    109   /* Furthermore, experiments only apply if the origin is GWS. Should there */ \
    110   /* somehow be an experiment ID if the origin is not GWS, ignore the */ \
    111   /* experiment ID. */ \
    112   static uint8 recording_experiment = kNoExperiment; \
    113   if (recording_experiment == kNoExperiment && experiment != kNoExperiment) \
    114     recording_experiment = experiment; \
    115   if (wash) { \
    116     HISTOGRAM; \
    117   } else if (experiment != kNoExperiment && \
    118              (origin != ORIGIN_GWS_PRERENDER || \
    119               experiment != recording_experiment)) { \
    120   } else if (origin == ORIGIN_OMNIBOX) { \
    121     HISTOGRAM; \
    122   } else if (origin == ORIGIN_NONE) { \
    123     HISTOGRAM; \
    124   } else if (origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) { \
    125     HISTOGRAM; \
    126   } else if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN) { \
    127     HISTOGRAM; \
    128   } else if (origin == ORIGIN_LOCAL_PREDICTOR) { \
    129     HISTOGRAM; \
    130   } else if (origin == ORIGIN_EXTERNAL_REQUEST) { \
    131     HISTOGRAM; \
    132   } else if (origin == ORIGIN_INSTANT) { \
    133     HISTOGRAM; \
    134   } else if (experiment != kNoExperiment) { \
    135     HISTOGRAM; \
    136   } else { \
    137     HISTOGRAM; \
    138   } \
    139 }
    140 
    141 PrerenderHistograms::PrerenderHistograms()
    142     : last_experiment_id_(kNoExperiment),
    143       last_origin_(ORIGIN_MAX),
    144       origin_experiment_wash_(false),
    145       seen_any_pageload_(true),
    146       seen_pageload_started_after_prerender_(true) {
    147 }
    148 
    149 void PrerenderHistograms::RecordPrerender(Origin origin, const GURL& url) {
    150   // Check if we are doing an experiment.
    151   uint8 experiment = GetQueryStringBasedExperiment(url);
    152 
    153   // We need to update last_experiment_id_, last_origin_, and
    154   // origin_experiment_wash_.
    155   if (!WithinWindow()) {
    156     // If we are outside a window, this is a fresh start and we are fine,
    157     // and there is no mix.
    158     origin_experiment_wash_ = false;
    159   } else {
    160     // If we are inside the last window, there is a mish mash of origins
    161     // and experiments if either there was a mish mash before, or the current
    162     // experiment/origin does not match the previous one.
    163     if (experiment != last_experiment_id_ || origin != last_origin_)
    164       origin_experiment_wash_ = true;
    165   }
    166 
    167   last_origin_ = origin;
    168   last_experiment_id_ = experiment;
    169 
    170   // If we observe multiple tags within the 30 second window, we will still
    171   // reset the window to begin at the most recent occurrence, so that we will
    172   // always be in a window in the 30 seconds from each occurrence.
    173   last_prerender_seen_time_ = GetCurrentTimeTicks();
    174   seen_any_pageload_ = false;
    175   seen_pageload_started_after_prerender_ = false;
    176 }
    177 
    178 void PrerenderHistograms::RecordPrerenderStarted(Origin origin) const {
    179   if (OriginIsOmnibox(origin)) {
    180     UMA_HISTOGRAM_ENUMERATION(
    181         base::StringPrintf("Prerender.OmniboxPrerenderCount%s",
    182                            PrerenderManager::GetModeString()), 1, 2);
    183   }
    184 }
    185 
    186 void PrerenderHistograms::RecordConcurrency(size_t prerender_count) const {
    187   static const size_t kMaxRecordableConcurrency = 20;
    188   DCHECK_GE(kMaxRecordableConcurrency, Config().max_link_concurrency);
    189   UMA_HISTOGRAM_ENUMERATION(
    190       base::StringPrintf("Prerender.PrerenderCountOf%" PRIuS "Max",
    191                          kMaxRecordableConcurrency),
    192       prerender_count, kMaxRecordableConcurrency + 1);
    193 }
    194 
    195 void PrerenderHistograms::RecordUsedPrerender(Origin origin) const {
    196   if (OriginIsOmnibox(origin)) {
    197     UMA_HISTOGRAM_ENUMERATION(
    198         base::StringPrintf("Prerender.OmniboxNavigationsUsedPrerenderCount%s",
    199                            PrerenderManager::GetModeString()), 1, 2);
    200   }
    201 }
    202 
    203 void PrerenderHistograms::RecordTimeSinceLastRecentVisit(
    204     Origin origin,
    205     base::TimeDelta delta) const {
    206   PREFIXED_HISTOGRAM(
    207       "TimeSinceLastRecentVisit", origin,
    208       UMA_HISTOGRAM_TIMES(name, delta));
    209 }
    210 
    211 void PrerenderHistograms::RecordFractionPixelsFinalAtSwapin(
    212     Origin origin,
    213     double fraction) const {
    214   if (fraction < 0.0 || fraction > 1.0)
    215     return;
    216   int percentage = static_cast<int>(fraction * 100);
    217   if (percentage < 0 || percentage > 100)
    218     return;
    219   PREFIXED_HISTOGRAM("FractionPixelsFinalAtSwapin",
    220                      origin, UMA_HISTOGRAM_PERCENTAGE(name, percentage));
    221 }
    222 
    223 base::TimeTicks PrerenderHistograms::GetCurrentTimeTicks() const {
    224   return base::TimeTicks::Now();
    225 }
    226 
    227 // Helper macro for histograms.
    228 #define RECORD_PLT(tag, perceived_page_load_time) { \
    229   PREFIXED_HISTOGRAM( \
    230       tag, origin, \
    231       UMA_HISTOGRAM_CUSTOM_TIMES( \
    232         name, \
    233         perceived_page_load_time, \
    234         base::TimeDelta::FromMilliseconds(10), \
    235         base::TimeDelta::FromSeconds(60), \
    236         100)); \
    237 }
    238 
    239 // Summary of all histograms Perceived PLT histograms:
    240 // (all prefixed PerceivedPLT)
    241 // PerceivedPLT -- Perceived Pageloadtimes (PPLT) for all pages in the group.
    242 // ...Windowed -- PPLT for pages in the 30s after a prerender is created.
    243 // ...Matched -- A prerendered page that was swapped in.  In the NoUse
    244 // and Control group cases, while nothing ever gets swapped in, we do keep
    245 // track of what would be prerendered and would be swapped in -- and those
    246 // cases are what is classified as Match for these groups.
    247 // ...MatchedComplete -- A prerendered page that was swapped in + a few
    248 // that were not swapped in so that the set of pages lines up more closely with
    249 // the control group.
    250 // ...FirstAfterMiss -- First page to finish loading after a prerender, which
    251 // is different from the page that was prerendered.
    252 // ...FirstAfterMissNonOverlapping -- Same as FirstAfterMiss, but only
    253 // triggering for the first page to finish after the prerender that also started
    254 // after the prerender started.
    255 // ...FirstAfterMissBoth -- pages meeting
    256 // FirstAfterMiss AND FirstAfterMissNonOverlapping
    257 // ...FirstAfterMissAnyOnly -- pages meeting
    258 // FirstAfterMiss but NOT FirstAfterMissNonOverlapping
    259 // ..FirstAfterMissNonOverlappingOnly -- pages meeting
    260 // FirstAfterMissNonOverlapping but NOT FirstAfterMiss
    261 
    262 void PrerenderHistograms::RecordPerceivedPageLoadTime(
    263     Origin origin,
    264     base::TimeDelta perceived_page_load_time,
    265     bool was_prerender,
    266     bool was_complete_prerender, const GURL& url) {
    267   if (!url.SchemeIsHTTPOrHTTPS())
    268     return;
    269   bool within_window = WithinWindow();
    270   bool is_google_url = IsGoogleDomain(url);
    271   RECORD_PLT("PerceivedPLT", perceived_page_load_time);
    272   if (within_window)
    273     RECORD_PLT("PerceivedPLTWindowed", perceived_page_load_time);
    274   if (was_prerender || was_complete_prerender) {
    275     if (was_prerender)
    276       RECORD_PLT("PerceivedPLTMatched", perceived_page_load_time);
    277     if (was_complete_prerender)
    278       RECORD_PLT("PerceivedPLTMatchedComplete", perceived_page_load_time);
    279     seen_any_pageload_ = true;
    280     seen_pageload_started_after_prerender_ = true;
    281   } else if (within_window) {
    282     RECORD_PLT("PerceivedPLTWindowNotMatched", perceived_page_load_time);
    283     if (!is_google_url) {
    284       bool recorded_any = false;
    285       bool recorded_non_overlapping = false;
    286       if (!seen_any_pageload_) {
    287         seen_any_pageload_ = true;
    288         RECORD_PLT("PerceivedPLTFirstAfterMiss", perceived_page_load_time);
    289         recorded_any = true;
    290       }
    291       if (!seen_pageload_started_after_prerender_ &&
    292           perceived_page_load_time <= GetTimeSinceLastPrerender()) {
    293         seen_pageload_started_after_prerender_ = true;
    294         RECORD_PLT("PerceivedPLTFirstAfterMissNonOverlapping",
    295                    perceived_page_load_time);
    296         recorded_non_overlapping = true;
    297       }
    298       if (recorded_any || recorded_non_overlapping) {
    299         if (recorded_any && recorded_non_overlapping) {
    300           RECORD_PLT("PerceivedPLTFirstAfterMissBoth",
    301                      perceived_page_load_time);
    302         } else if (recorded_any) {
    303           RECORD_PLT("PerceivedPLTFirstAfterMissAnyOnly",
    304                      perceived_page_load_time);
    305         } else if (recorded_non_overlapping) {
    306           RECORD_PLT("PerceivedPLTFirstAfterMissNonOverlappingOnly",
    307                      perceived_page_load_time);
    308         }
    309       }
    310     }
    311   }
    312 }
    313 
    314 void PrerenderHistograms::RecordPageLoadTimeNotSwappedIn(
    315     Origin origin,
    316     base::TimeDelta page_load_time,
    317     const GURL& url) const {
    318   // If the URL to be prerendered is not a http[s] URL, or is a Google URL,
    319   // do not record.
    320   if (!url.SchemeIsHTTPOrHTTPS() || IsGoogleDomain(url))
    321     return;
    322   RECORD_PLT("PrerenderNotSwappedInPLT", page_load_time);
    323 }
    324 
    325 void PrerenderHistograms::RecordPercentLoadDoneAtSwapin(Origin origin,
    326                                                         double fraction) const {
    327   if (fraction < 0.0 || fraction > 1.0)
    328     return;
    329   int percentage = static_cast<int>(fraction * 100);
    330   if (percentage < 0 || percentage > 100)
    331     return;
    332   PREFIXED_HISTOGRAM("PercentLoadDoneAtSwapin",
    333                      origin, UMA_HISTOGRAM_PERCENTAGE(name, percentage));
    334 }
    335 
    336 base::TimeDelta PrerenderHistograms::GetTimeSinceLastPrerender() const {
    337   return base::TimeTicks::Now() - last_prerender_seen_time_;
    338 }
    339 
    340 bool PrerenderHistograms::WithinWindow() const {
    341   if (last_prerender_seen_time_.is_null())
    342     return false;
    343   return GetTimeSinceLastPrerender() <=
    344       base::TimeDelta::FromSeconds(kWindowDurationSeconds);
    345 }
    346 
    347 void PrerenderHistograms::RecordTimeUntilUsed(
    348     Origin origin,
    349     base::TimeDelta time_until_used) const {
    350   PREFIXED_HISTOGRAM(
    351       "TimeUntilUsed2", origin,
    352       UMA_HISTOGRAM_CUSTOM_TIMES(
    353           name,
    354           time_until_used,
    355           base::TimeDelta::FromMilliseconds(10),
    356           base::TimeDelta::FromMinutes(30),
    357           50));
    358 }
    359 
    360 void PrerenderHistograms::RecordPerSessionCount(Origin origin,
    361                                                 int count) const {
    362   PREFIXED_HISTOGRAM(
    363       "PrerendersPerSessionCount", origin,
    364       UMA_HISTOGRAM_COUNTS(name, count));
    365 }
    366 
    367 void PrerenderHistograms::RecordTimeBetweenPrerenderRequests(
    368     Origin origin, base::TimeDelta time) const {
    369   PREFIXED_HISTOGRAM(
    370       "TimeBetweenPrerenderRequests", origin,
    371       UMA_HISTOGRAM_TIMES(name, time));
    372 }
    373 
    374 void PrerenderHistograms::RecordFinalStatus(
    375     Origin origin,
    376     uint8 experiment_id,
    377     PrerenderContents::MatchCompleteStatus mc_status,
    378     FinalStatus final_status) const {
    379   DCHECK(final_status != FINAL_STATUS_MAX);
    380 
    381   if (mc_status == PrerenderContents::MATCH_COMPLETE_DEFAULT ||
    382       mc_status == PrerenderContents::MATCH_COMPLETE_REPLACED) {
    383     PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
    384         "FinalStatus", origin, experiment_id,
    385         UMA_HISTOGRAM_ENUMERATION(name, final_status, FINAL_STATUS_MAX));
    386   }
    387   if (mc_status == PrerenderContents::MATCH_COMPLETE_DEFAULT ||
    388       mc_status == PrerenderContents::MATCH_COMPLETE_REPLACEMENT ||
    389       mc_status == PrerenderContents::MATCH_COMPLETE_REPLACEMENT_PENDING) {
    390     PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
    391         "FinalStatusMatchComplete", origin, experiment_id,
    392         UMA_HISTOGRAM_ENUMERATION(name, final_status, FINAL_STATUS_MAX));
    393   }
    394 }
    395 
    396 void PrerenderHistograms::RecordEvent(Origin origin, uint8 experiment_id,
    397                                       PrerenderEvent event) const {
    398   DCHECK_LT(event, PRERENDER_EVENT_MAX);
    399   PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
    400       "Event", origin, experiment_id,
    401       UMA_HISTOGRAM_ENUMERATION(name, event, PRERENDER_EVENT_MAX));
    402 }
    403 
    404 void PrerenderHistograms::RecordCookieStatus(Origin origin,
    405                                              uint8 experiment_id,
    406                                              int cookie_status) const {
    407   DCHECK_GE(cookie_status, 0);
    408   DCHECK_LT(cookie_status, PrerenderContents::kNumCookieStatuses);
    409   PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
    410       "CookieStatus", origin, experiment_id,
    411       UMA_HISTOGRAM_ENUMERATION(name, cookie_status,
    412                                 PrerenderContents::kNumCookieStatuses));
    413 }
    414 
    415 void PrerenderHistograms::RecordPrerenderPageVisitedStatus(
    416     Origin origin,
    417     uint8 experiment_id,
    418     bool visited_before) const {
    419   PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
    420       "PageVisitedStatus", origin, experiment_id,
    421       UMA_HISTOGRAM_BOOLEAN(name, visited_before));
    422 }
    423 
    424 uint8 PrerenderHistograms::GetCurrentExperimentId() const {
    425   if (!WithinWindow())
    426     return kNoExperiment;
    427   return last_experiment_id_;
    428 }
    429 
    430 bool PrerenderHistograms::IsOriginExperimentWash() const {
    431   if (!WithinWindow())
    432     return false;
    433   return origin_experiment_wash_;
    434 }
    435 
    436 }  // namespace prerender
    437