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 <map> 17 #include <string> 18 19 #include "base/basictypes.h" 20 #include "base/bind.h" 21 #include "base/file_util.h" 22 #include "base/files/file_path.h" 23 #include "base/metrics/histogram.h" 24 #include "base/metrics/sparse_histogram.h" 25 #include "base/metrics/statistics_recorder.h" 26 #include "base/posix/eintr_wrapper.h" 27 #include "base/sys_info.h" 28 #include "base/time/time.h" 29 #include "base/timer/elapsed_timer.h" 30 #include "chrome/browser/browser_process.h" 31 #include "chrome/browser/metrics/metrics_service.h" 32 #include "content/public/browser/browser_thread.h" 33 #include "content/public/browser/user_metrics.h" 34 35 using content::BrowserThread; 36 using content::UserMetricsAction; 37 38 namespace chromeos { 39 40 namespace { 41 42 bool CheckValues(const std::string& name, 43 int minimum, 44 int maximum, 45 size_t bucket_count) { 46 if (!base::Histogram::InspectConstructionArguments( 47 name, &minimum, &maximum, &bucket_count)) 48 return false; 49 base::HistogramBase* histogram = 50 base::StatisticsRecorder::FindHistogram(name); 51 if (!histogram) 52 return true; 53 return histogram->HasConstructionArguments(minimum, maximum, bucket_count); 54 } 55 56 bool CheckLinearValues(const std::string& name, int maximum) { 57 return CheckValues(name, 1, maximum, maximum + 1); 58 } 59 60 // Establishes field trial for wifi scanning in chromeos. crbug.com/242733. 61 void SetupProgressiveScanFieldTrial() { 62 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 63 const char name_of_experiment[] = "ProgressiveScan"; 64 const base::FilePath group_file_path( 65 "/home/chronos/.progressive_scan_variation"); 66 const base::FieldTrial::Probability kDivisor = 1000; 67 scoped_refptr<base::FieldTrial> trial = 68 base::FieldTrialList::FactoryGetFieldTrial( 69 name_of_experiment, kDivisor, "Default", 2013, 12, 31, 70 base::FieldTrial::SESSION_RANDOMIZED, NULL); 71 72 // Announce the groups with 0 percentage; the actual percentages come from 73 // the server configuration. 74 std::map<int, std::string> group_to_char; 75 group_to_char[trial->AppendGroup("FullScan", 0)] = "c"; 76 group_to_char[trial->AppendGroup("33Percent_4MinMax", 0)] = "1"; 77 group_to_char[trial->AppendGroup("50Percent_4MinMax", 0)] = "2"; 78 group_to_char[trial->AppendGroup("50Percent_8MinMax", 0)] = "3"; 79 group_to_char[trial->AppendGroup("100Percent_8MinMax", 0)] = "4"; 80 group_to_char[trial->AppendGroup("100Percent_1MinSeen_A", 0)] = "5"; 81 group_to_char[trial->AppendGroup("100Percent_1MinSeen_B", 0)] = "6"; 82 group_to_char[trial->AppendGroup("100Percent_1Min_4Max", 0)] = "7"; 83 84 // Announce the experiment to any listeners (especially important is the UMA 85 // software, which will append the group names to UMA statistics). 86 const int group_num = trial->group(); 87 std::string group_char = "x"; 88 if (ContainsKey(group_to_char, group_num)) 89 group_char = group_to_char[group_num]; 90 91 // Write the group to the file to be read by ChromeOS. 92 int size = static_cast<int>(group_char.length()); 93 if (file_util::WriteFile(group_file_path, group_char.c_str(), size) == size) { 94 VLOG(1) << "Configured in group '" << trial->group_name() 95 << "' ('" << group_char << "') for " 96 << name_of_experiment << " field trial"; 97 } else { 98 LOG(ERROR) << "Couldn't write to " << group_file_path.value(); 99 } 100 } 101 102 } // namespace 103 104 // The interval between external metrics collections in seconds 105 static const int kExternalMetricsCollectionIntervalSeconds = 30; 106 107 ExternalMetrics::ExternalMetrics() : test_recorder_(NULL) {} 108 109 ExternalMetrics::~ExternalMetrics() {} 110 111 void ExternalMetrics::Start() { 112 // Register user actions external to the browser. 113 // tools/metrics/actions/extract_actions.py won't understand these lines, so 114 // all of these are explicitly added in that script. 115 // TODO(derat): We shouldn't need to verify actions before reporting them; 116 // remove all of this once http://crosbug.com/11125 is fixed. 117 valid_user_actions_.insert("Cryptohome.PKCS11InitFail"); 118 valid_user_actions_.insert("Updater.ServerCertificateChanged"); 119 valid_user_actions_.insert("Updater.ServerCertificateFailed"); 120 121 // Initialize here field trials that don't need to read from files. 122 // (None for the moment.) 123 124 // Initialize any chromeos field trials that need to read from a file (e.g., 125 // those that have an upstart script determine their experimental group for 126 // them) then schedule the data collection. All of this is done on the file 127 // thread. 128 bool task_posted = BrowserThread::PostTask( 129 BrowserThread::FILE, 130 FROM_HERE, 131 base::Bind(&chromeos::ExternalMetrics::SetupFieldTrialsOnFileThread, 132 this)); 133 DCHECK(task_posted); 134 } 135 136 void ExternalMetrics::RecordActionUI(std::string action_string) { 137 if (valid_user_actions_.count(action_string)) { 138 content::RecordComputedAction(action_string); 139 } else { 140 DLOG(ERROR) << "undefined UMA action: " << action_string; 141 } 142 } 143 144 void ExternalMetrics::RecordAction(const char* action) { 145 std::string action_string(action); 146 BrowserThread::PostTask( 147 BrowserThread::UI, FROM_HERE, 148 base::Bind(&ExternalMetrics::RecordActionUI, this, action_string)); 149 } 150 151 void ExternalMetrics::RecordCrashUI(const std::string& crash_kind) { 152 if (g_browser_process && g_browser_process->metrics_service()) { 153 g_browser_process->metrics_service()->LogChromeOSCrash(crash_kind); 154 } 155 } 156 157 void ExternalMetrics::RecordCrash(const std::string& crash_kind) { 158 BrowserThread::PostTask( 159 BrowserThread::UI, FROM_HERE, 160 base::Bind(&ExternalMetrics::RecordCrashUI, this, crash_kind)); 161 } 162 163 void ExternalMetrics::RecordHistogram(const char* histogram_data) { 164 int sample, min, max, nbuckets; 165 char name[128]; // length must be consistent with sscanf format below. 166 int n = sscanf(histogram_data, "%127s %d %d %d %d", 167 name, &sample, &min, &max, &nbuckets); 168 if (n != 5) { 169 DLOG(ERROR) << "bad histogram request: " << histogram_data; 170 return; 171 } 172 173 if (!CheckValues(name, min, max, nbuckets)) { 174 DLOG(ERROR) << "Invalid histogram " << name 175 << ", min=" << min 176 << ", max=" << max 177 << ", nbuckets=" << nbuckets; 178 return; 179 } 180 // Do not use the UMA_HISTOGRAM_... macros here. They cache the Histogram 181 // instance and thus only work if |name| is constant. 182 base::HistogramBase* counter = base::Histogram::FactoryGet( 183 name, min, max, nbuckets, base::Histogram::kUmaTargetedHistogramFlag); 184 counter->Add(sample); 185 } 186 187 void ExternalMetrics::RecordLinearHistogram(const char* histogram_data) { 188 int sample, max; 189 char name[128]; // length must be consistent with sscanf format below. 190 int n = sscanf(histogram_data, "%127s %d %d", name, &sample, &max); 191 if (n != 3) { 192 DLOG(ERROR) << "bad linear histogram request: " << histogram_data; 193 return; 194 } 195 196 if (!CheckLinearValues(name, max)) { 197 DLOG(ERROR) << "Invalid linear histogram " << name 198 << ", max=" << max; 199 return; 200 } 201 // Do not use the UMA_HISTOGRAM_... macros here. They cache the Histogram 202 // instance and thus only work if |name| is constant. 203 base::HistogramBase* counter = base::LinearHistogram::FactoryGet( 204 name, 1, max, max + 1, base::Histogram::kUmaTargetedHistogramFlag); 205 counter->Add(sample); 206 } 207 208 void ExternalMetrics::RecordSparseHistogram(const char* histogram_data) { 209 int sample; 210 char name[128]; // length must be consistent with sscanf format below. 211 int n = sscanf(histogram_data, "%127s %d", name, &sample); 212 if (n != 2) { 213 DLOG(ERROR) << "bad sparse histogram request: " << histogram_data; 214 return; 215 } 216 217 // Do not use the UMA_HISTOGRAM_... macros here. They cache the Histogram 218 // instance and thus only work if |name| is constant. 219 base::HistogramBase* counter = base::SparseHistogram::FactoryGet( 220 name, base::HistogramBase::kUmaTargetedHistogramFlag); 221 counter->Add(sample); 222 } 223 224 void ExternalMetrics::CollectEvents() { 225 const char* event_file_path = "/var/log/metrics/uma-events"; 226 struct stat stat_buf; 227 int result; 228 if (!test_path_.empty()) { 229 event_file_path = test_path_.value().c_str(); 230 } 231 result = stat(event_file_path, &stat_buf); 232 if (result < 0) { 233 if (errno != ENOENT) { 234 DPLOG(ERROR) << event_file_path << ": bad metrics file stat"; 235 } 236 // Nothing to collect---try later. 237 return; 238 } 239 if (stat_buf.st_size == 0) { 240 // Also nothing to collect. 241 return; 242 } 243 int fd = open(event_file_path, O_RDWR); 244 if (fd < 0) { 245 DPLOG(ERROR) << event_file_path << ": cannot open"; 246 return; 247 } 248 result = flock(fd, LOCK_EX); 249 if (result < 0) { 250 DPLOG(ERROR) << event_file_path << ": cannot lock"; 251 close(fd); 252 return; 253 } 254 // This processes all messages in the log. Each message starts with a 4-byte 255 // field containing the length of the entire message. The length is followed 256 // by a name-value pair of null-terminated strings. When all messages are 257 // read and processed, or an error occurs, truncate the file to zero size. 258 for (;;) { 259 int32 message_size; 260 result = HANDLE_EINTR(read(fd, &message_size, sizeof(message_size))); 261 if (result < 0) { 262 DPLOG(ERROR) << "reading metrics message header"; 263 break; 264 } 265 if (result == 0) { // This indicates a normal EOF. 266 break; 267 } 268 if (result < static_cast<int>(sizeof(message_size))) { 269 DLOG(ERROR) << "bad read size " << result << 270 ", expecting " << sizeof(message_size); 271 break; 272 } 273 // kMetricsMessageMaxLength applies to the entire message: the 4-byte 274 // length field and the two null-terminated strings. 275 if (message_size < 2 + static_cast<int>(sizeof(message_size)) || 276 message_size > static_cast<int>(kMetricsMessageMaxLength)) { 277 DLOG(ERROR) << "bad message size " << message_size; 278 break; 279 } 280 message_size -= sizeof(message_size); // The message size includes itself. 281 uint8 buffer[kMetricsMessageMaxLength]; 282 result = HANDLE_EINTR(read(fd, buffer, message_size)); 283 if (result < 0) { 284 DPLOG(ERROR) << "reading metrics message body"; 285 break; 286 } 287 if (result < message_size) { 288 DLOG(ERROR) << "message too short: length " << result << 289 ", expected " << message_size; 290 break; 291 } 292 // The buffer should now contain a pair of null-terminated strings. 293 uint8* p = reinterpret_cast<uint8*>(memchr(buffer, '\0', message_size)); 294 uint8* q = NULL; 295 if (p != NULL) { 296 q = reinterpret_cast<uint8*>( 297 memchr(p + 1, '\0', message_size - (p + 1 - buffer))); 298 } 299 if (q == NULL) { 300 DLOG(ERROR) << "bad name-value pair for metrics"; 301 break; 302 } 303 char* name = reinterpret_cast<char*>(buffer); 304 char* value = reinterpret_cast<char*>(p + 1); 305 if (test_recorder_ != NULL) { 306 test_recorder_(name, value); 307 } else if (strcmp(name, "crash") == 0) { 308 RecordCrash(value); 309 } else if (strcmp(name, "histogram") == 0) { 310 RecordHistogram(value); 311 } else if (strcmp(name, "linearhistogram") == 0) { 312 RecordLinearHistogram(value); 313 } else if (strcmp(name, "sparsehistogram") == 0) { 314 RecordSparseHistogram(value); 315 } else if (strcmp(name, "useraction") == 0) { 316 RecordAction(value); 317 } else { 318 DLOG(ERROR) << "invalid event type: " << name; 319 } 320 } 321 322 result = ftruncate(fd, 0); 323 if (result < 0) { 324 DPLOG(ERROR) << "truncate metrics log"; 325 } 326 result = flock(fd, LOCK_UN); 327 if (result < 0) { 328 DPLOG(ERROR) << "unlock metrics log"; 329 } 330 result = close(fd); 331 if (result < 0) { 332 DPLOG(ERROR) << "close metrics log"; 333 } 334 } 335 336 void ExternalMetrics::CollectEventsAndReschedule() { 337 base::ElapsedTimer timer; 338 CollectEvents(); 339 UMA_HISTOGRAM_TIMES("UMA.CollectExternalEventsTime", timer.Elapsed()); 340 ScheduleCollector(); 341 } 342 343 void ExternalMetrics::ScheduleCollector() { 344 bool result; 345 result = BrowserThread::PostDelayedTask( 346 BrowserThread::FILE, FROM_HERE, 347 base::Bind(&chromeos::ExternalMetrics::CollectEventsAndReschedule, this), 348 base::TimeDelta::FromSeconds(kExternalMetricsCollectionIntervalSeconds)); 349 DCHECK(result); 350 } 351 352 void ExternalMetrics::SetupFieldTrialsOnFileThread() { 353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 354 // Field trials that do not read from files can be initialized in 355 // ExternalMetrics::Start() above. 356 SetupProgressiveScanFieldTrial(); 357 358 ScheduleCollector(); 359 } 360 361 } // namespace chromeos 362