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
ExternalMetrics()31 ExternalMetrics::ExternalMetrics()
32 : test_recorder_(NULL) {
33 }
34
Start()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
RecordActionUI(std::string action_string)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
RecordAction(const char * action)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
RecordCrashUI(const std::string & crash_kind)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
RecordCrash(const std::string & crash_kind)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
RecordHistogram(const char * histogram_data)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
RecordLinearHistogram(const char * histogram_data)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
CollectEvents()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
CollectEventsAndReschedule()220 void ExternalMetrics::CollectEventsAndReschedule() {
221 PerfTimer timer;
222 CollectEvents();
223 UMA_HISTOGRAM_TIMES("UMA.CollectExternalEventsTime", timer.Elapsed());
224 ScheduleCollector();
225 }
226
ScheduleCollector()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