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 "chrome/browser/chromeos/external_metrics.h"
6
7#include <map>
8#include <string>
9
10#include "base/bind.h"
11#include "base/files/file_path.h"
12#include "base/files/file_util.h"
13#include "base/metrics/field_trial.h"
14#include "base/metrics/histogram.h"
15#include "base/metrics/sparse_histogram.h"
16#include "base/metrics/statistics_recorder.h"
17#include "base/timer/elapsed_timer.h"
18#include "chrome/browser/browser_process.h"
19#include "chrome/browser/metrics/chromeos_metrics_provider.h"
20#include "components/metrics/metrics_service.h"
21#include "components/metrics/serialization/metric_sample.h"
22#include "components/metrics/serialization/serialization_utils.h"
23#include "content/public/browser/browser_thread.h"
24#include "content/public/browser/user_metrics.h"
25
26using base::UserMetricsAction;
27using content::BrowserThread;
28
29namespace chromeos {
30
31namespace {
32
33bool CheckValues(const std::string& name,
34                 int minimum,
35                 int maximum,
36                 size_t bucket_count) {
37  if (!base::Histogram::InspectConstructionArguments(
38      name, &minimum, &maximum, &bucket_count))
39    return false;
40  base::HistogramBase* histogram =
41      base::StatisticsRecorder::FindHistogram(name);
42  if (!histogram)
43    return true;
44  return histogram->HasConstructionArguments(minimum, maximum, bucket_count);
45}
46
47bool CheckLinearValues(const std::string& name, int maximum) {
48  return CheckValues(name, 1, maximum, maximum + 1);
49}
50
51// Establishes field trial for wifi scanning in chromeos.  crbug.com/242733.
52void SetupProgressiveScanFieldTrial() {
53  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
54  const char name_of_experiment[] = "ProgressiveScan";
55  const base::FilePath group_file_path(
56      "/home/chronos/.progressive_scan_variation");
57  const base::FieldTrial::Probability kDivisor = 1000;
58  scoped_refptr<base::FieldTrial> trial =
59      base::FieldTrialList::FactoryGetFieldTrial(
60          name_of_experiment, kDivisor, "Default", 2013, 12, 31,
61          base::FieldTrial::SESSION_RANDOMIZED, NULL);
62
63  // Announce the groups with 0 percentage; the actual percentages come from
64  // the server configuration.
65  std::map<int, std::string> group_to_char;
66  group_to_char[trial->AppendGroup("FullScan", 0)] = "c";
67  group_to_char[trial->AppendGroup("33Percent_4MinMax", 0)] = "1";
68  group_to_char[trial->AppendGroup("50Percent_4MinMax", 0)] = "2";
69  group_to_char[trial->AppendGroup("50Percent_8MinMax", 0)] = "3";
70  group_to_char[trial->AppendGroup("100Percent_8MinMax", 0)] = "4";
71  group_to_char[trial->AppendGroup("100Percent_1MinSeen_A", 0)] = "5";
72  group_to_char[trial->AppendGroup("100Percent_1MinSeen_B", 0)] = "6";
73  group_to_char[trial->AppendGroup("100Percent_1Min_4Max", 0)] = "7";
74
75  // Announce the experiment to any listeners (especially important is the UMA
76  // software, which will append the group names to UMA statistics).
77  const int group_num = trial->group();
78  std::string group_char = "x";
79  if (ContainsKey(group_to_char, group_num))
80    group_char = group_to_char[group_num];
81
82  // Write the group to the file to be read by ChromeOS.
83  int size = static_cast<int>(group_char.length());
84  if (base::WriteFile(group_file_path, group_char.c_str(), size) == size) {
85    VLOG(1) << "Configured in group '" << trial->group_name()
86            << "' ('" << group_char << "') for "
87            << name_of_experiment << " field trial";
88  } else {
89    VLOG(1) << "Couldn't write to " << group_file_path.value();
90  }
91}
92
93}  // namespace
94
95// The interval between external metrics collections in seconds
96static const int kExternalMetricsCollectionIntervalSeconds = 30;
97const char kEventsFilePath[] = "/var/run/metrics/uma-events";
98
99ExternalMetrics::ExternalMetrics() : uma_events_file_(kEventsFilePath) {
100}
101
102ExternalMetrics::~ExternalMetrics() {}
103
104void ExternalMetrics::Start() {
105  // Register user actions external to the browser.
106  // tools/metrics/actions/extract_actions.py won't understand these lines, so
107  // all of these are explicitly added in that script.
108  // TODO(derat): We shouldn't need to verify actions before reporting them;
109  // remove all of this once http://crosbug.com/11125 is fixed.
110  valid_user_actions_.insert("Cryptohome.PKCS11InitFail");
111  valid_user_actions_.insert("Updater.ServerCertificateChanged");
112  valid_user_actions_.insert("Updater.ServerCertificateFailed");
113
114  // Initialize here field trials that don't need to read from files.
115  // (None for the moment.)
116
117  // Initialize any chromeos field trials that need to read from a file (e.g.,
118  // those that have an upstart script determine their experimental group for
119  // them) then schedule the data collection.  All of this is done on the file
120  // thread.
121  bool task_posted = BrowserThread::PostTask(
122      BrowserThread::FILE,
123      FROM_HERE,
124      base::Bind(&chromeos::ExternalMetrics::SetupFieldTrialsOnFileThread,
125                 this));
126  DCHECK(task_posted);
127}
128
129// static
130scoped_refptr<ExternalMetrics> ExternalMetrics::CreateForTesting(
131    const std::string& filename) {
132  scoped_refptr<ExternalMetrics> external_metrics(new ExternalMetrics());
133  external_metrics->uma_events_file_ = filename;
134  return external_metrics;
135}
136
137void ExternalMetrics::RecordActionUI(std::string action_string) {
138  if (valid_user_actions_.count(action_string)) {
139    content::RecordComputedAction(action_string);
140  } else {
141    DLOG(ERROR) << "undefined UMA action: " << action_string;
142  }
143}
144
145void ExternalMetrics::RecordAction(const std::string& action) {
146  BrowserThread::PostTask(
147      BrowserThread::UI,
148      FROM_HERE,
149      base::Bind(&ExternalMetrics::RecordActionUI, this, action));
150}
151
152void ExternalMetrics::RecordCrashUI(const std::string& crash_kind) {
153  ChromeOSMetricsProvider::LogCrash(crash_kind);
154}
155
156void ExternalMetrics::RecordCrash(const std::string& crash_kind) {
157  BrowserThread::PostTask(
158      BrowserThread::UI, FROM_HERE,
159      base::Bind(&ExternalMetrics::RecordCrashUI, this, crash_kind));
160}
161
162void ExternalMetrics::RecordHistogram(const metrics::MetricSample& sample) {
163  CHECK_EQ(metrics::MetricSample::HISTOGRAM, sample.type());
164  if (!CheckValues(
165          sample.name(), sample.min(), sample.max(), sample.bucket_count())) {
166    DLOG(ERROR) << "Invalid histogram: " << sample.name();
167    return;
168  }
169
170  base::HistogramBase* counter =
171      base::Histogram::FactoryGet(sample.name(),
172                                  sample.min(),
173                                  sample.max(),
174                                  sample.bucket_count(),
175                                  base::Histogram::kUmaTargetedHistogramFlag);
176  counter->Add(sample.sample());
177}
178
179void ExternalMetrics::RecordLinearHistogram(
180    const metrics::MetricSample& sample) {
181  CHECK_EQ(metrics::MetricSample::LINEAR_HISTOGRAM, sample.type());
182  if (!CheckLinearValues(sample.name(), sample.max())) {
183    DLOG(ERROR) << "Invalid linear histogram: " << sample.name();
184    return;
185  }
186  base::HistogramBase* counter = base::LinearHistogram::FactoryGet(
187      sample.name(),
188      1,
189      sample.max(),
190      sample.max() + 1,
191      base::Histogram::kUmaTargetedHistogramFlag);
192  counter->Add(sample.sample());
193}
194
195void ExternalMetrics::RecordSparseHistogram(
196    const metrics::MetricSample& sample) {
197  CHECK_EQ(metrics::MetricSample::SPARSE_HISTOGRAM, sample.type());
198  base::HistogramBase* counter = base::SparseHistogram::FactoryGet(
199      sample.name(), base::HistogramBase::kUmaTargetedHistogramFlag);
200  counter->Add(sample.sample());
201}
202
203int ExternalMetrics::CollectEvents() {
204  ScopedVector<metrics::MetricSample> samples;
205  metrics::SerializationUtils::ReadAndTruncateMetricsFromFile(uma_events_file_,
206                                                              &samples);
207
208  for (ScopedVector<metrics::MetricSample>::iterator it = samples.begin();
209       it != samples.end();
210       ++it) {
211    const metrics::MetricSample& sample = **it;
212
213    // Do not use the UMA_HISTOGRAM_... macros here.  They cache the Histogram
214    // instance and thus only work if |sample.name()| is constant.
215    switch (sample.type()) {
216      case metrics::MetricSample::CRASH:
217        RecordCrash(sample.name());
218        break;
219      case metrics::MetricSample::USER_ACTION:
220        RecordAction(sample.name());
221        break;
222      case metrics::MetricSample::HISTOGRAM:
223        RecordHistogram(sample);
224        break;
225      case metrics::MetricSample::LINEAR_HISTOGRAM:
226        RecordLinearHistogram(sample);
227        break;
228      case metrics::MetricSample::SPARSE_HISTOGRAM:
229        RecordSparseHistogram(sample);
230        break;
231    }
232  }
233
234  return samples.size();
235}
236
237void ExternalMetrics::CollectEventsAndReschedule() {
238  base::ElapsedTimer timer;
239  CollectEvents();
240  UMA_HISTOGRAM_TIMES("UMA.CollectExternalEventsTime", timer.Elapsed());
241  ScheduleCollector();
242}
243
244void ExternalMetrics::ScheduleCollector() {
245  bool result;
246  result = BrowserThread::PostDelayedTask(
247      BrowserThread::FILE, FROM_HERE,
248      base::Bind(&chromeos::ExternalMetrics::CollectEventsAndReschedule, this),
249      base::TimeDelta::FromSeconds(kExternalMetricsCollectionIntervalSeconds));
250  DCHECK(result);
251}
252
253void ExternalMetrics::SetupFieldTrialsOnFileThread() {
254  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
255  // Field trials that do not read from files can be initialized in
256  // ExternalMetrics::Start() above.
257  SetupProgressiveScanFieldTrial();
258
259  ScheduleCollector();
260}
261
262}  // namespace chromeos
263