// Copyright (c) 2011 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/test/base/chrome_process_util.h"

#include <set>
#include <string>
#include <vector>

#include "base/command_line.h"
#include "base/process/kill.h"
#include "base/process/process_iterator.h"
#include "base/time/time.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/test/base/test_switches.h"
#include "content/public/common/result_codes.h"

using base::TimeDelta;
using base::TimeTicks;

namespace {

// Returns the executable name of the current Chrome helper process.
std::vector<base::FilePath::StringType> GetRunningHelperExecutableNames() {
  base::FilePath::StringType name = chrome::kHelperProcessExecutableName;

  std::vector<base::FilePath::StringType> names;
  names.push_back(name);

#if defined(OS_MACOSX)
  // The helper might show up as these different flavors depending on the
  // executable flags required.
  for (const char* const* suffix = chrome::kHelperFlavorSuffixes;
       *suffix;
       ++suffix) {
    std::string flavor_name(name);
    flavor_name.append(1, ' ');
    flavor_name.append(*suffix);
    names.push_back(flavor_name);
  }
#endif

  return names;
}

}  // namespace

void TerminateAllChromeProcesses(const ChromeProcessList& process_pids) {
  ChromeProcessList::const_iterator it;
  for (it = process_pids.begin(); it != process_pids.end(); ++it) {
    base::ProcessHandle handle;
    if (!base::OpenProcessHandle(*it, &handle)) {
      // Ignore processes for which we can't open the handle. We don't
      // guarantee that all processes will terminate, only try to do so.
      continue;
    }

    base::KillProcess(handle, content::RESULT_CODE_KILLED, true);
    base::CloseProcessHandle(handle);
  }
}

class ChildProcessFilter : public base::ProcessFilter {
 public:
  explicit ChildProcessFilter(base::ProcessId parent_pid)
      : parent_pids_(&parent_pid, (&parent_pid) + 1) {}

  explicit ChildProcessFilter(const std::vector<base::ProcessId>& parent_pids)
      : parent_pids_(parent_pids.begin(), parent_pids.end()) {}

  virtual bool Includes(const base::ProcessEntry& entry) const OVERRIDE {
    return parent_pids_.find(entry.parent_pid()) != parent_pids_.end();
  }

 private:
  const std::set<base::ProcessId> parent_pids_;

  DISALLOW_COPY_AND_ASSIGN(ChildProcessFilter);
};

ChromeProcessList GetRunningChromeProcesses(base::ProcessId browser_pid) {
  const base::FilePath::CharType* executable_name =
      chrome::kBrowserProcessExecutableName;
  ChromeProcessList result;
  if (browser_pid == static_cast<base::ProcessId>(-1))
    return result;

  ChildProcessFilter filter(browser_pid);
  base::NamedProcessIterator it(executable_name, &filter);
  while (const base::ProcessEntry* process_entry = it.NextProcessEntry()) {
    result.push_back(process_entry->pid());
  }

#if defined(OS_POSIX) && !defined(OS_MACOSX)
  // On Unix we might be running with a zygote process for the renderers.
  // Because of that we sweep the list of processes again and pick those which
  // are children of one of the processes that we've already seen.
  {
    ChildProcessFilter filter(result);
    base::NamedProcessIterator it(executable_name, &filter);
    while (const base::ProcessEntry* process_entry = it.NextProcessEntry())
      result.push_back(process_entry->pid());
  }
#endif  // defined(OS_POSIX) && !defined(OS_MACOSX)

#if defined(OS_POSIX)
  // On Mac OS X we run the subprocesses with a different bundle, and
  // on Linux via /proc/self/exe, so they end up with a different
  // name.  We must collect them in a second pass.
  {
    std::vector<base::FilePath::StringType> names =
        GetRunningHelperExecutableNames();
    for (size_t i = 0; i < names.size(); ++i) {
      base::FilePath::StringType name = names[i];
      ChildProcessFilter filter(browser_pid);
      base::NamedProcessIterator it(name, &filter);
      while (const base::ProcessEntry* process_entry = it.NextProcessEntry())
        result.push_back(process_entry->pid());
    }
  }
#endif  // defined(OS_POSIX)

  result.push_back(browser_pid);

  return result;
}

#if !defined(OS_MACOSX)

size_t ChromeTestProcessMetrics::GetPagefileUsage() {
  return process_metrics_->GetPagefileUsage();
}

size_t ChromeTestProcessMetrics::GetWorkingSetSize() {
  return process_metrics_->GetWorkingSetSize();
}

#endif  // !defined(OS_MACOSX)

ChromeTestProcessMetrics::~ChromeTestProcessMetrics() {}

ChromeTestProcessMetrics::ChromeTestProcessMetrics(
    base::ProcessHandle process) {
#if !defined(OS_MACOSX)
  process_metrics_.reset(
      base::ProcessMetrics::CreateProcessMetrics(process));
#else
  process_metrics_.reset(
      base::ProcessMetrics::CreateProcessMetrics(process, NULL));
#endif
  process_handle_ = process;
}