perf_provider_chromeos.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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// Default time in seconds between invocations of perf.
28// This period is roughly 6.5 hours.
29// This is chosen to be relatively prime with the number of seconds in:
30// - one minute (60)
31// - one hour (3600)
32// - one day (86400)
33const size_t kPerfCommandIntervalDefaultSeconds = 23093;
34
35// The first collection interval is different from the interval above. This is
36// because we want to collect the first profile quickly after Chrome is started.
37// If this period is too long, the user will log off and Chrome will be killed
38// before it is triggered. The following 2 variables determine the upper and
39// lower bound on the interval.
40// The reason we do not always want to collect the initial profile after a fixed
41// period is to not over-represent task X in the profile where task X always
42// runs at a fixed period after start-up. By selecting a period randomly between
43// a lower and upper bound, we will hopefully collect a more fair profile.
44const size_t kPerfCommandStartIntervalLowerBoundMinutes = 10;
45
46const size_t kPerfCommandStartIntervalUpperBoundMinutes = 20;
47
48// Default time in seconds perf is run for.
49const size_t kPerfCommandDurationDefaultSeconds = 2;
50
51// Limit the total size of protobufs that can be cached, so they don't take up
52// too much memory. If the size of cached protobufs exceeds this value, stop
53// collecting further perf data. The current value is 4 MB.
54const size_t kCachedPerfDataProtobufSizeThreshold = 4 * 1024 * 1024;
55
56// Enumeration representing success and various failure modes for collecting and
57// sending perf data.
58enum GetPerfDataOutcome {
59  SUCCESS,
60  NOT_READY_TO_UPLOAD,
61  NOT_READY_TO_COLLECT,
62  INCOGNITO_ACTIVE,
63  INCOGNITO_LAUNCHED,
64  PROTOBUF_NOT_PARSED,
65  NUM_OUTCOMES
66};
67
68// Name of the histogram that represents the success and various failure modes
69// for collecting and sending perf data.
70const char kGetPerfDataOutcomeHistogram[] = "UMA.Perf.GetData";
71
72void AddToPerfHistogram(GetPerfDataOutcome outcome) {
73  UMA_HISTOGRAM_ENUMERATION(kGetPerfDataOutcomeHistogram,
74                            outcome,
75                            NUM_OUTCOMES);
76}
77
78// Returns true if a normal user is logged in. Returns false if logged in as an
79// guest or as a kiosk app.
80bool IsNormalUserLoggedIn() {
81  chromeos::LoginState* login_state = chromeos::LoginState::Get();
82  return (login_state->IsUserLoggedIn() && !login_state->IsGuestUser() &&
83          !login_state->IsKioskApp());
84}
85
86}  // namespace
87
88
89namespace metrics {
90
91// This class must be created and used on the UI thread. It watches for any
92// incognito window being opened from the time it is instantiated to the time it
93// is destroyed.
94class WindowedIncognitoObserver : public chrome::BrowserListObserver {
95 public:
96  WindowedIncognitoObserver() : incognito_launched_(false) {
97    BrowserList::AddObserver(this);
98  }
99
100  virtual ~WindowedIncognitoObserver() {
101    BrowserList::RemoveObserver(this);
102  }
103
104  // This method can be checked to see whether any incognito window has been
105  // opened since the time this object was created.
106  bool incognito_launched() {
107    return incognito_launched_;
108  }
109
110 private:
111  // chrome::BrowserListObserver implementation.
112  virtual void OnBrowserAdded(Browser* browser) OVERRIDE {
113    if (browser->profile()->IsOffTheRecord())
114      incognito_launched_ = true;
115  }
116
117  bool incognito_launched_;
118};
119
120PerfProvider::PerfProvider()
121      : login_observer_(this),
122        weak_factory_(this) {
123  // Register the login observer with LoginState.
124  chromeos::LoginState::Get()->AddObserver(&login_observer_);
125
126  // Check the login state. At the time of writing, this class is instantiated
127  // before login. A subsequent login would activate the profiling. However,
128  // that behavior may change in the future so that the user is already logged
129  // when this class is instantiated. By calling LoggedInStateChanged() here,
130  // PerfProvider will recognize that the system is already logged in.
131  login_observer_.LoggedInStateChanged();
132}
133
134PerfProvider::~PerfProvider() {
135  chromeos::LoginState::Get()->RemoveObserver(&login_observer_);
136}
137
138bool PerfProvider::GetPerfData(std::vector<PerfDataProto>* perf_data) {
139  DCHECK(CalledOnValidThread());
140  if (cached_perf_data_.empty()) {
141    AddToPerfHistogram(NOT_READY_TO_UPLOAD);
142    return false;
143  }
144
145  perf_data->swap(cached_perf_data_);
146  cached_perf_data_.clear();
147
148  AddToPerfHistogram(SUCCESS);
149  return true;
150}
151
152PerfProvider::LoginObserver::LoginObserver(PerfProvider* perf_provider)
153    : perf_provider_(perf_provider) {}
154
155void PerfProvider::LoginObserver::LoggedInStateChanged() {
156  if (IsNormalUserLoggedIn())
157    perf_provider_->Activate();
158  else
159    perf_provider_->Deactivate();
160}
161
162void PerfProvider::Activate() {
163  size_t collection_interval_minutes = base::RandInt(
164      kPerfCommandStartIntervalLowerBoundMinutes,
165      kPerfCommandStartIntervalUpperBoundMinutes);
166  ScheduleCollection(base::TimeDelta::FromMinutes(collection_interval_minutes));
167}
168
169void PerfProvider::Deactivate() {
170  // Stop the timer, but leave |cached_perf_data_| intact.
171  timer_.Stop();
172}
173
174void PerfProvider::ScheduleCollection(const base::TimeDelta& interval) {
175  DCHECK(CalledOnValidThread());
176  if (timer_.IsRunning())
177    return;
178
179  timer_.Start(FROM_HERE, interval, this,
180               &PerfProvider::CollectIfNecessaryAndReschedule);
181}
182
183void PerfProvider::CollectIfNecessary() {
184  DCHECK(CalledOnValidThread());
185
186  // Do not collect further data if we've already collected a substantial amount
187  // of data, as indicated by |kCachedPerfDataProtobufSizeThreshold|.
188  size_t cached_perf_data_size = 0;
189  for (size_t i = 0; i < cached_perf_data_.size(); ++i) {
190    cached_perf_data_size += cached_perf_data_[i].ByteSize();
191  }
192  if (cached_perf_data_size >= kCachedPerfDataProtobufSizeThreshold) {
193    AddToPerfHistogram(NOT_READY_TO_COLLECT);
194    return;
195  }
196
197  // For privacy reasons, Chrome should only collect perf data if there is no
198  // incognito session active (or gets spawned during the collection).
199  if (BrowserList::IsOffTheRecordSessionActive()) {
200    AddToPerfHistogram(INCOGNITO_ACTIVE);
201    return;
202  }
203
204  scoped_ptr<WindowedIncognitoObserver> incognito_observer(
205      new WindowedIncognitoObserver);
206
207  chromeos::DebugDaemonClient* client =
208      chromeos::DBusThreadManager::Get()->GetDebugDaemonClient();
209
210  base::TimeDelta collection_duration = base::TimeDelta::FromSeconds(
211      kPerfCommandDurationDefaultSeconds);
212
213  client->GetPerfData(collection_duration.InSeconds(),
214                      base::Bind(&PerfProvider::ParseProtoIfValid,
215                                 weak_factory_.GetWeakPtr(),
216                                 base::Passed(&incognito_observer)));
217}
218
219void PerfProvider::CollectIfNecessaryAndReschedule() {
220  CollectIfNecessary();
221  ScheduleCollection(
222      base::TimeDelta::FromSeconds(kPerfCommandIntervalDefaultSeconds));
223}
224
225void PerfProvider::ParseProtoIfValid(
226    scoped_ptr<WindowedIncognitoObserver> incognito_observer,
227    const std::vector<uint8>& data) {
228  DCHECK(CalledOnValidThread());
229
230  if (incognito_observer->incognito_launched()) {
231    AddToPerfHistogram(INCOGNITO_LAUNCHED);
232    return;
233  }
234
235  PerfDataProto perf_data_proto;
236  if (!perf_data_proto.ParseFromArray(data.data(), data.size())) {
237    AddToPerfHistogram(PROTOBUF_NOT_PARSED);
238    return;
239  }
240
241  // Append a new PerfDataProto to the |cached_perf_data_| vector and swap in
242  // the contents.
243  cached_perf_data_.resize(cached_perf_data_.size() + 1);
244  cached_perf_data_.back().Swap(&perf_data_proto);
245}
246
247}  // namespace metrics
248