blob: c80b07dbd5706dcd7bd01f47007546df6439d6b8 [file] [log] [blame]
[email protected]896220042010-03-23 18:14:281// 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
[email protected]5ccaa412009-11-13 22:00:163// 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/histogram.h"
19#include "base/time.h"
[email protected]7aadea02009-12-02 08:22:3020#include "chrome/browser/chrome_thread.h"
[email protected]5ccaa412009-11-13 22:00:1621#include "chrome/browser/metrics/user_metrics.h"
[email protected]5ccaa412009-11-13 22:00:1622
[email protected]29cf16772010-04-21 15:13:4723// Steps to add an action.
[email protected]6694943c2009-12-16 01:46:0724//
[email protected]29cf16772010-04-21 15:13:4725// 1. Enter a helper function that calls UserMetrics::RecordAction.
[email protected]6694943c2009-12-16 01:46:0726//
[email protected]29cf16772010-04-21 15:13:4727// 2. Add a line for that function in InitializeUserActions.
[email protected]6694943c2009-12-16 01:46:0728//
29// 3. Enjoy the recompilation.
[email protected]29cf16772010-04-21 15:13:4730//
31// TODO(semenzato): should see if it is possible to avoid recompiling code
32// every time a new user action is added, and register it in some other way.
[email protected]6694943c2009-12-16 01:46:0733
[email protected]5ccaa412009-11-13 22:00:1634namespace chromeos {
35
36// The interval between external metrics collections, in milliseconds.
37static const int kExternalMetricsCollectionIntervalMs = 30 * 1000;
38
[email protected]5ccaa412009-11-13 22:00:1639// There is one of the following functions for every user action as we have to
40// call RecordAction in a way that gets picked up by the processing scripts.
[email protected]29cf16772010-04-21 15:13:4741static void RecordTabOverviewKeystroke() {
42 UserMetrics::RecordAction(UserMetricsAction("TabOverview_Keystroke"));
[email protected]5ccaa412009-11-13 22:00:1643}
44
[email protected]29cf16772010-04-21 15:13:4745static void RecordTabOverviewExitMouse() {
46 UserMetrics::RecordAction(UserMetricsAction("TabOverview_ExitMouse"));
[email protected]5ccaa412009-11-13 22:00:1647}
48
[email protected]29cf16772010-04-21 15:13:4749void ExternalMetrics::Start() {
50 InitializeUserActions();
[email protected]5ccaa412009-11-13 22:00:1651 ScheduleCollector();
52}
53
[email protected]29cf16772010-04-21 15:13:4754void ExternalMetrics::DefineUserAction(const std::string& name,
55 RecordFunctionType f) {
56 DCHECK(action_recorders_.find(name) == action_recorders_.end());
57 action_recorders_[name] = f;
[email protected]5ccaa412009-11-13 22:00:1658}
59
[email protected]29cf16772010-04-21 15:13:4760void ExternalMetrics::InitializeUserActions() {
61 DefineUserAction("TabOverviewExitMouse", RecordTabOverviewExitMouse);
62 DefineUserAction("TabOverviewKeystroke", RecordTabOverviewKeystroke);
[email protected]5ccaa412009-11-13 22:00:1663}
64
[email protected]29cf16772010-04-21 15:13:4765void ExternalMetrics::RecordActionUI(std::string action_string) {
66 base::hash_map<std::string, RecordFunctionType>::const_iterator iterator;
67 iterator = action_recorders_.find(action_string);
68 if (iterator == action_recorders_.end()) {
69 LOG(ERROR) << "undefined UMA action: " << action_string;
[email protected]5ccaa412009-11-13 22:00:1670 } else {
[email protected]29cf16772010-04-21 15:13:4771 iterator->second();
[email protected]5ccaa412009-11-13 22:00:1672 }
73}
74
[email protected]29cf16772010-04-21 15:13:4775void ExternalMetrics::RecordAction(const char* action) {
76 std::string action_string(action);
77 ChromeThread::PostTask(
78 ChromeThread::UI, FROM_HERE,
79 NewRunnableMethod(this, &ExternalMetrics::RecordActionUI, action));
80}
81
82void ExternalMetrics::RecordHistogram(const char* histogram_data) {
83 int sample, min, max, nbuckets;
84 char name[128]; // length must be consistent with sscanf format below.
85 int n = sscanf(histogram_data, "%127s %d %d %d %d",
86 name, &sample, &min, &max, &nbuckets);
87 if (n != 5) {
88 LOG(ERROR) << "bad histogram request: " << histogram_data;
89 return;
90 }
[email protected]e37f2c02010-04-21 20:36:4791 // Do not use the UMA_HISTOGRAM_... macros here. They cache the Histogram
92 // instance and thus only work if |name| is constant.
93 scoped_refptr<Histogram> counter = Histogram::FactoryGet(
94 name, min, max, nbuckets, Histogram::kUmaTargetedHistogramFlag);
95 counter->Add(sample);
[email protected]29cf16772010-04-21 15:13:4796}
97
98void ExternalMetrics::RecordLinearHistogram(const char* histogram_data) {
99 int sample, max;
100 char name[128]; // length must be consistent with sscanf format below.
101 int n = sscanf(histogram_data, "%127s %d %d", name, &sample, &max);
102 if (n != 3) {
103 LOG(ERROR) << "bad linear histogram request: " << histogram_data;
104 return;
105 }
[email protected]e37f2c02010-04-21 20:36:47106 // Do not use the UMA_HISTOGRAM_... macros here. They cache the Histogram
107 // instance and thus only work if |name| is constant.
108 scoped_refptr<Histogram> counter = LinearHistogram::FactoryGet(
109 name, 1, max, max + 1, Histogram::kUmaTargetedHistogramFlag);
110 counter->Add(sample);
[email protected]29cf16772010-04-21 15:13:47111}
112
[email protected]5ccaa412009-11-13 22:00:16113void ExternalMetrics::CollectEvents() {
[email protected]29cf16772010-04-21 15:13:47114 const char* event_file_path = "/var/log/metrics/uma-events";
[email protected]5ccaa412009-11-13 22:00:16115 struct stat stat_buf;
116 int result;
[email protected]29cf16772010-04-21 15:13:47117 if (!test_path_.empty()) {
118 event_file_path = test_path_.value().c_str();
119 }
[email protected]5ccaa412009-11-13 22:00:16120 result = stat(event_file_path, &stat_buf);
121 if (result < 0) {
122 if (errno != ENOENT) {
[email protected]29cf16772010-04-21 15:13:47123 PLOG(ERROR) << event_file_path << ": bad metrics file stat";
[email protected]5ccaa412009-11-13 22:00:16124 }
125 // Nothing to collect---try later.
126 return;
127 }
128 if (stat_buf.st_size == 0) {
129 // Also nothing to collect.
130 return;
131 }
132 int fd = open(event_file_path, O_RDWR);
133 if (fd < 0) {
134 PLOG(ERROR) << event_file_path << ": cannot open";
135 return;
136 }
137 result = flock(fd, LOCK_EX);
138 if (result < 0) {
139 PLOG(ERROR) << event_file_path << ": cannot lock";
[email protected]29cf16772010-04-21 15:13:47140 close(fd);
[email protected]5ccaa412009-11-13 22:00:16141 return;
142 }
143 // This processes all messages in the log. Each message starts with a 4-byte
144 // field containing the length of the entire message. The length is followed
145 // by a name-value pair of null-terminated strings. When all messages are
146 // read and processed, or an error occurs, truncate the file to zero size.
147 for (;;) {
148 int32 message_size;
149 result = HANDLE_EINTR(read(fd, &message_size, sizeof(message_size)));
150 if (result < 0) {
151 PLOG(ERROR) << "reading metrics message header";
152 break;
153 }
154 if (result == 0) { // normal EOF
155 break;
156 }
157 if (result < static_cast<int>(sizeof(message_size))) {
158 LOG(ERROR) << "bad read size " << result <<
159 ", expecting " << sizeof(message_size);
160 break;
161 }
162 // kMetricsMessageMaxLength applies to the entire message: the 4-byte
163 // length field and the two null-terminated strings.
164 if (message_size < 2 + static_cast<int>(sizeof(message_size)) ||
165 message_size > static_cast<int>(kMetricsMessageMaxLength)) {
166 LOG(ERROR) << "bad message size " << message_size;
167 break;
168 }
169 message_size -= sizeof(message_size); // already read this much
170 uint8 buffer[kMetricsMessageMaxLength];
171 result = HANDLE_EINTR(read(fd, buffer, message_size));
172 if (result < 0) {
173 PLOG(ERROR) << "reading metrics message body";
174 break;
175 }
176 if (result < message_size) {
177 LOG(ERROR) << "message too short: length " << result <<
178 ", expected " << message_size;
179 break;
180 }
181 // The buffer should now contain a pair of null-terminated strings.
182 uint8* p = reinterpret_cast<uint8*>(memchr(buffer, '\0', message_size));
183 uint8* q = NULL;
184 if (p != NULL) {
185 q = reinterpret_cast<uint8*>(
186 memchr(p + 1, '\0', message_size - (p + 1 - buffer)));
187 }
188 if (q == NULL) {
189 LOG(ERROR) << "bad name-value pair for metrics";
190 break;
191 } else {
192 char* name = reinterpret_cast<char*>(buffer);
193 char* value = reinterpret_cast<char*>(p + 1);
[email protected]29cf16772010-04-21 15:13:47194 if (test_recorder_ != NULL) {
195 test_recorder_(name, value);
196 } else if (strcmp(name, "histogram") == 0) {
197 RecordHistogram(value);
198 } else if (strcmp(name, "linearhistogram") == 0) {
199 RecordLinearHistogram(value);
200 } else if (strcmp(name, "useraction") == 0) {
201 RecordAction(value);
202 } else {
203 LOG(ERROR) << "invalid event type: " << name;
204 }
[email protected]5ccaa412009-11-13 22:00:16205 }
206 }
207
208 result = ftruncate(fd, 0);
209 if (result < 0) {
210 PLOG(ERROR) << "truncate metrics log";
211 }
212 result = flock(fd, LOCK_UN);
213 if (result < 0) {
214 PLOG(ERROR) << "unlock metrics log";
215 }
216 result = close(fd);
217 if (result < 0) {
218 PLOG(ERROR) << "close metrics log";
219 }
220}
221
222void ExternalMetrics::CollectEventsAndReschedule() {
223 CollectEvents();
224 ScheduleCollector();
225}
226
227void ExternalMetrics::ScheduleCollector() {
228 bool result;
229 result = ChromeThread::PostDelayedTask(
230 ChromeThread::FILE, FROM_HERE, NewRunnableMethod(
[email protected]29cf16772010-04-21 15:13:47231 this, &chromeos::ExternalMetrics::CollectEventsAndReschedule),
[email protected]5ccaa412009-11-13 22:00:16232 kExternalMetricsCollectionIntervalMs);
233 DCHECK(result);
234}
235
236} // namespace chromeos