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.
5 #include "chrome/browser/prerender/prerender_histograms.h"
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"
16 using predictors::AutocompleteActionPredictor;
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;
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;
35 std::string GetHistogramName(Origin origin, uint8 experiment_id,
36 bool is_wash, const std::string& name) {
38 return ComposeHistogramName("wash", name);
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'),
47 if (experiment_id != kNoExperiment)
48 return ComposeHistogramName("wash", name);
52 return ComposeHistogramName("omnibox", name);
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_GWS_PRERENDER: // Handled above.
67 // Dummy return value to make the compiler happy.
69 return ComposeHistogramName("wash", name);
72 bool OriginIsOmnibox(Origin origin) {
73 return origin == ORIGIN_OMNIBOX;
78 // Helper macros for experiment-based and origin-based histogram reporting.
79 // All HISTOGRAM arguments must be UMA_HISTOGRAM... macros that contain an
80 // argument "name" which these macros will eventually substitute for the
82 #define PREFIXED_HISTOGRAM(histogram_name, origin, HISTOGRAM) \
83 PREFIXED_HISTOGRAM_INTERNAL(origin, GetCurrentExperimentId(), \
84 IsOriginExperimentWash(), HISTOGRAM, \
87 #define PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(histogram_name, origin, \
88 experiment, HISTOGRAM) \
89 PREFIXED_HISTOGRAM_INTERNAL(origin, experiment, false, HISTOGRAM, \
92 #define PREFIXED_HISTOGRAM_INTERNAL(origin, experiment, wash, HISTOGRAM, \
95 /* Do not rename. HISTOGRAM expects a local variable "name". */ \
96 std::string name = ComposeHistogramName(std::string(), histogram_name); \
99 /* Do not rename. HISTOGRAM expects a local variable "name". */ \
100 std::string name = GetHistogramName(origin, experiment, wash, \
102 static uint8 recording_experiment = kNoExperiment; \
103 if (recording_experiment == kNoExperiment && experiment != kNoExperiment) \
104 recording_experiment = experiment; \
107 } else if (experiment != kNoExperiment && \
108 (origin != ORIGIN_GWS_PRERENDER || \
109 experiment != recording_experiment)) { \
110 } else if (origin == ORIGIN_OMNIBOX) { \
112 } else if (origin == ORIGIN_NONE) { \
114 } else if (origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) { \
116 } else if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN) { \
118 } else if (origin == ORIGIN_LOCAL_PREDICTOR) { \
120 } else if (experiment != kNoExperiment) { \
127 PrerenderHistograms::PrerenderHistograms()
128 : last_experiment_id_(kNoExperiment),
129 last_origin_(ORIGIN_MAX),
130 origin_experiment_wash_(false),
131 seen_any_pageload_(true),
132 seen_pageload_started_after_prerender_(true) {
135 void PrerenderHistograms::RecordPrerender(Origin origin, const GURL& url) {
136 // Check if we are doing an experiment.
137 uint8 experiment = GetQueryStringBasedExperiment(url);
139 // We need to update last_experiment_id_, last_origin_, and
140 // origin_experiment_wash_.
141 if (!WithinWindow()) {
142 // If we are outside a window, this is a fresh start and we are fine,
143 // and there is no mix.
144 origin_experiment_wash_ = false;
146 // If we are inside the last window, there is a mish mash of origins
147 // and experiments if either there was a mish mash before, or the current
148 // experiment/origin does not match the previous one.
149 if (experiment != last_experiment_id_ || origin != last_origin_)
150 origin_experiment_wash_ = true;
153 last_origin_ = origin;
154 last_experiment_id_ = experiment;
156 // If we observe multiple tags within the 30 second window, we will still
157 // reset the window to begin at the most recent occurrence, so that we will
158 // always be in a window in the 30 seconds from each occurrence.
159 last_prerender_seen_time_ = GetCurrentTimeTicks();
160 seen_any_pageload_ = false;
161 seen_pageload_started_after_prerender_ = false;
164 void PrerenderHistograms::RecordPrerenderStarted(Origin origin) const {
165 if (OriginIsOmnibox(origin)) {
166 UMA_HISTOGRAM_ENUMERATION(
167 base::StringPrintf("Prerender.OmniboxPrerenderCount%s",
168 PrerenderManager::GetModeString()), 1, 2);
172 void PrerenderHistograms::RecordConcurrency(size_t prerender_count) const {
173 static const size_t kMaxRecordableConcurrency = 20;
174 DCHECK_GE(kMaxRecordableConcurrency, Config().max_link_concurrency);
175 UMA_HISTOGRAM_ENUMERATION(
176 base::StringPrintf("Prerender.PrerenderCountOf%" PRIuS "Max",
177 kMaxRecordableConcurrency),
178 prerender_count, kMaxRecordableConcurrency + 1);
181 void PrerenderHistograms::RecordUsedPrerender(Origin origin) const {
182 if (OriginIsOmnibox(origin)) {
183 UMA_HISTOGRAM_ENUMERATION(
184 base::StringPrintf("Prerender.OmniboxNavigationsUsedPrerenderCount%s",
185 PrerenderManager::GetModeString()), 1, 2);
189 void PrerenderHistograms::RecordTimeSinceLastRecentVisit(
191 base::TimeDelta delta) const {
193 "TimeSinceLastRecentVisit", origin,
194 UMA_HISTOGRAM_TIMES(name, delta));
197 void PrerenderHistograms::RecordFractionPixelsFinalAtSwapin(
199 double fraction) const {
200 if (fraction < 0.0 || fraction > 1.0)
202 int percentage = static_cast<int>(fraction * 100);
203 if (percentage < 0 || percentage > 100)
205 PREFIXED_HISTOGRAM("FractionPixelsFinalAtSwapin",
206 origin, UMA_HISTOGRAM_PERCENTAGE(name, percentage));
209 base::TimeTicks PrerenderHistograms::GetCurrentTimeTicks() const {
210 return base::TimeTicks::Now();
213 // Helper macro for histograms.
214 #define RECORD_PLT(tag, perceived_page_load_time) { \
215 PREFIXED_HISTOGRAM( \
217 UMA_HISTOGRAM_CUSTOM_TIMES( \
219 perceived_page_load_time, \
220 base::TimeDelta::FromMilliseconds(10), \
221 base::TimeDelta::FromSeconds(60), \
225 // Summary of all histograms Perceived PLT histograms:
226 // (all prefixed PerceivedPLT)
227 // PerceivedPLT -- Perceived Pageloadtimes (PPLT) for all pages in the group.
228 // ...Windowed -- PPLT for pages in the 30s after a prerender is created.
229 // ...Matched -- A prerendered page that was swapped in. In the NoUse
230 // and Control group cases, while nothing ever gets swapped in, we do keep
231 // track of what would be prerendered and would be swapped in -- and those
232 // cases are what is classified as Match for these groups.
233 // ...MatchedComplete -- A prerendered page that was swapped in + a few
234 // that were not swapped in so that the set of pages lines up more closely with
235 // the control group.
236 // ...FirstAfterMiss -- First page to finish loading after a prerender, which
237 // is different from the page that was prerendered.
238 // ...FirstAfterMissNonOverlapping -- Same as FirstAfterMiss, but only
239 // triggering for the first page to finish after the prerender that also started
240 // after the prerender started.
241 // ...FirstAfterMissBoth -- pages meeting
242 // FirstAfterMiss AND FirstAfterMissNonOverlapping
243 // ...FirstAfterMissAnyOnly -- pages meeting
244 // FirstAfterMiss but NOT FirstAfterMissNonOverlapping
245 // ..FirstAfterMissNonOverlappingOnly -- pages meeting
246 // FirstAfterMissNonOverlapping but NOT FirstAfterMiss
248 void PrerenderHistograms::RecordPerceivedPageLoadTime(
250 base::TimeDelta perceived_page_load_time,
252 bool was_complete_prerender, const GURL& url) {
253 if (!url.SchemeIsHTTPOrHTTPS())
255 bool within_window = WithinWindow();
256 bool is_google_url = IsGoogleDomain(url);
257 RECORD_PLT("PerceivedPLT", perceived_page_load_time);
259 RECORD_PLT("PerceivedPLTWindowed", perceived_page_load_time);
260 if (was_prerender || was_complete_prerender) {
262 RECORD_PLT("PerceivedPLTMatched", perceived_page_load_time);
263 if (was_complete_prerender)
264 RECORD_PLT("PerceivedPLTMatchedComplete", perceived_page_load_time);
265 seen_any_pageload_ = true;
266 seen_pageload_started_after_prerender_ = true;
267 } else if (within_window) {
268 RECORD_PLT("PerceivedPLTWindowNotMatched", perceived_page_load_time);
269 if (!is_google_url) {
270 bool recorded_any = false;
271 bool recorded_non_overlapping = false;
272 if (!seen_any_pageload_) {
273 seen_any_pageload_ = true;
274 RECORD_PLT("PerceivedPLTFirstAfterMiss", perceived_page_load_time);
277 if (!seen_pageload_started_after_prerender_ &&
278 perceived_page_load_time <= GetTimeSinceLastPrerender()) {
279 seen_pageload_started_after_prerender_ = true;
280 RECORD_PLT("PerceivedPLTFirstAfterMissNonOverlapping",
281 perceived_page_load_time);
282 recorded_non_overlapping = true;
284 if (recorded_any || recorded_non_overlapping) {
285 if (recorded_any && recorded_non_overlapping) {
286 RECORD_PLT("PerceivedPLTFirstAfterMissBoth",
287 perceived_page_load_time);
288 } else if (recorded_any) {
289 RECORD_PLT("PerceivedPLTFirstAfterMissAnyOnly",
290 perceived_page_load_time);
291 } else if (recorded_non_overlapping) {
292 RECORD_PLT("PerceivedPLTFirstAfterMissNonOverlappingOnly",
293 perceived_page_load_time);
300 void PrerenderHistograms::RecordPageLoadTimeNotSwappedIn(
302 base::TimeDelta page_load_time,
303 const GURL& url) const {
304 // If the URL to be prerendered is not a http[s] URL, or is a Google URL,
306 if (!url.SchemeIsHTTPOrHTTPS() || IsGoogleDomain(url))
308 RECORD_PLT("PrerenderNotSwappedInPLT", page_load_time);
311 void PrerenderHistograms::RecordPercentLoadDoneAtSwapin(Origin origin,
312 double fraction) const {
313 if (fraction < 0.0 || fraction > 1.0)
315 int percentage = static_cast<int>(fraction * 100);
316 if (percentage < 0 || percentage > 100)
318 PREFIXED_HISTOGRAM("PercentLoadDoneAtSwapin",
319 origin, UMA_HISTOGRAM_PERCENTAGE(name, percentage));
322 base::TimeDelta PrerenderHistograms::GetTimeSinceLastPrerender() const {
323 return base::TimeTicks::Now() - last_prerender_seen_time_;
326 bool PrerenderHistograms::WithinWindow() const {
327 if (last_prerender_seen_time_.is_null())
329 return GetTimeSinceLastPrerender() <=
330 base::TimeDelta::FromSeconds(kWindowDurationSeconds);
333 void PrerenderHistograms::RecordTimeUntilUsed(
335 base::TimeDelta time_until_used) const {
337 "TimeUntilUsed2", origin,
338 UMA_HISTOGRAM_CUSTOM_TIMES(
341 base::TimeDelta::FromMilliseconds(10),
342 base::TimeDelta::FromMinutes(30),
346 void PrerenderHistograms::RecordPerSessionCount(Origin origin,
349 "PrerendersPerSessionCount", origin,
350 UMA_HISTOGRAM_COUNTS(name, count));
353 void PrerenderHistograms::RecordTimeBetweenPrerenderRequests(
354 Origin origin, base::TimeDelta time) const {
356 "TimeBetweenPrerenderRequests", origin,
357 UMA_HISTOGRAM_TIMES(name, time));
360 void PrerenderHistograms::RecordFinalStatus(
363 PrerenderContents::MatchCompleteStatus mc_status,
364 FinalStatus final_status) const {
365 DCHECK(final_status != FINAL_STATUS_MAX);
367 if (mc_status == PrerenderContents::MATCH_COMPLETE_DEFAULT ||
368 mc_status == PrerenderContents::MATCH_COMPLETE_REPLACED) {
369 PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
370 "FinalStatus", origin, experiment_id,
371 UMA_HISTOGRAM_ENUMERATION(name, final_status, FINAL_STATUS_MAX));
373 if (mc_status == PrerenderContents::MATCH_COMPLETE_DEFAULT ||
374 mc_status == PrerenderContents::MATCH_COMPLETE_REPLACEMENT ||
375 mc_status == PrerenderContents::MATCH_COMPLETE_REPLACEMENT_PENDING) {
376 PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
377 "FinalStatusMatchComplete", origin, experiment_id,
378 UMA_HISTOGRAM_ENUMERATION(name, final_status, FINAL_STATUS_MAX));
382 uint8 PrerenderHistograms::GetCurrentExperimentId() const {
384 return kNoExperiment;
385 return last_experiment_id_;
388 bool PrerenderHistograms::IsOriginExperimentWash() const {
391 return origin_experiment_wash_;
394 } // namespace prerender