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
16using predictors::AutocompleteActionPredictor;
17
18namespace prerender {
19
20namespace {
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.
26const int kWindowDurationSeconds = 30;
27
28std::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
35std::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
78bool 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
145PrerenderHistograms::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
153void 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
182void 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
190void 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
199void 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
207void PrerenderHistograms::RecordTimeSinceLastRecentVisit(
208    Origin origin,
209    base::TimeDelta delta) const {
210  PREFIXED_HISTOGRAM(
211      "TimeSinceLastRecentVisit", origin,
212      UMA_HISTOGRAM_TIMES(name, delta));
213}
214
215base::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
253void 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
306void 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
317void 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
328base::TimeDelta PrerenderHistograms::GetTimeSinceLastPrerender() const {
329  return base::TimeTicks::Now() - last_prerender_seen_time_;
330}
331
332bool PrerenderHistograms::WithinWindow() const {
333  if (last_prerender_seen_time_.is_null())
334    return false;
335  return GetTimeSinceLastPrerender() <=
336      base::TimeDelta::FromSeconds(kWindowDurationSeconds);
337}
338
339void 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
352void 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
365void PrerenderHistograms::RecordPerSessionCount(Origin origin,
366                                                int count) const {
367  PREFIXED_HISTOGRAM(
368      "PrerendersPerSessionCount", origin,
369      UMA_HISTOGRAM_COUNTS(name, count));
370}
371
372void PrerenderHistograms::RecordTimeBetweenPrerenderRequests(
373    Origin origin, base::TimeDelta time) const {
374  PREFIXED_HISTOGRAM(
375      "TimeBetweenPrerenderRequests", origin,
376      UMA_HISTOGRAM_TIMES(name, time));
377}
378
379void 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
401void 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
409void 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
420void 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
432void 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
441void 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
473uint8 PrerenderHistograms::GetCurrentExperimentId() const {
474  if (!WithinWindow())
475    return kNoExperiment;
476  return last_experiment_id_;
477}
478
479bool PrerenderHistograms::IsOriginExperimentWash() const {
480  if (!WithinWindow())
481    return false;
482  return origin_experiment_wash_;
483}
484
485}  // namespace prerender
486