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/network_time/network_time_tracker.h" 22 #include "chrome/common/chrome_switches.h" 23 #include "chrome/common/metrics/variations/variations_util.h" 24 #include "chrome/common/pref_names.h" 25 #include "components/variations/proto/variations_seed.pb.h" 26 #include "components/variations/variations_seed_processor.h" 27 #include "content/public/browser/browser_thread.h" 28 #include "net/base/load_flags.h" 29 #include "net/base/net_errors.h" 30 #include "net/base/network_change_notifier.h" 31 #include "net/base/url_util.h" 32 #include "net/http/http_response_headers.h" 33 #include "net/http/http_status_code.h" 34 #include "net/http/http_util.h" 35 #include "net/url_request/url_fetcher.h" 36 #include "net/url_request/url_request_status.h" 37 #include "ui/base/device_form_factor.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 // Gets the restrict parameter from |local_state| or from Chrome OS settings in 113 // the case of that platform. 114 std::string GetRestrictParameterPref(PrefService* local_state) { 115 std::string parameter; 116 #if defined(OS_CHROMEOS) 117 chromeos::CrosSettings::Get()->GetString( 118 chromeos::kVariationsRestrictParameter, ¶meter); 119 #else 120 if (local_state) 121 parameter = local_state->GetString(prefs::kVariationsRestrictParameter); 122 #endif 123 return parameter; 124 } 125 126 // Computes a hash of the serialized variations seed data. 127 std::string HashSeed(const std::string& seed_data) { 128 const std::string sha1 = base::SHA1HashString(seed_data); 129 return base::HexEncode(sha1.data(), sha1.size()); 130 } 131 132 enum ResourceRequestsAllowedState { 133 RESOURCE_REQUESTS_ALLOWED, 134 RESOURCE_REQUESTS_NOT_ALLOWED, 135 RESOURCE_REQUESTS_ALLOWED_NOTIFIED, 136 RESOURCE_REQUESTS_NOT_ALLOWED_EULA_NOT_ACCEPTED, 137 RESOURCE_REQUESTS_NOT_ALLOWED_NETWORK_DOWN, 138 RESOURCE_REQUESTS_NOT_ALLOWED_COMMAND_LINE_DISABLED, 139 RESOURCE_REQUESTS_ALLOWED_ENUM_SIZE, 140 }; 141 142 // Records UMA histogram with the current resource requests allowed state. 143 void RecordRequestsAllowedHistogram(ResourceRequestsAllowedState state) { 144 UMA_HISTOGRAM_ENUMERATION("Variations.ResourceRequestsAllowed", state, 145 RESOURCE_REQUESTS_ALLOWED_ENUM_SIZE); 146 } 147 148 // Converts ResourceRequestAllowedNotifier::State to the corresponding 149 // ResourceRequestsAllowedState value. 150 ResourceRequestsAllowedState ResourceRequestStateToHistogramValue( 151 ResourceRequestAllowedNotifier::State state) { 152 switch (state) { 153 case ResourceRequestAllowedNotifier::DISALLOWED_EULA_NOT_ACCEPTED: 154 return RESOURCE_REQUESTS_NOT_ALLOWED_EULA_NOT_ACCEPTED; 155 case ResourceRequestAllowedNotifier::DISALLOWED_NETWORK_DOWN: 156 return RESOURCE_REQUESTS_NOT_ALLOWED_NETWORK_DOWN; 157 case ResourceRequestAllowedNotifier::DISALLOWED_COMMAND_LINE_DISABLED: 158 return RESOURCE_REQUESTS_NOT_ALLOWED_COMMAND_LINE_DISABLED; 159 case ResourceRequestAllowedNotifier::ALLOWED: 160 return RESOURCE_REQUESTS_ALLOWED; 161 } 162 NOTREACHED(); 163 return RESOURCE_REQUESTS_NOT_ALLOWED; 164 } 165 166 enum VariationSeedEmptyState { 167 VARIATIONS_SEED_NOT_EMPTY, 168 VARIATIONS_SEED_EMPTY, 169 VARIATIONS_SEED_CORRUPT, 170 VARIATIONS_SEED_EMPTY_ENUM_SIZE, 171 }; 172 173 void RecordVariationSeedEmptyHistogram(VariationSeedEmptyState state) { 174 UMA_HISTOGRAM_ENUMERATION("Variations.SeedEmpty", state, 175 VARIATIONS_SEED_EMPTY_ENUM_SIZE); 176 } 177 178 // Get current form factor and convert it from enum DeviceFormFactor to enum 179 // Study_FormFactor. 180 Study_FormFactor GetCurrentFormFactor() { 181 switch (ui::GetDeviceFormFactor()) { 182 case ui::DEVICE_FORM_FACTOR_PHONE: 183 return Study_FormFactor_PHONE; 184 case ui::DEVICE_FORM_FACTOR_TABLET: 185 return Study_FormFactor_TABLET; 186 case ui::DEVICE_FORM_FACTOR_DESKTOP: 187 return Study_FormFactor_DESKTOP; 188 } 189 NOTREACHED(); 190 return Study_FormFactor_DESKTOP; 191 } 192 193 } // namespace 194 195 VariationsService::VariationsService(PrefService* local_state) 196 : local_state_(local_state), 197 variations_server_url_(GetVariationsServerURL(local_state)), 198 create_trials_from_seed_called_(false), 199 initial_request_completed_(false), 200 resource_request_allowed_notifier_( 201 new ResourceRequestAllowedNotifier) { 202 resource_request_allowed_notifier_->Init(this); 203 } 204 205 VariationsService::VariationsService(ResourceRequestAllowedNotifier* notifier, 206 PrefService* local_state) 207 : local_state_(local_state), 208 variations_server_url_(GetVariationsServerURL(NULL)), 209 create_trials_from_seed_called_(false), 210 initial_request_completed_(false), 211 resource_request_allowed_notifier_(notifier) { 212 resource_request_allowed_notifier_->Init(this); 213 } 214 215 VariationsService::~VariationsService() { 216 } 217 218 bool VariationsService::CreateTrialsFromSeed() { 219 create_trials_from_seed_called_ = true; 220 221 VariationsSeed seed; 222 if (!LoadVariationsSeedFromPref(&seed)) 223 return false; 224 225 const int64 date_value = local_state_->GetInt64(prefs::kVariationsSeedDate); 226 const base::Time seed_date = base::Time::FromInternalValue(date_value); 227 const base::Time build_time = base::GetBuildTime(); 228 // Use the build time for date checks if either the seed date is invalid or 229 // the build time is newer than the seed date. 230 base::Time reference_date = seed_date; 231 if (seed_date.is_null() || seed_date < build_time) 232 reference_date = build_time; 233 234 const chrome::VersionInfo current_version_info; 235 if (!current_version_info.is_valid()) 236 return false; 237 238 const base::Version current_version(current_version_info.Version()); 239 if (!current_version.IsValid()) 240 return false; 241 242 VariationsSeedProcessor().CreateTrialsFromSeed( 243 seed, g_browser_process->GetApplicationLocale(), reference_date, 244 current_version, GetChannelForVariations(), GetCurrentFormFactor()); 245 246 // Log the "freshness" of the seed that was just used. The freshness is the 247 // time between the last successful seed download and now. 248 const int64 last_fetch_time_internal = 249 local_state_->GetInt64(prefs::kVariationsLastFetchTime); 250 if (last_fetch_time_internal) { 251 const base::Time now = base::Time::Now(); 252 const base::TimeDelta delta = 253 now - base::Time::FromInternalValue(last_fetch_time_internal); 254 // Log the value in number of minutes. 255 UMA_HISTOGRAM_CUSTOM_COUNTS("Variations.SeedFreshness", delta.InMinutes(), 256 1, base::TimeDelta::FromDays(30).InMinutes(), 50); 257 } 258 259 return true; 260 } 261 262 void VariationsService::StartRepeatedVariationsSeedFetch() { 263 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 264 265 // Check that |CreateTrialsFromSeed| was called, which is necessary to 266 // retrieve the serial number that will be sent to the server. 267 DCHECK(create_trials_from_seed_called_); 268 269 DCHECK(!request_scheduler_.get()); 270 // Note that the act of instantiating the scheduler will start the fetch, if 271 // the scheduler deems appropriate. Using Unretained is fine here since the 272 // lifespan of request_scheduler_ is guaranteed to be shorter than that of 273 // this service. 274 request_scheduler_.reset(VariationsRequestScheduler::Create( 275 base::Bind(&VariationsService::FetchVariationsSeed, 276 base::Unretained(this)), local_state_)); 277 request_scheduler_->Start(); 278 } 279 280 // static 281 GURL VariationsService::GetVariationsServerURL(PrefService* local_state) { 282 std::string server_url_string(CommandLine::ForCurrentProcess()-> 283 GetSwitchValueASCII(switches::kVariationsServerURL)); 284 if (server_url_string.empty()) 285 server_url_string = kDefaultVariationsServerURL; 286 GURL server_url = GURL(server_url_string); 287 288 const std::string restrict_param = GetRestrictParameterPref(local_state); 289 if (!restrict_param.empty()) { 290 server_url = net::AppendOrReplaceQueryParameter(server_url, 291 "restrict", 292 restrict_param); 293 } 294 295 server_url = net::AppendOrReplaceQueryParameter(server_url, "osname", 296 GetPlatformString()); 297 298 DCHECK(server_url.is_valid()); 299 return server_url; 300 } 301 302 #if defined(OS_WIN) 303 void VariationsService::StartGoogleUpdateRegistrySync() { 304 registry_syncer_.RequestRegistrySync(); 305 } 306 #endif 307 308 void VariationsService::SetCreateTrialsFromSeedCalledForTesting(bool called) { 309 create_trials_from_seed_called_ = called; 310 } 311 312 // static 313 std::string VariationsService::GetDefaultVariationsServerURLForTesting() { 314 return kDefaultVariationsServerURL; 315 } 316 317 // static 318 void VariationsService::RegisterPrefs(PrefRegistrySimple* registry) { 319 registry->RegisterStringPref(prefs::kVariationsSeed, std::string()); 320 registry->RegisterStringPref(prefs::kVariationsSeedHash, std::string()); 321 registry->RegisterInt64Pref(prefs::kVariationsSeedDate, 322 base::Time().ToInternalValue()); 323 registry->RegisterInt64Pref(prefs::kVariationsLastFetchTime, 0); 324 registry->RegisterStringPref(prefs::kVariationsRestrictParameter, 325 std::string()); 326 } 327 328 // static 329 VariationsService* VariationsService::Create(PrefService* local_state) { 330 #if !defined(GOOGLE_CHROME_BUILD) 331 // Unless the URL was provided, unsupported builds should return NULL to 332 // indicate that the service should not be used. 333 if (!CommandLine::ForCurrentProcess()->HasSwitch( 334 switches::kVariationsServerURL)) { 335 DVLOG(1) << "Not creating VariationsService in unofficial build without --" 336 << switches::kVariationsServerURL << " specified."; 337 return NULL; 338 } 339 #endif 340 return new VariationsService(local_state); 341 } 342 343 void VariationsService::DoActualFetch() { 344 pending_seed_request_.reset(net::URLFetcher::Create( 345 0, variations_server_url_, net::URLFetcher::GET, this)); 346 pending_seed_request_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | 347 net::LOAD_DO_NOT_SAVE_COOKIES); 348 pending_seed_request_->SetRequestContext( 349 g_browser_process->system_request_context()); 350 pending_seed_request_->SetMaxRetriesOn5xx(kMaxRetrySeedFetch); 351 if (!variations_serial_number_.empty()) { 352 pending_seed_request_->AddExtraRequestHeader("If-Match:" + 353 variations_serial_number_); 354 } 355 pending_seed_request_->Start(); 356 357 const base::TimeTicks now = base::TimeTicks::Now(); 358 base::TimeDelta time_since_last_fetch; 359 // Record a time delta of 0 (default value) if there was no previous fetch. 360 if (!last_request_started_time_.is_null()) 361 time_since_last_fetch = now - last_request_started_time_; 362 UMA_HISTOGRAM_CUSTOM_COUNTS("Variations.TimeSinceLastFetchAttempt", 363 time_since_last_fetch.InMinutes(), 0, 364 base::TimeDelta::FromDays(7).InMinutes(), 50); 365 last_request_started_time_ = now; 366 } 367 368 void VariationsService::FetchVariationsSeed() { 369 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 370 371 const ResourceRequestAllowedNotifier::State state = 372 resource_request_allowed_notifier_->GetResourceRequestsAllowedState(); 373 if (state != ResourceRequestAllowedNotifier::ALLOWED) { 374 RecordRequestsAllowedHistogram(ResourceRequestStateToHistogramValue(state)); 375 DVLOG(1) << "Resource requests were not allowed. Waiting for notification."; 376 return; 377 } 378 379 RecordRequestsAllowedHistogram(RESOURCE_REQUESTS_ALLOWED); 380 DoActualFetch(); 381 } 382 383 void VariationsService::OnURLFetchComplete(const net::URLFetcher* source) { 384 DCHECK_EQ(pending_seed_request_.get(), source); 385 386 const bool is_first_request = !initial_request_completed_; 387 initial_request_completed_ = true; 388 389 // The fetcher will be deleted when the request is handled. 390 scoped_ptr<const net::URLFetcher> request(pending_seed_request_.release()); 391 const net::URLRequestStatus& request_status = request->GetStatus(); 392 if (request_status.status() != net::URLRequestStatus::SUCCESS) { 393 UMA_HISTOGRAM_SPARSE_SLOWLY("Variations.FailedRequestErrorCode", 394 -request_status.error()); 395 DVLOG(1) << "Variations server request failed with error: " 396 << request_status.error() << ": " 397 << net::ErrorToString(request_status.error()); 398 // It's common for the very first fetch attempt to fail (e.g. the network 399 // may not yet be available). In such a case, try again soon, rather than 400 // waiting the full time interval. 401 if (is_first_request) 402 request_scheduler_->ScheduleFetchShortly(); 403 return; 404 } 405 406 // Log the response code. 407 const int response_code = request->GetResponseCode(); 408 UMA_HISTOGRAM_SPARSE_SLOWLY("Variations.SeedFetchResponseCode", 409 response_code); 410 411 const base::TimeDelta latency = 412 base::TimeTicks::Now() - last_request_started_time_; 413 414 base::Time response_date; 415 if (response_code == net::HTTP_OK || 416 response_code == net::HTTP_NOT_MODIFIED) { 417 bool success = request->GetResponseHeaders()->GetDateValue(&response_date); 418 DCHECK(success || response_date.is_null()); 419 420 if (!response_date.is_null()) { 421 NetworkTimeTracker::BuildNotifierUpdateCallback().Run( 422 response_date, 423 base::TimeDelta::FromMilliseconds(kServerTimeResolutionMs), 424 latency); 425 } 426 } 427 428 if (response_code != net::HTTP_OK) { 429 DVLOG(1) << "Variations server request returned non-HTTP_OK response code: " 430 << response_code; 431 if (response_code == net::HTTP_NOT_MODIFIED) { 432 UMA_HISTOGRAM_MEDIUM_TIMES("Variations.FetchNotModifiedLatency", latency); 433 RecordLastFetchTime(); 434 // Update the seed date value in local state (used for expiry check on 435 // next start up), since 304 is a successful response. 436 local_state_->SetInt64(prefs::kVariationsSeedDate, 437 response_date.ToInternalValue()); 438 } else { 439 UMA_HISTOGRAM_MEDIUM_TIMES("Variations.FetchOtherLatency", latency); 440 } 441 return; 442 } 443 UMA_HISTOGRAM_MEDIUM_TIMES("Variations.FetchSuccessLatency", latency); 444 445 std::string seed_data; 446 bool success = request->GetResponseAsString(&seed_data); 447 DCHECK(success); 448 449 StoreSeedData(seed_data, response_date); 450 } 451 452 void VariationsService::OnResourceRequestsAllowed() { 453 // Note that this only attempts to fetch the seed at most once per period 454 // (kSeedFetchPeriodHours). This works because 455 // |resource_request_allowed_notifier_| only calls this method if an 456 // attempt was made earlier that fails (which implies that the period had 457 // elapsed). After a successful attempt is made, the notifier will know not 458 // to call this method again until another failed attempt occurs. 459 RecordRequestsAllowedHistogram(RESOURCE_REQUESTS_ALLOWED_NOTIFIED); 460 DVLOG(1) << "Retrying fetch."; 461 DoActualFetch(); 462 463 // This service must have created a scheduler in order for this to be called. 464 DCHECK(request_scheduler_.get()); 465 request_scheduler_->Reset(); 466 } 467 468 bool VariationsService::StoreSeedData(const std::string& seed_data, 469 const base::Time& seed_date) { 470 if (seed_data.empty()) { 471 VLOG(1) << "Variations Seed data from server is empty, rejecting the seed."; 472 return false; 473 } 474 475 // Only store the seed data if it parses correctly. 476 VariationsSeed seed; 477 if (!seed.ParseFromString(seed_data)) { 478 VLOG(1) << "Variations Seed data from server is not in valid proto format, " 479 << "rejecting the seed."; 480 return false; 481 } 482 483 std::string base64_seed_data; 484 base::Base64Encode(seed_data, &base64_seed_data); 485 486 local_state_->SetString(prefs::kVariationsSeed, base64_seed_data); 487 local_state_->SetString(prefs::kVariationsSeedHash, HashSeed(seed_data)); 488 local_state_->SetInt64(prefs::kVariationsSeedDate, 489 seed_date.ToInternalValue()); 490 variations_serial_number_ = seed.serial_number(); 491 492 RecordLastFetchTime(); 493 494 return true; 495 } 496 497 bool VariationsService::LoadVariationsSeedFromPref(VariationsSeed* seed) { 498 const std::string base64_seed_data = 499 local_state_->GetString(prefs::kVariationsSeed); 500 if (base64_seed_data.empty()) { 501 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_EMPTY); 502 return false; 503 } 504 505 const std::string hash_from_pref = 506 local_state_->GetString(prefs::kVariationsSeedHash); 507 // If the decode process fails, assume the pref value is corrupt and clear it. 508 std::string seed_data; 509 if (!base::Base64Decode(base64_seed_data, &seed_data) || 510 (!hash_from_pref.empty() && HashSeed(seed_data) != hash_from_pref) || 511 !seed->ParseFromString(seed_data)) { 512 VLOG(1) << "Variations seed data in local pref is corrupt, clearing the " 513 << "pref."; 514 local_state_->ClearPref(prefs::kVariationsSeed); 515 local_state_->ClearPref(prefs::kVariationsSeedDate); 516 local_state_->ClearPref(prefs::kVariationsSeedHash); 517 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT); 518 return false; 519 } 520 variations_serial_number_ = seed->serial_number(); 521 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_NOT_EMPTY); 522 return true; 523 } 524 525 void VariationsService::RecordLastFetchTime() { 526 // local_state_ is NULL in tests, so check it first. 527 if (local_state_) { 528 local_state_->SetInt64(prefs::kVariationsLastFetchTime, 529 base::Time::Now().ToInternalValue()); 530 } 531 } 532 533 } // namespace chrome_variations 534