// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/boot_times_loader.h"

#include <vector>

#include "base/command_line.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/message_loop.h"
#include "base/process_util.h"
#include "base/string_util.h"
#include "base/thread.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/chrome_switches.h"

namespace chromeos {

// Beginning of line we look for that gives version number.
static const char kPrefix[] = "CHROMEOS_RELEASE_DESCRIPTION=";

// File to look for version number in.
static const char kVersionPath[] = "/etc/lsb-release";

// File uptime logs are located in.
static const char kLogPath[] = "/tmp";

BootTimesLoader::BootTimesLoader() : backend_(new Backend()) {
}

BootTimesLoader::Handle BootTimesLoader::GetBootTimes(
    CancelableRequestConsumerBase* consumer,
    BootTimesLoader::GetBootTimesCallback* callback) {
  if (!g_browser_process->file_thread()) {
    // This should only happen if Chrome is shutting down, so we don't do
    // anything.
    return 0;
  }

  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
  if (command_line.HasSwitch(switches::kTestType)) {
    // TODO(davemoore) This avoids boottimes for tests. This needs to be
    // replaced with a mock of BootTimesLoader.
    return 0;
  }

  scoped_refptr<CancelableRequest<GetBootTimesCallback> > request(
      new CancelableRequest<GetBootTimesCallback>(callback));
  AddRequest(request, consumer);

  g_browser_process->file_thread()->message_loop()->PostTask(
      FROM_HERE,
      NewRunnableMethod(backend_.get(), &Backend::GetBootTimes, request));
  return request->handle();
}

// Executes command within a shell, allowing IO redirection. Searches
// for a whitespace delimited string prefixed by prefix in the output and
// returns it.
static std::string ExecuteInShell(
    const std::string& command, const std::string& prefix) {
  std::vector<std::string> args;
  args.push_back("/bin/sh");
  args.push_back("-c");
  args.push_back(command);
  CommandLine cmd(args);
  std::string out;

  if (base::GetAppOutput(cmd, &out)) {
    size_t index = out.find(prefix);
    if (index != std::string::npos) {
      size_t value_index = index + prefix.size();
      size_t whitespace_index = out.find(' ', value_index);
      size_t chars_left = std::string::npos;
      if (whitespace_index == std::string::npos)
        whitespace_index = out.find('\n', value_index);
      if (whitespace_index != std::string::npos)
        chars_left = whitespace_index - value_index;
      return out.substr(value_index, chars_left);
    }
  }
  return std::string();
}

// Extracts the uptime value from files located in /tmp, returning the
// value as a double in value.
static bool GetUptime(const std::string& log, double* value) {
  FilePath log_dir(kLogPath);
  FilePath log_file = log_dir.Append(log);
  std::string contents;
  *value = 0.0;
  if (file_util::ReadFileToString(log_file, &contents)) {
    size_t space_index = contents.find(' ');
    size_t chars_left =
        space_index != std::string::npos ? space_index : std::string::npos;
    std::string value_string = contents.substr(0, chars_left);
    return StringToDouble(value_string, value);
  }
  return false;
}

void BootTimesLoader::Backend::GetBootTimes(
    scoped_refptr<GetBootTimesRequest> request) {
  const char* kInitialTSCCommand = "dmesg | grep 'Initial TSC value:'";
  const char* kInitialTSCPrefix = "TSC value: ";
  const char* kClockSpeedCommand = "dmesg | grep -e 'Detected.*processor'";
  const char* kClockSpeedPrefix = "Detected ";
  const char* kPreStartup = "uptime-pre-startup";
  const char* kChromeExec = "uptime-chrome-exec";
  const char* kChromeMain = "uptime-chrome-main";
  const char* kXStarted = "uptime-x-started";
  const char* kLoginPromptReady = "uptime-login-prompt-ready";

  if (request->canceled())
    return;

  // Wait until login_prompt_ready is output.
  FilePath log_dir(kLogPath);
  FilePath log_file = log_dir.Append(kLoginPromptReady);
  while (!file_util::PathExists(log_file)) {
    usleep(500000);
  }

  BootTimes boot_times;
  std::string tsc_value = ExecuteInShell(kInitialTSCCommand, kInitialTSCPrefix);
  std::string speed_value =
      ExecuteInShell(kClockSpeedCommand, kClockSpeedPrefix);
  boot_times.firmware = 0;
  if (!tsc_value.empty() && !speed_value.empty()) {
    int64 tsc = 0;
    double speed = 0;
    if (StringToInt64(tsc_value, &tsc) &&
        StringToDouble(speed_value, &speed) &&
        speed > 0) {
      boot_times.firmware = static_cast<double>(tsc) / (speed * 1000000);
    }
  }
  GetUptime(kPreStartup, &boot_times.pre_startup);
  GetUptime(kXStarted, &boot_times.x_started);
  GetUptime(kChromeExec, &boot_times.chrome_exec);
  GetUptime(kChromeMain, &boot_times.chrome_main);
  GetUptime(kLoginPromptReady, &boot_times.login_prompt_ready);

  request->ForwardResult(
      GetBootTimesCallback::TupleType(request->handle(), boot_times));
}

}  // namespace chromeos