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_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
76bool 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
141PrerenderHistograms::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
149void 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
178void 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
186void 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
195void 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
203void PrerenderHistograms::RecordTimeSinceLastRecentVisit(
204    Origin origin,
205    base::TimeDelta delta) const {
206  PREFIXED_HISTOGRAM(
207      "TimeSinceLastRecentVisit", origin,
208      UMA_HISTOGRAM_TIMES(name, delta));
209}
210
211void 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
223base::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
262void 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
314void 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
325void 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
336base::TimeDelta PrerenderHistograms::GetTimeSinceLastPrerender() const {
337  return base::TimeTicks::Now() - last_prerender_seen_time_;
338}
339
340bool PrerenderHistograms::WithinWindow() const {
341  if (last_prerender_seen_time_.is_null())
342    return false;
343  return GetTimeSinceLastPrerender() <=
344      base::TimeDelta::FromSeconds(kWindowDurationSeconds);
345}
346
347void 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
360void PrerenderHistograms::RecordPerSessionCount(Origin origin,
361                                                int count) const {
362  PREFIXED_HISTOGRAM(
363      "PrerendersPerSessionCount", origin,
364      UMA_HISTOGRAM_COUNTS(name, count));
365}
366
367void PrerenderHistograms::RecordTimeBetweenPrerenderRequests(
368    Origin origin, base::TimeDelta time) const {
369  PREFIXED_HISTOGRAM(
370      "TimeBetweenPrerenderRequests", origin,
371      UMA_HISTOGRAM_TIMES(name, time));
372}
373
374void 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
396void 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
404void 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
415void 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
424uint8 PrerenderHistograms::GetCurrentExperimentId() const {
425  if (!WithinWindow())
426    return kNoExperiment;
427  return last_experiment_id_;
428}
429
430bool PrerenderHistograms::IsOriginExperimentWash() const {
431  if (!WithinWindow())
432    return false;
433  return origin_experiment_wash_;
434}
435
436}  // namespace prerender
437