普通文本  |  466行  |  13.27 KB

// 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/process_info_snapshot.h"

#include <sys/sysctl.h>

#include <sstream>

#include "base/command_line.h"
#include "base/logging.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "base/sys_info.h"
#include "base/threading/thread.h"

// Default constructor.
ProcessInfoSnapshot::ProcessInfoSnapshot() { }

// Destructor: just call |Reset()| to release everything.
ProcessInfoSnapshot::~ProcessInfoSnapshot() {
  Reset();
}

const size_t ProcessInfoSnapshot::kMaxPidListSize = 1000;

static bool GetKInfoForProcessID(pid_t pid, kinfo_proc* kinfo) {
  int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
  size_t len = sizeof(*kinfo);
  if (sysctl(mib, arraysize(mib), kinfo, &len, NULL, 0) != 0) {
    PLOG(ERROR) << "sysctl() for KERN_PROC";
    return false;
  }

  if (len == 0) {
    // If the process isn't found then sysctl returns a length of 0.
    return false;
  }

  return true;
}

static bool GetExecutableNameForProcessID(
    pid_t pid,
    std::string* executable_name) {
  if (!executable_name) {
    NOTREACHED();
    return false;
  }

  static int s_arg_max = 0;
  if (s_arg_max == 0) {
    int mib[] = {CTL_KERN, KERN_ARGMAX};
    size_t size = sizeof(s_arg_max);
    if (sysctl(mib, arraysize(mib), &s_arg_max, &size, NULL, 0) != 0)
      PLOG(ERROR) << "sysctl() for KERN_ARGMAX";
  }

  if (s_arg_max == 0)
    return false;

  int mib[] = {CTL_KERN, KERN_PROCARGS, pid};
  size_t size = s_arg_max;
  executable_name->resize(s_arg_max + 1);
  if (sysctl(mib, arraysize(mib), &(*executable_name)[0],
             &size, NULL, 0) != 0) {
    // Don't log the error since it's normal for this to fail.
    return false;
  }

  // KERN_PROCARGS returns multiple NULL terminated strings. Truncate
  // executable_name to just the first string.
  size_t end_pos = executable_name->find('\0');
  if (end_pos == std::string::npos) {
    return false;
  }

  executable_name->resize(end_pos);
  return true;
}

// Converts a byte unit such as 'K' or 'M' into the scale for the unit.
// The scale can then be used to calculate the number of bytes in a value.
// The units are based on humanize_number(). See:
// http://www.opensource.apple.com/source/libutil/libutil-21/humanize_number.c
static bool ConvertByteUnitToScale(char unit, uint64_t* out_scale) {
  int shift = 0;
  switch (unit) {
    case 'B':
      shift = 0;
      break;
    case 'K':
    case 'k':
      shift = 1;
      break;
    case 'M':
      shift = 2;
      break;
    case 'G':
      shift = 3;
      break;
    case 'T':
      shift = 4;
      break;
    case 'P':
      shift = 5;
      break;
    case 'E':
      shift = 6;
      break;
    default:
      return false;
  }

  uint64_t scale = 1;
  for (int i = 0; i < shift; i++)
    scale *= 1024;
  *out_scale = scale;

  return true;
}

// Capture the information by calling '/bin/ps'.
// Note: we ignore the "tsiz" (text size) display option of ps because it's
// always zero (tested on 10.5 and 10.6).
static bool GetProcessMemoryInfoUsingPS(
    const std::vector<base::ProcessId>& pid_list,
    std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) {
  const char kPsPathName[] = "/bin/ps";
  std::vector<std::string> argv;
  argv.push_back(kPsPathName);

  // Get resident set size, virtual memory size.
  argv.push_back("-o");
  argv.push_back("pid=,rss=,vsz=");
  // Only display the specified PIDs.
  for (std::vector<base::ProcessId>::const_iterator it = pid_list.begin();
      it != pid_list.end(); ++it) {
    argv.push_back("-p");
    argv.push_back(base::Int64ToString(static_cast<int64>(*it)));
  }

  std::string output;
  CommandLine command_line(argv);
  // Limit output read to a megabyte for safety.
  if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) {
    LOG(ERROR) << "Failure running " << kPsPathName << " to acquire data.";
    return false;
  }

  std::istringstream in(output, std::istringstream::in);
  std::string line;

  // Process lines until done.
  while (true) {
    // The format is as specified above to ps (see ps(1)):
    //   "-o pid=,rss=,vsz=".
    // Try to read the PID; if we get it, we should be able to get the rest of
    // the line.
    pid_t pid;
    in >> pid;
    if (in.eof())
      break;

    ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid];
    proc_info.pid = pid;
    in >> proc_info.rss;
    in >> proc_info.vsize;
    proc_info.rss *= 1024;                // Convert from kilobytes to bytes.
    proc_info.vsize *= 1024;
    in.ignore(1, ' ');                    // Eat the space.
    std::getline(in, proc_info.command);  // Get the rest of the line.
    if (!in.good()) {
      LOG(ERROR) << "Error parsing output from " << kPsPathName << ".";
      return false;
    }

    if (!proc_info.pid || ! proc_info.vsize) {
      LOG(WARNING) << "Invalid data from " << kPsPathName << ".";
      return false;
    }

    // Record the process information.
    proc_info_entries[proc_info.pid] = proc_info;
  }

  return true;
}

