Home | History | Annotate | Download | only in chromeos
      1 // Copyright (c) 2011 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 <unistd.h>
     12 #include <sys/file.h>
     13 #include <sys/stat.h>
     14 #include <sys/types.h>
     15 
     16 #include "base/basictypes.h"
     17 #include "base/eintr_wrapper.h"
     18 #include "base/metrics/histogram.h"
     19 #include "base/perftimer.h"
     20 #include "base/time.h"
     21 #include "chrome/browser/browser_process.h"
     22 #include "chrome/browser/metrics/metrics_service.h"
     23 #include "chrome/browser/metrics/user_metrics.h"
     24 #include "content/browser/browser_thread.h"
     25 
     26 namespace chromeos {
     27 
     28 // The interval between external metrics collections, in milliseconds.
     29 static const int kExternalMetricsCollectionIntervalMs = 30 * 1000;
     30 
     31 ExternalMetrics::ExternalMetrics()
     32     : test_recorder_(NULL) {
     33 }
     34 
     35 void ExternalMetrics::Start() {
     36   // Register user actions external to the browser.
     37   // chrome/tools/extract_actions.py won't understand these lines, so all of
     38   // these are explicitly added in that script.
     39   // TODO(derat): We shouldn't need to verify actions before reporting them;
     40   // remove all of this once http://crosbug.com/11125 is fixed.
     41   valid_user_actions_.insert("Accel_NextWindow_Tab");
     42   valid_user_actions_.insert("Accel_PrevWindow_Tab");
     43   valid_user_actions_.insert("Accel_NextWindow_F5");
     44   valid_user_actions_.insert("Accel_PrevWindow_F5");
     45   valid_user_actions_.insert("Accel_BrightnessDown_F6");
     46   valid_user_actions_.insert("Accel_BrightnessUp_F7");
     47 
     48   ScheduleCollector();
     49 }
     50 
     51 void ExternalMetrics::RecordActionUI(std::string action_string) {
     52   if (valid_user_actions_.count(action_string)) {
     53     UserMetrics::RecordComputedAction(action_string);
     54   } else {
     55     LOG(ERROR) << "undefined UMA action: " << action_string;
     56   }
     57 }
     58 
     59 void ExternalMetrics::RecordAction(const char* action) {
     60   std::string action_string(action);
     61   BrowserThread::PostTask(
     62       BrowserThread::UI, FROM_HERE,
     63       NewRunnableMethod(this, &ExternalMetrics::RecordActionUI, action_string));
     64 }
     65 
     66 void ExternalMetrics::RecordCrashUI(const std::string& crash_kind) {
     67   if (g_browser_process && g_browser_process->metrics_service()) {
     68     g_browser_process->metrics_service()->LogChromeOSCrash(crash_kind);
     69   }
     70 }
     71 
     72 void ExternalMetrics::RecordCrash(const std::string& crash_kind) {
     73   BrowserThread::PostTask(
     74       BrowserThread::UI, FROM_HERE,
     75       NewRunnableMethod(this, &ExternalMetrics::RecordCrashUI, crash_kind));
     76 }
     77 
     78 void ExternalMetrics::RecordHistogram(const char* histogram_data) {
     79   int sample, min, max, nbuckets;
     80   char name[128];   // length must be consistent with sscanf format below.
     81   int n = sscanf(histogram_data, "%127s %d %d %d %d",
     82                  name, &sample, &min, &max, &nbuckets);
     83   if (n != 5) {
     84     LOG(ERROR) << "bad histogram request: " << histogram_data;
     85     return;
     86   }
     87   // Do not use the UMA_HISTOGRAM_... macros here.  They cache the Histogram
     88   // instance and thus only work if |name| is constant.
     89   base::Histogram* counter = base::Histogram::FactoryGet(
     90       name, min, max, nbuckets, base::Histogram::kUmaTargetedHistogramFlag);
     91   counter->Add(sample);
     92 }
     93 
     94 void ExternalMetrics::RecordLinearHistogram(const char* histogram_data) {
     95   int sample, max;
     96   char name[128];   // length must be consistent with sscanf format below.
     97   int n = sscanf(histogram_data, "%127s %d %d", name, &sample, &max);
     98   if (n != 3) {
     99     LOG(ERROR) << "bad linear histogram request: " << histogram_data;
    100     return;
    101   }
    102   // Do not use the UMA_HISTOGRAM_... macros here.  They cache the Histogram
    103   // instance and thus only work if |name| is constant.
    104   base::Histogram* counter = base::LinearHistogram::FactoryGet(
    105       name, 1, max, max + 1, base::Histogram::kUmaTargetedHistogramFlag);
    106   counter->Add(sample);
    107 }
    108 
    109 void ExternalMetrics::CollectEvents() {
    110   const char* event_file_path = "/var/log/metrics/uma-events";
    111   struct stat stat_buf;
    112   int result;
    113   if (!test_path_.empty()) {
    114     event_file_path = test_path_.value().c_str();
    115   }
    116   result = stat(event_file_path, &stat_buf);
    117   if (result < 0) {
    118     if (errno != ENOENT) {
    119       PLOG(ERROR) << event_file_path << ": bad metrics file stat";
    120     }
    121     // Nothing to collect---try later.
    122     return;
    123   }
    124   if (stat_buf.st_size == 0) {
    125     // Also nothing to collect.
    126     return;
    127   }
    128   int fd = open(event_file_path, O_RDWR);
    129   if (fd < 0) {
    130     PLOG(ERROR) << event_file_path << ": cannot open";
    131     return;
    132   }
    133   result = flock(fd, LOCK_EX);
    134   if (result < 0) {
    135     PLOG(ERROR) << event_file_path << ": cannot lock";
    136     close(fd);
    137     return;
    138   }
    139   // This processes all messages in the log.  Each message starts with a 4-byte
    140   // field containing the length of the entire message.  The length is followed
    141   // by a name-value pair of null-terminated strings.  When all messages are
    142   // read and processed, or an error occurs, truncate the file to zero size.
    143   for (;;) {
    144     int32 message_size;
    145     result = HANDLE_EINTR(read(fd, &message_size, sizeof(message_size)));
    146     if (result < 0) {
    147       PLOG(ERROR) << "reading metrics message header";
    148       break;
    149     }
    150     if (result == 0) {  // normal EOF
    151       break;
    152     }
    153     if (result < static_cast<int>(sizeof(message_size))) {
    154       LOG(ERROR) << "bad read size " << result <<
    155                     ", expecting " << sizeof(message_size);
    156       break;
    157     }
    158     // kMetricsMessageMaxLength applies to the entire message: the 4-byte
    159     // length field and the two null-terminated strings.
    160     if (message_size < 2 + static_cast<int>(sizeof(message_size)) ||
    161         message_size > static_cast<int>(kMetricsMessageMaxLength)) {
    162       LOG(ERROR) << "bad message size " << message_size;
    163       break;
    164     }
    165     message_size -= sizeof(message_size);  // already read this much
    166     uint8 buffer[kMetricsMessageMaxLength];
    167     result = HANDLE_EINTR(read(fd, buffer, message_size));
    168     if (result < 0) {
    169       PLOG(ERROR) << "reading metrics message body";
    170       break;
    171     }
    172     if (result < message_size) {
    173       LOG(ERROR) << "message too short: length " << result <<
    174                     ", expected " << message_size;
    175       break;
    176     }
    177     // The buffer should now contain a pair of null-terminated strings.
    178     uint8* p = reinterpret_cast<uint8*>(memchr(buffer, '\0', message_size));
    179     uint8* q = NULL;
    180     if (p != NULL) {
    181       q = reinterpret_cast<uint8*>(
    182         memchr(p + 1, '\0', message_size - (p + 1 - buffer)));
    183     }
    184     if (q == NULL) {
    185       LOG(ERROR) << "bad name-value pair for metrics";
    186       break;
    187     } else {
    188       char* name = reinterpret_cast<char*>(buffer);
    189       char* value = reinterpret_cast<char*>(p + 1);
    190       if (test_recorder_ != NULL) {
    191         test_recorder_(name, value);
    192       } else if (strcmp(name, "crash") == 0) {
    193         RecordCrash(value);
    194       } else if (strcmp(name, "histogram") == 0) {
    195         RecordHistogram(value);
    196       } else if (strcmp(name, "linearhistogram") == 0) {
    197         RecordLinearHistogram(value);
    198       } else if (strcmp(name, "useraction") == 0) {
    199         RecordAction(value);
    200       } else {
    201         LOG(ERROR) << "invalid event type: " << name;
    202       }
    203     }
    204   }
    205 
    206   result = ftruncate(fd, 0);
    207   if (result < 0) {
    208     PLOG(ERROR) << "truncate metrics log";
    209   }
    210   result = flock(fd, LOCK_UN);
    211   if (result < 0) {
    212     PLOG(ERROR) << "unlock metrics log";
    213   }
    214   result = close(fd);
    215   if (result < 0) {
    216     PLOG(ERROR) << "close metrics log";
    217   }
    218 }
    219 
    220 void ExternalMetrics::CollectEventsAndReschedule() {
    221   PerfTimer timer;
    222   CollectEvents();
    223   UMA_HISTOGRAM_TIMES("UMA.CollectExternalEventsTime", timer.Elapsed());
    224   ScheduleCollector();
    225 }
    226 
    227 void ExternalMetrics::ScheduleCollector() {
    228   bool result;
    229   result = BrowserThread::PostDelayedTask(
    230     BrowserThread::FILE, FROM_HERE, NewRunnableMethod(
    231         this, &chromeos::ExternalMetrics::CollectEventsAndReschedule),
    232     kExternalMetricsCollectionIntervalMs);
    233   DCHECK(result);
    234 }
    235 
    236 }  // namespace chromeos
    237