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/perftimer.h"
     27 #include "base/posix/eintr_wrapper.h"
     28 #include "base/sys_info.h"
     29 #include "base/time/time.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 char path_to_group_file[] = "/home/chronos/.progressive_scan_variation";
     65   const base::FieldTrial::Probability kDivisor = 1000;
     66   scoped_refptr<base::FieldTrial> trial =
     67       base::FieldTrialList::FactoryGetFieldTrial(
     68           name_of_experiment, kDivisor, "Default", 2013, 12, 31,
     69           base::FieldTrial::SESSION_RANDOMIZED, NULL);
     70 
     71   // Announce the groups with 0 percentage; the actual percentages come from
     72   // the server configuration.
     73   std::map<int, std::string> group_to_char;
     74   group_to_char[trial->AppendGroup("FullScan", 0)] = "c";
     75   group_to_char[trial->AppendGroup("33Percent_4MinMax", 0)] = "1";
     76   group_to_char[trial->AppendGroup("50Percent_4MinMax", 0)] = "2";
     77   group_to_char[trial->AppendGroup("50Percent_8MinMax", 0)] = "3";
     78   group_to_char[trial->AppendGroup("100Percent_8MinMax", 0)] = "4";
     79 
     80   // Announce the experiment to any listeners (especially important is the UMA
     81   // software, which will append the group names to UMA statistics).
     82   const int group_num = trial->group();
     83   std::string group_char = "x";
     84   if (ContainsKey(group_to_char, group_num))
     85     group_char = group_to_char[group_num];
     86 
     87   // Write the group to the file to be read by ChromeOS.
     88   const base::FilePath kPathToGroupFile(path_to_group_file);
     89 
     90   if (file_util::WriteFile(kPathToGroupFile, group_char.c_str(),
     91                            group_char.length())) {
     92     LOG(INFO) << "Configured in group '" << trial->group_name()
     93               << "' ('" << group_char << "') for "
     94               << name_of_experiment << " field trial";
     95   } else {
     96     LOG(ERROR) << "Couldn't write to " << path_to_group_file;
     97   }
     98 }
     99 
    100 // Finds out if we're on a 2GB Parrot.
    101 //
    102 // This code reads and parses /etc/lsb-release. There are at least four other
    103 // places that open and parse /etc/lsb-release, and I wish I could fix the
    104 // mess.  At least this code is temporary.
    105 
    106 bool Is2GBParrot() {
    107   base::FilePath path("/etc/lsb-release");
    108   std::string contents;
    109   if (!file_util::ReadFileToString(path, &contents))
    110     return false;
    111   if (contents.find("CHROMEOS_RELEASE_BOARD=parrot") == std::string::npos)
    112     return false;
    113   // There are 2GB and 4GB models.
    114   return base::SysInfo::AmountOfPhysicalMemory() <= 2LL * 1024 * 1024 * 1024;
    115 }
    116 
    117 // Sets up field trial for measuring swap and CPU metrics after tab switch
    118 // and scroll events. crbug.com/253994
    119 void SetupSwapJankFieldTrial() {
    120   const char name_of_experiment[] = "SwapJank64vs32Parrot";
    121 
    122   // Determine if this is a 32 or 64 bit build of Chrome.
    123   bool is_chrome_64 = sizeof(void*) == 8;
    124 
    125   // Determine if this is a 32 or 64 bit kernel.
    126   bool is_kernel_64 = base::SysInfo::OperatingSystemArchitecture() == "x86_64";
    127 
    128   // A 32 bit kernel requires 32 bit Chrome.
    129   DCHECK(is_kernel_64 || !is_chrome_64);
    130 
    131   // Find out if we're on a 2GB Parrot.
    132   bool is_parrot = Is2GBParrot();
    133 
    134   // All groups are either on or off.
    135   const base::FieldTrial::Probability kTotalProbability = 1;
    136   scoped_refptr<base::FieldTrial> trial =
    137       base::FieldTrialList::FactoryGetFieldTrial(
    138           name_of_experiment, kTotalProbability, "default", 2013, 12, 31,
    139           base::FieldTrial::SESSION_RANDOMIZED, NULL);
    140 
    141   // Assign probability of 1 to this Chrome's group.  Assign 0 to all other
    142   // choices.
    143   trial->AppendGroup("kernel_64_chrome_64",
    144                      is_parrot && is_kernel_64 && is_chrome_64 ?
    145                      kTotalProbability : 0);
    146   trial->AppendGroup("kernel_64_chrome_32",
    147                      is_parrot && is_kernel_64 && !is_chrome_64 ?
    148                      kTotalProbability : 0);
    149   trial->AppendGroup("kernel_32_chrome_32",
    150                      is_parrot && !is_kernel_64 && !is_chrome_64 ?
    151                      kTotalProbability : 0);
    152   trial->AppendGroup("not_parrot",
    153                      !is_parrot ? kTotalProbability : 0);
    154 
    155   // Announce the experiment to any listeners (especially important is the UMA
    156   // software, which will append the group names to UMA statistics).
    157   trial->group();
    158   DVLOG(1) << "Configured in group '" << trial->group_name() << "' for "
    159            << name_of_experiment << " field trial";
    160 }
    161 
    162 }  // namespace
    163 
    164 // The interval between external metrics collections in seconds
    165 static const int kExternalMetricsCollectionIntervalSeconds = 30;
    166 
    167 ExternalMetrics::ExternalMetrics() : test_recorder_(NULL) {}
    168 
    169 ExternalMetrics::~ExternalMetrics() {}
    170 
    171 void ExternalMetrics::Start() {
    172   // Register user actions external to the browser.
    173   // chrome/tools/extract_actions.py won't understand these lines, so all of
    174   // these are explicitly added in that script.
    175   // TODO(derat): We shouldn't need to verify actions before reporting them;
    176   // remove all of this once http://crosbug.com/11125 is fixed.
    177   valid_user_actions_.insert("Cryptohome.PKCS11InitFail");
    178   valid_user_actions_.insert("Updater.ServerCertificateChanged");
    179   valid_user_actions_.insert("Updater.ServerCertificateFailed");
    180 
    181   // Initialize here field trials that don't need to read from files.
    182   // (None for the moment.)
    183 
    184   // Initialize any chromeos field trials that need to read from a file (e.g.,
    185   // those that have an upstart script determine their experimental group for
    186   // them) then schedule the data collection.  All of this is done on the file
    187   // thread.
    188   bool task_posted = BrowserThread::PostTask(
    189       BrowserThread::FILE,
    190       FROM_HERE,
    191       base::Bind(&chromeos::ExternalMetrics::SetupFieldTrialsOnFileThread,
    192                  this));
    193   DCHECK(task_posted);
    194 }
    195 
    196 void ExternalMetrics::RecordActionUI(std::string action_string) {
    197   if (valid_user_actions_.count(action_string)) {
    198     content::RecordComputedAction(action_string);
    199   } else {
    200     DLOG(ERROR) << "undefined UMA action: " << action_string;
    201   }
    202 }
    203 
    204 void ExternalMetrics::RecordAction(const char* action) {
    205   std::string action_string(action);
    206   BrowserThread::PostTask(
    207       BrowserThread::UI, FROM_HERE,
    208       base::Bind(&ExternalMetrics::RecordActionUI, this, action_string));
    209 }
    210 
    211 void ExternalMetrics::RecordCrashUI(const std::string& crash_kind) {
    212   if (g_browser_process && g_browser_process->metrics_service()) {
    213     g_browser_process->metrics_service()->LogChromeOSCrash(crash_kind);
    214   }
    215 }
    216 
    217 void ExternalMetrics::RecordCrash(const std::string& crash_kind) {
    218   BrowserThread::PostTask(
    219       BrowserThread::UI, FROM_HERE,
    220       base::Bind(&ExternalMetrics::RecordCrashUI, this, crash_kind));
    221 }
    222 
    223 void ExternalMetrics::RecordHistogram(const char* histogram_data) {
    224   int sample, min, max, nbuckets;
    225   char name[128];   // length must be consistent with sscanf format below.
    226   int n = sscanf(histogram_data, "%127s %d %d %d %d",
    227                  name, &sample, &min, &max, &nbuckets);
    228   if (n != 5) {
    229     DLOG(ERROR) << "bad histogram request: " << histogram_data;
    230     return;
    231   }
    232 
    233   if (!CheckValues(name, min, max, nbuckets)) {
    234     DLOG(ERROR) << "Invalid histogram " << name
    235                 << ", min=" << min
    236                 << ", max=" << max
    237                 << ", nbuckets=" << nbuckets;
    238     return;
    239   }
    240   // Do not use the UMA_HISTOGRAM_... macros here.  They cache the Histogram
    241   // instance and thus only work if |name| is constant.
    242   base::HistogramBase* counter = base::Histogram::FactoryGet(
    243       name, min, max, nbuckets, base::Histogram::kUmaTargetedHistogramFlag);
    244   counter->Add(sample);
    245 }
    246 
    247 void ExternalMetrics::RecordLinearHistogram(const char* histogram_data) {
    248   int sample, max;
    249   char name[128];   // length must be consistent with sscanf format below.
    250   int n = sscanf(histogram_data, "%127s %d %d", name, &sample, &max);
    251   if (n != 3) {
    252     DLOG(ERROR) << "bad linear histogram request: " << histogram_data;
    253     return;
    254   }
    255 
    256   if (!CheckLinearValues(name, max)) {
    257     DLOG(ERROR) << "Invalid linear histogram " << name
    258                 << ", max=" << max;
    259     return;
    260   }
    261   // Do not use the UMA_HISTOGRAM_... macros here.  They cache the Histogram
    262   // instance and thus only work if |name| is constant.
    263   base::HistogramBase* counter = base::LinearHistogram::FactoryGet(
    264       name, 1, max, max + 1, base::Histogram::kUmaTargetedHistogramFlag);
    265   counter->Add(sample);
    266 }
    267 
    268 void ExternalMetrics::RecordSparseHistogram(const char* histogram_data) {
    269   int sample;
    270   char name[128];   // length must be consistent with sscanf format below.
    271   int n = sscanf(histogram_data, "%127s %d", name, &sample);
    272   if (n != 2) {
    273     DLOG(ERROR) << "bad sparse histogram request: " << histogram_data;
    274     return;
    275   }
    276 
    277   // Do not use the UMA_HISTOGRAM_... macros here.  They cache the Histogram
    278   // instance and thus only work if |name| is constant.
    279   base::HistogramBase* counter = base::SparseHistogram::FactoryGet(
    280       name, base::HistogramBase::kUmaTargetedHistogramFlag);
    281   counter->Add(sample);
    282 }
    283 
    284 void ExternalMetrics::CollectEvents() {
    285   const char* event_file_path = "/var/log/metrics/uma-events";
    286   struct stat stat_buf;
    287   int result;
    288   if (!test_path_.empty()) {
    289     event_file_path = test_path_.value().c_str();
    290   }
    291   result = stat(event_file_path, &stat_buf);
    292   if (result < 0) {
    293     if (errno != ENOENT) {
    294       DPLOG(ERROR) << event_file_path << ": bad metrics file stat";
    295     }
    296     // Nothing to collect---try later.
    297     return;
    298   }
    299   if (stat_buf.st_size == 0) {
    300     // Also nothing to collect.
    301     return;
    302   }
    303   int fd = open(event_file_path, O_RDWR);
    304   if (fd < 0) {
    305     DPLOG(ERROR) << event_file_path << ": cannot open";
    306     return;
    307   }
    308   result = flock(fd, LOCK_EX);
    309   if (result < 0) {
    310     DPLOG(ERROR) << event_file_path << ": cannot lock";
    311     close(fd);
    312     return;
    313   }
    314   // This processes all messages in the log.  Each message starts with a 4-byte
    315   // field containing the length of the entire message.  The length is followed
    316   // by a name-value pair of null-terminated strings.  When all messages are
    317   // read and processed, or an error occurs, truncate the file to zero size.
    318   for (;;) {
    319     int32 message_size;
    320     result = HANDLE_EINTR(read(fd, &message_size, sizeof(message_size)));
    321     if (result < 0) {
    322       DPLOG(ERROR) << "reading metrics message header";
    323       break;
    324     }
    325     if (result == 0) {  // This indicates a normal EOF.
    326       break;
    327     }
    328     if (result < static_cast<int>(sizeof(message_size))) {
    329       DLOG(ERROR) << "bad read size " << result <<
    330                      ", expecting " << sizeof(message_size);
    331       break;
    332     }
    333     // kMetricsMessageMaxLength applies to the entire message: the 4-byte
    334     // length field and the two null-terminated strings.
    335     if (message_size < 2 + static_cast<int>(sizeof(message_size)) ||
    336         message_size > static_cast<int>(kMetricsMessageMaxLength)) {
    337       DLOG(ERROR) << "bad message size " << message_size;
    338       break;
    339     }
    340     message_size -= sizeof(message_size);  // The message size includes itself.
    341     uint8 buffer[kMetricsMessageMaxLength];
    342     result = HANDLE_EINTR(read(fd, buffer, message_size));
    343     if (result < 0) {
    344       DPLOG(ERROR) << "reading metrics message body";
    345       break;
    346     }
    347     if (result < message_size) {
    348       DLOG(ERROR) << "message too short: length " << result <<
    349                      ", expected " << message_size;
    350       break;
    351     }
    352     // The buffer should now contain a pair of null-terminated strings.
    353     uint8* p = reinterpret_cast<uint8*>(memchr(buffer, '\0', message_size));
    354     uint8* q = NULL;
    355     if (p != NULL) {
    356       q = reinterpret_cast<uint8*>(
    357           memchr(p + 1, '\0', message_size - (p + 1 - buffer)));
    358     }
    359     if (q == NULL) {
    360       DLOG(ERROR) << "bad name-value pair for metrics";
    361       break;
    362     }
    363     char* name = reinterpret_cast<char*>(buffer);
    364     char* value = reinterpret_cast<char*>(p + 1);
    365     if (test_recorder_ != NULL) {
    366       test_recorder_(name, value);
    367     } else if (strcmp(name, "crash") == 0) {
    368       RecordCrash(value);
    369     } else if (strcmp(name, "histogram") == 0) {
    370       RecordHistogram(value);
    371     } else if (strcmp(name, "linearhistogram") == 0) {
    372       RecordLinearHistogram(value);
    373     } else if (strcmp(name, "sparsehistogram") == 0) {
    374       RecordSparseHistogram(value);
    375     } else if (strcmp(name, "useraction") == 0) {
    376       RecordAction(value);
    377     } else {
    378       DLOG(ERROR) << "invalid event type: " << name;
    379     }
    380   }
    381 
    382   result = ftruncate(fd, 0);
    383   if (result < 0) {
    384     DPLOG(ERROR) << "truncate metrics log";
    385   }
    386   result = flock(fd, LOCK_UN);
    387   if (result < 0) {
    388     DPLOG(ERROR) << "unlock metrics log";
    389   }
    390   result = close(fd);
    391   if (result < 0) {
    392     DPLOG(ERROR) << "close metrics log";
    393   }
    394 }
    395 
    396 void ExternalMetrics::CollectEventsAndReschedule() {
    397   PerfTimer timer;
    398   CollectEvents();
    399   UMA_HISTOGRAM_TIMES("UMA.CollectExternalEventsTime", timer.Elapsed());
    400   ScheduleCollector();
    401 }
    402 
    403 void ExternalMetrics::ScheduleCollector() {
    404   bool result;
    405   result = BrowserThread::PostDelayedTask(
    406       BrowserThread::FILE, FROM_HERE,
    407       base::Bind(&chromeos::ExternalMetrics::CollectEventsAndReschedule, this),
    408       base::TimeDelta::FromSeconds(kExternalMetricsCollectionIntervalSeconds));
    409   DCHECK(result);
    410 }
    411 
    412 void ExternalMetrics::SetupFieldTrialsOnFileThread() {
    413   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    414   // Field trials that do not read from files can be initialized in
    415   // ExternalMetrics::Start() above.
    416   SetupProgressiveScanFieldTrial();
    417   SetupSwapJankFieldTrial();
    418 
    419   ScheduleCollector();
    420 }
    421 
    422 }  // namespace chromeos
    423