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