[email protected] | 89622004 | 2010-03-23 18:14:28 | [diff] [blame] | 1 | // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 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> |
| 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/histogram.h" |
| 19 | #include "base/time.h" |
[email protected] | 7aadea0 | 2009-12-02 08:22:30 | [diff] [blame] | 20 | #include "chrome/browser/chrome_thread.h" |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 21 | #include "chrome/browser/metrics/user_metrics.h" |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 22 | |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 23 | // Steps to add an action. |
[email protected] | 6694943c | 2009-12-16 01:46:07 | [diff] [blame] | 24 | // |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 25 | // 1. Enter a helper function that calls UserMetrics::RecordAction. |
[email protected] | 6694943c | 2009-12-16 01:46:07 | [diff] [blame] | 26 | // |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 27 | // 2. Add a line for that function in InitializeUserActions. |
[email protected] | 6694943c | 2009-12-16 01:46:07 | [diff] [blame] | 28 | // |
| 29 | // 3. Enjoy the recompilation. |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 30 | // |
| 31 | // TODO(semenzato): should see if it is possible to avoid recompiling code |
| 32 | // every time a new user action is added, and register it in some other way. |
[email protected] | 6694943c | 2009-12-16 01:46:07 | [diff] [blame] | 33 | |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 34 | namespace chromeos { |
| 35 | |
| 36 | // The interval between external metrics collections, in milliseconds. |
| 37 | static const int kExternalMetricsCollectionIntervalMs = 30 * 1000; |
| 38 | |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 39 | // There is one of the following functions for every user action as we have to |
| 40 | // call RecordAction in a way that gets picked up by the processing scripts. |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 41 | static void RecordTabOverviewKeystroke() { |
| 42 | UserMetrics::RecordAction(UserMetricsAction("TabOverview_Keystroke")); |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 43 | } |
| 44 | |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 45 | static void RecordTabOverviewExitMouse() { |
| 46 | UserMetrics::RecordAction(UserMetricsAction("TabOverview_ExitMouse")); |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 47 | } |
| 48 | |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 49 | void ExternalMetrics::Start() { |
| 50 | InitializeUserActions(); |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 51 | ScheduleCollector(); |
| 52 | } |
| 53 | |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 54 | void ExternalMetrics::DefineUserAction(const std::string& name, |
| 55 | RecordFunctionType f) { |
| 56 | DCHECK(action_recorders_.find(name) == action_recorders_.end()); |
| 57 | action_recorders_[name] = f; |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 58 | } |
| 59 | |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 60 | void ExternalMetrics::InitializeUserActions() { |
| 61 | DefineUserAction("TabOverviewExitMouse", RecordTabOverviewExitMouse); |
| 62 | DefineUserAction("TabOverviewKeystroke", RecordTabOverviewKeystroke); |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 63 | } |
| 64 | |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 65 | void ExternalMetrics::RecordActionUI(std::string action_string) { |
| 66 | base::hash_map<std::string, RecordFunctionType>::const_iterator iterator; |
| 67 | iterator = action_recorders_.find(action_string); |
| 68 | if (iterator == action_recorders_.end()) { |
| 69 | LOG(ERROR) << "undefined UMA action: " << action_string; |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 70 | } else { |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 71 | iterator->second(); |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 72 | } |
| 73 | } |
| 74 | |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 75 | void ExternalMetrics::RecordAction(const char* action) { |
| 76 | std::string action_string(action); |
| 77 | ChromeThread::PostTask( |
| 78 | ChromeThread::UI, FROM_HERE, |
| 79 | NewRunnableMethod(this, &ExternalMetrics::RecordActionUI, action)); |
| 80 | } |
| 81 | |
| 82 | void ExternalMetrics::RecordHistogram(const char* histogram_data) { |
| 83 | int sample, min, max, nbuckets; |
| 84 | char name[128]; // length must be consistent with sscanf format below. |
| 85 | int n = sscanf(histogram_data, "%127s %d %d %d %d", |
| 86 | name, &sample, &min, &max, &nbuckets); |
| 87 | if (n != 5) { |
| 88 | LOG(ERROR) << "bad histogram request: " << histogram_data; |
| 89 | return; |
| 90 | } |
[email protected] | e37f2c0 | 2010-04-21 20:36:47 | [diff] [blame^] | 91 | // Do not use the UMA_HISTOGRAM_... macros here. They cache the Histogram |
| 92 | // instance and thus only work if |name| is constant. |
| 93 | scoped_refptr<Histogram> counter = Histogram::FactoryGet( |
| 94 | name, min, max, nbuckets, Histogram::kUmaTargetedHistogramFlag); |
| 95 | counter->Add(sample); |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 96 | } |
| 97 | |
| 98 | void ExternalMetrics::RecordLinearHistogram(const char* histogram_data) { |
| 99 | int sample, max; |
| 100 | char name[128]; // length must be consistent with sscanf format below. |
| 101 | int n = sscanf(histogram_data, "%127s %d %d", name, &sample, &max); |
| 102 | if (n != 3) { |
| 103 | LOG(ERROR) << "bad linear histogram request: " << histogram_data; |
| 104 | return; |
| 105 | } |
[email protected] | e37f2c0 | 2010-04-21 20:36:47 | [diff] [blame^] | 106 | // Do not use the UMA_HISTOGRAM_... macros here. They cache the Histogram |
| 107 | // instance and thus only work if |name| is constant. |
| 108 | scoped_refptr<Histogram> counter = LinearHistogram::FactoryGet( |
| 109 | name, 1, max, max + 1, Histogram::kUmaTargetedHistogramFlag); |
| 110 | counter->Add(sample); |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 111 | } |
| 112 | |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 113 | void ExternalMetrics::CollectEvents() { |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 114 | const char* event_file_path = "/var/log/metrics/uma-events"; |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 115 | struct stat stat_buf; |
| 116 | int result; |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 117 | if (!test_path_.empty()) { |
| 118 | event_file_path = test_path_.value().c_str(); |
| 119 | } |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 120 | result = stat(event_file_path, &stat_buf); |
| 121 | if (result < 0) { |
| 122 | if (errno != ENOENT) { |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 123 | PLOG(ERROR) << event_file_path << ": bad metrics file stat"; |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 124 | } |
| 125 | // Nothing to collect---try later. |
| 126 | return; |
| 127 | } |
| 128 | if (stat_buf.st_size == 0) { |
| 129 | // Also nothing to collect. |
| 130 | return; |
| 131 | } |
| 132 | int fd = open(event_file_path, O_RDWR); |
| 133 | if (fd < 0) { |
| 134 | PLOG(ERROR) << event_file_path << ": cannot open"; |
| 135 | return; |
| 136 | } |
| 137 | result = flock(fd, LOCK_EX); |
| 138 | if (result < 0) { |
| 139 | PLOG(ERROR) << event_file_path << ": cannot lock"; |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 140 | close(fd); |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 141 | return; |
| 142 | } |
| 143 | // This processes all messages in the log. Each message starts with a 4-byte |
| 144 | // field containing the length of the entire message. The length is followed |
| 145 | // by a name-value pair of null-terminated strings. When all messages are |
| 146 | // read and processed, or an error occurs, truncate the file to zero size. |
| 147 | for (;;) { |
| 148 | int32 message_size; |
| 149 | result = HANDLE_EINTR(read(fd, &message_size, sizeof(message_size))); |
| 150 | if (result < 0) { |
| 151 | PLOG(ERROR) << "reading metrics message header"; |
| 152 | break; |
| 153 | } |
| 154 | if (result == 0) { // normal EOF |
| 155 | break; |
| 156 | } |
| 157 | if (result < static_cast<int>(sizeof(message_size))) { |
| 158 | LOG(ERROR) << "bad read size " << result << |
| 159 | ", expecting " << sizeof(message_size); |
| 160 | break; |
| 161 | } |
| 162 | // kMetricsMessageMaxLength applies to the entire message: the 4-byte |
| 163 | // length field and the two null-terminated strings. |
| 164 | if (message_size < 2 + static_cast<int>(sizeof(message_size)) || |
| 165 | message_size > static_cast<int>(kMetricsMessageMaxLength)) { |
| 166 | LOG(ERROR) << "bad message size " << message_size; |
| 167 | break; |
| 168 | } |
| 169 | message_size -= sizeof(message_size); // already read this much |
| 170 | uint8 buffer[kMetricsMessageMaxLength]; |
| 171 | result = HANDLE_EINTR(read(fd, buffer, message_size)); |
| 172 | if (result < 0) { |
| 173 | PLOG(ERROR) << "reading metrics message body"; |
| 174 | break; |
| 175 | } |
| 176 | if (result < message_size) { |
| 177 | LOG(ERROR) << "message too short: length " << result << |
| 178 | ", expected " << message_size; |
| 179 | break; |
| 180 | } |
| 181 | // The buffer should now contain a pair of null-terminated strings. |
| 182 | uint8* p = reinterpret_cast<uint8*>(memchr(buffer, '\0', message_size)); |
| 183 | uint8* q = NULL; |
| 184 | if (p != NULL) { |
| 185 | q = reinterpret_cast<uint8*>( |
| 186 | memchr(p + 1, '\0', message_size - (p + 1 - buffer))); |
| 187 | } |
| 188 | if (q == NULL) { |
| 189 | LOG(ERROR) << "bad name-value pair for metrics"; |
| 190 | break; |
| 191 | } else { |
| 192 | char* name = reinterpret_cast<char*>(buffer); |
| 193 | char* value = reinterpret_cast<char*>(p + 1); |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 194 | if (test_recorder_ != NULL) { |
| 195 | test_recorder_(name, value); |
| 196 | } else if (strcmp(name, "histogram") == 0) { |
| 197 | RecordHistogram(value); |
| 198 | } else if (strcmp(name, "linearhistogram") == 0) { |
| 199 | RecordLinearHistogram(value); |
| 200 | } else if (strcmp(name, "useraction") == 0) { |
| 201 | RecordAction(value); |
| 202 | } else { |
| 203 | LOG(ERROR) << "invalid event type: " << name; |
| 204 | } |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 205 | } |
| 206 | } |
| 207 | |
| 208 | result = ftruncate(fd, 0); |
| 209 | if (result < 0) { |
| 210 | PLOG(ERROR) << "truncate metrics log"; |
| 211 | } |
| 212 | result = flock(fd, LOCK_UN); |
| 213 | if (result < 0) { |
| 214 | PLOG(ERROR) << "unlock metrics log"; |
| 215 | } |
| 216 | result = close(fd); |
| 217 | if (result < 0) { |
| 218 | PLOG(ERROR) << "close metrics log"; |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | void ExternalMetrics::CollectEventsAndReschedule() { |
| 223 | CollectEvents(); |
| 224 | ScheduleCollector(); |
| 225 | } |
| 226 | |
| 227 | void ExternalMetrics::ScheduleCollector() { |
| 228 | bool result; |
| 229 | result = ChromeThread::PostDelayedTask( |
| 230 | ChromeThread::FILE, FROM_HERE, NewRunnableMethod( |
[email protected] | 29cf1677 | 2010-04-21 15:13:47 | [diff] [blame] | 231 | this, &chromeos::ExternalMetrics::CollectEventsAndReschedule), |
[email protected] | 5ccaa41 | 2009-11-13 22:00:16 | [diff] [blame] | 232 | kExternalMetricsCollectionIntervalMs); |
| 233 | DCHECK(result); |
| 234 | } |
| 235 | |
| 236 | } // namespace chromeos |