1 // Copyright (c) 2012 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.
5 #include "chrome/browser/chromeos/external_metrics.h"
13 #include <sys/types.h>
19 #include "base/basictypes.h"
20 #include "base/bind.h"
21 #include "base/file_util.h"
22 #include "base/files/file_path.h"
23 #include "base/metrics/histogram.h"
24 #include "base/metrics/sparse_histogram.h"
25 #include "base/metrics/statistics_recorder.h"
26 #include "base/posix/eintr_wrapper.h"
27 #include "base/sys_info.h"
28 #include "base/time/time.h"
29 #include "base/timer/elapsed_timer.h"
30 #include "chrome/browser/browser_process.h"
31 #include "chrome/browser/metrics/metrics_service.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "content/public/browser/user_metrics.h"
35 using base::UserMetricsAction;
36 using content::BrowserThread;
42 bool CheckValues(const std::string& name,
45 size_t bucket_count) {
46 if (!base::Histogram::InspectConstructionArguments(
47 name, &minimum, &maximum, &bucket_count))
49 base::HistogramBase* histogram =
50 base::StatisticsRecorder::FindHistogram(name);
53 return histogram->HasConstructionArguments(minimum, maximum, bucket_count);
56 bool CheckLinearValues(const std::string& name, int maximum) {
57 return CheckValues(name, 1, maximum, maximum + 1);
60 // Establishes field trial for wifi scanning in chromeos. crbug.com/242733.
61 void SetupProgressiveScanFieldTrial() {
62 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
63 const char name_of_experiment[] = "ProgressiveScan";
64 const base::FilePath group_file_path(
65 "/home/chronos/.progressive_scan_variation");
66 const base::FieldTrial::Probability kDivisor = 1000;
67 scoped_refptr<base::FieldTrial> trial =
68 base::FieldTrialList::FactoryGetFieldTrial(
69 name_of_experiment, kDivisor, "Default", 2013, 12, 31,
70 base::FieldTrial::SESSION_RANDOMIZED, NULL);
72 // Announce the groups with 0 percentage; the actual percentages come from
73 // the server configuration.
74 std::map<int, std::string> group_to_char;
75 group_to_char[trial->AppendGroup("FullScan", 0)] = "c";
76 group_to_char[trial->AppendGroup("33Percent_4MinMax", 0)] = "1";
77 group_to_char[trial->AppendGroup("50Percent_4MinMax", 0)] = "2";
78 group_to_char[trial->AppendGroup("50Percent_8MinMax", 0)] = "3";
79 group_to_char[trial->AppendGroup("100Percent_8MinMax", 0)] = "4";
80 group_to_char[trial->AppendGroup("100Percent_1MinSeen_A", 0)] = "5";
81 group_to_char[trial->AppendGroup("100Percent_1MinSeen_B", 0)] = "6";
82 group_to_char[trial->AppendGroup("100Percent_1Min_4Max", 0)] = "7";
84 // Announce the experiment to any listeners (especially important is the UMA
85 // software, which will append the group names to UMA statistics).
86 const int group_num = trial->group();
87 std::string group_char = "x";
88 if (ContainsKey(group_to_char, group_num))
89 group_char = group_to_char[group_num];
91 // Write the group to the file to be read by ChromeOS.
92 int size = static_cast<int>(group_char.length());
93 if (file_util::WriteFile(group_file_path, group_char.c_str(), size) == size) {
94 VLOG(1) << "Configured in group '" << trial->group_name()
95 << "' ('" << group_char << "') for "
96 << name_of_experiment << " field trial";
98 VLOG(1) << "Couldn't write to " << group_file_path.value();
104 // The interval between external metrics collections in seconds
105 static const int kExternalMetricsCollectionIntervalSeconds = 30;
107 ExternalMetrics::ExternalMetrics() : test_recorder_(NULL) {}
109 ExternalMetrics::~ExternalMetrics() {}
111 void ExternalMetrics::Start() {
112 // Register user actions external to the browser.
113 // tools/metrics/actions/extract_actions.py won't understand these lines, so
114 // all of these are explicitly added in that script.
115 // TODO(derat): We shouldn't need to verify actions before reporting them;
116 // remove all of this once http://crosbug.com/11125 is fixed.
117 valid_user_actions_.insert("Cryptohome.PKCS11InitFail");
118 valid_user_actions_.insert("Updater.ServerCertificateChanged");
119 valid_user_actions_.insert("Updater.ServerCertificateFailed");
121 // Initialize here field trials that don't need to read from files.
122 // (None for the moment.)
124 // Initialize any chromeos field trials that need to read from a file (e.g.,
125 // those that have an upstart script determine their experimental group for
126 // them) then schedule the data collection. All of this is done on the file
128 bool task_posted = BrowserThread::PostTask(
131 base::Bind(&chromeos::ExternalMetrics::SetupFieldTrialsOnFileThread,
136 void ExternalMetrics::RecordActionUI(std::string action_string) {
137 if (valid_user_actions_.count(action_string)) {
138 content::RecordComputedAction(action_string);
140 DLOG(ERROR) << "undefined UMA action: " << action_string;
144 void ExternalMetrics::RecordAction(const char* action) {
145 std::string action_string(action);
146 BrowserThread::PostTask(
147 BrowserThread::UI, FROM_HERE,
148 base::Bind(&ExternalMetrics::RecordActionUI, this, action_string));
151 void ExternalMetrics::RecordCrashUI(const std::string& crash_kind) {
152 if (g_browser_process && g_browser_process->metrics_service()) {
153 g_browser_process->metrics_service()->LogChromeOSCrash(crash_kind);
157 void ExternalMetrics::RecordCrash(const std::string& crash_kind) {
158 BrowserThread::PostTask(
159 BrowserThread::UI, FROM_HERE,
160 base::Bind(&ExternalMetrics::RecordCrashUI, this, crash_kind));
163 void ExternalMetrics::RecordHistogram(const char* histogram_data) {
164 int sample, min, max, nbuckets;
165 char name[128]; // length must be consistent with sscanf format below.
166 int n = sscanf(histogram_data, "%127s %d %d %d %d",
167 name, &sample, &min, &max, &nbuckets);
169 DLOG(ERROR) << "bad histogram request: " << histogram_data;
173 if (!CheckValues(name, min, max, nbuckets)) {
174 DLOG(ERROR) << "Invalid histogram " << name
177 << ", nbuckets=" << nbuckets;
180 // Do not use the UMA_HISTOGRAM_... macros here. They cache the Histogram
181 // instance and thus only work if |name| is constant.
182 base::HistogramBase* counter = base::Histogram::FactoryGet(
183 name, min, max, nbuckets, base::Histogram::kUmaTargetedHistogramFlag);
184 counter->Add(sample);
187 void ExternalMetrics::RecordLinearHistogram(const char* histogram_data) {
189 char name[128]; // length must be consistent with sscanf format below.
190 int n = sscanf(histogram_data, "%127s %d %d", name, &sample, &max);
192 DLOG(ERROR) << "bad linear histogram request: " << histogram_data;
196 if (!CheckLinearValues(name, max)) {
197 DLOG(ERROR) << "Invalid linear histogram " << name
201 // Do not use the UMA_HISTOGRAM_... macros here. They cache the Histogram
202 // instance and thus only work if |name| is constant.
203 base::HistogramBase* counter = base::LinearHistogram::FactoryGet(
204 name, 1, max, max + 1, base::Histogram::kUmaTargetedHistogramFlag);
205 counter->Add(sample);
208 void ExternalMetrics::RecordSparseHistogram(const char* histogram_data) {
210 char name[128]; // length must be consistent with sscanf format below.
211 int n = sscanf(histogram_data, "%127s %d", name, &sample);
213 DLOG(ERROR) << "bad sparse histogram request: " << histogram_data;
217 // Do not use the UMA_HISTOGRAM_... macros here. They cache the Histogram
218 // instance and thus only work if |name| is constant.
219 base::HistogramBase* counter = base::SparseHistogram::FactoryGet(
220 name, base::HistogramBase::kUmaTargetedHistogramFlag);
221 counter->Add(sample);
224 void ExternalMetrics::CollectEvents() {
225 const char* event_file_path = "/var/log/metrics/uma-events";
226 struct stat stat_buf;
228 if (!test_path_.empty()) {
229 event_file_path = test_path_.value().c_str();
231 result = stat(event_file_path, &stat_buf);
233 if (errno != ENOENT) {
234 DPLOG(ERROR) << event_file_path << ": bad metrics file stat";
236 // Nothing to collect---try later.
239 if (stat_buf.st_size == 0) {
240 // Also nothing to collect.
243 int fd = open(event_file_path, O_RDWR);
245 DPLOG(ERROR) << event_file_path << ": cannot open";
248 result = flock(fd, LOCK_EX);
250 DPLOG(ERROR) << event_file_path << ": cannot lock";
254 // This processes all messages in the log. Each message starts with a 4-byte
255 // field containing the length of the entire message. The length is followed
256 // by a name-value pair of null-terminated strings. When all messages are
257 // read and processed, or an error occurs, truncate the file to zero size.
260 result = HANDLE_EINTR(read(fd, &message_size, sizeof(message_size)));
262 DPLOG(ERROR) << "reading metrics message header";
265 if (result == 0) { // This indicates a normal EOF.
268 if (result < static_cast<int>(sizeof(message_size))) {
269 DLOG(ERROR) << "bad read size " << result <<
270 ", expecting " << sizeof(message_size);
273 // kMetricsMessageMaxLength applies to the entire message: the 4-byte
274 // length field and the two null-terminated strings.
275 if (message_size < 2 + static_cast<int>(sizeof(message_size)) ||
276 message_size > static_cast<int>(kMetricsMessageMaxLength)) {
277 DLOG(ERROR) << "bad message size " << message_size;
280 message_size -= sizeof(message_size); // The message size includes itself.
281 uint8 buffer[kMetricsMessageMaxLength];
282 result = HANDLE_EINTR(read(fd, buffer, message_size));
284 DPLOG(ERROR) << "reading metrics message body";
287 if (result < message_size) {
288 DLOG(ERROR) << "message too short: length " << result <<
289 ", expected " << message_size;
292 // The buffer should now contain a pair of null-terminated strings.
293 uint8* p = reinterpret_cast<uint8*>(memchr(buffer, '\0', message_size));
296 q = reinterpret_cast<uint8*>(
297 memchr(p + 1, '\0', message_size - (p + 1 - buffer)));
300 DLOG(ERROR) << "bad name-value pair for metrics";
303 char* name = reinterpret_cast<char*>(buffer);
304 char* value = reinterpret_cast<char*>(p + 1);
305 if (test_recorder_ != NULL) {
306 test_recorder_(name, value);
307 } else if (strcmp(name, "crash") == 0) {
309 } else if (strcmp(name, "histogram") == 0) {
310 RecordHistogram(value);
311 } else if (strcmp(name, "linearhistogram") == 0) {
312 RecordLinearHistogram(value);
313 } else if (strcmp(name, "sparsehistogram") == 0) {
314 RecordSparseHistogram(value);
315 } else if (strcmp(name, "useraction") == 0) {
318 DLOG(ERROR) << "invalid event type: " << name;
322 result = ftruncate(fd, 0);
324 DPLOG(ERROR) << "truncate metrics log";
326 result = flock(fd, LOCK_UN);
328 DPLOG(ERROR) << "unlock metrics log";
332 DPLOG(ERROR) << "close metrics log";
336 void ExternalMetrics::CollectEventsAndReschedule() {
337 base::ElapsedTimer timer;
339 UMA_HISTOGRAM_TIMES("UMA.CollectExternalEventsTime", timer.Elapsed());
343 void ExternalMetrics::ScheduleCollector() {
345 result = BrowserThread::PostDelayedTask(
346 BrowserThread::FILE, FROM_HERE,
347 base::Bind(&chromeos::ExternalMetrics::CollectEventsAndReschedule, this),
348 base::TimeDelta::FromSeconds(kExternalMetricsCollectionIntervalSeconds));
352 void ExternalMetrics::SetupFieldTrialsOnFileThread() {
353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
354 // Field trials that do not read from files can be initialized in
355 // ExternalMetrics::Start() above.
356 SetupProgressiveScanFieldTrial();
361 } // namespace chromeos