perf_provider_chromeos.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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// Enumeration representing success and various failure modes for collecting and 41// sending perf data. 42enum GetPerfDataOutcome { 43 SUCCESS, 44 NOT_READY_TO_UPLOAD, 45 NOT_READY_TO_COLLECT, 46 INCOGNITO_ACTIVE, 47 INCOGNITO_LAUNCHED, 48 PROTOBUF_NOT_PARSED, 49 NUM_OUTCOMES 50}; 51 52// Name of the histogram that represents the success and various failure modes 53// for collecting and sending perf data. 54const char kGetPerfDataOutcomeHistogram[] = "UMA.Perf.GetData"; 55 56void AddToPerfHistogram(GetPerfDataOutcome outcome) { 57 UMA_HISTOGRAM_ENUMERATION(kGetPerfDataOutcomeHistogram, 58 outcome, 59 NUM_OUTCOMES); 60} 61 62// Returns true if a normal user is logged in. Returns false if logged in as an 63// guest or as a kiosk app. 64bool IsNormalUserLoggedIn() { 65 chromeos::LoginState* login_state = chromeos::LoginState::Get(); 66 return (login_state->IsUserLoggedIn() && !login_state->IsGuestUser() && 67 !login_state->IsKioskApp()); 68} 69 70} // namespace 71 72 73namespace metrics { 74 75// This class must be created and used on the UI thread. It watches for any 76// incognito window being opened from the time it is instantiated to the time it 77// is destroyed. 78class WindowedIncognitoObserver : public chrome::BrowserListObserver { 79 public: 80 WindowedIncognitoObserver() : incognito_launched_(false) { 81 BrowserList::AddObserver(this); 82 } 83 84 virtual ~WindowedIncognitoObserver() { 85 BrowserList::RemoveObserver(this); 86 } 87 88 // This method can be checked to see whether any incognito window has been 89 // opened since the time this object was created. 90 bool incognito_launched() { 91 return incognito_launched_; 92 } 93 94 private: 95 // chrome::BrowserListObserver implementation. 96 virtual void OnBrowserAdded(Browser* browser) OVERRIDE { 97 if (browser->profile()->IsOffTheRecord()) 98 incognito_launched_ = true; 99 } 100 101 bool incognito_launched_; 102}; 103 104PerfProvider::PerfProvider() 105 : login_observer_(this), 106 next_profiling_interval_start_(base::TimeTicks::Now()), 107 weak_factory_(this) { 108 // Register the login observer with LoginState. 109 chromeos::LoginState::Get()->AddObserver(&login_observer_); 110 111 // Check the login state. At the time of writing, this class is instantiated 112 // before login. A subsequent login would activate the profiling. However, 113 // that behavior may change in the future so that the user is already logged 114 // when this class is instantiated. By calling LoggedInStateChanged() here, 115 // PerfProvider will recognize that the system is already logged in. 116 login_observer_.LoggedInStateChanged(); 117} 118 119PerfProvider::~PerfProvider() { 120 chromeos::LoginState::Get()->RemoveObserver(&login_observer_); 121} 122 123bool PerfProvider::GetSampledProfiles( 124 std::vector<SampledProfile>* sampled_profiles) { 125 DCHECK(CalledOnValidThread()); 126 if (cached_perf_data_.empty()) { 127 AddToPerfHistogram(NOT_READY_TO_UPLOAD); 128 return false; 129 } 130 131 sampled_profiles->swap(cached_perf_data_); 132 cached_perf_data_.clear(); 133 134 AddToPerfHistogram(SUCCESS); 135 return true; 136} 137 138PerfProvider::LoginObserver::LoginObserver(PerfProvider* perf_provider) 139 : perf_provider_(perf_provider) {} 140 141void PerfProvider::LoginObserver::LoggedInStateChanged() { 142 if (IsNormalUserLoggedIn()) 143 perf_provider_->OnUserLoggedIn(); 144 else 145 perf_provider_->Deactivate(); 146} 147 148void PerfProvider::OnUserLoggedIn() { 149 login_time_ = base::TimeTicks::Now(); 150 ScheduleCollection(); 151} 152 153void PerfProvider::Deactivate() { 154 // Stop the timer, but leave |cached_perf_data_| intact. 155 timer_.Stop(); 156} 157 158void PerfProvider::ScheduleCollection() { 159 DCHECK(CalledOnValidThread()); 160 if (timer_.IsRunning()) 161 return; 162 163 // Pick a random time in the current interval. 164 base::TimeTicks scheduled_time = 165 next_profiling_interval_start_ + 166 base::TimeDelta::FromMilliseconds( 167 base::RandGenerator(kPerfProfilingIntervalMs)); 168 169 // If the scheduled time has already passed in the time it took to make the 170 // above calculations, trigger the collection event immediately. 171 base::TimeTicks now = base::TimeTicks::Now(); 172 if (scheduled_time < now) 173 scheduled_time = now; 174 175 timer_.Start(FROM_HERE, scheduled_time - now, this, 176 &PerfProvider::DoPeriodicCollection); 177 178 // Update the profiling interval tracker to the start of the next interval. 179 next_profiling_interval_start_ += 180 base::TimeDelta::FromMilliseconds(kPerfProfilingIntervalMs); 181} 182 183void PerfProvider::CollectIfNecessary( 184 SampledProfile::TriggerEvent trigger_event) { 185 DCHECK(CalledOnValidThread()); 186 187 // Do not collect further data if we've already collected a substantial amount 188 // of data, as indicated by |kCachedPerfDataProtobufSizeThreshold|. 189 size_t cached_perf_data_size = 0; 190 for (size_t i = 0; i < cached_perf_data_.size(); ++i) { 191 cached_perf_data_size += cached_perf_data_[i].ByteSize(); 192 } 193 if (cached_perf_data_size >= kCachedPerfDataProtobufSizeThreshold) { 194 AddToPerfHistogram(NOT_READY_TO_COLLECT); 195 return; 196 } 197 198 // For privacy reasons, Chrome should only collect perf data if there is no 199 // incognito session active (or gets spawned during the collection). 200 if (BrowserList::IsOffTheRecordSessionActive()) { 201 AddToPerfHistogram(INCOGNITO_ACTIVE); 202 return; 203 } 204 205 scoped_ptr<WindowedIncognitoObserver> incognito_observer( 206 new WindowedIncognitoObserver); 207 208 chromeos::DebugDaemonClient* client = 209 chromeos::DBusThreadManager::Get()->GetDebugDaemonClient(); 210 211 base::TimeDelta collection_duration = base::TimeDelta::FromSeconds( 212 kPerfCommandDurationDefaultSeconds); 213 214 client->GetPerfData(collection_duration.InSeconds(), 215 base::Bind(&PerfProvider::ParseProtoIfValid, 216 weak_factory_.GetWeakPtr(), 217 base::Passed(&incognito_observer), 218 trigger_event)); 219} 220 221void PerfProvider::DoPeriodicCollection() { 222 CollectIfNecessary(SampledProfile::PERIODIC_COLLECTION); 223 ScheduleCollection(); 224} 225 226void PerfProvider::ParseProtoIfValid( 227 scoped_ptr<WindowedIncognitoObserver> incognito_observer, 228 SampledProfile::TriggerEvent trigger_event, 229 const std::vector<uint8>& data) { 230 DCHECK(CalledOnValidThread()); 231 232 if (incognito_observer->incognito_launched()) { 233 AddToPerfHistogram(INCOGNITO_LAUNCHED); 234 return; 235 } 236 237 PerfDataProto perf_data_proto; 238 if (!perf_data_proto.ParseFromArray(data.data(), data.size())) { 239 AddToPerfHistogram(PROTOBUF_NOT_PARSED); 240 return; 241 } 242 243 // Populate a profile collection protobuf with the collected perf data and 244 // extra metadata. 245 cached_perf_data_.resize(cached_perf_data_.size() + 1); 246 SampledProfile& collection_data = cached_perf_data_.back(); 247 collection_data.set_trigger_event(trigger_event); 248 collection_data.set_ms_after_boot( 249 perf_data_proto.timestamp_sec() * base::Time::kMillisecondsPerSecond); 250 251 DCHECK(!login_time_.is_null()); 252 collection_data. 253 set_ms_after_login((base::TimeTicks::Now() - login_time_) 254 .InMilliseconds()); 255 *collection_data.mutable_perf_data() = perf_data_proto; 256} 257 258} // namespace metrics 259