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