boot_times_loader.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
1// Copyright (c) 2009 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/boot_times_loader.h" 6 7#include <vector> 8 9#include "base/command_line.h" 10#include "base/file_path.h" 11#include "base/file_util.h" 12#include "base/message_loop.h" 13#include "base/metrics/histogram.h" 14#include "base/process_util.h" 15#include "base/singleton.h" 16#include "base/string_number_conversions.h" 17#include "base/string_util.h" 18#include "base/stringprintf.h" 19#include "base/thread.h" 20#include "base/time.h" 21#include "chrome/browser/browser_process.h" 22#include "chrome/browser/browser_thread.h" 23#include "chrome/browser/chromeos/login/authentication_notification_details.h" 24#include "chrome/browser/chromeos/network_state_notifier.h" 25#include "chrome/common/chrome_switches.h" 26#include "chrome/common/notification_service.h" 27 28namespace chromeos { 29 30// File uptime logs are located in. 31static const char kLogPath[] = "/tmp"; 32// Prefix for the time measurement files. 33static const char kUptimePrefix[] = "uptime-"; 34// Prefix for the disk usage files. 35static const char kDiskPrefix[] = "disk-"; 36// Name of the time that Chrome's main() is called. 37static const char kChromeMain[] = "chrome-main"; 38// Delay in milliseconds between file read attempts. 39static const int64 kReadAttemptDelayMs = 250; 40// Delay in milliseconds before writing the login times to disk. 41static const int64 kLoginTimeWriteDelayMs = 3000; 42 43// Names of login stats files. 44static const char kLoginSuccess[] = "login-success"; 45static const char kChromeFirstRender[] = "chrome-first-render"; 46 47// Names of login UMA values. 48static const char kUmaAuthenticate[] = "BootTime.Authenticate"; 49static const char kUmaLogin[] = "BootTime.Login"; 50 51// Name of file collecting login times. 52static const char kLoginTimes[] = "login-times-sent"; 53 54BootTimesLoader::BootTimesLoader() 55 : backend_(new Backend()), 56 have_registered_(false) { 57 login_time_markers_.reserve(30); 58} 59 60// static 61BootTimesLoader* BootTimesLoader::Get() { 62 return Singleton<BootTimesLoader>::get(); 63} 64 65BootTimesLoader::Handle BootTimesLoader::GetBootTimes( 66 CancelableRequestConsumerBase* consumer, 67 BootTimesLoader::GetBootTimesCallback* callback) { 68 if (!g_browser_process->file_thread()) { 69 // This should only happen if Chrome is shutting down, so we don't do 70 // anything. 71 return 0; 72 } 73 74 const CommandLine& command_line = *CommandLine::ForCurrentProcess(); 75 if (command_line.HasSwitch(switches::kTestType)) { 76 // TODO(davemoore) This avoids boottimes for tests. This needs to be 77 // replaced with a mock of BootTimesLoader. 78 return 0; 79 } 80 81 scoped_refptr<CancelableRequest<GetBootTimesCallback> > request( 82 new CancelableRequest<GetBootTimesCallback>(callback)); 83 AddRequest(request, consumer); 84 85 BrowserThread::PostTask( 86 BrowserThread::FILE, 87 FROM_HERE, 88 NewRunnableMethod(backend_.get(), &Backend::GetBootTimes, request)); 89 return request->handle(); 90} 91 92// Extracts the uptime value from files located in /tmp, returning the 93// value as a double in value. 94static bool GetTime(const std::string& log, double* value) { 95 FilePath log_dir(kLogPath); 96 FilePath log_file = log_dir.Append(log); 97 std::string contents; 98 *value = 0.0; 99 if (file_util::ReadFileToString(log_file, &contents)) { 100 size_t space_index = contents.find(' '); 101 size_t chars_left = 102 space_index != std::string::npos ? space_index : std::string::npos; 103 std::string value_string = contents.substr(0, chars_left); 104 return base::StringToDouble(value_string, value); 105 } 106 return false; 107} 108 109// Converts double seconds to a TimeDelta object. 110static base::TimeDelta SecondsToTimeDelta(double seconds) { 111 double ms = seconds * base::Time::kMillisecondsPerSecond; 112 return base::TimeDelta::FromMilliseconds(static_cast<int64>(ms)); 113} 114 115// Reports the collected boot times to UMA if they haven't been 116// reported yet and if metrics collection is enabled. 117static void SendBootTimesToUMA(const BootTimesLoader::BootTimes& boot_times) { 118 // Checks if the times for the most recent boot event have been 119 // reported already to avoid sending boot time histogram samples 120 // every time the user logs out. 121 static const char kBootTimesSent[] = "/tmp/boot-times-sent"; 122 FilePath sent(kBootTimesSent); 123 if (file_util::PathExists(sent)) 124 return; 125 126 UMA_HISTOGRAM_TIMES("BootTime.Total", 127 SecondsToTimeDelta(boot_times.total)); 128 UMA_HISTOGRAM_TIMES("BootTime.Firmware", 129 SecondsToTimeDelta(boot_times.firmware)); 130 UMA_HISTOGRAM_TIMES("BootTime.Kernel", 131 SecondsToTimeDelta(boot_times.pre_startup)); 132 UMA_HISTOGRAM_TIMES("BootTime.System", 133 SecondsToTimeDelta(boot_times.system)); 134 if (boot_times.chrome > 0) { 135 UMA_HISTOGRAM_TIMES("BootTime.Chrome", 136 SecondsToTimeDelta(boot_times.chrome)); 137 } 138 139 // Stores the boot times to a file in /tmp to indicate that the 140 // times for the most recent boot event have been reported 141 // already. The file will be deleted at system shutdown/reboot. 142 std::string boot_times_text = base::StringPrintf("total: %.2f\n" 143 "firmware: %.2f\n" 144 "kernel: %.2f\n" 145 "system: %.2f\n" 146 "chrome: %.2f\n", 147 boot_times.total, 148 boot_times.firmware, 149 boot_times.pre_startup, 150 boot_times.system, 151 boot_times.chrome); 152 file_util::WriteFile(sent, boot_times_text.data(), boot_times_text.size()); 153 DCHECK(file_util::PathExists(sent)); 154} 155 156void BootTimesLoader::Backend::GetBootTimes( 157 scoped_refptr<GetBootTimesRequest> request) { 158 const char* kFirmwareBootTime = "firmware-boot-time"; 159 const char* kPreStartup = "pre-startup"; 160 const char* kChromeExec = "chrome-exec"; 161 const char* kChromeMain = "chrome-main"; 162 const char* kXStarted = "x-started"; 163 const char* kLoginPromptReady = "login-prompt-ready"; 164 std::string uptime_prefix = kUptimePrefix; 165 166 if (request->canceled()) 167 return; 168 169 // Wait until login_prompt_ready is output by reposting. 170 FilePath log_dir(kLogPath); 171 FilePath log_file = log_dir.Append(uptime_prefix + kLoginPromptReady); 172 if (!file_util::PathExists(log_file)) { 173 BrowserThread::PostDelayedTask( 174 BrowserThread::FILE, 175 FROM_HERE, 176 NewRunnableMethod(this, &Backend::GetBootTimes, request), 177 kReadAttemptDelayMs); 178 return; 179 } 180 181 BootTimes boot_times; 182 183 GetTime(kFirmwareBootTime, &boot_times.firmware); 184 GetTime(uptime_prefix + kPreStartup, &boot_times.pre_startup); 185 GetTime(uptime_prefix + kXStarted, &boot_times.x_started); 186 GetTime(uptime_prefix + kChromeExec, &boot_times.chrome_exec); 187 GetTime(uptime_prefix + kChromeMain, &boot_times.chrome_main); 188 GetTime(uptime_prefix + kLoginPromptReady, &boot_times.login_prompt_ready); 189 190 boot_times.total = boot_times.firmware + boot_times.login_prompt_ready; 191 if (boot_times.chrome_exec > 0) { 192 boot_times.system = boot_times.chrome_exec - boot_times.pre_startup; 193 boot_times.chrome = boot_times.login_prompt_ready - boot_times.chrome_exec; 194 } else { 195 boot_times.system = boot_times.login_prompt_ready - boot_times.pre_startup; 196 } 197 198 SendBootTimesToUMA(boot_times); 199 200 request->ForwardResult( 201 GetBootTimesCallback::TupleType(request->handle(), boot_times)); 202} 203 204static void RecordStatsDelayed( 205 const std::string& name, 206 const std::string& uptime, 207 const std::string& disk) { 208 const FilePath log_path(kLogPath); 209 std::string disk_prefix = kDiskPrefix; 210 const FilePath uptime_output = 211 log_path.Append(FilePath(kUptimePrefix + name)); 212 const FilePath disk_output = log_path.Append(FilePath(kDiskPrefix + name)); 213 214 // Write out the files, ensuring that they don't exist already. 215 if (!file_util::PathExists(uptime_output)) 216 file_util::WriteFile(uptime_output, uptime.data(), uptime.size()); 217 if (!file_util::PathExists(disk_output)) 218 file_util::WriteFile(disk_output, disk.data(), disk.size()); 219} 220 221// static 222void BootTimesLoader::WriteLoginTimes( 223 const std::vector<TimeMarker> login_times) { 224 const int kMinTimeMillis = 1; 225 const int kMaxTimeMillis = 30; 226 const int kNumBuckets = 100; 227 const char kUmaPrefix[] = "BootTime."; 228 const FilePath log_path(kLogPath); 229 230 base::Time first = login_times.front().time(); 231 base::Time last = login_times.back().time(); 232 base::TimeDelta total = last - first; 233 scoped_refptr<base::Histogram>total_hist = base::Histogram::FactoryTimeGet( 234 kUmaLogin, 235 base::TimeDelta::FromMilliseconds(kMinTimeMillis), 236 base::TimeDelta::FromMilliseconds(kMaxTimeMillis), 237 kNumBuckets, 238 base::Histogram::kUmaTargetedHistogramFlag); 239 total_hist->AddTime(total); 240 std::string output = 241 base::StringPrintf("%s: %.2f", kUmaLogin, total.InSecondsF()); 242 base::Time prev = first; 243 for (unsigned int i = 0; i < login_times.size(); ++i) { 244 TimeMarker tm = login_times[i]; 245 base::TimeDelta since_first = tm.time() - first; 246 base::TimeDelta since_prev = tm.time() - prev; 247 std::string name; 248 249 if (tm.send_to_uma()) { 250 name = kUmaPrefix + tm.name(); 251 scoped_refptr<base::Histogram>prev_hist = base::Histogram::FactoryTimeGet( 252 name, 253 base::TimeDelta::FromMilliseconds(kMinTimeMillis), 254 base::TimeDelta::FromMilliseconds(kMaxTimeMillis), 255 kNumBuckets, 256 base::Histogram::kUmaTargetedHistogramFlag); 257 prev_hist->AddTime(since_prev); 258 } else { 259 name = tm.name(); 260 } 261 output += 262 StringPrintf( 263 "\n%.2f +%.2f %s", 264 since_first.InSecondsF(), 265 since_prev.InSecondsF(), 266 name.data()); 267 prev = tm.time(); 268 } 269 file_util::WriteFile( 270 log_path.Append(kLoginTimes), output.data(), output.size()); 271} 272 273void BootTimesLoader::RecordStats(const std::string& name, const Stats& stats) { 274 BrowserThread::PostTask( 275 BrowserThread::FILE, FROM_HERE, 276 NewRunnableFunction( 277 RecordStatsDelayed, name, stats.uptime, stats.disk)); 278} 279 280BootTimesLoader::Stats BootTimesLoader::GetCurrentStats() { 281 const FilePath kProcUptime("/proc/uptime"); 282 const FilePath kDiskStat("/sys/block/sda/stat"); 283 Stats stats; 284 285 file_util::ReadFileToString(kProcUptime, &stats.uptime); 286 file_util::ReadFileToString(kDiskStat, &stats.disk); 287 return stats; 288} 289 290void BootTimesLoader::RecordCurrentStats(const std::string& name) { 291 RecordStats(name, GetCurrentStats()); 292} 293 294void BootTimesLoader::SaveChromeMainStats() { 295 chrome_main_stats_ = GetCurrentStats(); 296} 297 298void BootTimesLoader::RecordChromeMainStats() { 299 RecordStats(kChromeMain, chrome_main_stats_); 300} 301 302void BootTimesLoader::RecordLoginAttempted() { 303 login_time_markers_.clear(); 304 AddLoginTimeMarker("LoginStarted", false); 305 if (!have_registered_) { 306 have_registered_ = true; 307 registrar_.Add(this, NotificationType::LOAD_START, 308 NotificationService::AllSources()); 309 registrar_.Add(this, NotificationType::LOGIN_AUTHENTICATION, 310 NotificationService::AllSources()); 311 } 312} 313 314void BootTimesLoader::AddLoginTimeMarker( 315 const std::string& marker_name, bool send_to_uma) { 316 login_time_markers_.push_back(TimeMarker(marker_name, send_to_uma)); 317} 318 319void BootTimesLoader::Observe( 320 NotificationType type, 321 const NotificationSource& source, 322 const NotificationDetails& details) { 323 if (type == NotificationType::LOGIN_AUTHENTICATION) { 324 Details<AuthenticationNotificationDetails> auth_details(details); 325 if (auth_details->success()) { 326 AddLoginTimeMarker("Authenticate", true); 327 RecordCurrentStats(kLoginSuccess); 328 registrar_.Remove(this, NotificationType::LOGIN_AUTHENTICATION, 329 NotificationService::AllSources()); 330 } 331 } else if (type == NotificationType::LOAD_START) { 332 // Only log for first tab to render. Make sure this is only done once. 333 // If the network isn't connected we'll get a second LOAD_START once it is 334 // and the page is reloaded. 335 if (NetworkStateNotifier::Get()->is_connected()) { 336 // Post difference between first tab and login success time. 337 AddLoginTimeMarker("LoginDone", false); 338 RecordCurrentStats(kChromeFirstRender); 339 // Post chrome first render stat. 340 registrar_.Remove(this, NotificationType::LOAD_START, 341 NotificationService::AllSources()); 342 // Don't swamp the FILE thread right away. 343 BrowserThread::PostDelayedTask( 344 BrowserThread::FILE, FROM_HERE, 345 NewRunnableFunction(WriteLoginTimes, login_time_markers_), 346 kLoginTimeWriteDelayMs); 347 have_registered_ = false; 348 } else { 349 AddLoginTimeMarker("LoginRenderNoNetwork", false); 350 } 351 } 352} 353 354} // namespace chromeos 355