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