static bool GetProcessMemoryInfoUsingTop(
    std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) {
  const char kTopPathName[] = "/usr/bin/top";
  std::vector<std::string> argv;
  argv.push_back(kTopPathName);

  // -stats tells top to print just the given fields as ordered.
  argv.push_back("-stats");
  argv.push_back("pid,"    // Process ID
                 "rsize,"  // Resident memory
                 "rshrd,"  // Resident shared memory
                 "rprvt,"  // Resident private memory
                 "vsize"); // Total virtual memory
  // Run top in logging (non-interactive) mode.
  argv.push_back("-l");
  argv.push_back("1");
  // Set the delay between updates to 0.
  argv.push_back("-s");
  argv.push_back("0");

  std::string output;
  CommandLine command_line(argv);
  // Limit output read to a megabyte for safety.
  if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) {
    LOG(ERROR) << "Failure running " << kTopPathName << " to acquire data.";
    return false;
  }

  // Process lines until done. Lines should look something like this:
  // PID    RSIZE  RSHRD  RPRVT  VSIZE
  // 58539  1276K+ 336K+  740K+  2378M+
  // 58485  1888K+ 592K+  1332K+ 2383M+
  std::istringstream top_in(output, std::istringstream::in);
  std::string line;
  while (std::getline(top_in, line)) {
    std::istringstream in(line, std::istringstream::in);

    // Try to read the PID.
    pid_t pid;
    in >> pid;
    if (in.fail())
      continue;

    // Make sure that caller is interested in this process.
    if (proc_info_entries.find(pid) == proc_info_entries.end())
      continue;

    // Skip the - or + sign that top puts after the pid.
    in.get();

    uint64_t values[4];
    size_t i;
    for (i = 0; i < arraysize(values); i++) {
      in >> values[i];
      if (in.fail())
        break;
      std::string unit;
      in >> unit;
      if (in.fail())
        break;

      if (unit.empty())
        break;

      uint64_t scale;
      if (!ConvertByteUnitToScale(unit[0], &scale))
        break;
      values[i] *= scale;
    }
    if (i != arraysize(values))
      continue;

    ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid];
    proc_info.rss = values[0];
    proc_info.rshrd = values[1];
    proc_info.rprvt = values[2];
    proc_info.vsize = values[3];
    // Record the process information.
    proc_info_entries[proc_info.pid] = proc_info;
  }

  return true;
}

static bool GetProcessMemoryInfoUsingTop_10_5(
    std::map<int,ProcessInfoSnapshot::ProcInfoEntry>& proc_info_entries) {
  const char kTopPathName[] = "/usr/bin/top";
  std::vector<std::string> argv;
  argv.push_back(kTopPathName);

  // -p tells top to print just the given fields as ordered.
  argv.push_back("-p");
  argv.push_back("^aaaaaaaaaaaaaaaaaaaa "  // Process ID (PID)
                 "^jjjjjjjjjjjjjjjjjjjj "  // Resident memory (RSIZE)
                 "^iiiiiiiiiiiiiiiiiiii "  // Resident shared memory (RSHRD)
                 "^hhhhhhhhhhhhhhhhhhhh "  // Resident private memory (RPRVT)
                 "^llllllllllllllllllll"); // Total virtual memory (VSIZE)
  // Run top in logging (non-interactive) mode.
  argv.push_back("-l");
  argv.push_back("1");
  // Set the delay between updates to 0.
  argv.push_back("-s");
  argv.push_back("0");

  std::string output;
  CommandLine command_line(argv);
  // Limit output read to a megabyte for safety.
  if (!base::GetAppOutputRestricted(command_line, &output, 1024 * 1024)) {
    LOG(ERROR) << "Failure running " << kTopPathName << " to acquire data.";
    return false;
  }

  // Process lines until done. Lines should look something like this:
  // PID      RSIZE     RSHRD     RPRVT     VSIZE
  // 16943    815104    262144    290816    18489344
  // 16922    954368    720896    278528    18976768
  std::istringstream top_in(output, std::istringstream::in);
  std::string line;
  while (std::getline(top_in, line)) {
    std::istringstream in(line, std::istringstream::in);

    // Try to read the PID.
    pid_t pid;
    in >> pid;
    if (in.fail())
      continue;

    // Make sure that caller is interested in this process.
    if (proc_info_entries.find(pid) == proc_info_entries.end())
      continue;

    uint64_t values[4];
    size_t i;
    for (i = 0; i < arraysize(values); i++) {
      in >> values[i];
      if (in.fail())
        break;
    }
    if (i != arraysize(values))
      continue;

    ProcessInfoSnapshot::ProcInfoEntry proc_info = proc_info_entries[pid];
    proc_info.rss = values[0];
    proc_info.rshrd = values[1];
    proc_info.rprvt = values[2];
    proc_info.vsize = values[3];
    // Record the process information.
    proc_info_entries[proc_info.pid] = proc_info;
  }

  return true;
}


