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 <map> 8 #include <string> 9 10 #include "base/bind.h" 11 #include "base/files/file_path.h" 12 #include "base/files/file_util.h" 13 #include "base/metrics/field_trial.h" 14 #include "base/metrics/histogram.h" 15 #include "base/metrics/sparse_histogram.h" 16 #include "base/metrics/statistics_recorder.h" 17 #include "base/timer/elapsed_timer.h" 18 #include "chrome/browser/browser_process.h" 19 #include "chrome/browser/metrics/chromeos_metrics_provider.h" 20 #include "components/metrics/metrics_service.h" 21 #include "components/metrics/serialization/metric_sample.h" 22 #include "components/metrics/serialization/serialization_utils.h" 23 #include "content/public/browser/browser_thread.h" 24 #include "content/public/browser/user_metrics.h" 25 26 using base::UserMetricsAction; 27 using content::BrowserThread; 28 29 namespace chromeos { 30 31 namespace { 32 33 bool CheckValues(const std::string& name, 34 int minimum, 35 int maximum, 36 size_t bucket_count) { 37 if (!base::Histogram::InspectConstructionArguments( 38 name, &minimum, &maximum, &bucket_count)) 39 return false; 40 base::HistogramBase* histogram = 41 base::StatisticsRecorder::FindHistogram(name); 42 if (!histogram) 43 return true; 44 return histogram->HasConstructionArguments(minimum, maximum, bucket_count); 45 } 46 47 bool CheckLinearValues(const std::string& name, int maximum) { 48 return CheckValues(name, 1, maximum, maximum + 1); 49 } 50 51 // Establishes field trial for wifi scanning in chromeos. crbug.com/242733. 52 void SetupProgressiveScanFieldTrial() { 53 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 54 const char name_of_experiment[] = "ProgressiveScan"; 55 const base::FilePath group_file_path( 56 "/home/chronos/.progressive_scan_variation"); 57 const base::FieldTrial::Probability kDivisor = 1000; 58 scoped_refptr<base::FieldTrial> trial = 59 base::FieldTrialList::FactoryGetFieldTrial( 60 name_of_experiment, kDivisor, "Default", 2013, 12, 31, 61 base::FieldTrial::SESSION_RANDOMIZED, NULL); 62 63 // Announce the groups with 0 percentage; the actual percentages come from 64 // the server configuration. 65 std::map<int, std::string> group_to_char; 66 group_to_char[trial->AppendGroup("FullScan", 0)] = "c"; 67 group_to_char[trial->AppendGroup("33Percent_4MinMax", 0)] = "1"; 68 group_to_char[trial->AppendGroup("50Percent_4MinMax", 0)] = "2"; 69 group_to_char[trial->AppendGroup("50Percent_8MinMax", 0)] = "3"; 70 group_to_char[trial->AppendGroup("100Percent_8MinMax", 0)] = "4"; 71 group_to_char[trial->AppendGroup("100Percent_1MinSeen_A", 0)] = "5"; 72 group_to_char[trial->AppendGroup("100Percent_1MinSeen_B", 0)] = "6"; 73 group_to_char[trial->AppendGroup("100Percent_1Min_4Max", 0)] = "7"; 74 75 // Announce the experiment to any listeners (especially important is the UMA 76 // software, which will append the group names to UMA statistics). 77 const int group_num = trial->group(); 78 std::string group_char = "x"; 79 if (ContainsKey(group_to_char, group_num)) 80 group_char = group_to_char[group_num]; 81 82 // Write the group to the file to be read by ChromeOS. 83 int size = static_cast<int>(group_char.length()); 84 if (base::WriteFile(group_file_path, group_char.c_str(), size) == size) { 85 VLOG(1) << "Configured in group '" << trial->group_name() 86 << "' ('" << group_char << "') for " 87 << name_of_experiment << " field trial"; 88 } else { 89 VLOG(1) << "Couldn't write to " << group_file_path.value(); 90 } 91 } 92 93 } // namespace 94 95 // The interval between external metrics collections in seconds 96 static const int kExternalMetricsCollectionIntervalSeconds = 30; 97 const char kEventsFilePath[] = "/var/run/metrics/uma-events"; 98 99 ExternalMetrics::ExternalMetrics() : uma_events_file_(kEventsFilePath) { 100 } 101 102 ExternalMetrics::~ExternalMetrics() {} 103 104 void ExternalMetrics::Start() { 105 // Register user actions external to the browser. 106 // tools/metrics/actions/extract_actions.py won't understand these lines, so 107 // all of these are explicitly added in that script. 108 // TODO(derat): We shouldn't need to verify actions before reporting them; 109 // remove all of this once http://crosbug.com/11125 is fixed. 110 valid_user_actions_.insert("Cryptohome.PKCS11InitFail"); 111 valid_user_actions_.insert("Updater.ServerCertificateChanged"); 112 valid_user_actions_.insert("Updater.ServerCertificateFailed"); 113 114 // Initialize here field trials that don't need to read from files. 115 // (None for the moment.) 116 117 // Initialize any chromeos field trials that need to read from a file (e.g., 118 // those that have an upstart script determine their experimental group for 119 // them) then schedule the data collection. All of this is done on the file 120 // thread. 121 bool task_posted = BrowserThread::PostTask( 122 BrowserThread::FILE, 123 FROM_HERE, 124 base::Bind(&chromeos::ExternalMetrics::SetupFieldTrialsOnFileThread, 125 this)); 126 DCHECK(task_posted); 127 } 128 129 // static 130 scoped_refptr<ExternalMetrics> ExternalMetrics::CreateForTesting( 131 const std::string& filename) { 132 scoped_refptr<ExternalMetrics> external_metrics(new ExternalMetrics()); 133 external_metrics->uma_events_file_ = filename; 134 return external_metrics; 135 } 136 137 void ExternalMetrics::RecordActionUI(std::string action_string) { 138 if (valid_user_actions_.count(action_string)) { 139 content::RecordComputedAction(action_string); 140 } else { 141 DLOG(ERROR) << "undefined UMA action: " << action_string; 142 } 143 } 144 145 void ExternalMetrics::RecordAction(const std::string& action) { 146 BrowserThread::PostTask( 147 BrowserThread::UI, 148 FROM_HERE, 149 base::Bind(&ExternalMetrics::RecordActionUI, this, action)); 150 } 151 152 void ExternalMetrics::RecordCrashUI(const std::string& crash_kind) { 153 ChromeOSMetricsProvider::LogCrash(crash_kind); 154 } 155 156 void ExternalMetrics::RecordCrash(const std::string& crash_kind) { 157 BrowserThread::PostTask( 158 BrowserThread::UI, FROM_HERE, 159 base::Bind(&ExternalMetrics::RecordCrashUI, this, crash_kind)); 160 } 161 162 void ExternalMetrics::RecordHistogram(const metrics::MetricSample& sample) { 163 CHECK_EQ(metrics::MetricSample::HISTOGRAM, sample.type()); 164 if (!CheckValues( 165 sample.name(), sample.min(), sample.max(), sample.bucket_count())) { 166 DLOG(ERROR) << "Invalid histogram: " << sample.name(); 167 return; 168 } 169 170 base::HistogramBase* counter = 171 base::Histogram::FactoryGet(sample.name(), 172 sample.min(), 173 sample.max(), 174 sample.bucket_count(), 175 base::Histogram::kUmaTargetedHistogramFlag); 176 counter->Add(sample.sample()); 177 } 178 179 void ExternalMetrics::RecordLinearHistogram( 180 const metrics::MetricSample& sample) { 181 CHECK_EQ(metrics::MetricSample::LINEAR_HISTOGRAM, sample.type()); 182 if (!CheckLinearValues(sample.name(), sample.max())) { 183 DLOG(ERROR) << "Invalid linear histogram: " << sample.name(); 184 return; 185 } 186 base::HistogramBase* counter = base::LinearHistogram::FactoryGet( 187 sample.name(), 188 1, 189 sample.max(), 190 sample.max() + 1, 191 base::Histogram::kUmaTargetedHistogramFlag); 192 counter->Add(sample.sample()); 193 } 194 195 void ExternalMetrics::RecordSparseHistogram( 196 const metrics::MetricSample& sample) { 197 CHECK_EQ(metrics::MetricSample::SPARSE_HISTOGRAM, sample.type()); 198 base::HistogramBase* counter = base::SparseHistogram::FactoryGet( 199 sample.name(), base::HistogramBase::kUmaTargetedHistogramFlag); 200 counter->Add(sample.sample()); 201 } 202 203 int ExternalMetrics::CollectEvents() { 204 ScopedVector<metrics::MetricSample> samples; 205 metrics::SerializationUtils::ReadAndTruncateMetricsFromFile(uma_events_file_, 206 &samples); 207 208 for (ScopedVector<metrics::MetricSample>::iterator it = samples.begin(); 209 it != samples.end(); 210 ++it) { 211 const metrics::MetricSample& sample = **it; 212 213 // Do not use the UMA_HISTOGRAM_... macros here. They cache the Histogram 214 // instance and thus only work if |sample.name()| is constant. 215 switch (sample.type()) { 216 case metrics::MetricSample::CRASH: 217 RecordCrash(sample.name()); 218 break; 219 case metrics::MetricSample::USER_ACTION: 220 RecordAction(sample.name()); 221 break; 222 case metrics::MetricSample::HISTOGRAM: 223 RecordHistogram(sample); 224 break; 225 case metrics::MetricSample::LINEAR_HISTOGRAM: 226 RecordLinearHistogram(sample); 227 break; 228 case metrics::MetricSample::SPARSE_HISTOGRAM: 229 RecordSparseHistogram(sample); 230 break; 231 } 232 } 233 234 return samples.size(); 235 } 236 237 void ExternalMetrics::CollectEventsAndReschedule() { 238 base::ElapsedTimer timer; 239 CollectEvents(); 240 UMA_HISTOGRAM_TIMES("UMA.CollectExternalEventsTime", timer.Elapsed()); 241 ScheduleCollector(); 242 } 243 244 void ExternalMetrics::ScheduleCollector() { 245 bool result; 246 result = BrowserThread::PostDelayedTask( 247 BrowserThread::FILE, FROM_HERE, 248 base::Bind(&chromeos::ExternalMetrics::CollectEventsAndReschedule, this), 249 base::TimeDelta::FromSeconds(kExternalMetricsCollectionIntervalSeconds)); 250 DCHECK(result); 251 } 252 253 void ExternalMetrics::SetupFieldTrialsOnFileThread() { 254 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 255 // Field trials that do not read from files can be initialized in 256 // ExternalMetrics::Start() above. 257 SetupProgressiveScanFieldTrial(); 258 259 ScheduleCollector(); 260 } 261 262 } // namespace chromeos 263