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 (base::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