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