• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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