Home | History | Annotate | Download | only in chromeos
      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 
     26 using base::UserMetricsAction;
     27 using content::BrowserThread;
     28 
     29 namespace chromeos {
     30 
     31 namespace {
     32 
     33 bool 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 
     47 bool 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.
     52 void 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
     96 static const int kExternalMetricsCollectionIntervalSeconds = 30;
     97 const char kEventsFilePath[] = "/var/run/metrics/uma-events";
     98 
     99 ExternalMetrics::ExternalMetrics() : uma_events_file_(kEventsFilePath) {
    100 }
    101 
    102 ExternalMetrics::~ExternalMetrics() {}
    103 
    104 void 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
    130 scoped_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 
    137 void 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 
    145 void ExternalMetrics::RecordAction(const std::string& action) {
    146   BrowserThread::PostTask(
    147       BrowserThread::UI,
    148       FROM_HERE,
    149       base::Bind(&ExternalMetrics::RecordActionUI, this, action));
    150 }
    151 
    152 void ExternalMetrics::RecordCrashUI(const std::string& crash_kind) {
    153   ChromeOSMetricsProvider::LogCrash(crash_kind);
    154 }
    155 
    156 void 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 
    162 void 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 
    179 void 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 
    195 void 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 
    203 int 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 
    237 void ExternalMetrics::CollectEventsAndReschedule() {
    238   base::ElapsedTimer timer;
    239   CollectEvents();
    240   UMA_HISTOGRAM_TIMES("UMA.CollectExternalEventsTime", timer.Elapsed());
    241   ScheduleCollector();
    242 }
    243 
    244 void 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 
    253 void 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