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