Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / external_metrics.cc
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.
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 <sys/file.h>
12 #include <sys/stat.h>
13 #include <sys/types.h>
14 #include <unistd.h>
15
16 #include <map>
17 #include <string>
18
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"
34
35 using base::UserMetricsAction;
36 using content::BrowserThread;
37
38 namespace chromeos {
39
40 namespace {
41
42 bool CheckValues(const std::string& name,
43                  int minimum,
44                  int maximum,
45                  size_t bucket_count) {
46   if (!base::Histogram::InspectConstructionArguments(
47       name, &minimum, &maximum, &bucket_count))
48     return false;
49   base::HistogramBase* histogram =
50       base::StatisticsRecorder::FindHistogram(name);
51   if (!histogram)
52     return true;
53   return histogram->HasConstructionArguments(minimum, maximum, bucket_count);
54 }
55
56 bool CheckLinearValues(const std::string& name, int maximum) {
57   return CheckValues(name, 1, maximum, maximum + 1);
58 }
59
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);
71
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";
83
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];
90
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";
97   } else {
98     VLOG(1) << "Couldn't write to " << group_file_path.value();
99   }
100 }
101
102 }  // namespace
103
104 // The interval between external metrics collections in seconds
105 static const int kExternalMetricsCollectionIntervalSeconds = 30;
106
107 ExternalMetrics::ExternalMetrics() : test_recorder_(NULL) {}
108
109 ExternalMetrics::~ExternalMetrics() {}
110
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");
120
121   // Initialize here field trials that don't need to read from files.
122   // (None for the moment.)
123
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
127   // thread.
128   bool task_posted = BrowserThread::PostTask(
129       BrowserThread::FILE,
130       FROM_HERE,
131       base::Bind(&chromeos::ExternalMetrics::SetupFieldTrialsOnFileThread,
132                  this));
133   DCHECK(task_posted);
134 }
135
136 void ExternalMetrics::RecordActionUI(std::string action_string) {
137   if (valid_user_actions_.count(action_string)) {
138     content::RecordComputedAction(action_string);
139   } else {
140     DLOG(ERROR) << "undefined UMA action: " << action_string;
141   }
142 }
143
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));
149 }
150
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);
154   }
155 }
156
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));
161 }
162
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);
168   if (n != 5) {
169     DLOG(ERROR) << "bad histogram request: " << histogram_data;
170     return;
171   }
172
173   if (!CheckValues(name, min, max, nbuckets)) {
174     DLOG(ERROR) << "Invalid histogram " << name
175                 << ", min=" << min
176                 << ", max=" << max
177                 << ", nbuckets=" << nbuckets;
178     return;
179   }
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);
185 }
186
187 void ExternalMetrics::RecordLinearHistogram(const char* histogram_data) {
188   int sample, max;
189   char name[128];   // length must be consistent with sscanf format below.
190   int n = sscanf(histogram_data, "%127s %d %d", name, &sample, &max);
191   if (n != 3) {
192     DLOG(ERROR) << "bad linear histogram request: " << histogram_data;
193     return;
194   }
195
196   if (!CheckLinearValues(name, max)) {
197     DLOG(ERROR) << "Invalid linear histogram " << name
198                 << ", max=" << max;
199     return;
200   }
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);
206 }
207
208 void ExternalMetrics::RecordSparseHistogram(const char* histogram_data) {
209   int sample;
210   char name[128];   // length must be consistent with sscanf format below.
211   int n = sscanf(histogram_data, "%127s %d", name, &sample);
212   if (n != 2) {
213     DLOG(ERROR) << "bad sparse histogram request: " << histogram_data;
214     return;
215   }
216
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);
222 }
223
224 void ExternalMetrics::CollectEvents() {
225   const char* event_file_path = "/var/log/metrics/uma-events";
226   struct stat stat_buf;
227   int result;
228   if (!test_path_.empty()) {
229     event_file_path = test_path_.value().c_str();
230   }
231   result = stat(event_file_path, &stat_buf);
232   if (result < 0) {
233     if (errno != ENOENT) {
234       DPLOG(ERROR) << event_file_path << ": bad metrics file stat";
235     }
236     // Nothing to collect---try later.
237     return;
238   }
239   if (stat_buf.st_size == 0) {
240     // Also nothing to collect.
241     return;
242   }
243   int fd = open(event_file_path, O_RDWR);
244   if (fd < 0) {
245     DPLOG(ERROR) << event_file_path << ": cannot open";
246     return;
247   }
248   result = flock(fd, LOCK_EX);
249   if (result < 0) {
250     DPLOG(ERROR) << event_file_path << ": cannot lock";
251     close(fd);
252     return;
253   }
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.
258   for (;;) {
259     int32 message_size;
260     result = HANDLE_EINTR(read(fd, &message_size, sizeof(message_size)));
261     if (result < 0) {
262       DPLOG(ERROR) << "reading metrics message header";
263       break;
264     }
265     if (result == 0) {  // This indicates a normal EOF.
266       break;
267     }
268     if (result < static_cast<int>(sizeof(message_size))) {
269       DLOG(ERROR) << "bad read size " << result <<
270                      ", expecting " << sizeof(message_size);
271       break;
272     }
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;
278       break;
279     }
280     message_size -= sizeof(message_size);  // The message size includes itself.
281     uint8 buffer[kMetricsMessageMaxLength];
282     result = HANDLE_EINTR(read(fd, buffer, message_size));
283     if (result < 0) {
284       DPLOG(ERROR) << "reading metrics message body";
285       break;
286     }
287     if (result < message_size) {
288       DLOG(ERROR) << "message too short: length " << result <<
289                      ", expected " << message_size;
290       break;
291     }
292     // The buffer should now contain a pair of null-terminated strings.
293     uint8* p = reinterpret_cast<uint8*>(memchr(buffer, '\0', message_size));
294     uint8* q = NULL;
295     if (p != NULL) {
296       q = reinterpret_cast<uint8*>(
297           memchr(p + 1, '\0', message_size - (p + 1 - buffer)));
298     }
299     if (q == NULL) {
300       DLOG(ERROR) << "bad name-value pair for metrics";
301       break;
302     }
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) {
308       RecordCrash(value);
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) {
316       RecordAction(value);
317     } else {
318       DLOG(ERROR) << "invalid event type: " << name;
319     }
320   }
321
322   result = ftruncate(fd, 0);
323   if (result < 0) {
324     DPLOG(ERROR) << "truncate metrics log";
325   }
326   result = flock(fd, LOCK_UN);
327   if (result < 0) {
328     DPLOG(ERROR) << "unlock metrics log";
329   }
330   result = close(fd);
331   if (result < 0) {
332     DPLOG(ERROR) << "close metrics log";
333   }
334 }
335
336 void ExternalMetrics::CollectEventsAndReschedule() {
337   base::ElapsedTimer timer;
338   CollectEvents();
339   UMA_HISTOGRAM_TIMES("UMA.CollectExternalEventsTime", timer.Elapsed());
340   ScheduleCollector();
341 }
342
343 void ExternalMetrics::ScheduleCollector() {
344   bool result;
345   result = BrowserThread::PostDelayedTask(
346       BrowserThread::FILE, FROM_HERE,
347       base::Bind(&chromeos::ExternalMetrics::CollectEventsAndReschedule, this),
348       base::TimeDelta::FromSeconds(kExternalMetricsCollectionIntervalSeconds));
349   DCHECK(result);
350 }
351
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();
357
358   ScheduleCollector();
359 }
360
361 }  // namespace chromeos