external_metrics.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
1// Copyright (c) 2010 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_thread.h"
22#include "chrome/browser/metrics/user_metrics.h"
23
24// Steps to add an action.
25//
26// 1. Enter a helper function that calls UserMetrics::RecordAction.
27//
28// 2. Add a line for that function in InitializeUserActions.
29//
30// 3. Enjoy the recompilation.
31//
32// TODO(semenzato): should see if it is possible to avoid recompiling code
33// every time a new user action is added, and register it in some other way.
34
35namespace chromeos {
36
37// The interval between external metrics collections, in milliseconds.
38static const int kExternalMetricsCollectionIntervalMs = 30 * 1000;
39
40// There is one of the following functions for every user action as we have to
41// call RecordAction in a way that gets picked up by the processing scripts.
42static void RecordTabOverviewKeystroke() {
43  UserMetrics::RecordAction(UserMetricsAction("TabOverview_Keystroke"));
44}
45
46static void RecordTabOverviewExitMouse() {
47  UserMetrics::RecordAction(UserMetricsAction("TabOverview_ExitMouse"));
48}
49
50void ExternalMetrics::Start() {
51  InitializeUserActions();
52  ScheduleCollector();
53}
54
55void ExternalMetrics::DefineUserAction(const std::string& name,
56                                       RecordFunctionType f) {
57  DCHECK(action_recorders_.find(name) == action_recorders_.end());
58  action_recorders_[name] = f;
59}
60
61void ExternalMetrics::InitializeUserActions() {
62  DefineUserAction("TabOverviewExitMouse", RecordTabOverviewExitMouse);
63  DefineUserAction("TabOverviewKeystroke", RecordTabOverviewKeystroke);
64}
65
66void ExternalMetrics::RecordActionUI(std::string action_string) {
67  base::hash_map<std::string, RecordFunctionType>::const_iterator iterator;
68  iterator = action_recorders_.find(action_string);
69  if (iterator == action_recorders_.end()) {
70    LOG(ERROR) << "undefined UMA action: " << action_string;
71  } else {
72    iterator->second();
73  }
74}
75
76void ExternalMetrics::RecordAction(const char* action) {
77  std::string action_string(action);
78  BrowserThread::PostTask(
79      BrowserThread::UI, FROM_HERE,
80      NewRunnableMethod(this, &ExternalMetrics::RecordActionUI, action));
81}
82
83void ExternalMetrics::RecordHistogram(const char* histogram_data) {
84  int sample, min, max, nbuckets;
85  char name[128];   // length must be consistent with sscanf format below.
86  int n = sscanf(histogram_data, "%127s %d %d %d %d",
87                 name, &sample, &min, &max, &nbuckets);
88  if (n != 5) {
89    LOG(ERROR) << "bad histogram request: " << histogram_data;
90    return;
91  }
92  // Do not use the UMA_HISTOGRAM_... macros here.  They cache the Histogram
93  // instance and thus only work if |name| is constant.
94  scoped_refptr<base::Histogram> counter = base::Histogram::FactoryGet(
95      name, min, max, nbuckets, base::Histogram::kUmaTargetedHistogramFlag);
96  counter->Add(sample);
97}
98
99void ExternalMetrics::RecordLinearHistogram(const char* histogram_data) {
100  int sample, max;
101  char name[128];   // length must be consistent with sscanf format below.
102  int n = sscanf(histogram_data, "%127s %d %d", name, &sample, &max);
103  if (n != 3) {
104    LOG(ERROR) << "bad linear histogram request: " << histogram_data;
105    return;
106  }
107  // Do not use the UMA_HISTOGRAM_... macros here.  They cache the Histogram
108  // instance and thus only work if |name| is constant.
109  scoped_refptr<base::Histogram> counter = base::LinearHistogram::FactoryGet(
110      name, 1, max, max + 1, base::Histogram::kUmaTargetedHistogramFlag);
111  counter->Add(sample);
112}
113
114void ExternalMetrics::CollectEvents() {
115  const char* event_file_path = "/var/log/metrics/uma-events";
116  struct stat stat_buf;
117  int result;
118  if (!test_path_.empty()) {
119    event_file_path = test_path_.value().c_str();
120  }
121  result = stat(event_file_path, &stat_buf);
122  if (result < 0) {
123    if (errno != ENOENT) {
124      PLOG(ERROR) << event_file_path << ": bad metrics file stat";
125    }
126    // Nothing to collect---try later.
127    return;
128  }
129  if (stat_buf.st_size == 0) {
130    // Also nothing to collect.
131    return;
132  }
133  int fd = open(event_file_path, O_RDWR);
134  if (fd < 0) {
135    PLOG(ERROR) << event_file_path << ": cannot open";
136    return;
137  }
138  result = flock(fd, LOCK_EX);
139  if (result < 0) {
140    PLOG(ERROR) << event_file_path << ": cannot lock";
141    close(fd);
142    return;
143  }
144  // This processes all messages in the log.  Each message starts with a 4-byte
145  // field containing the length of the entire message.  The length is followed
146  // by a name-value pair of null-terminated strings.  When all messages are
147  // read and processed, or an error occurs, truncate the file to zero size.
148  for (;;) {
149    int32 message_size;
150    result = HANDLE_EINTR(read(fd, &message_size, sizeof(message_size)));
151    if (result < 0) {
152      PLOG(ERROR) << "reading metrics message header";
153      break;
154    }
155    if (result == 0) {  // normal EOF
156      break;
157    }
158    if (result < static_cast<int>(sizeof(message_size))) {
159      LOG(ERROR) << "bad read size " << result <<
160                    ", expecting " << sizeof(message_size);
161      break;
162    }
163    // kMetricsMessageMaxLength applies to the entire message: the 4-byte
164    // length field and the two null-terminated strings.
165    if (message_size < 2 + static_cast<int>(sizeof(message_size)) ||
166        message_size > static_cast<int>(kMetricsMessageMaxLength)) {
167      LOG(ERROR) << "bad message size " << message_size;
168      break;
169    }
170    message_size -= sizeof(message_size);  // already read this much
171    uint8 buffer[kMetricsMessageMaxLength];
172    result = HANDLE_EINTR(read(fd, buffer, message_size));
173    if (result < 0) {
174      PLOG(ERROR) << "reading metrics message body";
175      break;
176    }
177    if (result < message_size) {
178      LOG(ERROR) << "message too short: length " << result <<
179                    ", expected " << message_size;
180      break;
181    }
182    // The buffer should now contain a pair of null-terminated strings.
183    uint8* p = reinterpret_cast<uint8*>(memchr(buffer, '\0', message_size));
184    uint8* q = NULL;
185    if (p != NULL) {
186      q = reinterpret_cast<uint8*>(
187        memchr(p + 1, '\0', message_size - (p + 1 - buffer)));
188    }
189    if (q == NULL) {
190      LOG(ERROR) << "bad name-value pair for metrics";
191      break;
192    } else {
193      char* name = reinterpret_cast<char*>(buffer);
194      char* value = reinterpret_cast<char*>(p + 1);
195      if (test_recorder_ != NULL) {
196        test_recorder_(name, value);
197      } else if (strcmp(name, "histogram") == 0) {
198        RecordHistogram(value);
199      } else if (strcmp(name, "linearhistogram") == 0) {
200        RecordLinearHistogram(value);
201      } else if (strcmp(name, "useraction") == 0) {
202        RecordAction(value);
203      } else {
204        LOG(ERROR) << "invalid event type: " << name;
205      }
206    }
207  }
208
209  result = ftruncate(fd, 0);
210  if (result < 0) {
211    PLOG(ERROR) << "truncate metrics log";
212  }
213  result = flock(fd, LOCK_UN);
214  if (result < 0) {
215    PLOG(ERROR) << "unlock metrics log";
216  }
217  result = close(fd);
218  if (result < 0) {
219    PLOG(ERROR) << "close metrics log";
220  }
221}
222
223void ExternalMetrics::CollectEventsAndReschedule() {
224  PerfTimer timer;
225  CollectEvents();
226  UMA_HISTOGRAM_TIMES("UMA.CollectExternalEventsTime", timer.Elapsed());
227  ScheduleCollector();
228}
229
230void ExternalMetrics::ScheduleCollector() {
231  bool result;
232  result = BrowserThread::PostDelayedTask(
233    BrowserThread::FILE, FROM_HERE, NewRunnableMethod(
234        this, &chromeos::ExternalMetrics::CollectEventsAndReschedule),
235    kExternalMetricsCollectionIntervalMs);
236  DCHECK(result);
237}
238
239}  // namespace chromeos
240