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