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