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/metrics/variations/variations_service.h" 6 7 #include <set> 8 9 #include "base/base64.h" 10 #include "base/build_time.h" 11 #include "base/command_line.h" 12 #include "base/memory/scoped_ptr.h" 13 #include "base/metrics/histogram.h" 14 #include "base/metrics/sparse_histogram.h" 15 #include "base/prefs/pref_registry_simple.h" 16 #include "base/prefs/pref_service.h" 17 #include "base/sha1.h" 18 #include "base/strings/string_number_conversions.h" 19 #include "base/version.h" 20 #include "chrome/browser/browser_process.h" 21 #include "chrome/browser/metrics/proto/trials_seed.pb.h" 22 #include "chrome/browser/metrics/variations/variations_seed_processor.h" 23 #include "chrome/browser/net/network_time_tracker.h" 24 #include "chrome/common/chrome_switches.h" 25 #include "chrome/common/metrics/variations/variations_util.h" 26 #include "chrome/common/pref_names.h" 27 #include "content/public/browser/browser_thread.h" 28 #include "content/public/common/url_fetcher.h" 29 #include "net/base/load_flags.h" 30 #include "net/base/net_errors.h" 31 #include "net/base/network_change_notifier.h" 32 #include "net/base/url_util.h" 33 #include "net/http/http_response_headers.h" 34 #include "net/http/http_status_code.h" 35 #include "net/http/http_util.h" 36 #include "net/url_request/url_fetcher.h" 37 #include "net/url_request/url_request_status.h" 38 #include "url/gurl.h" 39 40 #if defined(OS_CHROMEOS) 41 #include "chrome/browser/chromeos/settings/cros_settings.h" 42 #endif 43 44 namespace chrome_variations { 45 46 namespace { 47 48 // Default server of Variations seed info. 49 const char kDefaultVariationsServerURL[] = 50 "https://clients4.google.com/chrome-variations/seed"; 51 const int kMaxRetrySeedFetch = 5; 52 53 // TODO(mad): To be removed when we stop updating the NetworkTimeTracker. 54 // For the HTTP date headers, the resolution of the server time is 1 second. 55 const int64 kServerTimeResolutionMs = 1000; 56 57 // Wrapper around channel checking, used to enable channel mocking for 58 // testing. If the current browser channel is not UNKNOWN, this will return 59 // that channel value. Otherwise, if the fake channel flag is provided, this 60 // will return the fake channel. Failing that, this will return the UNKNOWN 61 // channel. 62 Study_Channel GetChannelForVariations() { 63 switch (chrome::VersionInfo::GetChannel()) { 64 case chrome::VersionInfo::CHANNEL_CANARY: 65 return Study_Channel_CANARY; 66 case chrome::VersionInfo::CHANNEL_DEV: 67 return Study_Channel_DEV; 68 case chrome::VersionInfo::CHANNEL_BETA: 69 return Study_Channel_BETA; 70 case chrome::VersionInfo::CHANNEL_STABLE: 71 return Study_Channel_STABLE; 72 case chrome::VersionInfo::CHANNEL_UNKNOWN: 73 break; 74 } 75 const std::string forced_channel = 76 CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 77 switches::kFakeVariationsChannel); 78 if (forced_channel == "stable") 79 return Study_Channel_STABLE; 80 if (forced_channel == "beta") 81 return Study_Channel_BETA; 82 if (forced_channel == "dev") 83 return Study_Channel_DEV; 84 if (forced_channel == "canary") 85 return Study_Channel_CANARY; 86 DVLOG(1) << "Invalid channel provided: " << forced_channel; 87 return Study_Channel_UNKNOWN; 88 } 89 90 // Returns a string that will be used for the value of the 'osname' URL param 91 // to the variations server. 92 std::string GetPlatformString() { 93 #if defined(OS_WIN) 94 return "win"; 95 #elif defined(OS_IOS) 96 return "ios"; 97 #elif defined(OS_MACOSX) 98 return "mac"; 99 #elif defined(OS_CHROMEOS) 100 return "chromeos"; 101 #elif defined(OS_ANDROID) 102 return "android"; 103 #elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) 104 // Default BSD and SOLARIS to Linux to not break those builds, although these 105 // platforms are not officially supported by Chrome. 106 return "linux"; 107 #else 108 #error Unknown platform 109 #endif 110 } 111 112 // Converts |date_time| in Study date format to base::Time. 113 base::Time ConvertStudyDateToBaseTime(int64 date_time) { 114 return base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(date_time); 115 } 116 117 // Gets the restrict parameter from |local_state| or from Chrome OS settings in 118 // the case of that platform. 119 std::string GetRestrictParameterPref(PrefService* local_state) { 120 std::string parameter; 121 #if defined(OS_CHROMEOS) 122 chromeos::CrosSettings::Get()->GetString( 123 chromeos::kVariationsRestrictParameter, ¶meter); 124 #else 125 if (local_state) 126 parameter = local_state->GetString(prefs::kVariationsRestrictParameter); 127 #endif 128 return parameter; 129 } 130 131 // Computes a hash of the serialized variations seed data. 132 std::string HashSeed(const std::string& seed_data) { 133 const std::string sha1 = base::SHA1HashString(seed_data); 134 return base::HexEncode(sha1.data(), sha1.size()); 135 } 136 137 enum ResourceRequestsAllowedState { 138 RESOURCE_REQUESTS_ALLOWED, 139 RESOURCE_REQUESTS_NOT_ALLOWED, 140 RESOURCE_REQUESTS_ALLOWED_NOTIFIED, 141 RESOURCE_REQUESTS_ALLOWED_ENUM_SIZE, 142 }; 143 144 // Records UMA histogram with the current resource requests allowed state. 145 void RecordRequestsAllowedHistogram(ResourceRequestsAllowedState state) { 146 UMA_HISTOGRAM_ENUMERATION("Variations.ResourceRequestsAllowed", state, 147 RESOURCE_REQUESTS_ALLOWED_ENUM_SIZE); 148 } 149 150 enum VariationSeedEmptyState { 151 VARIATIONS_SEED_NOT_EMPTY, 152 VARIATIONS_SEED_EMPTY, 153 VARIATIONS_SEED_CORRUPT, 154 VARIATIONS_SEED_EMPTY_ENUM_SIZE, 155 }; 156 157 void RecordVariationSeedEmptyHistogram(VariationSeedEmptyState state) { 158 UMA_HISTOGRAM_ENUMERATION("Variations.SeedEmpty", state, 159 VARIATIONS_SEED_EMPTY_ENUM_SIZE); 160 } 161 162 } // namespace 163 164 VariationsService::VariationsService(PrefService* local_state) 165 : local_state_(local_state), 166 variations_server_url_(GetVariationsServerURL(local_state)), 167 create_trials_from_seed_called_(false), 168 initial_request_completed_(false), 169 resource_request_allowed_notifier_( 170 new ResourceRequestAllowedNotifier) { 171 resource_request_allowed_notifier_->Init(this); 172 } 173 174 VariationsService::VariationsService(ResourceRequestAllowedNotifier* notifier, 175 PrefService* local_state) 176 : local_state_(local_state), 177 variations_server_url_(GetVariationsServerURL(NULL)), 178 create_trials_from_seed_called_(false), 179 initial_request_completed_(false), 180 resource_request_allowed_notifier_(notifier) { 181 resource_request_allowed_notifier_->Init(this); 182 } 183 184 VariationsService::~VariationsService() { 185 } 186 187 bool VariationsService::CreateTrialsFromSeed() { 188 create_trials_from_seed_called_ = true; 189 190 TrialsSeed seed; 191 if (!LoadTrialsSeedFromPref(&seed)) 192 return false; 193 194 const int64 date_value = local_state_->GetInt64(prefs::kVariationsSeedDate); 195 const base::Time seed_date = base::Time::FromInternalValue(date_value); 196 const base::Time build_time = base::GetBuildTime(); 197 // Use the build time for date checks if either the seed date is invalid or 198 // the build time is newer than the seed date. 199 base::Time reference_date = seed_date; 200 if (seed_date.is_null() || seed_date < build_time) 201 reference_date = build_time; 202 203 const chrome::VersionInfo current_version_info; 204 if (!current_version_info.is_valid()) 205 return false; 206 207 const base::Version current_version(current_version_info.Version()); 208 if (!current_version.IsValid()) 209 return false; 210 211 VariationsSeedProcessor().CreateTrialsFromSeed( 212 seed, g_browser_process->GetApplicationLocale(), reference_date, 213 current_version, GetChannelForVariations()); 214 215 // Log the "freshness" of the seed that was just used. The freshness is the 216 // time between the last successful seed download and now. 217 const int64 last_fetch_time_internal = 218 local_state_->GetInt64(prefs::kVariationsLastFetchTime); 219 if (last_fetch_time_internal) { 220 const base::Time now = base::Time::Now(); 221 const base::TimeDelta delta = 222 now - base::Time::FromInternalValue(last_fetch_time_internal); 223 // Log the value in number of minutes. 224 UMA_HISTOGRAM_CUSTOM_COUNTS("Variations.SeedFreshness", delta.InMinutes(), 225 1, base::TimeDelta::FromDays(30).InMinutes(), 50); 226 } 227 228 return true; 229 } 230 231 void VariationsService::StartRepeatedVariationsSeedFetch() { 232 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 233 234 // Check that |CreateTrialsFromSeed| was called, which is necessary to 235 // retrieve the serial number that will be sent to the server. 236 DCHECK(create_trials_from_seed_called_); 237 238 DCHECK(!request_scheduler_.get()); 239 // Note that the act of instantiating the scheduler will start the fetch, if 240 // the scheduler deems appropriate. Using Unretained is fine here since the 241 // lifespan of request_scheduler_ is guaranteed to be shorter than that of 242 // this service. 243 request_scheduler_.reset(VariationsRequestScheduler::Create( 244 base::Bind(&VariationsService::FetchVariationsSeed, 245 base::Unretained(this)), local_state_)); 246 request_scheduler_->Start(); 247 } 248 249 // static 250 GURL VariationsService::GetVariationsServerURL(PrefService* local_state) { 251 std::string server_url_string(CommandLine::ForCurrentProcess()-> 252 GetSwitchValueASCII(switches::kVariationsServerURL)); 253 if (server_url_string.empty()) 254 server_url_string = kDefaultVariationsServerURL; 255 GURL server_url = GURL(server_url_string); 256 257 const std::string restrict_param = GetRestrictParameterPref(local_state); 258 if (!restrict_param.empty()) { 259 server_url = net::AppendOrReplaceQueryParameter(server_url, 260 "restrict", 261 restrict_param); 262 } 263 264 server_url = net::AppendOrReplaceQueryParameter(server_url, "osname", 265 GetPlatformString()); 266 267 DCHECK(server_url.is_valid()); 268 return server_url; 269 } 270 271 #if defined(OS_WIN) 272 void VariationsService::StartGoogleUpdateRegistrySync() { 273 registry_syncer_.RequestRegistrySync(); 274 } 275 #endif 276 277 void VariationsService::SetCreateTrialsFromSeedCalledForTesting(bool called) { 278 create_trials_from_seed_called_ = called; 279 } 280 281 // static 282 std::string VariationsService::GetDefaultVariationsServerURLForTesting() { 283 return kDefaultVariationsServerURL; 284 } 285 286 // static 287 void VariationsService::RegisterPrefs(PrefRegistrySimple* registry) { 288 registry->RegisterStringPref(prefs::kVariationsSeed, std::string()); 289 registry->RegisterStringPref(prefs::kVariationsSeedHash, std::string()); 290 registry->RegisterInt64Pref(prefs::kVariationsSeedDate, 291 base::Time().ToInternalValue()); 292 registry->RegisterInt64Pref(prefs::kVariationsLastFetchTime, 0); 293 registry->RegisterStringPref(prefs::kVariationsRestrictParameter, 294 std::string()); 295 } 296 297 // static 298 VariationsService* VariationsService::Create(PrefService* local_state) { 299 #if !defined(GOOGLE_CHROME_BUILD) 300 // Unless the URL was provided, unsupported builds should return NULL to 301 // indicate that the service should not be used. 302 if (!CommandLine::ForCurrentProcess()->HasSwitch( 303 switches::kVariationsServerURL)) { 304 DVLOG(1) << "Not creating VariationsService in unofficial build without --" 305 << switches::kVariationsServerURL << " specified."; 306 return NULL; 307 } 308 #endif 309 return new VariationsService(local_state); 310 } 311 312 void VariationsService::DoActualFetch() { 313 pending_seed_request_.reset(net::URLFetcher::Create( 314 0, variations_server_url_, net::URLFetcher::GET, this)); 315 pending_seed_request_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | 316 net::LOAD_DO_NOT_SAVE_COOKIES); 317 pending_seed_request_->SetRequestContext( 318 g_browser_process->system_request_context()); 319 pending_seed_request_->SetMaxRetriesOn5xx(kMaxRetrySeedFetch); 320 if (!variations_serial_number_.empty()) { 321 pending_seed_request_->AddExtraRequestHeader("If-Match:" + 322 variations_serial_number_); 323 } 324 pending_seed_request_->Start(); 325 326 const base::TimeTicks now = base::TimeTicks::Now(); 327 base::TimeDelta time_since_last_fetch; 328 // Record a time delta of 0 (default value) if there was no previous fetch. 329 if (!last_request_started_time_.is_null()) 330 time_since_last_fetch = now - last_request_started_time_; 331 UMA_HISTOGRAM_CUSTOM_COUNTS("Variations.TimeSinceLastFetchAttempt", 332 time_since_last_fetch.InMinutes(), 0, 333 base::TimeDelta::FromDays(7).InMinutes(), 50); 334 last_request_started_time_ = now; 335 } 336 337 void VariationsService::FetchVariationsSeed() { 338 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 339 340 if (!resource_request_allowed_notifier_->ResourceRequestsAllowed()) { 341 RecordRequestsAllowedHistogram(RESOURCE_REQUESTS_NOT_ALLOWED); 342 DVLOG(1) << "Resource requests were not allowed. Waiting for notification."; 343 return; 344 } 345 346 RecordRequestsAllowedHistogram(RESOURCE_REQUESTS_ALLOWED); 347 DoActualFetch(); 348 } 349 350 void VariationsService::OnURLFetchComplete(const net::URLFetcher* source) { 351 DCHECK_EQ(pending_seed_request_.get(), source); 352 353 const bool is_first_request = !initial_request_completed_; 354 initial_request_completed_ = true; 355 356 // The fetcher will be deleted when the request is handled. 357 scoped_ptr<const net::URLFetcher> request(pending_seed_request_.release()); 358 const net::URLRequestStatus& request_status = request->GetStatus(); 359 if (request_status.status() != net::URLRequestStatus::SUCCESS) { 360 UMA_HISTOGRAM_SPARSE_SLOWLY("Variations.FailedRequestErrorCode", 361 -request_status.error()); 362 DVLOG(1) << "Variations server request failed with error: " 363 << request_status.error() << ": " 364 << net::ErrorToString(request_status.error()); 365 // It's common for the very first fetch attempt to fail (e.g. the network 366 // may not yet be available). In such a case, try again soon, rather than 367 // waiting the full time interval. 368 if (is_first_request) 369 request_scheduler_->ScheduleFetchShortly(); 370 return; 371 } 372 373 // Log the response code. 374 const int response_code = request->GetResponseCode(); 375 UMA_HISTOGRAM_SPARSE_SLOWLY("Variations.SeedFetchResponseCode", 376 response_code); 377 378 const base::TimeDelta latency = 379 base::TimeTicks::Now() - last_request_started_time_; 380 381 base::Time response_date; 382 if (response_code == net::HTTP_OK || 383 response_code == net::HTTP_NOT_MODIFIED) { 384 bool success = request->GetResponseHeaders()->GetDateValue(&response_date); 385 DCHECK(success || response_date.is_null()); 386 387 if (!response_date.is_null()) { 388 NetworkTimeTracker::BuildNotifierUpdateCallback().Run( 389 response_date, 390 base::TimeDelta::FromMilliseconds(kServerTimeResolutionMs), 391 latency); 392 } 393 } 394 395 if (response_code != net::HTTP_OK) { 396 DVLOG(1) << "Variations server request returned non-HTTP_OK response code: " 397 << response_code; 398 if (response_code == net::HTTP_NOT_MODIFIED) { 399 UMA_HISTOGRAM_MEDIUM_TIMES("Variations.FetchNotModifiedLatency", latency); 400 RecordLastFetchTime(); 401 } else { 402 UMA_HISTOGRAM_MEDIUM_TIMES("Variations.FetchOtherLatency", latency); 403 } 404 return; 405 } 406 UMA_HISTOGRAM_MEDIUM_TIMES("Variations.FetchSuccessLatency", latency); 407 408 std::string seed_data; 409 bool success = request->GetResponseAsString(&seed_data); 410 DCHECK(success); 411 412 StoreSeedData(seed_data, response_date); 413 } 414 415 void VariationsService::OnResourceRequestsAllowed() { 416 // Note that this only attempts to fetch the seed at most once per period 417 // (kSeedFetchPeriodHours). This works because 418 // |resource_request_allowed_notifier_| only calls this method if an 419 // attempt was made earlier that fails (which implies that the period had 420 // elapsed). After a successful attempt is made, the notifier will know not 421 // to call this method again until another failed attempt occurs. 422 RecordRequestsAllowedHistogram(RESOURCE_REQUESTS_ALLOWED_NOTIFIED); 423 DVLOG(1) << "Retrying fetch."; 424 DoActualFetch(); 425 426 // This service must have created a scheduler in order for this to be called. 427 DCHECK(request_scheduler_.get()); 428 request_scheduler_->Reset(); 429 } 430 431 bool VariationsService::StoreSeedData(const std::string& seed_data, 432 const base::Time& seed_date) { 433 if (seed_data.empty()) { 434 VLOG(1) << "Variations Seed data from server is empty, rejecting the seed."; 435 return false; 436 } 437 438 // Only store the seed data if it parses correctly. 439 TrialsSeed seed; 440 if (!seed.ParseFromString(seed_data)) { 441 VLOG(1) << "Variations Seed data from server is not in valid proto format, " 442 << "rejecting the seed."; 443 return false; 444 } 445 446 std::string base64_seed_data; 447 if (!base::Base64Encode(seed_data, &base64_seed_data)) { 448 VLOG(1) << "Variations Seed data from server fails Base64Encode, rejecting " 449 << "the seed."; 450 return false; 451 } 452 453 local_state_->SetString(prefs::kVariationsSeed, base64_seed_data); 454 local_state_->SetString(prefs::kVariationsSeedHash, HashSeed(seed_data)); 455 local_state_->SetInt64(prefs::kVariationsSeedDate, 456 seed_date.ToInternalValue()); 457 variations_serial_number_ = seed.serial_number(); 458 459 RecordLastFetchTime(); 460 461 return true; 462 } 463 464 bool VariationsService::LoadTrialsSeedFromPref(TrialsSeed* seed) { 465 const std::string base64_seed_data = 466 local_state_->GetString(prefs::kVariationsSeed); 467 if (base64_seed_data.empty()) { 468 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_EMPTY); 469 return false; 470 } 471 472 const std::string hash_from_pref = 473 local_state_->GetString(prefs::kVariationsSeedHash); 474 // If the decode process fails, assume the pref value is corrupt and clear it. 475 std::string seed_data; 476 if (!base::Base64Decode(base64_seed_data, &seed_data) || 477 (!hash_from_pref.empty() && HashSeed(seed_data) != hash_from_pref) || 478 !seed->ParseFromString(seed_data)) { 479 VLOG(1) << "Variations seed data in local pref is corrupt, clearing the " 480 << "pref."; 481 local_state_->ClearPref(prefs::kVariationsSeed); 482 local_state_->ClearPref(prefs::kVariationsSeedDate); 483 local_state_->ClearPref(prefs::kVariationsSeedHash); 484 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT); 485 return false; 486 } 487 variations_serial_number_ = seed->serial_number(); 488 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_NOT_EMPTY); 489 return true; 490 } 491 492 void VariationsService::RecordLastFetchTime() { 493 // local_state_ is NULL in tests, so check it first. 494 if (local_state_) { 495 local_state_->SetInt64(prefs::kVariationsLastFetchTime, 496 base::Time::Now().ToInternalValue()); 497 } 498 } 499 500 } // namespace chrome_variations 501