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 <string> 6 7 #include "base/bind.h" 8 #include "base/bind_helpers.h" 9 #include "base/callback.h" 10 #include "base/command_line.h" 11 #include "base/compiler_specific.h" 12 #include "base/metrics/histogram.h" 13 #include "base/rand_util.h" 14 #include "base/strings/string_number_conversions.h" 15 #include "base/threading/sequenced_worker_pool.h" 16 #include "chrome/browser/chrome_notification_types.h" 17 #include "chrome/browser/metrics/perf_provider_chromeos.h" 18 #include "chrome/browser/profiles/profile.h" 19 #include "chrome/browser/ui/browser.h" 20 #include "chrome/browser/ui/browser_list.h" 21 #include "chrome/browser/ui/browser_list_observer.h" 22 #include "chrome/common/chrome_switches.h" 23 #include "chromeos/dbus/dbus_thread_manager.h" 24 #include "chromeos/dbus/debug_daemon_client.h" 25 #include "content/public/browser/notification_service.h" 26 27 namespace { 28 29 // Partition time since login into successive intervals of this size. In each 30 // interval, pick a random time to collect a profile. 31 const size_t kPerfProfilingIntervalMs = 3 * 60 * 60 * 1000; 32 33 // Default time in seconds perf is run for. 34 const size_t kPerfCommandDurationDefaultSeconds = 2; 35 36 // Limit the total size of protobufs that can be cached, so they don't take up 37 // too much memory. If the size of cached protobufs exceeds this value, stop 38 // collecting further perf data. The current value is 4 MB. 39 const size_t kCachedPerfDataProtobufSizeThreshold = 4 * 1024 * 1024; 40 41 // There may be too many suspends to collect a profile each time there is a 42 // resume. To limit the number of profiles, collect one for 1 in 10 resumes. 43 // Adjust this number as needed. 44 const int kResumeSamplingFactor = 10; 45 46 // There may be too many session restores to collect a profile each time. Limit 47 // the collection rate by collecting one per 10 restores. Adjust this number as 48 // needed. 49 const int kRestoreSessionSamplingFactor = 10; 50 51 // This is used to space out session restore collections in the face of several 52 // notifications in a short period of time. There should be no less than this 53 // much time between collections. The current value is 30 seconds. 54 const int kMinIntervalBetweenSessionRestoreCollectionsMs = 30 * 1000; 55 56 // If collecting after a resume, add a random delay before collecting. The delay 57 // should be randomly selected between 0 and this value. Currently the value is 58 // equal to 5 seconds. 59 const int kMaxResumeCollectionDelayMs = 5 * 1000; 60 61 // If collecting after a session restore, add a random delay before collecting. 62 // The delay should be randomly selected between 0 and this value. Currently the 63 // value is equal to 10 seconds. 64 const int kMaxRestoreSessionCollectionDelayMs = 10 * 1000; 65 66 // Enumeration representing success and various failure modes for collecting and 67 // sending perf data. 68 enum GetPerfDataOutcome { 69 SUCCESS, 70 NOT_READY_TO_UPLOAD, 71 NOT_READY_TO_COLLECT, 72 INCOGNITO_ACTIVE, 73 INCOGNITO_LAUNCHED, 74 PROTOBUF_NOT_PARSED, 75 NUM_OUTCOMES 76 }; 77 78 // Name of the histogram that represents the success and various failure modes 79 // for collecting and sending perf data. 80 const char kGetPerfDataOutcomeHistogram[] = "UMA.Perf.GetData"; 81 82 void AddToPerfHistogram(GetPerfDataOutcome outcome) { 83 UMA_HISTOGRAM_ENUMERATION(kGetPerfDataOutcomeHistogram, 84 outcome, 85 NUM_OUTCOMES); 86 } 87 88 // Returns true if a normal user is logged in. Returns false otherwise (e.g. if 89 // logged in as a guest or as a kiosk app). 90 bool IsNormalUserLoggedIn() { 91 return chromeos::LoginState::Get()->IsUserAuthenticated(); 92 } 93 94 } // namespace 95 96 97 namespace metrics { 98 99 // This class must be created and used on the UI thread. It watches for any 100 // incognito window being opened from the time it is instantiated to the time it 101 // is destroyed. 102 class WindowedIncognitoObserver : public chrome::BrowserListObserver { 103 public: 104 WindowedIncognitoObserver() : incognito_launched_(false) { 105 BrowserList::AddObserver(this); 106 } 107 108 virtual ~WindowedIncognitoObserver() { 109 BrowserList::RemoveObserver(this); 110 } 111 112 // This method can be checked to see whether any incognito window has been 113 // opened since the time this object was created. 114 bool incognito_launched() { 115 return incognito_launched_; 116 } 117 118 private: 119 // chrome::BrowserListObserver implementation. 120 virtual void OnBrowserAdded(Browser* browser) OVERRIDE { 121 if (browser->profile()->IsOffTheRecord()) 122 incognito_launched_ = true; 123 } 124 125 bool incognito_launched_; 126 }; 127 128 PerfProvider::PerfProvider() 129 : login_observer_(this), 130 next_profiling_interval_start_(base::TimeTicks::Now()), 131 weak_factory_(this) { 132 // Register the login observer with LoginState. 133 chromeos::LoginState::Get()->AddObserver(&login_observer_); 134 135 // Register as an observer of power manager events. 136 chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> 137 AddObserver(this); 138 139 // Register as an observer of session restore. 140 // TODO(sque): clean this up to use something other than notifications. 141 session_restore_registrar_.Add( 142 this, 143 chrome::NOTIFICATION_SESSION_RESTORE_DONE, 144 content::NotificationService::AllBrowserContextsAndSources()); 145 146 // Check the login state. At the time of writing, this class is instantiated 147 // before login. A subsequent login would activate the profiling. However, 148 // that behavior may change in the future so that the user is already logged 149 // when this class is instantiated. By calling LoggedInStateChanged() here, 150 // PerfProvider will recognize that the system is already logged in. 151 login_observer_.LoggedInStateChanged(); 152 } 153 154 PerfProvider::~PerfProvider() { 155 chromeos::LoginState::Get()->RemoveObserver(&login_observer_); 156 } 157 158 bool PerfProvider::GetSampledProfiles( 159 std::vector<SampledProfile>* sampled_profiles) { 160 DCHECK(CalledOnValidThread()); 161 if (cached_perf_data_.empty()) { 162 AddToPerfHistogram(NOT_READY_TO_UPLOAD); 163 return false; 164 } 165 166 sampled_profiles->swap(cached_perf_data_); 167 cached_perf_data_.clear(); 168 169 AddToPerfHistogram(SUCCESS); 170 return true; 171 } 172 173 PerfProvider::LoginObserver::LoginObserver(PerfProvider* perf_provider) 174 : perf_provider_(perf_provider) {} 175 176 void PerfProvider::LoginObserver::LoggedInStateChanged() { 177 if (IsNormalUserLoggedIn()) 178 perf_provider_->OnUserLoggedIn(); 179 else 180 perf_provider_->Deactivate(); 181 } 182 183 void PerfProvider::SuspendDone(const base::TimeDelta& sleep_duration) { 184 // A zero value for the suspend duration indicates that the suspend was 185 // canceled. Do not collect anything if that's the case. 186 if (sleep_duration == base::TimeDelta()) 187 return; 188 189 // Do not collect a profile unless logged in. The system behavior when closing 190 // the lid or idling when not logged in is currently to shut down instead of 191 // suspending. But it's good to enforce the rule here in case that changes. 192 if (!IsNormalUserLoggedIn()) 193 return; 194 195 // Collect a profile only 1/|kResumeSamplingFactor| of the time, to avoid 196 // collecting too much data. 197 if (base::RandGenerator(kResumeSamplingFactor) != 0) 198 return; 199 200 // Override any existing profiling. 201 if (timer_.IsRunning()) 202 timer_.Stop(); 203 204 // Randomly pick a delay before doing the collection. 205 base::TimeDelta collection_delay = 206 base::TimeDelta::FromMilliseconds( 207 base::RandGenerator(kMaxResumeCollectionDelayMs)); 208 timer_.Start(FROM_HERE, 209 collection_delay, 210 base::Bind(&PerfProvider::CollectPerfDataAfterResume, 211 weak_factory_.GetWeakPtr(), 212 sleep_duration, 213 collection_delay)); 214 } 215 216 void PerfProvider::Observe(int type, 217 const content::NotificationSource& source, 218 const content::NotificationDetails& details) { 219 // Only handle session restore notifications. 220 DCHECK_EQ(type, chrome::NOTIFICATION_SESSION_RESTORE_DONE); 221 222 // Do not collect a profile unless logged in as a normal user. 223 if (!IsNormalUserLoggedIn()) 224 return; 225 226 // Collect a profile only 1/|kRestoreSessionSamplingFactor| of the time, to 227 // avoid collecting too much data and potentially causing UI latency. 228 if (base::RandGenerator(kRestoreSessionSamplingFactor) != 0) 229 return; 230 231 const base::TimeDelta min_interval = 232 base::TimeDelta::FromMilliseconds( 233 kMinIntervalBetweenSessionRestoreCollectionsMs); 234 const base::TimeDelta time_since_last_collection = 235 (base::TimeTicks::Now() - last_session_restore_collection_time_); 236 // Do not collect if there hasn't been enough elapsed time since the last 237 // collection. 238 if (!last_session_restore_collection_time_.is_null() && 239 time_since_last_collection < min_interval) { 240 return; 241 } 242 243 // Stop any existing scheduled collection. 244 if (timer_.IsRunning()) 245 timer_.Stop(); 246 247 // Randomly pick a delay before doing the collection. 248 base::TimeDelta collection_delay = 249 base::TimeDelta::FromMilliseconds( 250 base::RandGenerator(kMaxRestoreSessionCollectionDelayMs)); 251 timer_.Start( 252 FROM_HERE, 253 collection_delay, 254 base::Bind(&PerfProvider::CollectPerfDataAfterSessionRestore, 255 weak_factory_.GetWeakPtr(), 256 collection_delay)); 257 } 258 259 void PerfProvider::OnUserLoggedIn() { 260 login_time_ = base::TimeTicks::Now(); 261 ScheduleIntervalCollection(); 262 } 263 264 void PerfProvider::Deactivate() { 265 // Stop the timer, but leave |cached_perf_data_| intact. 266 timer_.Stop(); 267 } 268 269 void PerfProvider::ScheduleIntervalCollection() { 270 DCHECK(CalledOnValidThread()); 271 if (timer_.IsRunning()) 272 return; 273 274 // Pick a random time in the current interval. 275 base::TimeTicks scheduled_time = 276 next_profiling_interval_start_ + 277 base::TimeDelta::FromMilliseconds( 278 base::RandGenerator(kPerfProfilingIntervalMs)); 279 280 // If the scheduled time has already passed in the time it took to make the 281 // above calculations, trigger the collection event immediately. 282 base::TimeTicks now = base::TimeTicks::Now(); 283 if (scheduled_time < now) 284 scheduled_time = now; 285 286 timer_.Start(FROM_HERE, scheduled_time - now, this, 287 &PerfProvider::DoPeriodicCollection); 288 289 // Update the profiling interval tracker to the start of the next interval. 290 next_profiling_interval_start_ += 291 base::TimeDelta::FromMilliseconds(kPerfProfilingIntervalMs); 292 } 293 294 void PerfProvider::CollectIfNecessary( 295 scoped_ptr<SampledProfile> sampled_profile) { 296 DCHECK(CalledOnValidThread()); 297 298 // Schedule another interval collection. This call makes sense regardless of 299 // whether or not the current collection was interval-triggered. If it had 300 // been another type of trigger event, the interval timer would have been 301 // halted, so it makes sense to reschedule a new interval collection. 302 ScheduleIntervalCollection(); 303 304 // Do not collect further data if we've already collected a substantial amount 305 // of data, as indicated by |kCachedPerfDataProtobufSizeThreshold|. 306 size_t cached_perf_data_size = 0; 307 for (size_t i = 0; i < cached_perf_data_.size(); ++i) { 308 cached_perf_data_size += cached_perf_data_[i].ByteSize(); 309 } 310 if (cached_perf_data_size >= kCachedPerfDataProtobufSizeThreshold) { 311 AddToPerfHistogram(NOT_READY_TO_COLLECT); 312 return; 313 } 314 315 // For privacy reasons, Chrome should only collect perf data if there is no 316 // incognito session active (or gets spawned during the collection). 317 if (BrowserList::IsOffTheRecordSessionActive()) { 318 AddToPerfHistogram(INCOGNITO_ACTIVE); 319 return; 320 } 321 322 scoped_ptr<WindowedIncognitoObserver> incognito_observer( 323 new WindowedIncognitoObserver); 324 325 chromeos::DebugDaemonClient* client = 326 chromeos::DBusThreadManager::Get()->GetDebugDaemonClient(); 327 328 base::TimeDelta collection_duration = base::TimeDelta::FromSeconds( 329 kPerfCommandDurationDefaultSeconds); 330 331 client->GetPerfData(collection_duration.InSeconds(), 332 base::Bind(&PerfProvider::ParseProtoIfValid, 333 weak_factory_.GetWeakPtr(), 334 base::Passed(&incognito_observer), 335 base::Passed(&sampled_profile))); 336 } 337 338 void PerfProvider::DoPeriodicCollection() { 339 scoped_ptr<SampledProfile> sampled_profile(new SampledProfile); 340 sampled_profile->set_trigger_event(SampledProfile::PERIODIC_COLLECTION); 341 342 CollectIfNecessary(sampled_profile.Pass()); 343 } 344 345 void PerfProvider::CollectPerfDataAfterResume( 346 const base::TimeDelta& sleep_duration, 347 const base::TimeDelta& time_after_resume) { 348 // Fill out a SampledProfile protobuf that will contain the collected data. 349 scoped_ptr<SampledProfile> sampled_profile(new SampledProfile); 350 sampled_profile->set_trigger_event(SampledProfile::RESUME_FROM_SUSPEND); 351 sampled_profile->set_suspend_duration_ms(sleep_duration.InMilliseconds()); 352 sampled_profile->set_ms_after_resume(time_after_resume.InMilliseconds()); 353 354 CollectIfNecessary(sampled_profile.Pass()); 355 } 356 357 void PerfProvider::CollectPerfDataAfterSessionRestore( 358 const base::TimeDelta& time_after_restore) { 359 // Fill out a SampledProfile protobuf that will contain the collected data. 360 scoped_ptr<SampledProfile> sampled_profile(new SampledProfile); 361 sampled_profile->set_trigger_event(SampledProfile::RESTORE_SESSION); 362 sampled_profile->set_ms_after_restore(time_after_restore.InMilliseconds()); 363 364 CollectIfNecessary(sampled_profile.Pass()); 365 last_session_restore_collection_time_ = base::TimeTicks::Now(); 366 } 367 368 void PerfProvider::ParseProtoIfValid( 369 scoped_ptr<WindowedIncognitoObserver> incognito_observer, 370 scoped_ptr<SampledProfile> sampled_profile, 371 const std::vector<uint8>& data) { 372 DCHECK(CalledOnValidThread()); 373 374 if (incognito_observer->incognito_launched()) { 375 AddToPerfHistogram(INCOGNITO_LAUNCHED); 376 return; 377 } 378 379 PerfDataProto perf_data_proto; 380 if (!perf_data_proto.ParseFromArray(data.data(), data.size())) { 381 AddToPerfHistogram(PROTOBUF_NOT_PARSED); 382 return; 383 } 384 385 // Populate a profile collection protobuf with the collected perf data and 386 // extra metadata. 387 cached_perf_data_.resize(cached_perf_data_.size() + 1); 388 SampledProfile& collection_data = cached_perf_data_.back(); 389 collection_data.Swap(sampled_profile.get()); 390 391 // Fill out remaining fields of the SampledProfile protobuf. 392 collection_data.set_ms_after_boot( 393 perf_data_proto.timestamp_sec() * base::Time::kMillisecondsPerSecond); 394 395 DCHECK(!login_time_.is_null()); 396 collection_data. 397 set_ms_after_login((base::TimeTicks::Now() - login_time_) 398 .InMilliseconds()); 399 400 // Finally, store the perf data itself. 401 collection_data.mutable_perf_data()->Swap(&perf_data_proto); 402 } 403 404 } // namespace metrics 405