boot_times_loader.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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/lazy_instance.h"
14#include "base/location.h"
15#include "base/message_loop/message_loop.h"
16#include "base/message_loop/message_loop_proxy.h"
17#include "base/metrics/histogram.h"
18#include "base/strings/string_number_conversions.h"
19#include "base/strings/string_util.h"
20#include "base/strings/stringprintf.h"
21#include "base/threading/thread.h"
22#include "base/threading/thread_restrictions.h"
23#include "base/time/time.h"
24#include "chrome/browser/browser_process.h"
25#include "chrome/browser/chrome_notification_types.h"
26#include "chrome/browser/chromeos/login/auth/authentication_notification_details.h"
27#include "chrome/browser/chromeos/login/users/user_manager.h"
28#include "chrome/browser/ui/browser.h"
29#include "chrome/browser/ui/browser_iterator.h"
30#include "chrome/browser/ui/tabs/tab_strip_model.h"
31#include "chrome/common/chrome_switches.h"
32#include "content/public/browser/browser_thread.h"
33#include "content/public/browser/navigation_controller.h"
34#include "content/public/browser/notification_service.h"
35#include "content/public/browser/render_widget_host_view.h"
36#include "content/public/browser/web_contents.h"
37
38using content::BrowserThread;
39using content::NavigationController;
40using content::RenderWidgetHost;
41using content::RenderWidgetHostView;
42using content::WebContents;
43
44namespace {
45
46RenderWidgetHost* GetRenderWidgetHost(NavigationController* tab) {
47  WebContents* web_contents = tab->GetWebContents();
48  if (web_contents) {
49    RenderWidgetHostView* render_widget_host_view =
50        web_contents->GetRenderWidgetHostView();
51    if (render_widget_host_view)
52      return render_widget_host_view->GetRenderWidgetHost();
53  }
54  return NULL;
55}
56
57const std::string GetTabUrl(RenderWidgetHost* rwh) {
58  RenderWidgetHostView* rwhv = rwh->GetView();
59  for (chrome::BrowserIterator it; !it.done(); it.Next()) {
60    Browser* browser = *it;
61    for (int i = 0, tab_count = browser->tab_strip_model()->count();
62         i < tab_count;
63         ++i) {
64      WebContents* tab = browser->tab_strip_model()->GetWebContentsAt(i);
65      if (tab->GetRenderWidgetHostView() == rwhv) {
66        return tab->GetLastCommittedURL().spec();
67      }
68    }
69  }
70  return std::string();
71}
72
73}  // namespace
74
75namespace chromeos {
76
77#define FPL(value) FILE_PATH_LITERAL(value)
78
79// Dir uptime & disk logs are located in.
80static const base::FilePath::CharType kLogPath[] = FPL("/tmp");
81// Dir log{in,out} logs are located in.
82static const base::FilePath::CharType kLoginLogPath[] =
83    FPL("/home/chronos/user");
84// Prefix for the time measurement files.
85static const base::FilePath::CharType kUptimePrefix[] = FPL("uptime-");
86// Prefix for the disk usage files.
87static const base::FilePath::CharType kDiskPrefix[] = FPL("disk-");
88// Name of the time that Chrome's main() is called.
89static const base::FilePath::CharType kChromeMain[] = FPL("chrome-main");
90// Delay in milliseconds before writing the login times to disk.
91static const int64 kLoginTimeWriteDelayMs = 3000;
92
93// Names of login stats files.
94static const base::FilePath::CharType kLoginSuccess[] = FPL("login-success");
95static const base::FilePath::CharType kChromeFirstRender[] =
96    FPL("chrome-first-render");
97
98// Names of login UMA values.
99static const char kUmaLogin[] = "BootTime.Login";
100static const char kUmaLoginNewUser[] = "BootTime.LoginNewUser";
101static const char kUmaLoginPrefix[] = "BootTime.";
102static const char kUmaLogout[] = "ShutdownTime.Logout";
103static const char kUmaLogoutPrefix[] = "ShutdownTime.";
104static const char kUmaRestart[] = "ShutdownTime.Restart";
105
106// Name of file collecting login times.
107static const base::FilePath::CharType kLoginTimes[] = FPL("login-times");
108
109// Name of file collecting logout times.
110static const char kLogoutTimes[] = "logout-times";
111
112static base::LazyInstance<BootTimesLoader> g_boot_times_loader =
113    LAZY_INSTANCE_INITIALIZER;
114
115BootTimesLoader::BootTimesLoader()
116    : backend_(new Backend()),
117      have_registered_(false),
118      login_done_(false),
119      restart_requested_(false) {
120  login_time_markers_.reserve(30);
121  logout_time_markers_.reserve(30);
122}
123
124BootTimesLoader::~BootTimesLoader() {}
125
126// static
127BootTimesLoader* BootTimesLoader::Get() {
128  return g_boot_times_loader.Pointer();
129}
130
131// Appends the given buffer into the file. Returns the number of bytes
132// written, or -1 on error.
133// TODO(satorux): Move this to file_util.
134static int AppendFile(const base::FilePath& file_path,
135                      const char* data,
136                      int size) {
137  FILE* file = base::OpenFile(file_path, "a");
138  if (!file) {
139    return -1;
140  }
141  const int num_bytes_written = fwrite(data, 1, size, file);
142  base::CloseFile(file);
143  return num_bytes_written;
144}
145
146static void RecordStatsDelayed(const base::FilePath::StringType& name,
147                               const std::string& uptime,
148                               const std::string& disk) {
149  const base::FilePath log_path(kLogPath);
150  const base::FilePath uptime_output =
151      log_path.Append(base::FilePath(kUptimePrefix + name));
152  const base::FilePath disk_output =
153      log_path.Append(base::FilePath(kDiskPrefix + name));
154
155  // Append numbers to the files.
156  AppendFile(uptime_output, uptime.data(), uptime.size());
157  AppendFile(disk_output, disk.data(), disk.size());
158}
159
160// static
161void BootTimesLoader::WriteTimes(
162    const std::string base_name,
163    const std::string uma_name,
164    const std::string uma_prefix,
165    std::vector<TimeMarker> login_times) {
166  const int kMinTimeMillis = 1;
167  const int kMaxTimeMillis = 30000;
168  const int kNumBuckets = 100;
169  const base::FilePath log_path(kLoginLogPath);
170
171  // Need to sort by time since the entries may have been pushed onto the
172  // vector (on the UI thread) in a different order from which they were
173  // created (potentially on other threads).
174  std::sort(login_times.begin(), login_times.end());
175
176  base::Time first = login_times.front().time();
177  base::Time last = login_times.back().time();
178  base::TimeDelta total = last - first;
179  base::HistogramBase* total_hist = base::Histogram::FactoryTimeGet(
180      uma_name,
181      base::TimeDelta::FromMilliseconds(kMinTimeMillis),
182      base::TimeDelta::FromMilliseconds(kMaxTimeMillis),
183      kNumBuckets,
184      base::HistogramBase::kUmaTargetedHistogramFlag);
185  total_hist->AddTime(total);
186  std::string output =
187      base::StringPrintf("%s: %.2f", uma_name.c_str(), total.InSecondsF());
188  base::Time prev = first;
189  for (unsigned int i = 0; i < login_times.size(); ++i) {
190    TimeMarker tm = login_times[i];
191    base::TimeDelta since_first = tm.time() - first;
192    base::TimeDelta since_prev = tm.time() - prev;
193    std::string name;
194
195    if (tm.send_to_uma()) {
196      name = uma_prefix + tm.name();
197      base::HistogramBase* prev_hist = base::Histogram::FactoryTimeGet(
198          name,
199          base::TimeDelta::FromMilliseconds(kMinTimeMillis),
200          base::TimeDelta::FromMilliseconds(kMaxTimeMillis),
201          kNumBuckets,
202          base::HistogramBase::kUmaTargetedHistogramFlag);
203      prev_hist->AddTime(since_prev);
204    } else {
205      name = tm.name();
206    }
207    output +=
208        base::StringPrintf(
209            "\n%.2f +%.4f %s",
210            since_first.InSecondsF(),
211            since_prev.InSecondsF(),
212            name.data());
213    prev = tm.time();
214  }
215  output += '\n';
216
217  base::WriteFile(log_path.Append(base_name), output.data(), output.size());
218}
219
220void BootTimesLoader::LoginDone(bool is_user_new) {
221  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
222  if (login_done_)
223    return;
224
225  login_done_ = true;
226  AddLoginTimeMarker("LoginDone", false);
227  RecordCurrentStats(kChromeFirstRender);
228  if (have_registered_) {
229    registrar_.Remove(this,
230                      content::NOTIFICATION_LOAD_START,
231                      content::NotificationService::AllSources());
232    registrar_.Remove(this,
233                      content::NOTIFICATION_LOAD_STOP,
234                      content::NotificationService::AllSources());
235    registrar_.Remove(this,
236                      content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
237                      content::NotificationService::AllSources());
238    registrar_.Remove(
239        this,
240        content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
241        content::NotificationService::AllSources());
242  }
243  // Don't swamp the FILE thread right away.
244  BrowserThread::PostDelayedTask(
245      BrowserThread::FILE,
246      FROM_HERE,
247      base::Bind(&WriteTimes,
248                 kLoginTimes,
249                 (is_user_new ? kUmaLoginNewUser : kUmaLogin),
250                 kUmaLoginPrefix,
251                 login_time_markers_),
252      base::TimeDelta::FromMilliseconds(kLoginTimeWriteDelayMs));
253}
254
255void BootTimesLoader::WriteLogoutTimes() {
256  // Either we're on the browser thread, or (more likely) Chrome is in the
257  // process of shutting down and we're on the main thread but the message loop
258  // has already been terminated.
259  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
260         !BrowserThread::IsMessageLoopValid(BrowserThread::UI));
261
262  WriteTimes(kLogoutTimes,
263             (restart_requested_ ? kUmaRestart : kUmaLogout),
264             kUmaLogoutPrefix,
265             logout_time_markers_);
266}
267
268void BootTimesLoader::RecordStats(const std::string& name, const Stats& stats) {
269  BrowserThread::PostTask(
270      BrowserThread::FILE, FROM_HERE,
271      base::Bind(&RecordStatsDelayed, name, stats.uptime, stats.disk));
272}
273
274BootTimesLoader::Stats BootTimesLoader::GetCurrentStats() {
275  const base::FilePath kProcUptime(FPL("/proc/uptime"));
276  const base::FilePath kDiskStat(FPL("/sys/block/sda/stat"));
277  Stats stats;
278  base::ThreadRestrictions::ScopedAllowIO allow_io;
279  base::ReadFileToString(kProcUptime, &stats.uptime);
280  base::ReadFileToString(kDiskStat, &stats.disk);
281  return stats;
282}
283
284void BootTimesLoader::RecordCurrentStats(const std::string& name) {
285  RecordStats(name, GetCurrentStats());
286}
287
288void BootTimesLoader::SaveChromeMainStats() {
289  chrome_main_stats_ = GetCurrentStats();
290}
291
292void BootTimesLoader::RecordChromeMainStats() {
293  RecordStats(kChromeMain, chrome_main_stats_);
294}
295
296void BootTimesLoader::RecordLoginAttempted() {
297  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
298  if (login_done_)
299    return;
300
301  login_time_markers_.clear();
302  AddLoginTimeMarker("LoginStarted", false);
303  if (!have_registered_) {
304    have_registered_ = true;
305    registrar_.Add(this, chrome::NOTIFICATION_LOGIN_AUTHENTICATION,
306                   content::NotificationService::AllSources());
307    registrar_.Add(this, content::NOTIFICATION_LOAD_START,
308                   content::NotificationService::AllSources());
309    registrar_.Add(this, content::NOTIFICATION_LOAD_STOP,
310                   content::NotificationService::AllSources());
311    registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
312                   content::NotificationService::AllSources());
313    registrar_.Add(
314        this,
315        content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
316        content::NotificationService::AllSources());
317  }
318}
319
320void BootTimesLoader::AddLoginTimeMarker(
321    const std::string& marker_name, bool send_to_uma) {
322  AddMarker(&login_time_markers_, TimeMarker(marker_name, send_to_uma));
323}
324
325void BootTimesLoader::AddLogoutTimeMarker(
326    const std::string& marker_name, bool send_to_uma) {
327  AddMarker(&logout_time_markers_, TimeMarker(marker_name, send_to_uma));
328}
329
330// static
331void BootTimesLoader::AddMarker(std::vector<TimeMarker>* vector,
332                                TimeMarker marker)
333{
334  // The marker vectors can only be safely manipulated on the main thread.
335  // If we're late in the process of shutting down (eg. as can be the case at
336  // logout), then we have to assume we're on the main thread already.
337  if (BrowserThread::CurrentlyOn(BrowserThread::UI) ||
338      !BrowserThread::IsMessageLoopValid(BrowserThread::UI)) {
339    vector->push_back(marker);
340  } else {
341    // Add the marker on the UI thread.
342    // Note that it's safe to use an unretained pointer to the vector because
343    // BootTimesLoader's lifetime exceeds that of the UI thread message loop.
344    BrowserThread::PostTask(
345        BrowserThread::UI, FROM_HERE,
346        base::Bind(&BootTimesLoader::AddMarker,
347                   base::Unretained(vector),
348                   marker));
349  }
350}
351
352void BootTimesLoader::Observe(
353    int type,
354    const content::NotificationSource& source,
355    const content::NotificationDetails& details) {
356  switch (type) {
357    case chrome::NOTIFICATION_LOGIN_AUTHENTICATION: {
358      content::Details<AuthenticationNotificationDetails> auth_details(details);
359      if (auth_details->success()) {
360        AddLoginTimeMarker("Authenticate", true);
361        RecordCurrentStats(kLoginSuccess);
362        registrar_.Remove(this, chrome::NOTIFICATION_LOGIN_AUTHENTICATION,
363                          content::NotificationService::AllSources());
364      }
365      break;
366    }
367    case content::NOTIFICATION_LOAD_START: {
368      NavigationController* tab =
369          content::Source<NavigationController>(source).ptr();
370      RenderWidgetHost* rwh = GetRenderWidgetHost(tab);
371      DCHECK(rwh);
372      AddLoginTimeMarker("TabLoad-Start: " + GetTabUrl(rwh), false);
373      render_widget_hosts_loading_.insert(rwh);
374      break;
375    }
376    case content::NOTIFICATION_LOAD_STOP: {
377      NavigationController* tab =
378          content::Source<NavigationController>(source).ptr();
379      RenderWidgetHost* rwh = GetRenderWidgetHost(tab);
380      if (render_widget_hosts_loading_.find(rwh) !=
381          render_widget_hosts_loading_.end()) {
382        AddLoginTimeMarker("TabLoad-End: " + GetTabUrl(rwh), false);
383      }
384      break;
385    }
386    case content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE: {
387      RenderWidgetHost* rwh = content::Source<RenderWidgetHost>(source).ptr();
388      if (render_widget_hosts_loading_.find(rwh) !=
389          render_widget_hosts_loading_.end()) {
390        AddLoginTimeMarker("TabPaint: " + GetTabUrl(rwh), false);
391        LoginDone(UserManager::Get()->IsCurrentUserNew());
392      }
393      break;
394    }
395    case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: {
396      WebContents* web_contents = content::Source<WebContents>(source).ptr();
397      RenderWidgetHost* render_widget_host =
398          GetRenderWidgetHost(&web_contents->GetController());
399      render_widget_hosts_loading_.erase(render_widget_host);
400      break;
401    }
402    default:
403      break;
404  }
405}
406
407}  // namespace chromeos
408