• 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_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 
OriginIsOmnibox(Origin origin)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 
PrerenderHistograms()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 
RecordPrerender(Origin origin,const GURL & url)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 
RecordPrerenderStarted(Origin origin) const182 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 
RecordConcurrency(size_t prerender_count) const190 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 
RecordUsedPrerender(Origin origin) const199 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 
RecordTimeSinceLastRecentVisit(Origin origin,base::TimeDelta delta) const207 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 
GetCurrentTimeTicks() const215 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 
RecordPerceivedPageLoadTime(Origin origin,base::TimeDelta perceived_page_load_time,NavigationType navigation_type,const GURL & url)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 
RecordPageLoadTimeNotSwappedIn(Origin origin,base::TimeDelta page_load_time,const GURL & url) const306 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 
RecordPercentLoadDoneAtSwapin(Origin origin,double fraction) const317 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 
GetTimeSinceLastPrerender() const328 base::TimeDelta PrerenderHistograms::GetTimeSinceLastPrerender() const {
329   return base::TimeTicks::Now() - last_prerender_seen_time_;
330 }
331 
WithinWindow() const332 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 
RecordTimeUntilUsed(Origin origin,base::TimeDelta time_until_used) const339 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 
RecordAbandonTimeUntilUsed(Origin origin,base::TimeDelta time_until_used) const352 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 
RecordPerSessionCount(Origin origin,int count) const365 void PrerenderHistograms::RecordPerSessionCount(Origin origin,
366                                                 int count) const {
367   PREFIXED_HISTOGRAM(
368       "PrerendersPerSessionCount", origin,
369       UMA_HISTOGRAM_COUNTS(name, count));
370 }
371 
RecordTimeBetweenPrerenderRequests(Origin origin,base::TimeDelta time) const372 void PrerenderHistograms::RecordTimeBetweenPrerenderRequests(
373     Origin origin, base::TimeDelta time) const {
374   PREFIXED_HISTOGRAM(
375       "TimeBetweenPrerenderRequests", origin,
376       UMA_HISTOGRAM_TIMES(name, time));
377 }
378 
RecordFinalStatus(Origin origin,uint8 experiment_id,PrerenderContents::MatchCompleteStatus mc_status,FinalStatus final_status) const379 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 
RecordEvent(Origin origin,uint8 experiment_id,PrerenderEvent event) const401 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 
RecordCookieStatus(Origin origin,uint8 experiment_id,int cookie_status) const409 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 
RecordCookieSendType(Origin origin,uint8 experiment_id,int cookie_send_type) const420 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 
RecordPrerenderPageVisitedStatus(Origin origin,uint8 experiment_id,bool visited_before) const432 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 
RecordNetworkBytes(Origin origin,bool used,int64 prerender_bytes,int64 profile_bytes)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 
GetCurrentExperimentId() const473 uint8 PrerenderHistograms::GetCurrentExperimentId() const {
474   if (!WithinWindow())
475     return kNoExperiment;
476   return last_experiment_id_;
477 }
478 
IsOriginExperimentWash() const479 bool PrerenderHistograms::IsOriginExperimentWash() const {
480   if (!WithinWindow())
481     return false;
482   return origin_experiment_wash_;
483 }
484 
485 }  // namespace prerender
486