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 <fcntl.h>
      8 #include <stdio.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 #include <sys/file.h>
     12 #include <sys/stat.h>
     13 #include <sys/types.h>
     14 #include <unistd.h>
     15 
     16 #include <map>
     17 #include <string>
     18 
     19 #include "base/basictypes.h"
     20 #include "base/bind.h"
     21 #include "base/file_util.h"
     22 #include "base/files/file_path.h"
     23 #include "base/metrics/histogram.h"
     24 #include "base/metrics/sparse_histogram.h"
     25 #include "base/metrics/statistics_recorder.h"
     26 #include "base/posix/eintr_wrapper.h"
     27 #include "base/sys_info.h"
     28 #include "base/time/time.h"
     29 #include "base/timer/elapsed_timer.h"
     30 #include "chrome/browser/browser_process.h"
     31 #include "chrome/browser/metrics/metrics_service.h"
     32 #include "content/public/browser/browser_thread.h"
     33 #include "content/public/browser/user_metrics.h"
     34 
     35 using content::BrowserThread;
     36 using content::UserMetricsAction;
     37 
     38 namespace chromeos {
     39 
     40 namespace {
     41 
     42 bool CheckValues(const std::string& name,
     43                  int minimum,
     44                  int maximum,
     45                  size_t bucket_count) {
     46   if (!base::Histogram::InspectConstructionArguments(
     47       name, &minimum, &maximum, &bucket_count))
     48     return false;
     49   base::HistogramBase* histogram =
     50       base::StatisticsRecorder::FindHistogram(name);
     51   if (!histogram)
     52     return true;
     53   return histogram->HasConstructionArguments(minimum, maximum, bucket_count);
     54 }
     55 
     56 bool CheckLinearValues(const std::string& name, int maximum) {
     57   return CheckValues(name, 1, maximum, maximum + 1);
     58 }
     59 
     60 // Establishes field trial for wifi scanning in chromeos.  crbug.com/242733.
     61 void SetupProgressiveScanFieldTrial() {
     62   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
     63   const char name_of_experiment[] = "ProgressiveScan";
     64   const base::FilePath group_file_path(
     65       "/home/chronos/.progressive_scan_variation");
     66   const base::FieldTrial::Probability kDivisor = 1000;
     67   scoped_refptr<base::FieldTrial> trial =
     68       base::FieldTrialList::FactoryGetFieldTrial(
     69           name_of_experiment, kDivisor, "Default", 2013, 12, 31,
     70           base::FieldTrial::SESSION_RANDOMIZED, NULL);
     71 
     72   // Announce the groups with 0 percentage; the actual percentages come from
     73   // the server configuration.
     74   std::map<int, std::string> group_to_char;
     75   group_to_char[trial->AppendGroup("FullScan", 0)] = "c";
     76   group_to_char[trial->AppendGroup("33Percent_4MinMax", 0)] = "1";
     77   group_to_char[trial->AppendGroup("50Percent_4MinMax", 0)] = "2";
     78   group_to_char[trial->AppendGroup("50Percent_8MinMax", 0)] = "3";
     79   group_to_char[trial->AppendGroup("100Percent_8MinMax", 0)] = "4";
     80   group_to_char[trial->AppendGroup("100Percent_1MinSeen_A", 0)] = "5";
     81   group_to_char[trial->AppendGroup("100Percent_1MinSeen_B", 0)] = "6";
     82   group_to_char[trial->AppendGroup("100Percent_1Min_4Max", 0)] = "7";
     83 
     84   // Announce the experiment to any listeners (especially important is the UMA
     85   // software, which will append the group names to UMA statistics).
     86   const int group_num = trial->group();
     87   std::string group_char = "x";
     88   if (ContainsKey(group_to_char, group_num))
     89     group_char = group_to_char[group_num];
     90 
     91   // Write the group to the file to be read by ChromeOS.
     92   int size = static_cast<int>(group_char.length());
     93   if (file_util::WriteFile(group_file_path, group_char.c_str(), size) == size) {
     94     VLOG(1) << "Configured in group '" << trial->group_name()
     95             << "' ('" << group_char << "') for "
     96             << name_of_experiment << " field trial";
     97   } else {
     98     LOG(ERROR) << "Couldn't write to " << group_file_path.value();
     99   }
    100 }
    101 
    102 }  // namespace
    103 
    104 // The interval between external metrics collections in seconds
    105 static const int kExternalMetricsCollectionIntervalSeconds = 30;
    106 
    107 ExternalMetrics::ExternalMetrics() : test_recorder_(NULL) {}
    108 
    109 ExternalMetrics::~ExternalMetrics() {}
    110 
    111 void ExternalMetrics::Start() {
    112   // Register user actions external to the browser.
    113   // tools/metrics/actions/extract_actions.py won't understand these lines, so
    114   // all of these are explicitly added in that script.
    115   // TODO(derat): We shouldn't need to verify actions before reporting them;
    116   // remove all of this once http://crosbug.com/11125 is fixed.
    117   valid_user_actions_.insert("Cryptohome.PKCS11InitFail");
    118   valid_user_actions_.insert("Updater.ServerCertificateChanged");
    119   valid_user_actions_.insert("Updater.ServerCertificateFailed");
    120 
    121   // Initialize here field trials that don't need to read from files.
    122   // (None for the moment.)
    123 
    124   // Initialize any chromeos field trials that need to read from a file (e.g.,
    125   // those that have an upstart script determine their experimental group for
    126   // them) then schedule the data collection.  All of this is done on the file
    127   // thread.
    128   bool task_posted = BrowserThread::PostTask(
    129       BrowserThread::FILE,
    130       FROM_HERE,
    131       base::Bind(&chromeos::ExternalMetrics::SetupFieldTrialsOnFileThread,
    132                  this));
    133   DCHECK(task_posted);
    134 }
    135 
    136 void ExternalMetrics::RecordActionUI(std::string action_string) {
    137   if (valid_user_actions_.count(action_string)) {
    138     content::RecordComputedAction(action_string);
    139   } else {
    140     DLOG(ERROR) << "undefined UMA action: " << action_string;
    141   }
    142 }
    143 
    144 void ExternalMetrics::RecordAction(const char* action) {
    145   std::string action_string(action);
    146   BrowserThread::PostTask(
    147       BrowserThread::UI, FROM_HERE,
    148       base::Bind(&ExternalMetrics::RecordActionUI, this, action_string));
    149 }
    150 
    151 void ExternalMetrics::RecordCrashUI(const std::string& crash_kind) {
    152   if (g_browser_process && g_browser_process->metrics_service()) {
    153     g_browser_process->metrics_service()->LogChromeOSCrash(crash_kind);
    154   }
    155 }
    156 
    157 void ExternalMetrics::RecordCrash(const std::string& crash_kind) {
    158   BrowserThread::PostTask(
    159       BrowserThread::UI, FROM_HERE,
    160       base::Bind(&ExternalMetrics::RecordCrashUI, this, crash_kind));
    161 }
    162 
    163 void ExternalMetrics::RecordHistogram(const char* histogram_data) {
    164   int sample, min, max, nbuckets;
    165   char name[128];   // length must be consistent with sscanf format below.
    166   int n = sscanf(histogram_data, "%127s %d %d %d %d",
    167                  name, &sample, &min, &max, &nbuckets);
    168   if (n != 5) {
    169     DLOG(ERROR) << "bad histogram request: " << histogram_data;
    170     return;
    171   }
    172 
    173   if (!CheckValues(name, min, max, nbuckets)) {
    174     DLOG(ERROR) << "Invalid histogram " << name
    175                 << ", min=" << min
    176                 << ", max=" << max
    177                 << ", nbuckets=" << nbuckets;
    178     return;
    179   }
    180   // Do not use the UMA_HISTOGRAM_... macros here.  They cache the Histogram
    181   // instance and thus only work if |name| is constant.
    182   base::HistogramBase* counter = base::Histogram::FactoryGet(
    183       name, min, max, nbuckets, base::Histogram::kUmaTargetedHistogramFlag);
    184   counter->Add(sample);
    185 }
    186 
    187 void ExternalMetrics::RecordLinearHistogram(const char* histogram_data) {
    188   int sample, max;
    189   char name[128];   // length must be consistent with sscanf format below.
    190   int n = sscanf(histogram_data, "%127s %d %d", name, &sample, &max);
    191   if (n != 3) {
    192     DLOG(ERROR) << "bad linear histogram request: " << histogram_data;
    193     return;
    194   }
    195 
    196   if (!CheckLinearValues(name, max)) {
    197     DLOG(ERROR) << "Invalid linear histogram " << name
    198                 << ", max=" << max;
    199     return;
    200   }
    201   // Do not use the UMA_HISTOGRAM_... macros here.  They cache the Histogram
    202   // instance and thus only work if |name| is constant.
    203   base::HistogramBase* counter = base::LinearHistogram::FactoryGet(
    204       name, 1, max, max + 1, base::Histogram::kUmaTargetedHistogramFlag);
    205   counter->Add(sample);
    206 }
    207 
    208 void ExternalMetrics::RecordSparseHistogram(const char* histogram_data) {
    209   int sample;
    210   char name[128];   // length must be consistent with sscanf format below.
    211   int n = sscanf(histogram_data, "%127s %d", name, &sample);
    212   if (n != 2) {
    213     DLOG(ERROR) << "bad sparse histogram request: " << histogram_data;
    214     return;
    215   }
    216 
    217   // Do not use the UMA_HISTOGRAM_... macros here.  They cache the Histogram
    218   // instance and thus only work if |name| is constant.
    219   base::HistogramBase* counter = base::SparseHistogram::FactoryGet(
    220       name, base::HistogramBase::kUmaTargetedHistogramFlag);
    221   counter->Add(sample);
    222 }
    223 
    224 void ExternalMetrics::CollectEvents() {
    225   const char* event_file_path = "/var/log/metrics/uma-events";
    226   struct stat stat_buf;
    227   int result;
    228   if (!test_path_.empty()) {
    229     event_file_path = test_path_.value().c_str();
    230   }
    231   result = stat(event_file_path, &stat_buf);
    232   if (result < 0) {
    233     if (errno != ENOENT) {
    234       DPLOG(ERROR) << event_file_path << ": bad metrics file stat";
    235     }
    236     // Nothing to collect---try later.
    237     return;
    238   }
    239   if (stat_buf.st_size == 0) {
    240     // Also nothing to collect.
    241     return;
    242   }
    243   int fd = open(event_file_path, O_RDWR);
    244   if (fd < 0) {
    245     DPLOG(ERROR) << event_file_path << ": cannot open";
    246     return;
    247   }
    248   result = flock(fd, LOCK_EX);
    249   if (result < 0) {
    250     DPLOG(ERROR) << event_file_path << ": cannot lock";
    251     close(fd);
    252     return;
    253   }
    254   // This processes all messages in the log.  Each message starts with a 4-byte
    255   // field containing the length of the entire message.  The length is followed
    256   // by a name-value pair of null-terminated strings.  When all messages are
    257   // read and processed, or an error occurs, truncate the file to zero size.
    258   for (;;) {
    259     int32 message_size;
    260     result = HANDLE_EINTR(read(fd, &message_size, sizeof(message_size)));
    261     if (result < 0) {
    262       DPLOG(ERROR) << "reading metrics message header";
    263       break;
    264     }
    265     if (result == 0) {  // This indicates a normal EOF.
    266       break;
    267     }
    268     if (result < static_cast<int>(sizeof(message_size))) {
    269       DLOG(ERROR) << "bad read size " << result <<
    270                      ", expecting " << sizeof(message_size);
    271       break;
    272     }
    273     // kMetricsMessageMaxLength applies to the entire message: the 4-byte
    274     // length field and the two null-terminated strings.
    275     if (message_size < 2 + static_cast<int>(sizeof(message_size)) ||
    276         message_size > static_cast<int>(kMetricsMessageMaxLength)) {
    277       DLOG(ERROR) << "bad message size " << message_size;
    278       break;
    279     }
    280     message_size -= sizeof(message_size);  // The message size includes itself.
    281     uint8 buffer[kMetricsMessageMaxLength];
    282     result = HANDLE_EINTR(read(fd, buffer, message_size));
    283     if (result < 0) {
    284       DPLOG(ERROR) << "reading metrics message body";
    285       break;
    286     }
    287     if (result < message_size) {
    288       DLOG(ERROR) << "message too short: length " << result <<
    289                      ", expected " << message_size;
    290       break;
    291     }
    292     // The buffer should now contain a pair of null-terminated strings.
    293     uint8* p = reinterpret_cast<uint8*>(memchr(buffer, '\0', message_size));
    294     uint8* q = NULL;
    295     if (p != NULL) {
    296       q = reinterpret_cast<uint8*>(
    297           memchr(p + 1, '\0', message_size - (p + 1 - buffer)));
    298     }
    299     if (q == NULL) {
    300       DLOG(ERROR) << "bad name-value pair for metrics";
    301       break;
    302     }
    303     char* name = reinterpret_cast<char*>(buffer);
    304     char* value = reinterpret_cast<char*>(p + 1);
    305     if (test_recorder_ != NULL) {
    306       test_recorder_(name, value);
    307     } else if (strcmp(name, "crash") == 0) {
    308       RecordCrash(value);
    309     } else if (strcmp(name, "histogram") == 0) {
    310       RecordHistogram(value);
    311     } else if (strcmp(name, "linearhistogram") == 0) {
    312       RecordLinearHistogram(value);
    313     } else if (strcmp(name, "sparsehistogram") == 0) {
    314       RecordSparseHistogram(value);
    315     } else if (strcmp(name, "useraction") == 0) {
    316       RecordAction(value);
    317     } else {
    318       DLOG(ERROR) << "invalid event type: " << name;
    319     }
    320   }
    321 
    322   result = ftruncate(fd, 0);
    323   if (result < 0) {
    324     DPLOG(ERROR) << "truncate metrics log";
    325   }
    326   result = flock(fd, LOCK_UN);
    327   if (result < 0) {
    328     DPLOG(ERROR) << "unlock metrics log";
    329   }
    330   result = close(fd);
    331   if (result < 0) {
    332     DPLOG(ERROR) << "close metrics log";
    333   }
    334 }
    335 
    336 void ExternalMetrics::CollectEventsAndReschedule() {
    337   base::ElapsedTimer timer;
    338   CollectEvents();
    339   UMA_HISTOGRAM_TIMES("UMA.CollectExternalEventsTime", timer.Elapsed());
    340   ScheduleCollector();
    341 }
    342 
    343 void ExternalMetrics::ScheduleCollector() {
    344   bool result;
    345   result = BrowserThread::PostDelayedTask(
    346       BrowserThread::FILE, FROM_HERE,
    347       base::Bind(&chromeos::ExternalMetrics::CollectEventsAndReschedule, this),
    348       base::TimeDelta::FromSeconds(kExternalMetricsCollectionIntervalSeconds));
    349   DCHECK(result);
    350 }
    351 
    352 void ExternalMetrics::SetupFieldTrialsOnFileThread() {
    353   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    354   // Field trials that do not read from files can be initialized in
    355   // ExternalMetrics::Start() above.
    356   SetupProgressiveScanFieldTrial();
    357 
    358   ScheduleCollector();
    359 }
    360 
    361 }  // namespace chromeos
    362