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_GWS_PRERENDER: // Handled above. 62 default: 63 NOTREACHED(); 64 break; 65 }; 66 67 // Dummy return value to make the compiler happy. 68 NOTREACHED(); 69 return ComposeHistogramName("wash", name); 70 } 71 72 bool OriginIsOmnibox(Origin origin) { 73 return origin == ORIGIN_OMNIBOX; 74 } 75 76 } // namespace 77 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 81 // actual name used. 82 #define PREFIXED_HISTOGRAM(histogram_name, origin, HISTOGRAM) \ 83 PREFIXED_HISTOGRAM_INTERNAL(origin, GetCurrentExperimentId(), \ 84 IsOriginExperimentWash(), HISTOGRAM, \ 85 histogram_name) 86 87 #define PREFIXED_HISTOGRAM_ORIGIN_EXPERIMENT(histogram_name, origin, \ 88 experiment, HISTOGRAM) \ 89 PREFIXED_HISTOGRAM_INTERNAL(origin, experiment, false, HISTOGRAM, \ 90 histogram_name) 91 92 #define PREFIXED_HISTOGRAM_INTERNAL(origin, experiment, wash, HISTOGRAM, \ 93 histogram_name) { \ 94 { \ 95 /* Do not rename. HISTOGRAM expects a local variable "name". */ \ 96 std::string name = ComposeHistogramName(std::string(), histogram_name); \ 97 HISTOGRAM; \ 98 } \ 99 /* Do not rename. HISTOGRAM expects a local variable "name". */ \ 100 std::string name = GetHistogramName(origin, experiment, wash, \ 101 histogram_name); \ 102 static uint8 recording_experiment = kNoExperiment; \ 103 if (recording_experiment == kNoExperiment && experiment != kNoExperiment) \ 104 recording_experiment = experiment; \ 105 if (wash) { \ 106 HISTOGRAM; \ 107 } else if (experiment != kNoExperiment && \ 108 (origin != ORIGIN_GWS_PRERENDER || \ 109 experiment != recording_experiment)) { \ 110 } else if (origin == ORIGIN_OMNIBOX) { \ 111 HISTOGRAM; \ 112 } else if (origin == ORIGIN_NONE) { \ 113 HISTOGRAM; \ 114 } else if (origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) { \ 115 HISTOGRAM; \ 116 } else if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN) { \ 117 HISTOGRAM; \ 118 } else if (origin == ORIGIN_LOCAL_PREDICTOR) { \ 119 HISTOGRAM; \ 120 } else if (experiment != kNoExperiment) { \ 121 HISTOGRAM; \ 122 } else { \ 123 HISTOGRAM; \ 124 } \ 125 } 126 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) { 133 } 134 135 void PrerenderHistograms::RecordPrerender(Origin origin, const GURL& url) { 136 // Check if we are doing an experiment. 137 uint8 experiment = GetQueryStringBasedExperiment(url); 138 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; 145 } else { 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; 151 } 152 153 last_origin_ = origin; 154 last_experiment_id_ = experiment; 155 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; 162 } 163 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); 169 } 170 } 171 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); 179 } 180 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); 186 } 187 } 188 189 void PrerenderHistograms::RecordTimeSinceLastRecentVisit( 190 Origin origin, 191 base::TimeDelta delta) const { 192 PREFIXED_HISTOGRAM( 193 "TimeSinceLastRecentVisit", origin, 194 UMA_HISTOGRAM_TIMES(name, delta)); 195 } 196 197 void PrerenderHistograms::RecordFractionPixelsFinalAtSwapin( 198 Origin origin, 199 double fraction) const { 200 if (fraction < 0.0 || fraction > 1.0) 201 return; 202 int percentage = static_cast<int>(fraction * 100); 203 if (percentage < 0 || percentage > 100) 204 return; 205 PREFIXED_HISTOGRAM("FractionPixelsFinalAtSwapin", 206 origin, UMA_HISTOGRAM_PERCENTAGE(name, percentage)); 207 } 208 209 base::TimeTicks PrerenderHistograms::GetCurrentTimeTicks() const { 210 return base::TimeTicks::Now(); 211 } 212 213 // Helper macro for histograms. 214 #define RECORD_PLT(tag, perceived_page_load_time) { \ 215 PREFIXED_HISTOGRAM( \ 216 tag, origin, \ 217 UMA_HISTOGRAM_CUSTOM_TIMES( \ 218 name, \ 219 perceived_page_load_time, \ 220 base::TimeDelta::FromMilliseconds(10), \ 221 base::TimeDelta::FromSeconds(60), \ 222 100)); \ 223 } 224 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 247 248 void PrerenderHistograms::RecordPerceivedPageLoadTime( 249 Origin origin, 250 base::TimeDelta perceived_page_load_time, 251 bool was_prerender, 252 bool was_complete_prerender, const GURL& url) { 253 if (!IsWebURL(url)) 254 return; 255 bool within_window = WithinWindow(); 256 bool is_google_url = IsGoogleDomain(url); 257 RECORD_PLT("PerceivedPLT", perceived_page_load_time); 258 if (within_window) 259 RECORD_PLT("PerceivedPLTWindowed", perceived_page_load_time); 260 if (was_prerender || was_complete_prerender) { 261 if (was_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); 275 recorded_any = true; 276 } 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; 283 } 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); 294 } 295 } 296 } 297 } 298 } 299 300 void PrerenderHistograms::RecordPageLoadTimeNotSwappedIn( 301 Origin origin, 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, 305 // do not record. 306 if (!IsWebURL(url) || IsGoogleDomain(url)) 307 return; 308 RECORD_PLT("PrerenderNotSwappedInPLT", page_load_time); 309 } 310 311 void PrerenderHistograms::RecordPercentLoadDoneAtSwapin(Origin origin, 312 double fraction) const { 313 if (fraction < 0.0 || fraction > 1.0) 314 return; 315 int percentage = static_cast<int>(fraction * 100); 316 if (percentage < 0 || percentage > 100) 317 return; 318 PREFIXED_HISTOGRAM("PercentLoadDoneAtSwapin", 319 origin, UMA_HISTOGRAM_PERCENTAGE(name, percentage)); 320 } 321 322 base::TimeDelta PrerenderHistograms::GetTimeSinceLastPrerender() const { 323 return base::TimeTicks::Now() - last_prerender_seen_time_; 324 } 325 326 bool PrerenderHistograms::WithinWindow() const { 327 if (last_prerender_seen_time_.is_null()) 328 return false; 329 return GetTimeSinceLastPrerender() <= 330 base::TimeDelta::FromSeconds(kWindowDurationSeconds); 331 } 332 333 void PrerenderHistograms::RecordTimeUntilUsed( 334 Origin origin, 335 base::TimeDelta time_until_used) const { 336 PREFIXED_HISTOGRAM( 337 "TimeUntilUsed2", origin, 338 UMA_HISTOGRAM_CUSTOM_TIMES( 339 name, 340 time_until_used, 341 base::TimeDelta::FromMilliseconds(10), 342 base::TimeDelta::FromMinutes(30), 343 50)); 344 } 345 346 void PrerenderHistograms::RecordPerSessionCount(Origin origin, 347 int count) const { 348 PREFIXED_HISTOGRAM( 349 "PrerendersPerSessionCount", origin, 350 UMA_HISTOGRAM_COUNTS(name, count)); 351 } 352 353 void PrerenderHistograms::RecordTimeBetweenPrerenderRequests( 354 Origin origin, base::TimeDelta time) const { 355 PREFIXED_HISTOGRAM( 356 "TimeBetweenPrerenderRequests", origin, 357 UMA_HISTOGRAM_TIMES(name, time)); 358 } 359 360 void PrerenderHistograms::RecordFinalStatus( 361 Origin origin, 362 uint8 experiment_id, 363 PrerenderContents::MatchCompleteStatus mc_status, 364 FinalStatus final_status) const { 365 DCHECK(final_status != FINAL_STATUS_MAX); 366 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)); 372 } 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)); 379 } 380 } 381 382 uint8 PrerenderHistograms::GetCurrentExperimentId() const { 383 if (!WithinWindow()) 384 return kNoExperiment; 385 return last_experiment_id_; 386 } 387 388 bool PrerenderHistograms::IsOriginExperimentWash() const { 389 if (!WithinWindow()) 390 return false; 391 return origin_experiment_wash_; 392 } 393 394 } // namespace prerender 395