Home | History | Annotate | Download | only in chromeos
      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/boot_times_loader.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/bind.h"
     10 #include "base/command_line.h"
     11 #include "base/file_util.h"
     12 #include "base/files/file_path.h"
     13 #include "base/json/json_reader.h"
     14 #include "base/json/json_writer.h"
     15 #include "base/lazy_instance.h"
     16 #include "base/location.h"
     17 #include "base/message_loop/message_loop.h"
     18 #include "base/message_loop/message_loop_proxy.h"
     19 #include "base/metrics/histogram.h"
     20 #include "base/prefs/pref_service.h"
     21 #include "base/strings/string_number_conversions.h"
     22 #include "base/strings/string_util.h"
     23 #include "base/strings/stringprintf.h"
     24 #include "base/threading/thread.h"
     25 #include "base/threading/thread_restrictions.h"
     26 #include "base/time/time.h"
     27 #include "chrome/browser/browser_process.h"
     28 #include "chrome/browser/chrome_notification_types.h"
     29 #include "chrome/browser/chromeos/login/auth/authentication_notification_details.h"
     30 #include "chrome/browser/chromeos/login/users/user_manager.h"
     31 #include "chrome/browser/ui/browser.h"
     32 #include "chrome/browser/ui/browser_iterator.h"
     33 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     34 #include "chrome/common/chrome_switches.h"
     35 #include "chrome/common/pref_names.h"
     36 #include "content/public/browser/browser_thread.h"
     37 #include "content/public/browser/navigation_controller.h"
     38 #include "content/public/browser/notification_service.h"
     39 #include "content/public/browser/render_widget_host_view.h"
     40 #include "content/public/browser/web_contents.h"
     41 
     42 using content::BrowserThread;
     43 using content::NavigationController;
     44 using content::RenderWidgetHost;
     45 using content::RenderWidgetHostView;
     46 using content::WebContents;
     47 
     48 namespace {
     49 
     50 const char kUptime[] = "uptime";
     51 const char kDisk[] = "disk";
     52 
     53 RenderWidgetHost* GetRenderWidgetHost(NavigationController* tab) {
     54   WebContents* web_contents = tab->GetWebContents();
     55   if (web_contents) {
     56     RenderWidgetHostView* render_widget_host_view =
     57         web_contents->GetRenderWidgetHostView();
     58     if (render_widget_host_view)
     59       return render_widget_host_view->GetRenderWidgetHost();
     60   }
     61   return NULL;
     62 }
     63 
     64 const std::string GetTabUrl(RenderWidgetHost* rwh) {
     65   RenderWidgetHostView* rwhv = rwh->GetView();
     66   for (chrome::BrowserIterator it; !it.done(); it.Next()) {
     67     Browser* browser = *it;
     68     for (int i = 0, tab_count = browser->tab_strip_model()->count();
     69          i < tab_count;
     70          ++i) {
     71       WebContents* tab = browser->tab_strip_model()->GetWebContentsAt(i);
     72       if (tab->GetRenderWidgetHostView() == rwhv) {
     73         return tab->GetLastCommittedURL().spec();
     74       }
     75     }
     76   }
     77   return std::string();
     78 }
     79 
     80 // Appends the given buffer into the file. Returns the number of bytes
     81 // written, or -1 on error.
     82 // TODO(satorux): Move this to file_util.
     83 int AppendFile(const base::FilePath& file_path, const char* data, int size) {
     84   FILE* file = base::OpenFile(file_path, "a");
     85   if (!file)
     86     return -1;
     87 
     88   const int num_bytes_written = fwrite(data, 1, size, file);
     89   base::CloseFile(file);
     90   return num_bytes_written;
     91 }
     92 
     93 }  // namespace
     94 
     95 namespace chromeos {
     96 
     97 #define FPL(value) FILE_PATH_LITERAL(value)
     98 
     99 // Dir uptime & disk logs are located in.
    100 static const base::FilePath::CharType kLogPath[] = FPL("/tmp");
    101 // Dir log{in,out} logs are located in.
    102 static const base::FilePath::CharType kLoginLogPath[] =
    103     FPL("/home/chronos/user");
    104 // Prefix for the time measurement files.
    105 static const base::FilePath::CharType kUptimePrefix[] = FPL("uptime-");
    106 // Prefix for the disk usage files.
    107 static const base::FilePath::CharType kDiskPrefix[] = FPL("disk-");
    108 // Name of the time that Chrome's main() is called.
    109 static const base::FilePath::CharType kChromeMain[] = FPL("chrome-main");
    110 // Delay in milliseconds before writing the login times to disk.
    111 static const int64 kLoginTimeWriteDelayMs = 3000;
    112 
    113 // Names of login stats files.
    114 static const base::FilePath::CharType kLoginSuccess[] = FPL("login-success");
    115 static const base::FilePath::CharType kChromeFirstRender[] =
    116     FPL("chrome-first-render");
    117 
    118 // Names of login UMA values.
    119 static const char kUmaLogin[] = "BootTime.Login";
    120 static const char kUmaLoginNewUser[] = "BootTime.LoginNewUser";
    121 static const char kUmaLoginPrefix[] = "BootTime.";
    122 static const char kUmaLogout[] = "ShutdownTime.Logout";
    123 static const char kUmaLogoutPrefix[] = "ShutdownTime.";
    124 static const char kUmaRestart[] = "ShutdownTime.Restart";
    125 
    126 // Name of file collecting login times.
    127 static const base::FilePath::CharType kLoginTimes[] = FPL("login-times");
    128 
    129 // Name of file collecting logout times.
    130 static const char kLogoutTimes[] = "logout-times";
    131 
    132 static base::LazyInstance<BootTimesLoader> g_boot_times_loader =
    133     LAZY_INSTANCE_INITIALIZER;
    134 
    135 // static
    136 BootTimesLoader::Stats BootTimesLoader::Stats::GetCurrentStats() {
    137   const base::FilePath kProcUptime(FPL("/proc/uptime"));
    138   const base::FilePath kDiskStat(FPL("/sys/block/sda/stat"));
    139   Stats stats;
    140   // Callers of this method expect synchronous behavior.
    141   // It's safe to allow IO here, because only virtual FS are accessed.
    142   base::ThreadRestrictions::ScopedAllowIO allow_io;
    143   base::ReadFileToString(kProcUptime, &stats.uptime_);
    144   base::ReadFileToString(kDiskStat, &stats.disk_);
    145   return stats;
    146 }
    147 
    148 std::string BootTimesLoader::Stats::SerializeToString() const {
    149   if (uptime_.empty() || disk_.empty())
    150     return std::string();
    151   base::DictionaryValue dictionary;
    152   dictionary.SetString(kUptime, uptime_);
    153   dictionary.SetString(kDisk, disk_);
    154 
    155   std::string result;
    156   if (!base::JSONWriter::Write(&dictionary, &result)) {
    157     LOG(WARNING) << "BootTimesLoader::Stats::SerializeToString(): failed.";
    158     return std::string();
    159   }
    160 
    161   return result;
    162 }
    163 
    164 // static
    165 BootTimesLoader::Stats BootTimesLoader::Stats::DeserializeFromString(
    166     const std::string& source) {
    167   if (source.empty())
    168     return Stats();
    169 
    170   scoped_ptr<base::Value> value(base::JSONReader::Read(source));
    171   base::DictionaryValue* dictionary;
    172   if (!value || !value->GetAsDictionary(&dictionary)) {
    173     LOG(ERROR) << "BootTimesLoader::Stats::DeserializeFromString(): not a "
    174                   "dictionary: '" << source << "'";
    175     return Stats();
    176   }
    177 
    178   Stats result;
    179   if (!dictionary->GetString(kUptime, &result.uptime_) ||
    180       !dictionary->GetString(kDisk, &result.disk_)) {
    181     LOG(ERROR)
    182         << "BootTimesLoader::Stats::DeserializeFromString(): format error: '"
    183         << source << "'";
    184     return Stats();
    185   }
    186 
    187   return result;
    188 }
    189 
    190 bool BootTimesLoader::Stats::UptimeDouble(double* result) const {
    191   std::string uptime = uptime_;
    192   const size_t space_at = uptime.find_first_of(' ');
    193   if (space_at == std::string::npos)
    194     return false;
    195 
    196   uptime.resize(space_at);
    197 
    198   if (base::StringToDouble(uptime, result))
    199     return true;
    200 
    201   return false;
    202 }
    203 
    204 void BootTimesLoader::Stats::RecordStats(const std::string& name) const {
    205   BrowserThread::PostBlockingPoolTask(
    206       FROM_HERE,
    207       base::Bind(&BootTimesLoader::Stats::RecordStatsImpl,
    208                  base::Owned(new Stats(*this)),
    209                  name));
    210 }
    211 
    212 void BootTimesLoader::Stats::RecordStatsWithCallback(
    213     const std::string& name,
    214     const base::Closure& callback) const {
    215   BrowserThread::PostBlockingPoolTaskAndReply(
    216       FROM_HERE,
    217       base::Bind(&BootTimesLoader::Stats::RecordStatsImpl,
    218                  base::Owned(new Stats(*this)),
    219                  name),
    220       callback);
    221 }
    222 
    223 void BootTimesLoader::Stats::RecordStatsImpl(
    224     const base::FilePath::StringType& name) const {
    225   DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
    226 
    227   const base::FilePath log_path(kLogPath);
    228   const base::FilePath uptime_output =
    229       log_path.Append(base::FilePath(kUptimePrefix + name));
    230   const base::FilePath disk_output =
    231       log_path.Append(base::FilePath(kDiskPrefix + name));
    232 
    233   // Append numbers to the files.
    234   AppendFile(uptime_output, uptime_.data(), uptime_.size());
    235   AppendFile(disk_output, disk_.data(), disk_.size());
    236 }
    237 
    238 BootTimesLoader::BootTimesLoader()
    239     : backend_(new Backend()),
    240       have_registered_(false),
    241       login_done_(false),
    242       restart_requested_(false) {
    243   login_time_markers_.reserve(30);
    244   logout_time_markers_.reserve(30);
    245 }
    246 
    247 BootTimesLoader::~BootTimesLoader() {
    248 }
    249 
    250 // static
    251 BootTimesLoader* BootTimesLoader::Get() {
    252   return g_boot_times_loader.Pointer();
    253 }
    254 
    255 // static
    256 void BootTimesLoader::WriteTimes(
    257     const std::string base_name,
    258     const std::string uma_name,
    259     const std::string uma_prefix,
    260     std::vector<TimeMarker> login_times) {
    261   const int kMinTimeMillis = 1;
    262   const int kMaxTimeMillis = 30000;
    263   const int kNumBuckets = 100;
    264   const base::FilePath log_path(kLoginLogPath);
    265 
    266   // Need to sort by time since the entries may have been pushed onto the
    267   // vector (on the UI thread) in a different order from which they were
    268   // created (potentially on other threads).
    269   std::sort(login_times.begin(), login_times.end());
    270 
    271   base::Time first = login_times.front().time();
    272   base::Time last = login_times.back().time();
    273   base::TimeDelta total = last - first;
    274   base::HistogramBase* total_hist = base::Histogram::FactoryTimeGet(
    275       uma_name,
    276       base::TimeDelta::FromMilliseconds(kMinTimeMillis),
    277       base::TimeDelta::FromMilliseconds(kMaxTimeMillis),
    278       kNumBuckets,
    279       base::HistogramBase::kUmaTargetedHistogramFlag);
    280   total_hist->AddTime(total);
    281   std::string output =
    282       base::StringPrintf("%s: %.2f", uma_name.c_str(), total.InSecondsF());
    283   base::Time prev = first;
    284   for (unsigned int i = 0; i < login_times.size(); ++i) {
    285     TimeMarker tm = login_times[i];
    286     base::TimeDelta since_first = tm.time() - first;
    287     base::TimeDelta since_prev = tm.time() - prev;
    288     std::string name;
    289 
    290     if (tm.send_to_uma()) {
    291       name = uma_prefix + tm.name();
    292       base::HistogramBase* prev_hist = base::Histogram::FactoryTimeGet(
    293           name,
    294           base::TimeDelta::FromMilliseconds(kMinTimeMillis),
    295           base::TimeDelta::FromMilliseconds(kMaxTimeMillis),
    296           kNumBuckets,
    297           base::HistogramBase::kUmaTargetedHistogramFlag);
    298       prev_hist->AddTime(since_prev);
    299     } else {
    300       name = tm.name();
    301     }
    302     output +=
    303         base::StringPrintf(
    304             "\n%.2f +%.4f %s",
    305             since_first.InSecondsF(),
    306             since_prev.InSecondsF(),
    307             name.data());
    308     prev = tm.time();
    309   }
    310   output += '\n';
    311 
    312   base::WriteFile(log_path.Append(base_name), output.data(), output.size());
    313 }
    314 
    315 void BootTimesLoader::LoginDone(bool is_user_new) {
    316   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    317   if (login_done_)
    318     return;
    319 
    320   login_done_ = true;
    321   AddLoginTimeMarker("LoginDone", false);
    322   RecordCurrentStats(kChromeFirstRender);
    323   if (have_registered_) {
    324     registrar_.Remove(this,
    325                       content::NOTIFICATION_LOAD_START,
    326                       content::NotificationService::AllSources());
    327     registrar_.Remove(this,
    328                       content::NOTIFICATION_LOAD_STOP,
    329                       content::NotificationService::AllSources());
    330     registrar_.Remove(this,
    331                       content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
    332                       content::NotificationService::AllSources());
    333     registrar_.Remove(
    334         this,
    335         content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
    336         content::NotificationService::AllSources());
    337   }
    338   // Don't swamp the FILE thread right away.
    339   BrowserThread::PostDelayedTask(
    340       BrowserThread::FILE,
    341       FROM_HERE,
    342       base::Bind(&WriteTimes,
    343                  kLoginTimes,
    344                  (is_user_new ? kUmaLoginNewUser : kUmaLogin),
    345                  kUmaLoginPrefix,
    346                  login_time_markers_),
    347       base::TimeDelta::FromMilliseconds(kLoginTimeWriteDelayMs));
    348 }
    349 
    350 void BootTimesLoader::WriteLogoutTimes() {
    351   // Either we're on the browser thread, or (more likely) Chrome is in the
    352   // process of shutting down and we're on the main thread but the message loop
    353   // has already been terminated.
    354   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
    355          !BrowserThread::IsMessageLoopValid(BrowserThread::UI));
    356 
    357   WriteTimes(kLogoutTimes,
    358              (restart_requested_ ? kUmaRestart : kUmaLogout),
    359              kUmaLogoutPrefix,
    360              logout_time_markers_);
    361 }
    362 
    363 // static
    364 void BootTimesLoader::ClearLogoutStartedLastPreference() {
    365   PrefService* local_state = g_browser_process->local_state();
    366   local_state->ClearPref(prefs::kLogoutStartedLast);
    367 }
    368 
    369 void BootTimesLoader::OnChromeProcessStart() {
    370   PrefService* local_state = g_browser_process->local_state();
    371   const std::string logout_started_last_str =
    372       local_state->GetString(prefs::kLogoutStartedLast);
    373   if (logout_started_last_str.empty())
    374     return;
    375 
    376   // Note that kLogoutStartedLast is not cleared on format error to stay in
    377   // logs in case of other fatal system errors.
    378 
    379   const Stats logout_started_last_stats =
    380       Stats::DeserializeFromString(logout_started_last_str);
    381   if (logout_started_last_stats.uptime().empty())
    382     return;
    383 
    384   double logout_started_last;
    385   double uptime;
    386   if (!logout_started_last_stats.UptimeDouble(&logout_started_last) ||
    387       !Stats::GetCurrentStats().UptimeDouble(&uptime)) {
    388     return;
    389   }
    390 
    391   if (logout_started_last >= uptime) {
    392     // Reboot happened.
    393     ClearLogoutStartedLastPreference();
    394     return;
    395   }
    396 
    397   // Write /tmp/uptime-logout-started as well.
    398   const char kLogoutStarted[] = "logout-started";
    399   logout_started_last_stats.RecordStatsWithCallback(
    400       kLogoutStarted,
    401       base::Bind(&BootTimesLoader::ClearLogoutStartedLastPreference));
    402 }
    403 
    404 void BootTimesLoader::OnLogoutStarted(PrefService* state) {
    405   const std::string uptime = Stats::GetCurrentStats().SerializeToString();
    406   if (!uptime.empty())
    407     state->SetString(prefs::kLogoutStartedLast, uptime);
    408 }
    409 
    410 void BootTimesLoader::RecordCurrentStats(const std::string& name) {
    411   Stats::GetCurrentStats().RecordStats(name);
    412 }
    413 
    414 void BootTimesLoader::SaveChromeMainStats() {
    415   chrome_main_stats_ = Stats::GetCurrentStats();
    416 }
    417 
    418 void BootTimesLoader::RecordChromeMainStats() {
    419   chrome_main_stats_.RecordStats(kChromeMain);
    420 }
    421 
    422 void BootTimesLoader::RecordLoginAttempted() {
    423   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    424   if (login_done_)
    425     return;
    426 
    427   login_time_markers_.clear();
    428   AddLoginTimeMarker("LoginStarted", false);
    429   if (!have_registered_) {
    430     have_registered_ = true;
    431     registrar_.Add(this, chrome::NOTIFICATION_LOGIN_AUTHENTICATION,
    432                    content::NotificationService::AllSources());
    433     registrar_.Add(this, content::NOTIFICATION_LOAD_START,
    434                    content::NotificationService::AllSources());
    435     registrar_.Add(this, content::NOTIFICATION_LOAD_STOP,
    436                    content::NotificationService::AllSources());
    437     registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
    438                    content::NotificationService::AllSources());
    439     registrar_.Add(
    440         this,
    441         content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
    442         content::NotificationService::AllSources());
    443   }
    444 }
    445 
    446 void BootTimesLoader::AddLoginTimeMarker(
    447     const std::string& marker_name, bool send_to_uma) {
    448   AddMarker(&login_time_markers_, TimeMarker(marker_name, send_to_uma));
    449 }
    450 
    451 void BootTimesLoader::AddLogoutTimeMarker(
    452     const std::string& marker_name, bool send_to_uma) {
    453   AddMarker(&logout_time_markers_, TimeMarker(marker_name, send_to_uma));
    454 }
    455 
    456 // static
    457 void BootTimesLoader::AddMarker(std::vector<TimeMarker>* vector,
    458                                 TimeMarker marker)
    459 {
    460   // The marker vectors can only be safely manipulated on the main thread.
    461   // If we're late in the process of shutting down (eg. as can be the case at
    462   // logout), then we have to assume we're on the main thread already.
    463   if (BrowserThread::CurrentlyOn(BrowserThread::UI) ||
    464       !BrowserThread::IsMessageLoopValid(BrowserThread::UI)) {
    465     vector->push_back(marker);
    466   } else {
    467     // Add the marker on the UI thread.
    468     // Note that it's safe to use an unretained pointer to the vector because
    469     // BootTimesLoader's lifetime exceeds that of the UI thread message loop.
    470     BrowserThread::PostTask(
    471         BrowserThread::UI, FROM_HERE,
    472         base::Bind(&BootTimesLoader::AddMarker,
    473                    base::Unretained(vector),
    474                    marker));
    475   }
    476 }
    477 
    478 void BootTimesLoader::Observe(
    479     int type,
    480     const content::NotificationSource& source,
    481     const content::NotificationDetails& details) {
    482   switch (type) {
    483     case chrome::NOTIFICATION_LOGIN_AUTHENTICATION: {
    484       content::Details<AuthenticationNotificationDetails> auth_details(details);
    485       if (auth_details->success()) {
    486         AddLoginTimeMarker("Authenticate", true);
    487         RecordCurrentStats(kLoginSuccess);
    488         registrar_.Remove(this, chrome::NOTIFICATION_LOGIN_AUTHENTICATION,
    489                           content::NotificationService::AllSources());
    490       }
    491       break;
    492     }
    493     case content::NOTIFICATION_LOAD_START: {
    494       NavigationController* tab =
    495           content::Source<NavigationController>(source).ptr();
    496       RenderWidgetHost* rwh = GetRenderWidgetHost(tab);
    497       DCHECK(rwh);
    498       AddLoginTimeMarker("TabLoad-Start: " + GetTabUrl(rwh), false);
    499       render_widget_hosts_loading_.insert(rwh);
    500       break;
    501     }
    502     case content::NOTIFICATION_LOAD_STOP: {
    503       NavigationController* tab =
    504           content::Source<NavigationController>(source).ptr();
    505       RenderWidgetHost* rwh = GetRenderWidgetHost(tab);
    506       if (render_widget_hosts_loading_.find(rwh) !=
    507           render_widget_hosts_loading_.end()) {
    508         AddLoginTimeMarker("TabLoad-End: " + GetTabUrl(rwh), false);
    509       }
    510       break;
    511     }
    512     case content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE: {
    513       RenderWidgetHost* rwh = content::Source<RenderWidgetHost>(source).ptr();
    514       if (render_widget_hosts_loading_.find(rwh) !=
    515           render_widget_hosts_loading_.end()) {
    516         AddLoginTimeMarker("TabPaint: " + GetTabUrl(rwh), false);
    517         LoginDone(UserManager::Get()->IsCurrentUserNew());
    518       }
    519       break;
    520     }
    521     case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: {
    522       WebContents* web_contents = content::Source<WebContents>(source).ptr();
    523       RenderWidgetHost* render_widget_host =
    524           GetRenderWidgetHost(&web_contents->GetController());
    525       render_widget_hosts_loading_.erase(render_widget_host);
    526       break;
    527     }
    528     default:
    529       break;
    530   }
    531 }
    532 
    533 }  // namespace chromeos
    534