• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ComposeHistogramName(const std::string & prefix_type,const std::string & name)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 
GetHistogramName(Origin origin,uint8 experiment_id,bool is_wash,const std::string & name)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 
OriginIsOmnibox(Origin origin)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 
PrerenderHistograms()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 
RecordPrerender(Origin origin,const GURL & url)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 
RecordPrerenderStarted(Origin origin) const178 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 
RecordConcurrency(size_t prerender_count) const186 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 
RecordUsedPrerender(Origin origin) const195 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 
RecordTimeSinceLastRecentVisit(Origin origin,base::TimeDelta delta) const203 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 
RecordFractionPixelsFinalAtSwapin(Origin origin,double fraction) const211 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 
GetCurrentTimeTicks() const223 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 
RecordPerceivedPageLoadTime(Origin origin,base::TimeDelta perceived_page_load_time,bool was_prerender,bool was_complete_prerender,const GURL & url)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 
RecordPageLoadTimeNotSwappedIn(Origin origin,base::TimeDelta page_load_time,const GURL & url) const314 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 
RecordPercentLoadDoneAtSwapin(Origin origin,double fraction) const325 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 
GetTimeSinceLastPrerender() const336 base::TimeDelta PrerenderHistograms::GetTimeSinceLastPrerender() const {
337   return base::TimeTicks::Now() - last_prerender_seen_time_;
338 }
339 
WithinWindow() const340 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 
RecordTimeUntilUsed(Origin origin,base::TimeDelta time_until_used) const347 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 
RecordPerSessionCount(Origin origin,int count) const360 void PrerenderHistograms::RecordPerSessionCount(Origin origin,
361                                                 int count) const {
362   PREFIXED_HISTOGRAM(
363       "PrerendersPerSessionCount", origin,
364       UMA_HISTOGRAM_COUNTS(name, count));
365 }
366 
RecordTimeBetweenPrerenderRequests(Origin origin,base::TimeDelta time) const367 void PrerenderHistograms::RecordTimeBetweenPrerenderRequests(
368     Origin origin, base::TimeDelta time) const {
369   PREFIXED_HISTOGRAM(
370       "TimeBetweenPrerenderRequests", origin,
371       UMA_HISTOGRAM_TIMES(name, time));
372 }
373 
RecordFinalStatus(Origin origin,uint8 experiment_id,PrerenderContents::MatchCompleteStatus mc_status,FinalStatus final_status) const374 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 
RecordEvent(Origin origin,uint8 experiment_id,PrerenderEvent event) const396 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 
RecordCookieStatus(Origin origin,uint8 experiment_id,int cookie_status) const404 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 
RecordPrerenderPageVisitedStatus(Origin origin,uint8 experiment_id,bool visited_before) const415 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 
GetCurrentExperimentId() const424 uint8 PrerenderHistograms::GetCurrentExperimentId() const {
425   if (!WithinWindow())
426     return kNoExperiment;
427   return last_experiment_id_;
428 }
429 
IsOriginExperimentWash() const430 bool PrerenderHistograms::IsOriginExperimentWash() const {
431   if (!WithinWindow())
432     return false;
433   return origin_experiment_wash_;
434 }
435 
436 }  // namespace prerender
437