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