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
26namespace chromeos {
27
28// The interval between external metrics collections, in milliseconds.
29static const int kExternalMetricsCollectionIntervalMs = 30 * 1000;
30
31ExternalMetrics::ExternalMetrics()
32    : test_recorder_(NULL) {
33}
34
35void 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
51void 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
59void 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
66void 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
72void ExternalMetrics::RecordCrash(const std::string& crash_kind) {
73  BrowserThread::PostTask(
74      BrowserThread::UI, FROM_HERE,
75      NewRunnableMethod(this, &ExternalMetrics::RecordCrashUI, crash_kind));
76}
77
78void 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
94void 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
109void 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
220void ExternalMetrics::CollectEventsAndReschedule() {
221  PerfTimer timer;
222  CollectEvents();
223  UMA_HISTOGRAM_TIMES("UMA.CollectExternalEventsTime", timer.Elapsed());
224  ScheduleCollector();
225}
226
227void 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