bool ProcessInfoSnapshot::Sample(std::vector<base::ProcessId> pid_list) {
  Reset();

  // Nothing to do if no PIDs given.
  if (pid_list.empty())
    return true;
  if (pid_list.size() > kMaxPidListSize) {
    // The spec says |pid_list| *must* not have more than this many entries.
    NOTREACHED();
    return false;
  }

  // Get basic process info from KERN_PROC.
  for (std::vector<base::ProcessId>::iterator it = pid_list.begin();
       it != pid_list.end(); ++it) {
    ProcInfoEntry proc_info;
    proc_info.pid = *it;

    kinfo_proc kinfo;
    if (!GetKInfoForProcessID(*it, &kinfo))
      return false;

    proc_info.ppid = kinfo.kp_eproc.e_ppid;
    proc_info.uid = kinfo.kp_eproc.e_pcred.p_ruid;
    proc_info.euid = kinfo.kp_eproc.e_ucred.cr_uid;
    // Note, p_comm is truncated to 16 characters.
    proc_info.command = kinfo.kp_proc.p_comm;
    proc_info_entries_[*it] = proc_info;
  }

  // Use KERN_PROCARGS to get the full executable name. This may fail if this
  // process doesn't have privileges to inspect the target process.
  for (std::vector<base::ProcessId>::iterator it = pid_list.begin();
       it != pid_list.end(); ++it) {
    std::string exectuable_name;
    if (GetExecutableNameForProcessID(*it, &exectuable_name)) {
      ProcInfoEntry proc_info = proc_info_entries_[*it];
      proc_info.command = exectuable_name;
    }
  }

  // Get memory information using top.
  bool memory_info_success = false;
  int32 major, minor, bugfix;
  base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix);
  if (major == 10 && minor == 5)
    memory_info_success = GetProcessMemoryInfoUsingTop_10_5(proc_info_entries_);
  else if ((major == 10 && minor >= 6) || major > 10)
    memory_info_success = GetProcessMemoryInfoUsingTop(proc_info_entries_);

  // If top didn't work then fall back to ps.
  if (!memory_info_success) {
    memory_info_success = GetProcessMemoryInfoUsingPS(pid_list,
                                                      proc_info_entries_);
  }

  return memory_info_success;
}

// Clear all the stored information.
void ProcessInfoSnapshot::Reset() {
  proc_info_entries_.clear();
}

bool ProcessInfoSnapshot::GetProcInfo(int pid,
                                      ProcInfoEntry* proc_info) const {
  std::map<int,ProcInfoEntry>::const_iterator it = proc_info_entries_.find(pid);
  if (it == proc_info_entries_.end())
    return false;

  *proc_info = it->second;
  return true;
}

bool ProcessInfoSnapshot::GetCommittedKBytesOfPID(
    int pid,
    base::CommittedKBytes* usage) const {
  // Try to avoid crashing on a bug; stats aren't usually so crucial.
  if (!usage) {
    NOTREACHED();
    return false;
  }

  // Failure of |GetProcInfo()| is "normal", due to racing.
  ProcInfoEntry proc_info;
  if (!GetProcInfo(pid, &proc_info)) {
    usage->priv = 0;
    usage->mapped = 0;
    usage->image = 0;
    return false;
  }

  usage->priv = proc_info.vsize / 1024;
  usage->mapped = 0;
  usage->image = 0;
  return true;
}

bool ProcessInfoSnapshot::GetWorkingSetKBytesOfPID(
    int pid,
    base::WorkingSetKBytes* ws_usage) const {
  // Try to avoid crashing on a bug; stats aren't usually so crucial.
  if (!ws_usage) {
    NOTREACHED();
    return false;
  }

  // Failure of |GetProcInfo()| is "normal", due to racing.
  ProcInfoEntry proc_info;
  if (!GetProcInfo(pid, &proc_info)) {
    ws_usage->priv = 0;
    ws_usage->shareable = 0;
    ws_usage->shared = 0;
    return false;
  }

  ws_usage->priv = proc_info.rprvt / 1024;
  ws_usage->shareable = proc_info.rss / 1024;
  ws_usage->shared = proc_info.rshrd / 1024;
  return true;
}