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