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_EXTERNAL_REQUEST
:
62 return ComposeHistogramName("externalrequest", name
);
64 return ComposeHistogramName("Instant", name
);
65 case ORIGIN_GWS_PRERENDER
: // Handled above.
71 // Dummy return value to make the compiler happy.
73 return ComposeHistogramName("wash", name
);
76 bool OriginIsOmnibox(Origin origin
) {
77 return origin
== ORIGIN_OMNIBOX
;
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
86 #define PREFIXED_HISTOGRAM(histogram_name, origin, HISTOGRAM) \
87 PREFIXED_HISTOGRAM_INTERNAL(origin, GetCurrentExperimentId(), \
88 IsOriginExperimentWash(), HISTOGRAM, \
91 #define PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(histogram_name, origin, \
92 experiment, HISTOGRAM) \
93 PREFIXED_HISTOGRAM_INTERNAL(origin, experiment, false, HISTOGRAM, \
96 #define PREFIXED_HISTOGRAM_INTERNAL(origin, experiment, wash, HISTOGRAM, \
99 /* Do not rename. HISTOGRAM expects a local variable "name". */ \
100 std::string name = ComposeHistogramName(std::string(), histogram_name); \
103 /* Do not rename. HISTOGRAM expects a local variable "name". */ \
104 std::string name = GetHistogramName(origin, experiment, wash, \
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; \
117 } else if (experiment != kNoExperiment && \
118 (origin != ORIGIN_GWS_PRERENDER || \
119 experiment != recording_experiment)) { \
120 } else if (origin == ORIGIN_OMNIBOX) { \
122 } else if (origin == ORIGIN_NONE) { \
124 } else if (origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) { \
126 } else if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN) { \
128 } else if (origin == ORIGIN_LOCAL_PREDICTOR) { \
130 } else if (origin == ORIGIN_EXTERNAL_REQUEST) { \
132 } else if (origin == ORIGIN_INSTANT) { \
134 } else if (experiment != kNoExperiment) { \
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) {
149 void PrerenderHistograms::RecordPrerender(Origin origin
, const GURL
& url
) {
150 // Check if we are doing an experiment.
151 uint8 experiment
= GetQueryStringBasedExperiment(url
);
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;
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;
167 last_origin_
= origin
;
168 last_experiment_id_
= experiment
;
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;
178 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);
186 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);
195 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);
203 void PrerenderHistograms::RecordTimeSinceLastRecentVisit(
205 base::TimeDelta delta
) const {
207 "TimeSinceLastRecentVisit", origin
,
208 UMA_HISTOGRAM_TIMES(name
, delta
));
211 base::TimeTicks
PrerenderHistograms::GetCurrentTimeTicks() const {
212 return base::TimeTicks::Now();
215 // Helper macro for histograms.
216 #define RECORD_PLT(tag, perceived_page_load_time) { \
217 PREFIXED_HISTOGRAM( \
219 UMA_HISTOGRAM_CUSTOM_TIMES( \
221 perceived_page_load_time, \
222 base::TimeDelta::FromMilliseconds(10), \
223 base::TimeDelta::FromSeconds(60), \
227 // Summary of all histograms Perceived PLT histograms:
228 // (all prefixed PerceivedPLT)
229 // PerceivedPLT -- Perceived Pageloadtimes (PPLT) for all pages in the group.
230 // ...Windowed -- PPLT for pages in the 30s after a prerender is created.
231 // ...Matched -- A prerendered page that was swapped in. In the NoUse
232 // and Control group cases, while nothing ever gets swapped in, we do keep
233 // track of what would be prerendered and would be swapped in -- and those
234 // cases are what is classified as Match for these groups.
235 // ...MatchedComplete -- A prerendered page that was swapped in + a few
236 // that were not swapped in so that the set of pages lines up more closely with
237 // the control group.
238 // ...FirstAfterMiss -- First page to finish loading after a prerender, which
239 // is different from the page that was prerendered.
240 // ...FirstAfterMissNonOverlapping -- Same as FirstAfterMiss, but only
241 // triggering for the first page to finish after the prerender that also started
242 // after the prerender started.
243 // ...FirstAfterMissBoth -- pages meeting
244 // FirstAfterMiss AND FirstAfterMissNonOverlapping
245 // ...FirstAfterMissAnyOnly -- pages meeting
246 // FirstAfterMiss but NOT FirstAfterMissNonOverlapping
247 // ..FirstAfterMissNonOverlappingOnly -- pages meeting
248 // FirstAfterMissNonOverlapping but NOT FirstAfterMiss
250 void PrerenderHistograms::RecordPerceivedPageLoadTime(
252 base::TimeDelta perceived_page_load_time
,
254 bool was_complete_prerender
, const GURL
& url
) {
255 if (!url
.SchemeIsHTTPOrHTTPS())
257 bool within_window
= WithinWindow();
258 bool is_google_url
= IsGoogleDomain(url
);
259 RECORD_PLT("PerceivedPLT", perceived_page_load_time
);
261 RECORD_PLT("PerceivedPLTWindowed", perceived_page_load_time
);
262 if (was_prerender
|| was_complete_prerender
) {
264 RECORD_PLT("PerceivedPLTMatched", perceived_page_load_time
);
265 if (was_complete_prerender
)
266 RECORD_PLT("PerceivedPLTMatchedComplete", perceived_page_load_time
);
267 seen_any_pageload_
= true;
268 seen_pageload_started_after_prerender_
= true;
269 } else if (within_window
) {
270 RECORD_PLT("PerceivedPLTWindowNotMatched", perceived_page_load_time
);
271 if (!is_google_url
) {
272 bool recorded_any
= false;
273 bool recorded_non_overlapping
= false;
274 if (!seen_any_pageload_
) {
275 seen_any_pageload_
= true;
276 RECORD_PLT("PerceivedPLTFirstAfterMiss", perceived_page_load_time
);
279 if (!seen_pageload_started_after_prerender_
&&
280 perceived_page_load_time
<= GetTimeSinceLastPrerender()) {
281 seen_pageload_started_after_prerender_
= true;
282 RECORD_PLT("PerceivedPLTFirstAfterMissNonOverlapping",
283 perceived_page_load_time
);
284 recorded_non_overlapping
= true;
286 if (recorded_any
|| recorded_non_overlapping
) {
287 if (recorded_any
&& recorded_non_overlapping
) {
288 RECORD_PLT("PerceivedPLTFirstAfterMissBoth",
289 perceived_page_load_time
);
290 } else if (recorded_any
) {
291 RECORD_PLT("PerceivedPLTFirstAfterMissAnyOnly",
292 perceived_page_load_time
);
293 } else if (recorded_non_overlapping
) {
294 RECORD_PLT("PerceivedPLTFirstAfterMissNonOverlappingOnly",
295 perceived_page_load_time
);
302 void PrerenderHistograms::RecordPageLoadTimeNotSwappedIn(
304 base::TimeDelta page_load_time
,
305 const GURL
& url
) const {
306 // If the URL to be prerendered is not a http[s] URL, or is a Google URL,
308 if (!url
.SchemeIsHTTPOrHTTPS() || IsGoogleDomain(url
))
310 RECORD_PLT("PrerenderNotSwappedInPLT", page_load_time
);
313 void PrerenderHistograms::RecordPercentLoadDoneAtSwapin(Origin origin
,
314 double fraction
) const {
315 if (fraction
< 0.0 || fraction
> 1.0)
317 int percentage
= static_cast<int>(fraction
* 100);
318 if (percentage
< 0 || percentage
> 100)
320 PREFIXED_HISTOGRAM("PercentLoadDoneAtSwapin",
321 origin
, UMA_HISTOGRAM_PERCENTAGE(name
, percentage
));
324 base::TimeDelta
PrerenderHistograms::GetTimeSinceLastPrerender() const {
325 return base::TimeTicks::Now() - last_prerender_seen_time_
;
328 bool PrerenderHistograms::WithinWindow() const {
329 if (last_prerender_seen_time_
.is_null())
331 return GetTimeSinceLastPrerender() <=
332 base::TimeDelta::FromSeconds(kWindowDurationSeconds
);
335 void PrerenderHistograms::RecordTimeUntilUsed(
337 base::TimeDelta time_until_used
) const {
339 "TimeUntilUsed2", origin
,
340 UMA_HISTOGRAM_CUSTOM_TIMES(
343 base::TimeDelta::FromMilliseconds(10),
344 base::TimeDelta::FromMinutes(30),
348 void PrerenderHistograms::RecordPerSessionCount(Origin origin
,
351 "PrerendersPerSessionCount", origin
,
352 UMA_HISTOGRAM_COUNTS(name
, count
));
355 void PrerenderHistograms::RecordTimeBetweenPrerenderRequests(
356 Origin origin
, base::TimeDelta time
) const {
358 "TimeBetweenPrerenderRequests", origin
,
359 UMA_HISTOGRAM_TIMES(name
, time
));
362 void PrerenderHistograms::RecordFinalStatus(
365 PrerenderContents::MatchCompleteStatus mc_status
,
366 FinalStatus final_status
) const {
367 DCHECK(final_status
!= FINAL_STATUS_MAX
);
369 if (mc_status
== PrerenderContents::MATCH_COMPLETE_DEFAULT
||
370 mc_status
== PrerenderContents::MATCH_COMPLETE_REPLACED
) {
371 PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
372 "FinalStatus", origin
, experiment_id
,
373 UMA_HISTOGRAM_ENUMERATION(name
, final_status
, FINAL_STATUS_MAX
));
375 if (mc_status
== PrerenderContents::MATCH_COMPLETE_DEFAULT
||
376 mc_status
== PrerenderContents::MATCH_COMPLETE_REPLACEMENT
||
377 mc_status
== PrerenderContents::MATCH_COMPLETE_REPLACEMENT_PENDING
) {
378 PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
379 "FinalStatusMatchComplete", origin
, experiment_id
,
380 UMA_HISTOGRAM_ENUMERATION(name
, final_status
, FINAL_STATUS_MAX
));
384 void PrerenderHistograms::RecordEvent(Origin origin
, uint8 experiment_id
,
385 PrerenderEvent event
) const {
386 DCHECK_LT(event
, PRERENDER_EVENT_MAX
);
387 PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
388 "Event", origin
, experiment_id
,
389 UMA_HISTOGRAM_ENUMERATION(name
, event
, PRERENDER_EVENT_MAX
));
392 void PrerenderHistograms::RecordCookieStatus(Origin origin
,
394 int cookie_status
) const {
395 DCHECK_GE(cookie_status
, 0);
396 DCHECK_LT(cookie_status
, PrerenderContents::kNumCookieStatuses
);
397 PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(
398 "CookieStatus", origin
, experiment_id
,
399 UMA_HISTOGRAM_ENUMERATION(name
, cookie_status
,
400 PrerenderContents::kNumCookieStatuses
));
403 uint8
PrerenderHistograms::GetCurrentExperimentId() const {
405 return kNoExperiment
;
406 return last_experiment_id_
;
409 bool PrerenderHistograms::IsOriginExperimentWash() const {
412 return origin_experiment_wash_
;
415 } // namespace prerender