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_LINK_REL_NEXT:
     66       return ComposeHistogramName("webnext", name);
     67     case ORIGIN_GWS_PRERENDER:  // Handled above.
     68     default:
     69       NOTREACHED();
     70       break;
     71   };
     72 
     73   // Dummy return value to make the compiler happy.
     74   NOTREACHED();
     75   return ComposeHistogramName("wash", name);
     76 }
     77 
     78 bool OriginIsOmnibox(Origin origin) {
     79   return origin == ORIGIN_OMNIBOX;
     80 }
     81 
     82 }  // namespace
     83 
     84 // Helper macros for experiment-based and origin-based histogram reporting.
     85 // All HISTOGRAM arguments must be UMA_HISTOGRAM... macros that contain an
     86 // argument "name" which these macros will eventually substitute for the
     87 // actual name used.
     88 #define PREFIXED_HISTOGRAM(histogram_name, origin, HISTOGRAM)           \
     89   PREFIXED_HISTOGRAM_INTERNAL(origin, GetCurrentExperimentId(),         \
     90                               IsOriginExperimentWash(), HISTOGRAM, \
     91                               histogram_name)
     92 
     93 #define PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(histogram_name, origin, \
     94                                              experiment, HISTOGRAM) \
     95   PREFIXED_HISTOGRAM_INTERNAL(origin, experiment, false, HISTOGRAM, \
     96                               histogram_name)
     97 
     98 #define PREFIXED_HISTOGRAM_INTERNAL(origin, experiment, wash, HISTOGRAM, \
     99                                     histogram_name) do { \
    100   { \
    101     /* Do not rename.  HISTOGRAM expects a local variable "name". */           \
    102     std::string name = ComposeHistogramName(std::string(), histogram_name);    \
    103     HISTOGRAM;                                                                 \
    104   } \
    105   /* Do not rename.  HISTOGRAM expects a local variable "name". */ \
    106   std::string name = GetHistogramName(origin, experiment, wash, \
    107                                       histogram_name); \
    108   /* Usually, a browsing session should only have a single experiment. */ \
    109   /* Therefore, when there is a second experiment ID other than the one */ \
    110   /* being recorded, don't record anything. */ \
    111   /* Furthermore, experiments only apply if the origin is GWS. Should there */ \
    112   /* somehow be an experiment ID if the origin is not GWS, ignore the */ \
    113   /* experiment ID. */ \
    114   static uint8 recording_experiment = kNoExperiment; \
    115   if (recording_experiment == kNoExperiment && experiment != kNoExperiment) \
    116     recording_experiment = experiment; \
    117   if (wash) { \
    118     HISTOGRAM; \
    119   } else if (experiment != kNoExperiment && \
    120              (origin != ORIGIN_GWS_PRERENDER || \
    121               experiment != recording_experiment)) { \
    122   } else if (origin == ORIGIN_OMNIBOX) { \
    123     HISTOGRAM; \
    124   } else if (origin == ORIGIN_NONE) { \
    125     HISTOGRAM; \
    126   } else if (origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) { \
    127     HISTOGRAM; \
    128   } else if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN) { \
    129     HISTOGRAM; \
    130   } else if (origin == ORIGIN_LOCAL_PREDICTOR) { \
    131     HISTOGRAM; \
    132   } else if (origin == ORIGIN_EXTERNAL_REQUEST) { \
    133     HISTOGRAM; \
    134   } else if (origin == ORIGIN_INSTANT) { \
    135     HISTOGRAM; \
    136   } else if (origin == ORIGIN_LINK_REL_NEXT) { \
    137     HISTOGRAM; \
    138   } else if (experiment != kNoExperiment) { \
    139     HISTOGRAM; \
    140   } else { \
    141     HISTOGRAM; \
    142   } \
    143 } while (0)
    144 
    145 PrerenderHistograms::PrerenderHistograms()
    146     : last_experiment_id_(kNoExperiment),
    147       last_origin_(ORIGIN_MAX),
    148       origin_experiment_wash_(false),
    149       seen_any_pageload_(true),
    150       seen_pageload_started_after_prerender_(true) {
    151 }
    152 
    153 void PrerenderHistograms::RecordPrerender(Origin origin, const GURL& url) {
    154   // Check if we are doing an experiment.
    155   uint8 experiment = GetQueryStringBasedExperiment(url);
    156 
    157   // We need to update last_experiment_id_, last_origin_, and
    158   // origin_experiment_wash_.
    159   if (!WithinWindow()) {
    160     // If we are outside a window, this is a fresh start and we are fine,
    161     // and there is no mix.
    162     origin_experiment_wash_ = false;
    163   } else {
    164     // If we are inside the last window, there is a mish mash of origins
    165     // and experiments if either there was a mish mash before, or the current
    166     // experiment/origin does not match the previous one.
    167     if (experiment != last_experiment_id_ || origin != last_origin_)
    168       origin_experiment_wash_ = true;
    169   }
    170 
    171   last_origin_ = origin;
    172   last_experiment_id_ = experiment;
    173 
    174   // If we observe multiple tags within the 30 second window, we will still
    175   // reset the window to begin at the most recent occurrence, so that we will
    176   // always be in a window in the 30 seconds from each occurrence.
    177   last_prerender_seen_time_ = GetCurrentTimeTicks();
    178   seen_any_pageload_ = false;
    179   seen_pageload_started_after_prerender_ = false;
    180 }
    181 
    182 void PrerenderHistograms::RecordPrerenderStarted(Origin origin) const {
    183   if (OriginIsOmnibox(origin)) {
    184     UMA_HISTOGRAM_ENUMERATION(
    185         base::StringPrintf("Prerender.OmniboxPrerenderCount%s",
    186                            PrerenderManager::GetModeString()), 1, 2);
    187   }
    188 }
    189 
    190 void PrerenderHistograms::RecordConcurrency(size_t prerender_count) const {
    191   static const size_t kMaxRecordableConcurrency = 20;
    192   DCHECK_GE(kMaxRecordableConcurrency, Config().max_link_concurrency);
    193   UMA_HISTOGRAM_ENUMERATION(
    194       base::StringPrintf("Prerender.PrerenderCountOf%" PRIuS "Max",
    195                          kMaxRecordableConcurrency),
    196       prerender_count, kMaxRecordableConcurrency + 1);
    197 }
    198 
    199 void PrerenderHistograms::RecordUsedPrerender(Origin origin) const {
    200   if (OriginIsOmnibox(origin)) {
    201     UMA_HISTOGRAM_ENUMERATION(
    202         base::StringPrintf("Prerender.OmniboxNavigationsUsedPrerenderCount%s",
    203                            PrerenderManager::GetModeString()), 1, 2);
    204   }
    205 }
    206 
    207 void PrerenderHistograms::RecordTimeSinceLastRecentVisit(
    208     Origin origin,
    209     base::TimeDelta delta) const {
    210   PREFIXED_HISTOGRAM(
    211       "TimeSinceLastRecentVisit", origin,
    212       UMA_HISTOGRAM_TIMES(name, delta));
    213 }
    214 
    215 base::TimeTicks PrerenderHistograms::GetCurrentTimeTicks() const {
    216   return base::TimeTicks::Now();
    217 }
    218 
    219 // Helper macro for histograms.
    220 #define RECORD_PLT(tag, perceived_page_load_time) \
    221   PREFIXED_HISTOGRAM( \
    222       tag, origin, \
    223       UMA_HISTOGRAM_CUSTOM_TIMES( \
    224         name, \
    225         perceived_page_load_time, \
    226         base::TimeDelta::FromMilliseconds(10), \
    227         base::TimeDelta::FromSeconds(60), \
    228         100))
    229 
    230 // Summary of all histograms Perceived PLT histograms:
    231 // (all prefixed PerceivedPLT)
    232 // PerceivedPLT -- Perceived Pageloadtimes (PPLT) for all pages in the group.
    233 // ...Windowed -- PPLT for pages in the 30s after a prerender is created.
    234 // ...Matched -- A prerendered page that was swapped in.  In the NoUse
    235 // and Control group cases, while nothing ever gets swapped in, we do keep
    236 // track of what would be prerendered and would be swapped in -- and those
    237 // cases are what is classified as Match for these groups.
    238 // ...MatchedComplete -- A prerendered page that was swapped in + a few
    239 // that were not swapped in so that the set of pages lines up more closely with
    240 // the control group.
    241 // ...FirstAfterMiss -- First page to finish loading after a prerender, which
    242 // is different from the page that was prerendered.
    243 // ...FirstAfterMissNonOverlapping -- Same as FirstAfterMiss, but only
    244 // triggering for the first page to finish after the prerender that also started
    245 // after the prerender started.
    246 // ...FirstAfterMissBoth -- pages meeting
    247 // FirstAfterMiss AND FirstAfterMissNonOverlapping
    248 // ...FirstAfterMissAnyOnly -- pages meeting
    249 // FirstAfterMiss but NOT FirstAfterMissNonOverlapping
    250 // ..FirstAfterMissNonOverlappingOnly -- pages meeting
    251 // FirstAfterMissNonOverlapping but NOT FirstAfterMiss
    252 
    253 void PrerenderHistograms::RecordPerceivedPageLoadTime(
    254     Origin origin,
    255     base::TimeDelta perceived_page_load_time,
    256     NavigationType navigation_type,
    257     const GURL& url) {
    258   if (!url.SchemeIsHTTPOrHTTPS())
    259     return;
    260   bool within_window = WithinWindow();
    261   bool is_google_url = IsGoogleDomain(url);
    262   RECORD_PLT("PerceivedPLT", perceived_page_load_time);
    263   if (within_window)
    264     RECORD_PLT("PerceivedPLTWindowed", perceived_page_load_time);
    265   if (navigation_type != NAVIGATION_TYPE_NORMAL) {
    266     DCHECK(navigation_type == NAVIGATION_TYPE_WOULD_HAVE_BEEN_PRERENDERED ||
    267            navigation_type == NAVIGATION_TYPE_PRERENDERED);
    268     RECORD_PLT("PerceivedPLTMatchedComplete", perceived_page_load_time);
    269     if (navigation_type == NAVIGATION_TYPE_PRERENDERED)
    270       RECORD_PLT("PerceivedPLTMatched", perceived_page_load_time);
    271     seen_any_pageload_ = true;
    272     seen_pageload_started_after_prerender_ = true;
    273   } else if (within_window) {
    274     RECORD_PLT("PerceivedPLTWindowNotMatched", perceived_page_load_time);
    275     if (!is_google_url) {
    276       bool recorded_any = false;
    277       bool recorded_non_overlapping = false;
    278       if (!seen_any_pageload_) {
    279         seen_any_pageload_ = true;
    280         RECORD_PLT("PerceivedPLTFirstAfterMiss", perceived_page_load_time);
    281         recorded_any = true;
    282       }
    283       if (!seen_pageload_started_after_prerender_ &&
    284           perceived_page_load_time <= GetTimeSinceLastPrerender()) {
    285         seen_pageload_started_after_prerender_ = true;
    286         RECORD_PLT("PerceivedPLTFirstAfterMissNonOverlapping",
    287                    perceived_page_load_time);
    288         recorded_non_overlapping = true;
    289       }
    290       if (recorded_any || recorded_non_overlapping) {
    291         if (recorded_any && recorded_non_overlapping) {
    292           RECORD_PLT("PerceivedPLTFirstAfterMissBoth",
    293                      perceived_page_load_time);
    294         } else if (recorded_any) {
    295           RECORD_PLT("PerceivedPLTFirstAfterMissAnyOnly",
    296                      perceived_page_load_time);
    297         } else if (recorded_non_overlapping) {
    298           RECORD_PLT("PerceivedPLTFirstAfterMissNonOverlappingOnly",
    299                      perceived_page_load_time);
    300         }
    301       }
    302     }
    303   }
    304 }
    305 
    306 void PrerenderHistograms::RecordPageLoadTimeNotSwappedIn(
    307     Origin origin,
    308     base::TimeDelta page_load_time,
    309     const GURL& url) const {
    310   // If the URL to be prerendered is not a http[s] URL, or is a Google URL,
    311   // do not record.
    312   if (!url.SchemeIsHTTPOrHTTPS() || IsGoogleDomain(url))
    313     return;
    314   RECORD_PLT("PrerenderNotSwappedInPLT", page_load_time);
    315 }
    316 
    317 void PrerenderHistograms::RecordPercentLoadDoneAtSwapin(Origin origin,
    318                                                         double fraction) const {
    319   if (fraction < 0.0 || fraction > 1.0)
    320     return;
    321   int percentage = static_cast<int>(fraction * 100);
    322   if (percentage < 0 || percentage > 100)
    323     return;
    324   PREFIXED_HISTOGRAM("PercentLoadDoneAtSwapin",
    325                      origin, UMA_HISTOGRAM_PERCENTAGE(name, percentage));
    326 }
    327 
    328 base::TimeDelta PrerenderHistograms::GetTimeSinceLastPrerender() const {
    329   return base::TimeTicks::Now() - last_prerender_seen_time_;
    330 }
    331 
    332 bool PrerenderHistograms::WithinWindow() const {
    333   if (last_prerender_seen_time_.is_null())
    334     return false;
    335   return GetTimeSinceLastPrerender() <=
    336       base::TimeDelta::FromSeconds(kWindowDurationSeconds);
    337 }
    338 
    339 void PrerenderHistograms::RecordTimeUntilUsed(
    340     Origin origin,
    341     base::TimeDelta time_until_used) const {
    342   PREFIXED_HISTOGRAM(
    343       "TimeUntilUsed2", origin,
    344       UMA_HISTOGRAM_CUSTOM_TIMES(
    345           name,
    346           time_until_used,
    347           base::TimeDelta::FromMilliseconds(10),
    348           base::TimeDelta::FromMinutes(30),
    349           50));
    350 }
    351 
    352 void PrerenderHistograms::RecordAbandonTimeUntilUsed(
    353     Origin origin,
    354     base::TimeDelta time_until_used) const {
    355   PREFIXED_HISTOGRAM(
    356       "AbandonTimeUntilUsed", origin,
    357       UMA_HISTOGRAM_CUSTOM_TIMES(
    358           name,
    359           time_until_used,
    360           base::TimeDelta::FromMilliseconds(10),
    361           base::TimeDelta::FromSeconds(30),
    362           50));
    363 }
    364 
    365 void PrerenderHistograms::RecordPerSessionCount(Origin origin,
    366                                                 int count) const {
    367   PREFIXED_HISTOGRAM(
    368       "PrerendersPerSessionCount", origin,
    369       UMA_HISTOGRAM_COUNTS(name, count));
    370 }
    371 
    372 void PrerenderHistograms::RecordTimeBetweenPrerenderRequests(
    373     Origin origin, base::TimeDelta time) const {
    374   PREFIXED_HISTOGRAM(
    375       "TimeBetweenPrerenderRequests", origin,
    376       UMA_HISTOGRAM_TIMES(name, time));
    377 }
    378 
    379 void PrerenderHistograms::RecordFinalStatus(
    380     Origin origin,
    381     uint8 experiment_id,
    382     PrerenderContents::MatchCompleteStatus mc_status,
    383     FinalStatus final_status) const {
    384   DCHECK(final_status != FINAL_STATUS_MAX);
    385 
    386   if (mc_status == PrerenderContents::MATCH_COMPLETE_DEFAULT ||
    387       mc_status == PrerenderContents::MATCH_COMPLETE_REPLACED) {
    388     PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
    389         "FinalStatus", origin, experiment_id,
    390         UMA_HISTOGRAM_ENUMERATION(name, final_status, FINAL_STATUS_MAX));
    391   }
    392   if (mc_status == PrerenderContents::MATCH_COMPLETE_DEFAULT ||
    393       mc_status == PrerenderContents::MATCH_COMPLETE_REPLACEMENT ||
    394       mc_status == PrerenderContents::MATCH_COMPLETE_REPLACEMENT_PENDING) {
    395     PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
    396         "FinalStatusMatchComplete", origin, experiment_id,
    397         UMA_HISTOGRAM_ENUMERATION(name, final_status, FINAL_STATUS_MAX));
    398   }
    399 }
    400 
    401 void PrerenderHistograms::RecordEvent(Origin origin, uint8 experiment_id,
    402                                       PrerenderEvent event) const {
    403   DCHECK_LT(event, PRERENDER_EVENT_MAX);
    404   PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
    405       "Event", origin, experiment_id,
    406       UMA_HISTOGRAM_ENUMERATION(name, event, PRERENDER_EVENT_MAX));
    407 }
    408 
    409 void PrerenderHistograms::RecordCookieStatus(Origin origin,
    410                                              uint8 experiment_id,
    411                                              int cookie_status) const {
    412   DCHECK_GE(cookie_status, 0);
    413   DCHECK_LT(cookie_status, PrerenderContents::kNumCookieStatuses);
    414   PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
    415       "CookieStatus", origin, experiment_id,
    416       UMA_HISTOGRAM_ENUMERATION(name, cookie_status,
    417                                 PrerenderContents::kNumCookieStatuses));
    418 }
    419 
    420 void PrerenderHistograms::RecordCookieSendType(
    421     Origin origin,
    422     uint8 experiment_id,
    423     int cookie_send_type) const {
    424   DCHECK_GE(cookie_send_type, 0);
    425   DCHECK_LT(cookie_send_type, PrerenderContents::kNumCookieSendTypes);
    426   PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
    427       "CookieSendType", origin, experiment_id,
    428       UMA_HISTOGRAM_ENUMERATION(name, cookie_send_type,
    429                                 PrerenderContents::kNumCookieSendTypes));
    430 }
    431 
    432 void PrerenderHistograms::RecordPrerenderPageVisitedStatus(
    433     Origin origin,
    434     uint8 experiment_id,
    435     bool visited_before) const {
    436   PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
    437       "PageVisitedStatus", origin, experiment_id,
    438       UMA_HISTOGRAM_BOOLEAN(name, visited_before));
    439 }
    440 
    441 void PrerenderHistograms::RecordNetworkBytes(Origin origin,
    442                                              bool used,
    443                                              int64 prerender_bytes,
    444                                              int64 profile_bytes) {
    445   const int kHistogramMin = 1;
    446   const int kHistogramMax = 100000000;  // 100M.
    447   const int kBucketCount = 50;
    448 
    449   UMA_HISTOGRAM_CUSTOM_COUNTS("Prerender.NetworkBytesTotalForProfile",
    450                               profile_bytes,
    451                               kHistogramMin,
    452                               1000000000,  // 1G
    453                               kBucketCount);
    454 
    455   if (prerender_bytes == 0)
    456     return;
    457 
    458   if (used) {
    459     PREFIXED_HISTOGRAM(
    460         "NetworkBytesUsed",
    461         origin,
    462         UMA_HISTOGRAM_CUSTOM_COUNTS(
    463             name, prerender_bytes, kHistogramMin, kHistogramMax, kBucketCount));
    464   } else {
    465     PREFIXED_HISTOGRAM(
    466         "NetworkBytesWasted",
    467         origin,
    468         UMA_HISTOGRAM_CUSTOM_COUNTS(
    469             name, prerender_bytes, kHistogramMin, kHistogramMax, kBucketCount));
    470   }
    471 }
    472 
    473 uint8 PrerenderHistograms::GetCurrentExperimentId() const {
    474   if (!WithinWindow())
    475     return kNoExperiment;
    476   return last_experiment_id_;
    477 }
    478 
    479 bool PrerenderHistograms::IsOriginExperimentWash() const {
    480   if (!WithinWindow())
    481     return false;
    482   return origin_experiment_wash_;
    483 }
    484 
    485 }  // namespace prerender
    486