perf_provider_chromeos.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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/metrics/perf_provider_chromeos.h" 17#include "chrome/browser/profiles/profile.h" 18#include "chrome/browser/ui/browser.h" 19#include "chrome/browser/ui/browser_list.h" 20#include "chrome/browser/ui/browser_list_observer.h" 21#include "chrome/common/chrome_switches.h" 22#include "chromeos/dbus/dbus_thread_manager.h" 23#include "chromeos/dbus/debug_daemon_client.h" 24 25namespace { 26 27// Partition time since login into successive intervals of this size. In each 28// interval, pick a random time to collect a profile. 29// This interval is twenty-four hours. 30const size_t kPerfProfilingIntervalMs = 24 * 60 * 60 * 1000; 31 32// Default time in seconds perf is run for. 33const size_t kPerfCommandDurationDefaultSeconds = 2; 34 35// Limit the total size of protobufs that can be cached, so they don't take up 36// too much memory. If the size of cached protobufs exceeds this value, stop 37// collecting further perf data. The current value is 4 MB. 38const size_t kCachedPerfDataProtobufSizeThreshold = 4 * 1024 * 1024; 39 40// There may be too many suspends to collect a profile each time there is a 41// resume. To limit the number of profiles, collect one for 1 in 10 resumes. 42// Adjust this number as needed. 43const int kResumeSamplingFactor = 10; 44 45// Enumeration representing success and various failure modes for collecting and 46// sending perf data. 47enum GetPerfDataOutcome { 48 SUCCESS, 49 NOT_READY_TO_UPLOAD, 50 NOT_READY_TO_COLLECT, 51 INCOGNITO_ACTIVE, 52 INCOGNITO_LAUNCHED, 53 PROTOBUF_NOT_PARSED, 54 NUM_OUTCOMES 55}; 56 57// Name of the histogram that represents the success and various failure modes 58// for collecting and sending perf data. 59const char kGetPerfDataOutcomeHistogram[] = "UMA.Perf.GetData"; 60 61void AddToPerfHistogram(GetPerfDataOutcome outcome) { 62 UMA_HISTOGRAM_ENUMERATION(kGetPerfDataOutcomeHistogram, 63 outcome, 64 NUM_OUTCOMES); 65} 66 67// Returns true if a normal user is logged in. Returns false if logged in as an 68// guest or as a kiosk app. 69bool IsNormalUserLoggedIn() { 70 chromeos::LoginState* login_state = chromeos::LoginState::Get(); 71 return (login_state->IsUserLoggedIn() && !login_state->IsGuestUser() && 72 !login_state->IsKioskApp()); 73} 74 75} // namespace 76 77 78namespace metrics { 79 80// This class must be created and used on the UI thread. It watches for any 81// incognito window being opened from the time it is instantiated to the time it 82// is destroyed. 83class WindowedIncognitoObserver : public chrome::BrowserListObserver { 84 public: 85 WindowedIncognitoObserver() : incognito_launched_(false) { 86 BrowserList::AddObserver(this); 87 } 88 89 virtual ~WindowedIncognitoObserver() { 90 BrowserList::RemoveObserver(this); 91 } 92 93 // This method can be checked to see whether any incognito window has been 94 // opened since the time this object was created. 95 bool incognito_launched() { 96 return incognito_launched_; 97 } 98 99 private: 100 // chrome::BrowserListObserver implementation. 101 virtual void OnBrowserAdded(Browser* browser) OVERRIDE { 102 if (browser->profile()->IsOffTheRecord()) 103 incognito_launched_ = true; 104 } 105 106 bool incognito_launched_; 107}; 108 109PerfProvider::PerfProvider() 110 : login_observer_(this), 111 next_profiling_interval_start_(base::TimeTicks::Now()), 112 weak_factory_(this) { 113 // Register the login observer with LoginState. 114 chromeos::LoginState::Get()->AddObserver(&login_observer_); 115 116 // Register as an observer of power manager events. 117 chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> 118 AddObserver(this); 119 120 // Check the login state. At the time of writing, this class is instantiated 121 // before login. A subsequent login would activate the profiling. However, 122 // that behavior may change in the future so that the user is already logged 123 // when this class is instantiated. By calling LoggedInStateChanged() here, 124 // PerfProvider will recognize that the system is already logged in. 125 login_observer_.LoggedInStateChanged(); 126} 127 128PerfProvider::~PerfProvider() { 129 chromeos::LoginState::Get()->RemoveObserver(&login_observer_); 130} 131 132bool PerfProvider::GetSampledProfiles( 133 std::vector<SampledProfile>* sampled_profiles) { 134 DCHECK(CalledOnValidThread()); 135 if (cached_perf_data_.empty()) { 136 AddToPerfHistogram(NOT_READY_TO_UPLOAD); 137 return false; 138 } 139 140 sampled_profiles->swap(cached_perf_data_); 141 cached_perf_data_.clear(); 142 143 AddToPerfHistogram(SUCCESS); 144 return true; 145} 146 147PerfProvider::LoginObserver::LoginObserver(PerfProvider* perf_provider) 148 : perf_provider_(perf_provider) {} 149 150void PerfProvider::LoginObserver::LoggedInStateChanged() { 151 if (IsNormalUserLoggedIn()) 152 perf_provider_->OnUserLoggedIn(); 153 else 154 perf_provider_->Deactivate(); 155} 156 157void PerfProvider::SuspendDone(const base::TimeDelta& sleep_duration) { 158 // A zero value for the suspend duration indicates that the suspend was 159 // canceled. Do not collect anything if that's the case. 160 if (sleep_duration == base::TimeDelta()) 161 return; 162 163 // Do not collect a profile unless logged in. The system behavior when closing 164 // the lid or idling when not logged in is currently to shut down instead of 165 // suspending. But it's good to enforce the rule here in case that changes. 166 if (!IsNormalUserLoggedIn()) 167 return; 168 169 // Collect a profile only 1/|kResumeSamplingFactor| of the time, to avoid 170 // collecting too much data. 171 if (base::RandGenerator(kResumeSamplingFactor) != 0) 172 return; 173 174 // Fill out a SampledProfile protobuf that will contain the collected data. 175 scoped_ptr<SampledProfile> sampled_profile(new SampledProfile); 176 sampled_profile->set_trigger_event(SampledProfile::RESUME_FROM_SUSPEND); 177 sampled_profile->set_suspend_duration_ms(sleep_duration.InMilliseconds()); 178 // TODO(sque): Vary the time after resume at which to collect a profile. 179 // http://crbug.com/358778. 180 sampled_profile->set_ms_after_resume(0); 181 CollectIfNecessary(sampled_profile.Pass()); 182} 183 184void PerfProvider::OnUserLoggedIn() { 185 login_time_ = base::TimeTicks::Now(); 186 ScheduleCollection(); 187} 188 189void PerfProvider::Deactivate() { 190 // Stop the timer, but leave |cached_perf_data_| intact. 191 timer_.Stop(); 192} 193 194void PerfProvider::ScheduleCollection() { 195 DCHECK(CalledOnValidThread()); 196 if (timer_.IsRunning()) 197 return; 198 199 // Pick a random time in the current interval. 200 base::TimeTicks scheduled_time = 201 next_profiling_interval_start_ + 202 base::TimeDelta::FromMilliseconds( 203 base::RandGenerator(kPerfProfilingIntervalMs)); 204 205 // If the scheduled time has already passed in the time it took to make the 206 // above calculations, trigger the collection event immediately. 207 base::TimeTicks now = base::TimeTicks::Now(); 208 if (scheduled_time < now) 209 scheduled_time = now; 210 211 timer_.Start(FROM_HERE, scheduled_time - now, this, 212 &PerfProvider::DoPeriodicCollection); 213 214 // Update the profiling interval tracker to the start of the next interval. 215 next_profiling_interval_start_ += 216 base::TimeDelta::FromMilliseconds(kPerfProfilingIntervalMs); 217} 218 219void PerfProvider::CollectIfNecessary( 220 scoped_ptr<SampledProfile> sampled_profile) { 221 DCHECK(CalledOnValidThread()); 222 223 // Do not collect further data if we've already collected a substantial amount 224 // of data, as indicated by |kCachedPerfDataProtobufSizeThreshold|. 225 size_t cached_perf_data_size = 0; 226 for (size_t i = 0; i < cached_perf_data_.size(); ++i) { 227 cached_perf_data_size += cached_perf_data_[i].ByteSize(); 228 } 229 if (cached_perf_data_size >= kCachedPerfDataProtobufSizeThreshold) { 230 AddToPerfHistogram(NOT_READY_TO_COLLECT); 231 return; 232 } 233 234 // For privacy reasons, Chrome should only collect perf data if there is no 235 // incognito session active (or gets spawned during the collection). 236 if (BrowserList::IsOffTheRecordSessionActive()) { 237 AddToPerfHistogram(INCOGNITO_ACTIVE); 238 return; 239 } 240 241 scoped_ptr<WindowedIncognitoObserver> incognito_observer( 242 new WindowedIncognitoObserver); 243 244 chromeos::DebugDaemonClient* client = 245 chromeos::DBusThreadManager::Get()->GetDebugDaemonClient(); 246 247 base::TimeDelta collection_duration = base::TimeDelta::FromSeconds( 248 kPerfCommandDurationDefaultSeconds); 249 250 client->GetPerfData(collection_duration.InSeconds(), 251 base::Bind(&PerfProvider::ParseProtoIfValid, 252 weak_factory_.GetWeakPtr(), 253 base::Passed(&incognito_observer), 254 base::Passed(&sampled_profile))); 255} 256 257void PerfProvider::DoPeriodicCollection() { 258 scoped_ptr<SampledProfile> sampled_profile(new SampledProfile); 259 sampled_profile->set_trigger_event(SampledProfile::PERIODIC_COLLECTION); 260 261 CollectIfNecessary(sampled_profile.Pass()); 262 ScheduleCollection(); 263} 264 265void PerfProvider::ParseProtoIfValid( 266 scoped_ptr<WindowedIncognitoObserver> incognito_observer, 267 scoped_ptr<SampledProfile> sampled_profile, 268 const std::vector<uint8>& data) { 269 DCHECK(CalledOnValidThread()); 270 271 if (incognito_observer->incognito_launched()) { 272 AddToPerfHistogram(INCOGNITO_LAUNCHED); 273 return; 274 } 275 276 PerfDataProto perf_data_proto; 277 if (!perf_data_proto.ParseFromArray(data.data(), data.size())) { 278 AddToPerfHistogram(PROTOBUF_NOT_PARSED); 279 return; 280 } 281 282 // Populate a profile collection protobuf with the collected perf data and 283 // extra metadata. 284 cached_perf_data_.resize(cached_perf_data_.size() + 1); 285 SampledProfile& collection_data = cached_perf_data_.back(); 286 collection_data.Swap(sampled_profile.get()); 287 288 // Fill out remaining fields of the SampledProfile protobuf. 289 collection_data.set_ms_after_boot( 290 perf_data_proto.timestamp_sec() * base::Time::kMillisecondsPerSecond); 291 292 DCHECK(!login_time_.is_null()); 293 collection_data. 294 set_ms_after_login((base::TimeTicks::Now() - login_time_) 295 .InMilliseconds()); 296 297 // Finally, store the perf data itself. 298 collection_data.mutable_perf_data()->Swap(&perf_data_proto); 299} 300 301} // namespace metrics 302