// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/metrics/histogram.h" #include "base/rand_util.h" #include "base/strings/string_number_conversions.h" #include "base/threading/sequenced_worker_pool.h" #include "chrome/browser/metrics/perf_provider_chromeos.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_list_observer.h" #include "chrome/common/chrome_switches.h" #include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/debug_daemon_client.h" namespace { // Partition time since login into successive intervals of this size. In each // interval, pick a random time to collect a profile. // This interval is twenty-four hours. const size_t kPerfProfilingIntervalMs = 24 * 60 * 60 * 1000; // Default time in seconds perf is run for. const size_t kPerfCommandDurationDefaultSeconds = 2; // Limit the total size of protobufs that can be cached, so they don't take up // too much memory. If the size of cached protobufs exceeds this value, stop // collecting further perf data. The current value is 4 MB. const size_t kCachedPerfDataProtobufSizeThreshold = 4 * 1024 * 1024; // There may be too many suspends to collect a profile each time there is a // resume. To limit the number of profiles, collect one for 1 in 10 resumes. // Adjust this number as needed. const int kResumeSamplingFactor = 10; // Enumeration representing success and various failure modes for collecting and // sending perf data. enum GetPerfDataOutcome { SUCCESS, NOT_READY_TO_UPLOAD, NOT_READY_TO_COLLECT, INCOGNITO_ACTIVE, INCOGNITO_LAUNCHED, PROTOBUF_NOT_PARSED, NUM_OUTCOMES }; // Name of the histogram that represents the success and various failure modes // for collecting and sending perf data. const char kGetPerfDataOutcomeHistogram[] = "UMA.Perf.GetData"; void AddToPerfHistogram(GetPerfDataOutcome outcome) { UMA_HISTOGRAM_ENUMERATION(kGetPerfDataOutcomeHistogram, outcome, NUM_OUTCOMES); } // Returns true if a normal user is logged in. Returns false if logged in as an // guest or as a kiosk app. bool IsNormalUserLoggedIn() { chromeos::LoginState* login_state = chromeos::LoginState::Get(); return (login_state->IsUserLoggedIn() && !login_state->IsGuestUser() && !login_state->IsKioskApp()); } } // namespace namespace metrics { // This class must be created and used on the UI thread. It watches for any // incognito window being opened from the time it is instantiated to the time it // is destroyed. class WindowedIncognitoObserver : public chrome::BrowserListObserver { public: WindowedIncognitoObserver() : incognito_launched_(false) { BrowserList::AddObserver(this); } virtual ~WindowedIncognitoObserver() { BrowserList::RemoveObserver(this); } // This method can be checked to see whether any incognito window has been // opened since the time this object was created. bool incognito_launched() { return incognito_launched_; } private: // chrome::BrowserListObserver implementation. virtual void OnBrowserAdded(Browser* browser) OVERRIDE { if (browser->profile()->IsOffTheRecord()) incognito_launched_ = true; } bool incognito_launched_; }; PerfProvider::PerfProvider() : login_observer_(this), next_profiling_interval_start_(base::TimeTicks::Now()), weak_factory_(this) { // Register the login observer with LoginState. chromeos::LoginState::Get()->AddObserver(&login_observer_); // Register as an observer of power manager events. chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> AddObserver(this); // Check the login state. At the time of writing, this class is instantiated // before login. A subsequent login would activate the profiling. However, // that behavior may change in the future so that the user is already logged // when this class is instantiated. By calling LoggedInStateChanged() here, // PerfProvider will recognize that the system is already logged in. login_observer_.LoggedInStateChanged(); } PerfProvider::~PerfProvider() { chromeos::LoginState::Get()->RemoveObserver(&login_observer_); } bool PerfProvider::GetSampledProfiles( std::vector* sampled_profiles) { DCHECK(CalledOnValidThread()); if (cached_perf_data_.empty()) { AddToPerfHistogram(NOT_READY_TO_UPLOAD); return false; } sampled_profiles->swap(cached_perf_data_); cached_perf_data_.clear(); AddToPerfHistogram(SUCCESS); return true; } PerfProvider::LoginObserver::LoginObserver(PerfProvider* perf_provider) : perf_provider_(perf_provider) {} void PerfProvider::LoginObserver::LoggedInStateChanged() { if (IsNormalUserLoggedIn()) perf_provider_->OnUserLoggedIn(); else perf_provider_->Deactivate(); } void PerfProvider::SuspendDone(const base::TimeDelta& sleep_duration) { // A zero value for the suspend duration indicates that the suspend was // canceled. Do not collect anything if that's the case. if (sleep_duration == base::TimeDelta()) return; // Do not collect a profile unless logged in. The system behavior when closing // the lid or idling when not logged in is currently to shut down instead of // suspending. But it's good to enforce the rule here in case that changes. if (!IsNormalUserLoggedIn()) return; // Collect a profile only 1/|kResumeSamplingFactor| of the time, to avoid // collecting too much data. if (base::RandGenerator(kResumeSamplingFactor) != 0) return; // Fill out a SampledProfile protobuf that will contain the collected data. scoped_ptr sampled_profile(new SampledProfile); sampled_profile->set_trigger_event(SampledProfile::RESUME_FROM_SUSPEND); sampled_profile->set_suspend_duration_ms(sleep_duration.InMilliseconds()); // TODO(sque): Vary the time after resume at which to collect a profile. // http://crbug.com/358778. sampled_profile->set_ms_after_resume(0); CollectIfNecessary(sampled_profile.Pass()); } void PerfProvider::OnUserLoggedIn() { login_time_ = base::TimeTicks::Now(); ScheduleCollection(); } void PerfProvider::Deactivate() { // Stop the timer, but leave |cached_perf_data_| intact. timer_.Stop(); } void PerfProvider::ScheduleCollection() { DCHECK(CalledOnValidThread()); if (timer_.IsRunning()) return; // Pick a random time in the current interval. base::TimeTicks scheduled_time = next_profiling_interval_start_ + base::TimeDelta::FromMilliseconds( base::RandGenerator(kPerfProfilingIntervalMs)); // If the scheduled time has already passed in the time it took to make the // above calculations, trigger the collection event immediately. base::TimeTicks now = base::TimeTicks::Now(); if (scheduled_time < now) scheduled_time = now; timer_.Start(FROM_HERE, scheduled_time - now, this, &PerfProvider::DoPeriodicCollection); // Update the profiling interval tracker to the start of the next interval. next_profiling_interval_start_ += base::TimeDelta::FromMilliseconds(kPerfProfilingIntervalMs); } void PerfProvider::CollectIfNecessary( scoped_ptr sampled_profile) { DCHECK(CalledOnValidThread()); // Do not collect further data if we've already collected a substantial amount // of data, as indicated by |kCachedPerfDataProtobufSizeThreshold|. size_t cached_perf_data_size = 0; for (size_t i = 0; i < cached_perf_data_.size(); ++i) { cached_perf_data_size += cached_perf_data_[i].ByteSize(); } if (cached_perf_data_size >= kCachedPerfDataProtobufSizeThreshold) { AddToPerfHistogram(NOT_READY_TO_COLLECT); return; } // For privacy reasons, Chrome should only collect perf data if there is no // incognito session active (or gets spawned during the collection). if (BrowserList::IsOffTheRecordSessionActive()) { AddToPerfHistogram(INCOGNITO_ACTIVE); return; } scoped_ptr incognito_observer( new WindowedIncognitoObserver); chromeos::DebugDaemonClient* client = chromeos::DBusThreadManager::Get()->GetDebugDaemonClient(); base::TimeDelta collection_duration = base::TimeDelta::FromSeconds( kPerfCommandDurationDefaultSeconds); client->GetPerfData(collection_duration.InSeconds(), base::Bind(&PerfProvider::ParseProtoIfValid, weak_factory_.GetWeakPtr(), base::Passed(&incognito_observer), base::Passed(&sampled_profile))); } void PerfProvider::DoPeriodicCollection() { scoped_ptr sampled_profile(new SampledProfile); sampled_profile->set_trigger_event(SampledProfile::PERIODIC_COLLECTION); CollectIfNecessary(sampled_profile.Pass()); ScheduleCollection(); } void PerfProvider::ParseProtoIfValid( scoped_ptr incognito_observer, scoped_ptr sampled_profile, const std::vector& data) { DCHECK(CalledOnValidThread()); if (incognito_observer->incognito_launched()) { AddToPerfHistogram(INCOGNITO_LAUNCHED); return; } PerfDataProto perf_data_proto; if (!perf_data_proto.ParseFromArray(data.data(), data.size())) { AddToPerfHistogram(PROTOBUF_NOT_PARSED); return; } // Populate a profile collection protobuf with the collected perf data and // extra metadata. cached_perf_data_.resize(cached_perf_data_.size() + 1); SampledProfile& collection_data = cached_perf_data_.back(); collection_data.Swap(sampled_profile.get()); // Fill out remaining fields of the SampledProfile protobuf. collection_data.set_ms_after_boot( perf_data_proto.timestamp_sec() * base::Time::kMillisecondsPerSecond); DCHECK(!login_time_.is_null()); collection_data. set_ms_after_login((base::TimeTicks::Now() - login_time_) .InMilliseconds()); // Finally, store the perf data itself. collection_data.mutable_perf_data()->Swap(&perf_data_proto); } } // namespace metrics