[email protected] | df32e89c | 2012-05-17 17:47:34 | [diff] [blame] | 1 | // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
[email protected] | 89622004 | 2010-03-23 18:14:28 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 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> |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 11 | #include <sys/file.h> |
| 12 | #include <sys/stat.h> |
| 13 | #include <sys/types.h> |
[email protected] | c38831a1 | 2011-10-28 12:44:49 | [diff] [blame] | 14 | #include <unistd.h> |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 15 | |
[email protected] | d8e1cf9c | 2011-11-03 19:37:57 | [diff] [blame] | 16 | #include <string> |
| 17 | |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 18 | #include "base/basictypes.h" |
[email protected] | 7f6f44c | 2011-12-14 13:23:38 | [diff] [blame] | 19 | #include "base/bind.h" |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 20 | #include "base/eintr_wrapper.h" |
[email protected] | 835d7c8 | 2010-10-14 04:38:38 | [diff] [blame] | 21 | #include "base/metrics/histogram.h" |
[email protected] | cbf1444 | 2012-09-05 03:00:25 | [diff] [blame^] | 22 | #include "base/metrics/statistics_recorder.h" |
[email protected] | d2b6af67 | 2010-07-01 20:38:54 | [diff] [blame] | 23 | #include "base/perftimer.h" |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 24 | #include "base/time.h" |
[email protected] | c1834a9 | 2011-01-21 18:21:03 | [diff] [blame] | 25 | #include "chrome/browser/browser_process.h" |
[email protected] | c1834a9 | 2011-01-21 18:21:03 | [diff] [blame] | 26 | #include "chrome/browser/metrics/metrics_service.h" |
[email protected] | c38831a1 | 2011-10-28 12:44:49 | [diff] [blame] | 27 | #include "content/public/browser/browser_thread.h" |
[email protected] | 7f6f44c | 2011-12-14 13:23:38 | [diff] [blame] | 28 | #include "content/public/browser/user_metrics.h" |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 29 | |
[email protected] | 631bb74 | 2011-11-02 11:29:39 | [diff] [blame] | 30 | using content::BrowserThread; |
[email protected] | 7f6f44c | 2011-12-14 13:23:38 | [diff] [blame] | 31 | using content::UserMetricsAction; |
[email protected] | 631bb74 | 2011-11-02 11:29:39 | [diff] [blame] | 32 | |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 33 | namespace chromeos { |
| 34 | |
[email protected] | df32e89c | 2012-05-17 17:47:34 | [diff] [blame] | 35 | // The interval between external metrics collections in seconds |
| 36 | static const int kExternalMetricsCollectionIntervalSeconds = 30; |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 37 | |
[email protected] | e24a853 | 2012-08-03 07:18:44 | [diff] [blame] | 38 | class SystemHistogram : public base::Histogram { |
| 39 | public: |
| 40 | static bool CheckValues(const std::string& name, |
| 41 | int minimum, |
| 42 | int maximum, |
| 43 | size_t bucket_count) { |
| 44 | return base::Histogram::InspectConstructionArguments( |
| 45 | name, &minimum, &maximum, &bucket_count); |
| 46 | } |
[email protected] | cbf1444 | 2012-09-05 03:00:25 | [diff] [blame^] | 47 | static bool CheckLinearValues(const std::string& name, int maximum) { |
| 48 | if (!CheckValues(name, 1, maximum, maximum + 1)) |
| 49 | return false; |
| 50 | Histogram* histogram = base::StatisticsRecorder::FindHistogram(name); |
| 51 | if (!histogram) |
| 52 | return true; |
| 53 | return histogram->HasConstructionArguments(1, maximum, maximum + 1); |
| 54 | } |
[email protected] | e24a853 | 2012-08-03 07:18:44 | [diff] [blame] | 55 | }; |
| 56 | |
[email protected] | 196ff47 | 2011-01-21 19:25:27 | [diff] [blame] | 57 | ExternalMetrics::ExternalMetrics() |
| 58 | : test_recorder_(NULL) { |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 59 | } |
| 60 | |
[email protected] | 41baad0 | 2011-05-15 20:37:46 | [diff] [blame] | 61 | ExternalMetrics::~ExternalMetrics() {} |
| 62 | |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 63 | void ExternalMetrics::Start() { |
[email protected] | 196ff47 | 2011-01-21 19:25:27 | [diff] [blame] | 64 | // Register user actions external to the browser. |
| 65 | // chrome/tools/extract_actions.py won't understand these lines, so all of |
| 66 | // these are explicitly added in that script. |
| 67 | // TODO(derat): We shouldn't need to verify actions before reporting them; |
| 68 | // remove all of this once http://crosbug.com/11125 is fixed. |
[email protected] | 54e9f75 | 2011-09-19 22:38:14 | [diff] [blame] | 69 | valid_user_actions_.insert("Cryptohome.PKCS11InitFail"); |
[email protected] | 1078339c | 2011-09-16 18:30:01 | [diff] [blame] | 70 | valid_user_actions_.insert("Updater.ServerCertificateChanged"); |
| 71 | valid_user_actions_.insert("Updater.ServerCertificateFailed"); |
[email protected] | 196ff47 | 2011-01-21 19:25:27 | [diff] [blame] | 72 | |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 73 | ScheduleCollector(); |
| 74 | } |
| 75 | |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 76 | void ExternalMetrics::RecordActionUI(std::string action_string) { |
[email protected] | 196ff47 | 2011-01-21 19:25:27 | [diff] [blame] | 77 | if (valid_user_actions_.count(action_string)) { |
[email protected] | 7f6f44c | 2011-12-14 13:23:38 | [diff] [blame] | 78 | content::RecordComputedAction(action_string); |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 79 | } else { |
[email protected] | 196ff47 | 2011-01-21 19:25:27 | [diff] [blame] | 80 | LOG(ERROR) << "undefined UMA action: " << action_string; |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 81 | } |
| 82 | } |
| 83 | |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 84 | void ExternalMetrics::RecordAction(const char* action) { |
| 85 | std::string action_string(action); |
[email protected] | cca169b5 | 2010-10-08 22:15:55 | [diff] [blame] | 86 | BrowserThread::PostTask( |
| 87 | BrowserThread::UI, FROM_HERE, |
[email protected] | d8e1cf9c | 2011-11-03 19:37:57 | [diff] [blame] | 88 | base::Bind(&ExternalMetrics::RecordActionUI, this, action_string)); |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 89 | } |
| 90 | |
[email protected] | c1834a9 | 2011-01-21 18:21:03 | [diff] [blame] | 91 | void ExternalMetrics::RecordCrashUI(const std::string& crash_kind) { |
| 92 | if (g_browser_process && g_browser_process->metrics_service()) { |
| 93 | g_browser_process->metrics_service()->LogChromeOSCrash(crash_kind); |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | void ExternalMetrics::RecordCrash(const std::string& crash_kind) { |
| 98 | BrowserThread::PostTask( |
| 99 | BrowserThread::UI, FROM_HERE, |
[email protected] | d8e1cf9c | 2011-11-03 19:37:57 | [diff] [blame] | 100 | base::Bind(&ExternalMetrics::RecordCrashUI, this, crash_kind)); |
[email protected] | c1834a9 | 2011-01-21 18:21:03 | [diff] [blame] | 101 | } |
| 102 | |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 103 | void ExternalMetrics::RecordHistogram(const char* histogram_data) { |
| 104 | int sample, min, max, nbuckets; |
| 105 | char name[128]; // length must be consistent with sscanf format below. |
| 106 | int n = sscanf(histogram_data, "%127s %d %d %d %d", |
| 107 | name, &sample, &min, &max, &nbuckets); |
| 108 | if (n != 5) { |
| 109 | LOG(ERROR) << "bad histogram request: " << histogram_data; |
| 110 | return; |
| 111 | } |
[email protected] | e24a853 | 2012-08-03 07:18:44 | [diff] [blame] | 112 | |
| 113 | if (!SystemHistogram::CheckValues(name, min, max, nbuckets)) { |
| 114 | LOG(ERROR) << "Invalid histogram " << name |
| 115 | << ", min=" << min |
| 116 | << ", max=" << max |
| 117 | << ", nbuckets=" << nbuckets; |
| 118 | return; |
| 119 | } |
[email protected] | e37f2c0 | 2010-04-21 20:36:47 | [diff] [blame] | 120 | // Do not use the UMA_HISTOGRAM_... macros here. They cache the Histogram |
| 121 | // instance and thus only work if |name| is constant. |
[email protected] | 81ce9f3b | 2011-04-05 04:48:53 | [diff] [blame] | 122 | base::Histogram* counter = base::Histogram::FactoryGet( |
[email protected] | 835d7c8 | 2010-10-14 04:38:38 | [diff] [blame] | 123 | name, min, max, nbuckets, base::Histogram::kUmaTargetedHistogramFlag); |
[email protected] | e37f2c0 | 2010-04-21 20:36:47 | [diff] [blame] | 124 | counter->Add(sample); |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 125 | } |
| 126 | |
| 127 | void ExternalMetrics::RecordLinearHistogram(const char* histogram_data) { |
| 128 | int sample, max; |
| 129 | char name[128]; // length must be consistent with sscanf format below. |
| 130 | int n = sscanf(histogram_data, "%127s %d %d", name, &sample, &max); |
| 131 | if (n != 3) { |
| 132 | LOG(ERROR) << "bad linear histogram request: " << histogram_data; |
| 133 | return; |
| 134 | } |
[email protected] | d3cc7016 | 2012-08-24 00:50:05 | [diff] [blame] | 135 | |
[email protected] | cbf1444 | 2012-09-05 03:00:25 | [diff] [blame^] | 136 | if (!SystemHistogram::CheckLinearValues(name, max)) { |
[email protected] | d3cc7016 | 2012-08-24 00:50:05 | [diff] [blame] | 137 | LOG(ERROR) << "Invalid linear histogram " << name |
| 138 | << ", max=" << max; |
| 139 | return; |
| 140 | } |
[email protected] | e37f2c0 | 2010-04-21 20:36:47 | [diff] [blame] | 141 | // Do not use the UMA_HISTOGRAM_... macros here. They cache the Histogram |
| 142 | // instance and thus only work if |name| is constant. |
[email protected] | 81ce9f3b | 2011-04-05 04:48:53 | [diff] [blame] | 143 | base::Histogram* counter = base::LinearHistogram::FactoryGet( |
[email protected] | 835d7c8 | 2010-10-14 04:38:38 | [diff] [blame] | 144 | name, 1, max, max + 1, base::Histogram::kUmaTargetedHistogramFlag); |
[email protected] | e37f2c0 | 2010-04-21 20:36:47 | [diff] [blame] | 145 | counter->Add(sample); |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 146 | } |
| 147 | |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 148 | void ExternalMetrics::CollectEvents() { |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 149 | const char* event_file_path = "/var/log/metrics/uma-events"; |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 150 | struct stat stat_buf; |
| 151 | int result; |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 152 | if (!test_path_.empty()) { |
| 153 | event_file_path = test_path_.value().c_str(); |
| 154 | } |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 155 | result = stat(event_file_path, &stat_buf); |
| 156 | if (result < 0) { |
| 157 | if (errno != ENOENT) { |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 158 | PLOG(ERROR) << event_file_path << ": bad metrics file stat"; |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 159 | } |
| 160 | // Nothing to collect---try later. |
| 161 | return; |
| 162 | } |
| 163 | if (stat_buf.st_size == 0) { |
| 164 | // Also nothing to collect. |
| 165 | return; |
| 166 | } |
| 167 | int fd = open(event_file_path, O_RDWR); |
| 168 | if (fd < 0) { |
| 169 | PLOG(ERROR) << event_file_path << ": cannot open"; |
| 170 | return; |
| 171 | } |
| 172 | result = flock(fd, LOCK_EX); |
| 173 | if (result < 0) { |
| 174 | PLOG(ERROR) << event_file_path << ": cannot lock"; |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 175 | close(fd); |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 176 | return; |
| 177 | } |
| 178 | // This processes all messages in the log. Each message starts with a 4-byte |
| 179 | // field containing the length of the entire message. The length is followed |
| 180 | // by a name-value pair of null-terminated strings. When all messages are |
| 181 | // read and processed, or an error occurs, truncate the file to zero size. |
| 182 | for (;;) { |
| 183 | int32 message_size; |
| 184 | result = HANDLE_EINTR(read(fd, &message_size, sizeof(message_size))); |
| 185 | if (result < 0) { |
| 186 | PLOG(ERROR) << "reading metrics message header"; |
| 187 | break; |
| 188 | } |
| 189 | if (result == 0) { // normal EOF |
| 190 | break; |
| 191 | } |
| 192 | if (result < static_cast<int>(sizeof(message_size))) { |
| 193 | LOG(ERROR) << "bad read size " << result << |
| 194 | ", expecting " << sizeof(message_size); |
| 195 | break; |
| 196 | } |
| 197 | // kMetricsMessageMaxLength applies to the entire message: the 4-byte |
| 198 | // length field and the two null-terminated strings. |
| 199 | if (message_size < 2 + static_cast<int>(sizeof(message_size)) || |
| 200 | message_size > static_cast<int>(kMetricsMessageMaxLength)) { |
| 201 | LOG(ERROR) << "bad message size " << message_size; |
| 202 | break; |
| 203 | } |
| 204 | message_size -= sizeof(message_size); // already read this much |
| 205 | uint8 buffer[kMetricsMessageMaxLength]; |
| 206 | result = HANDLE_EINTR(read(fd, buffer, message_size)); |
| 207 | if (result < 0) { |
| 208 | PLOG(ERROR) << "reading metrics message body"; |
| 209 | break; |
| 210 | } |
| 211 | if (result < message_size) { |
| 212 | LOG(ERROR) << "message too short: length " << result << |
| 213 | ", expected " << message_size; |
| 214 | break; |
| 215 | } |
| 216 | // The buffer should now contain a pair of null-terminated strings. |
| 217 | uint8* p = reinterpret_cast<uint8*>(memchr(buffer, '\0', message_size)); |
| 218 | uint8* q = NULL; |
| 219 | if (p != NULL) { |
| 220 | q = reinterpret_cast<uint8*>( |
| 221 | memchr(p + 1, '\0', message_size - (p + 1 - buffer))); |
| 222 | } |
| 223 | if (q == NULL) { |
| 224 | LOG(ERROR) << "bad name-value pair for metrics"; |
| 225 | break; |
| 226 | } else { |
| 227 | char* name = reinterpret_cast<char*>(buffer); |
| 228 | char* value = reinterpret_cast<char*>(p + 1); |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 229 | if (test_recorder_ != NULL) { |
| 230 | test_recorder_(name, value); |
[email protected] | c1834a9 | 2011-01-21 18:21:03 | [diff] [blame] | 231 | } else if (strcmp(name, "crash") == 0) { |
| 232 | RecordCrash(value); |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 233 | } else if (strcmp(name, "histogram") == 0) { |
| 234 | RecordHistogram(value); |
| 235 | } else if (strcmp(name, "linearhistogram") == 0) { |
| 236 | RecordLinearHistogram(value); |
| 237 | } else if (strcmp(name, "useraction") == 0) { |
| 238 | RecordAction(value); |
| 239 | } else { |
| 240 | LOG(ERROR) << "invalid event type: " << name; |
| 241 | } |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 242 | } |
| 243 | } |
| 244 | |
| 245 | result = ftruncate(fd, 0); |
| 246 | if (result < 0) { |
| 247 | PLOG(ERROR) << "truncate metrics log"; |
| 248 | } |
| 249 | result = flock(fd, LOCK_UN); |
| 250 | if (result < 0) { |
| 251 | PLOG(ERROR) << "unlock metrics log"; |
| 252 | } |
| 253 | result = close(fd); |
| 254 | if (result < 0) { |
| 255 | PLOG(ERROR) << "close metrics log"; |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | void ExternalMetrics::CollectEventsAndReschedule() { |
[email protected] | d2b6af67 | 2010-07-01 20:38:54 | [diff] [blame] | 260 | PerfTimer timer; |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 261 | CollectEvents(); |
[email protected] | d2b6af67 | 2010-07-01 20:38:54 | [diff] [blame] | 262 | UMA_HISTOGRAM_TIMES("UMA.CollectExternalEventsTime", timer.Elapsed()); |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 263 | ScheduleCollector(); |
| 264 | } |
| 265 | |
| 266 | void ExternalMetrics::ScheduleCollector() { |
| 267 | bool result; |
[email protected] | cca169b5 | 2010-10-08 22:15:55 | [diff] [blame] | 268 | result = BrowserThread::PostDelayedTask( |
[email protected] | d8e1cf9c | 2011-11-03 19:37:57 | [diff] [blame] | 269 | BrowserThread::FILE, FROM_HERE, |
| 270 | base::Bind(&chromeos::ExternalMetrics::CollectEventsAndReschedule, this), |
[email protected] | df32e89c | 2012-05-17 17:47:34 | [diff] [blame] | 271 | base::TimeDelta::FromSeconds(kExternalMetricsCollectionIntervalSeconds)); |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 272 | DCHECK(result); |
| 273 | } |
| 274 | |
| 275 | } // namespace chromeos |