diff options
author | rsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-23 12:59:54 +0000 |
---|---|---|
committer | rsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-23 12:59:54 +0000 |
commit | 32f5e9a0783c3d03ec2351401d8c4100c153a499 (patch) | |
tree | 2a0d58988e8c1429380500961d5f20ce08dc5c60 /base/process | |
parent | 2ac90b1f8eff5bf3788f258bd8fbbcaaa023df91 (diff) | |
download | chromium_src-32f5e9a0783c3d03ec2351401d8c4100c153a499.zip chromium_src-32f5e9a0783c3d03ec2351401d8c4100c153a499.tar.gz chromium_src-32f5e9a0783c3d03ec2351401d8c4100c153a499.tar.bz2 |
Split ProcessMetrics out of base/process_util.h and into base/process/process_metrics.h.
BUG=242290
R=brettw@chromium.org
Review URL: https://codereview.chromium.org/15564006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@201775 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/process')
-rw-r--r-- | base/process/internal_linux.cc | 142 | ||||
-rw-r--r-- | base/process/internal_linux.h | 78 | ||||
-rw-r--r-- | base/process/process_metrics.h | 234 | ||||
-rw-r--r-- | base/process/process_metrics_freebsd.cc | 122 | ||||
-rw-r--r-- | base/process/process_metrics_ios.cc | 47 | ||||
-rw-r--r-- | base/process/process_metrics_linux.cc | 439 | ||||
-rw-r--r-- | base/process/process_metrics_mac.cc | 325 | ||||
-rw-r--r-- | base/process/process_metrics_openbsd.cc | 168 | ||||
-rw-r--r-- | base/process/process_metrics_posix.cc | 21 | ||||
-rw-r--r-- | base/process/process_metrics_win.cc | 315 |
10 files changed, 1891 insertions, 0 deletions
diff --git a/base/process/internal_linux.cc b/base/process/internal_linux.cc new file mode 100644 index 0000000..c1fcaef --- /dev/null +++ b/base/process/internal_linux.cc @@ -0,0 +1,142 @@ +// Copyright (c) 2012 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 "base/process/internal_linux.h" + +#include <unistd.h> + +#include <string> +#include <vector> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/threading/thread_restrictions.h" + +namespace base { +namespace internal { + +const char kProcDir[] = "/proc"; + +const char kStatFile[] = "stat"; + +base::FilePath GetProcPidDir(pid_t pid) { + return base::FilePath(kProcDir).Append(IntToString(pid)); +} + +pid_t ProcDirSlotToPid(const char* d_name) { + int i; + for (i = 0; i < NAME_MAX && d_name[i]; ++i) { + if (!IsAsciiDigit(d_name[i])) { + return 0; + } + } + if (i == NAME_MAX) + return 0; + + // Read the process's command line. + pid_t pid; + std::string pid_string(d_name); + if (!StringToInt(pid_string, &pid)) { + NOTREACHED(); + return 0; + } + return pid; +} + +bool ReadProcStats(pid_t pid, std::string* buffer) { + buffer->clear(); + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + + FilePath stat_file = internal::GetProcPidDir(pid).Append(kStatFile); + if (!file_util::ReadFileToString(stat_file, buffer)) { + DLOG(WARNING) << "Failed to get process stats."; + return false; + } + return !buffer->empty(); +} + +bool ParseProcStats(const std::string& stats_data, + std::vector<std::string>* proc_stats) { + // |stats_data| may be empty if the process disappeared somehow. + // e.g. http://crbug.com/145811 + if (stats_data.empty()) + return false; + + // The stat file is formatted as: + // pid (process name) data1 data2 .... dataN + // Look for the closing paren by scanning backwards, to avoid being fooled by + // processes with ')' in the name. + size_t open_parens_idx = stats_data.find(" ("); + size_t close_parens_idx = stats_data.rfind(") "); + if (open_parens_idx == std::string::npos || + close_parens_idx == std::string::npos || + open_parens_idx > close_parens_idx) { + DLOG(WARNING) << "Failed to find matched parens in '" << stats_data << "'"; + NOTREACHED(); + return false; + } + open_parens_idx++; + + proc_stats->clear(); + // PID. + proc_stats->push_back(stats_data.substr(0, open_parens_idx)); + // Process name without parentheses. + proc_stats->push_back( + stats_data.substr(open_parens_idx + 1, + close_parens_idx - (open_parens_idx + 1))); + + // Split the rest. + std::vector<std::string> other_stats; + SplitString(stats_data.substr(close_parens_idx + 2), ' ', &other_stats); + for (size_t i = 0; i < other_stats.size(); ++i) + proc_stats->push_back(other_stats[i]); + return true; +} + +int GetProcStatsFieldAsInt(const std::vector<std::string>& proc_stats, + ProcStatsFields field_num) { + DCHECK_GE(field_num, VM_PPID); + CHECK_LT(static_cast<size_t>(field_num), proc_stats.size()); + + int value; + return StringToInt(proc_stats[field_num], &value) ? value : 0; +} + +size_t GetProcStatsFieldAsSizeT(const std::vector<std::string>& proc_stats, + ProcStatsFields field_num) { + DCHECK_GE(field_num, VM_PPID); + CHECK_LT(static_cast<size_t>(field_num), proc_stats.size()); + + size_t value; + return StringToSizeT(proc_stats[field_num], &value) ? value : 0; +} + +int ReadProcStatsAndGetFieldAsInt(pid_t pid, + ProcStatsFields field_num) { + std::string stats_data; + if (!ReadProcStats(pid, &stats_data)) + return 0; + std::vector<std::string> proc_stats; + if (!ParseProcStats(stats_data, &proc_stats)) + return 0; + return GetProcStatsFieldAsInt(proc_stats, field_num); +} + +size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid, + ProcStatsFields field_num) { + std::string stats_data; + if (!ReadProcStats(pid, &stats_data)) + return 0; + std::vector<std::string> proc_stats; + if (!ParseProcStats(stats_data, &proc_stats)) + return 0; + return GetProcStatsFieldAsSizeT(proc_stats, field_num); +} + +} // namespace internal +} // namespace base diff --git a/base/process/internal_linux.h b/base/process/internal_linux.h new file mode 100644 index 0000000..88d32ee --- /dev/null +++ b/base/process/internal_linux.h @@ -0,0 +1,78 @@ +// Copyright (c) 2013 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. + +// This file contains internal routines that are called by other files in +// base/process/. + +#ifndef BASE_PROCESS_LINUX_INTERNAL_H_ +#define BASE_PROCESS_LINUX_INTERNAL_H_ + +#include "base/files/file_path.h" + +namespace base { +namespace internal { + +// "/proc" +extern const char kProcDir[]; + +// "stat" +extern const char kStatFile[]; + +// Returns a FilePath to "/proc/pid". +base::FilePath GetProcPidDir(pid_t pid); + +// Take a /proc directory entry named |d_name|, and if it is the directory for +// a process, convert it to a pid_t. +// Returns 0 on failure. +// e.g. /proc/self/ will return 0, whereas /proc/1234 will return 1234. +pid_t ProcDirSlotToPid(const char* d_name); + +// Reads /proc/<pid>/stat into |buffer|. Returns true if the file can be read +// and is non-empty. +bool ReadProcStats(pid_t pid, std::string* buffer); + +// Takes |stats_data| and populates |proc_stats| with the values split by +// spaces. Taking into account the 2nd field may, in itself, contain spaces. +// Returns true if successful. +bool ParseProcStats(const std::string& stats_data, + std::vector<std::string>* proc_stats); + +// Fields from /proc/<pid>/stat, 0-based. See man 5 proc. +// If the ordering ever changes, carefully review functions that use these +// values. +enum ProcStatsFields { + VM_COMM = 1, // Filename of executable, without parentheses. + VM_STATE = 2, // Letter indicating the state of the process. + VM_PPID = 3, // PID of the parent. + VM_PGRP = 4, // Process group id. + VM_UTIME = 13, // Time scheduled in user mode in clock ticks. + VM_STIME = 14, // Time scheduled in kernel mode in clock ticks. + VM_NUMTHREADS = 19, // Number of threads. + VM_VSIZE = 22, // Virtual memory size in bytes. + VM_RSS = 23, // Resident Set Size in pages. +}; + +// Reads the |field_num|th field from |proc_stats|. Returns 0 on failure. +// This version does not handle the first 3 values, since the first value is +// simply |pid|, and the next two values are strings. +int GetProcStatsFieldAsInt(const std::vector<std::string>& proc_stats, + ProcStatsFields field_num); + +// Same as GetProcStatsFieldAsInt(), but for size_t values. +size_t GetProcStatsFieldAsSizeT(const std::vector<std::string>& proc_stats, + ProcStatsFields field_num); + +// Convenience wrapper around GetProcStatsFieldAsInt(), ParseProcStats() and +// ReadProcStats(). See GetProcStatsFieldAsInt() for details. +int ReadProcStatsAndGetFieldAsInt(pid_t pid, + ProcStatsFields field_num); + +// Same as ReadProcStatsAndGetFieldAsInt() but for size_t values. +size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid, + ProcStatsFields field_num); + +} // namespace internal +} // namespace base + +#endif // BASE_PROCESS_LINUX_INTERNAL_H_ diff --git a/base/process/process_metrics.h b/base/process/process_metrics.h new file mode 100644 index 0000000..4acc354 --- /dev/null +++ b/base/process/process_metrics.h @@ -0,0 +1,234 @@ +// Copyright (c) 2013 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. + +// This file contains routines for gathering resource statistics for processes +// running on the system. + +#ifndef BASE_PROCESS_PROCESS_METRICS_H_ +#define BASE_PROCESS_PROCESS_METRICS_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/process.h" +#include "base/time.h" + +#if defined(OS_MACOSX) +#include <mach/mach.h> +#endif + +namespace base { + +#if defined(OS_WIN) +struct IoCounters : public IO_COUNTERS { +}; +#elif defined(OS_POSIX) +struct IoCounters { + uint64_t ReadOperationCount; + uint64_t WriteOperationCount; + uint64_t OtherOperationCount; + uint64_t ReadTransferCount; + uint64_t WriteTransferCount; + uint64_t OtherTransferCount; +}; +#endif + +// Working Set (resident) memory usage broken down by +// +// On Windows: +// priv (private): These pages (kbytes) cannot be shared with any other process. +// shareable: These pages (kbytes) can be shared with other processes under +// the right circumstances. +// shared : These pages (kbytes) are currently shared with at least one +// other process. +// +// On Linux: +// priv: Pages mapped only by this process +// shared: PSS or 0 if the kernel doesn't support this +// shareable: 0 +// +// On OS X: TODO(thakis): Revise. +// priv: Memory. +// shared: 0 +// shareable: 0 +struct WorkingSetKBytes { + WorkingSetKBytes() : priv(0), shareable(0), shared(0) {} + size_t priv; + size_t shareable; + size_t shared; +}; + +// Committed (resident + paged) memory usage broken down by +// private: These pages cannot be shared with any other process. +// mapped: These pages are mapped into the view of a section (backed by +// pagefile.sys) +// image: These pages are mapped into the view of an image section (backed by +// file system) +struct CommittedKBytes { + CommittedKBytes() : priv(0), mapped(0), image(0) {} + size_t priv; + size_t mapped; + size_t image; +}; + +// Free memory (Megabytes marked as free) in the 2G process address space. +// total : total amount in megabytes marked as free. Maximum value is 2048. +// largest : size of the largest contiguous amount of memory found. It is +// always smaller or equal to FreeMBytes::total. +// largest_ptr: starting address of the largest memory block. +struct FreeMBytes { + size_t total; + size_t largest; + void* largest_ptr; +}; + +// Convert a POSIX timeval to microseconds. +BASE_EXPORT int64 TimeValToMicroseconds(const struct timeval& tv); + +// Provides performance metrics for a specified process (CPU usage, memory and +// IO counters). To use it, invoke CreateProcessMetrics() to get an instance +// for a specific process, then access the information with the different get +// methods. +class BASE_EXPORT ProcessMetrics { + public: + ~ProcessMetrics(); + + // Creates a ProcessMetrics for the specified process. + // The caller owns the returned object. +#if !defined(OS_MACOSX) || defined(OS_IOS) + static ProcessMetrics* CreateProcessMetrics(ProcessHandle process); +#else + class PortProvider { + public: + virtual ~PortProvider() {} + + // Should return the mach task for |process| if possible, or else + // |MACH_PORT_NULL|. Only processes that this returns tasks for will have + // metrics on OS X (except for the current process, which always gets + // metrics). + virtual mach_port_t TaskForPid(ProcessHandle process) const = 0; + }; + + // The port provider needs to outlive the ProcessMetrics object returned by + // this function. If NULL is passed as provider, the returned object + // only returns valid metrics if |process| is the current process. + static ProcessMetrics* CreateProcessMetrics(ProcessHandle process, + PortProvider* port_provider); +#endif // !defined(OS_MACOSX) || defined(OS_IOS) + + // Returns the current space allocated for the pagefile, in bytes (these pages + // may or may not be in memory). On Linux, this returns the total virtual + // memory size. + size_t GetPagefileUsage() const; + // Returns the peak space allocated for the pagefile, in bytes. + size_t GetPeakPagefileUsage() const; + // Returns the current working set size, in bytes. On Linux, this returns + // the resident set size. + size_t GetWorkingSetSize() const; + // Returns the peak working set size, in bytes. + size_t GetPeakWorkingSetSize() const; + // Returns private and sharedusage, in bytes. Private bytes is the amount of + // memory currently allocated to a process that cannot be shared. Returns + // false on platform specific error conditions. Note: |private_bytes| + // returns 0 on unsupported OSes: prior to XP SP2. + bool GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes); + // Fills a CommittedKBytes with both resident and paged + // memory usage as per definition of CommittedBytes. + void GetCommittedKBytes(CommittedKBytes* usage) const; + // Fills a WorkingSetKBytes containing resident private and shared memory + // usage in bytes, as per definition of WorkingSetBytes. + bool GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const; + + // Computes the current process available memory for allocation. + // It does a linear scan of the address space querying each memory region + // for its free (unallocated) status. It is useful for estimating the memory + // load and fragmentation. + bool CalculateFreeMemory(FreeMBytes* free) const; + + // Returns the CPU usage in percent since the last time this method was + // called. The first time this method is called it returns 0 and will return + // the actual CPU info on subsequent calls. + // On Windows, the CPU usage value is for all CPUs. So if you have 2 CPUs and + // your process is using all the cycles of 1 CPU and not the other CPU, this + // method returns 50. + double GetCPUUsage(); + + // Retrieves accounting information for all I/O operations performed by the + // process. + // If IO information is retrieved successfully, the function returns true + // and fills in the IO_COUNTERS passed in. The function returns false + // otherwise. + bool GetIOCounters(IoCounters* io_counters) const; + + private: +#if !defined(OS_MACOSX) || defined(OS_IOS) + explicit ProcessMetrics(ProcessHandle process); +#else + ProcessMetrics(ProcessHandle process, PortProvider* port_provider); +#endif // !defined(OS_MACOSX) || defined(OS_IOS) + + ProcessHandle process_; + + int processor_count_; + + // Used to store the previous times and CPU usage counts so we can + // compute the CPU usage between calls. + int64 last_time_; + int64 last_system_time_; + +#if !defined(OS_IOS) +#if defined(OS_MACOSX) + // Queries the port provider if it's set. + mach_port_t TaskForPid(ProcessHandle process) const; + + PortProvider* port_provider_; +#elif defined(OS_POSIX) + // Jiffie count at the last_time_ we updated. + int last_cpu_; +#endif // defined(OS_POSIX) +#endif // !defined(OS_IOS) + + DISALLOW_COPY_AND_ASSIGN(ProcessMetrics); +}; + +// Returns the memory committed by the system in KBytes. +// Returns 0 if it can't compute the commit charge. +BASE_EXPORT size_t GetSystemCommitCharge(); + +#if defined(OS_LINUX) || defined(OS_ANDROID) +// Parse the data found in /proc/<pid>/stat and return the sum of the +// CPU-related ticks. Returns -1 on parse error. +// Exposed for testing. +BASE_EXPORT int ParseProcStatCPU(const std::string& input); + +// Data from /proc/meminfo about system-wide memory consumption. +// Values are in KB. +struct BASE_EXPORT SystemMemoryInfoKB { + SystemMemoryInfoKB(); + + int total; + int free; + int buffers; + int cached; + int active_anon; + int inactive_anon; + int active_file; + int inactive_file; + int shmem; + + // Gem data will be -1 if not supported. + int gem_objects; + long long gem_size; +}; +// Retrieves data from /proc/meminfo about system-wide memory consumption. +// Fills in the provided |meminfo| structure. Returns true on success. +// Exposed for memory debugging widget. +BASE_EXPORT bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo); +#endif // defined(OS_LINUX) || defined(OS_ANDROID) + +} // namespace base + +#endif // BASE_PROCESS_PROCESS_METRICS_H_ diff --git a/base/process/process_metrics_freebsd.cc b/base/process/process_metrics_freebsd.cc new file mode 100644 index 0000000..019454c --- /dev/null +++ b/base/process/process_metrics_freebsd.cc @@ -0,0 +1,122 @@ +// Copyright (c) 2013 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 "base/process/process_metrics.h" + +namespace base { + +ProcessMetrics::ProcessMetrics(ProcessHandle process) + : process_(process), + last_time_(0), + last_system_time_(0), + last_cpu_(0) { + processor_count_ = base::SysInfo::NumberOfProcessors(); +} + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + struct kinfo_proc info; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_ }; + size_t length = sizeof(info); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return 0; + + return info.ki_size; +} + +size_t ProcessMetrics::GetPeakPagefileUsage() const { + return 0; +} + +size_t ProcessMetrics::GetWorkingSetSize() const { + struct kinfo_proc info; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_ }; + size_t length = sizeof(info); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return 0; + + return info.ki_rssize * getpagesize(); +} + +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + return 0; +} + +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + WorkingSetKBytes ws_usage; + if (!GetWorkingSetKBytes(&ws_usage)) + return false; + + if (private_bytes) + *private_bytes = ws_usage.priv << 10; + + if (shared_bytes) + *shared_bytes = ws_usage.shared * 1024; + + return true; +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { +// TODO(bapt) be sure we can't be precise + size_t priv = GetWorkingSetSize(); + if (!priv) + return false; + ws_usage->priv = priv / 1024; + ws_usage->shareable = 0; + ws_usage->shared = 0; + + return true; +} + +double ProcessMetrics::GetCPUUsage() { + struct kinfo_proc info; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_ }; + size_t length = sizeof(info); + + struct timeval now; + int retval = gettimeofday(&now, NULL); + if (retval) + return 0; + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return 0; + + return (info.ki_pctcpu / FSCALE) * 100.0; +} + +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + return false; +} + +size_t GetSystemCommitCharge() { + int mib[2], pagesize; + unsigned long mem_total, mem_free, mem_inactive; + size_t length = sizeof(mem_total); + + if (sysctl(mib, arraysize(mib), &mem_total, &length, NULL, 0) < 0) + return 0; + + length = sizeof(mem_free); + if (sysctlbyname("vm.stats.vm.v_free_count", &mem_free, &length, NULL, 0) < 0) + return 0; + + length = sizeof(mem_inactive); + if (sysctlbyname("vm.stats.vm.v_inactive_count", &mem_inactive, &length, + NULL, 0) < 0) { + return 0; + } + + pagesize = getpagesize(); + + return mem_total - (mem_free*pagesize) - (mem_inactive*pagesize); +} + +} // namespace base diff --git a/base/process/process_metrics_ios.cc b/base/process/process_metrics_ios.cc new file mode 100644 index 0000000..9f0e8c6b --- /dev/null +++ b/base/process/process_metrics_ios.cc @@ -0,0 +1,47 @@ +// Copyright (c) 2013 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 "base/process/process_metrics.h" + +#include <mach/task.h> + +namespace base { + +namespace { + +bool GetTaskInfo(task_basic_info_64* task_info_data) { + mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT; + kern_return_t kr = task_info(mach_task_self(), + TASK_BASIC_INFO_64, + reinterpret_cast<task_info_t>(task_info_data), + &count); + return kr == KERN_SUCCESS; +} + +} // namespace + +ProcessMetrics::ProcessMetrics(ProcessHandle process) {} + +ProcessMetrics::~ProcessMetrics() {} + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + task_basic_info_64 task_info_data; + if (!GetTaskInfo(&task_info_data)) + return 0; + return task_info_data.virtual_size; +} + +size_t ProcessMetrics::GetWorkingSetSize() const { + task_basic_info_64 task_info_data; + if (!GetTaskInfo(&task_info_data)) + return 0; + return task_info_data.resident_size; +} + +} // namespace base diff --git a/base/process/process_metrics_linux.cc b/base/process/process_metrics_linux.cc new file mode 100644 index 0000000..b52d356 --- /dev/null +++ b/base/process/process_metrics_linux.cc @@ -0,0 +1,439 @@ +// Copyright (c) 2013 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 "base/process/process_metrics.h" + +#include <dirent.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/process/internal_linux.h" +#include "base/string_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_tokenizer.h" +#include "base/sys_info.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +namespace { + +enum ParsingState { + KEY_NAME, + KEY_VALUE +}; + +// Read /proc/<pid>/status and returns the value for |field|, or 0 on failure. +// Only works for fields in the form of "Field: value kB". +size_t ReadProcStatusAndGetFieldAsSizeT(pid_t pid, const std::string& field) { + FilePath stat_file = internal::GetProcPidDir(pid).Append("status"); + std::string status; + { + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + if (!file_util::ReadFileToString(stat_file, &status)) + return 0; + } + + StringTokenizer tokenizer(status, ":\n"); + ParsingState state = KEY_NAME; + StringPiece last_key_name; + while (tokenizer.GetNext()) { + switch (state) { + case KEY_NAME: + last_key_name = tokenizer.token_piece(); + state = KEY_VALUE; + break; + case KEY_VALUE: + DCHECK(!last_key_name.empty()); + if (last_key_name == field) { + std::string value_str; + tokenizer.token_piece().CopyToString(&value_str); + std::string value_str_trimmed; + TrimWhitespaceASCII(value_str, TRIM_ALL, &value_str_trimmed); + std::vector<std::string> split_value_str; + SplitString(value_str_trimmed, ' ', &split_value_str); + if (split_value_str.size() != 2 || split_value_str[1] != "kB") { + NOTREACHED(); + return 0; + } + size_t value; + if (!StringToSizeT(split_value_str[0], &value)) { + NOTREACHED(); + return 0; + } + return value; + } + state = KEY_NAME; + break; + } + } + NOTREACHED(); + return 0; +} + +// Get the total CPU of a single process. Return value is number of jiffies +// on success or -1 on error. +int GetProcessCPU(pid_t pid) { + // Use /proc/<pid>/task to find all threads and parse their /stat file. + FilePath task_path = internal::GetProcPidDir(pid).Append("task"); + + DIR* dir = opendir(task_path.value().c_str()); + if (!dir) { + DPLOG(ERROR) << "opendir(" << task_path.value() << ")"; + return -1; + } + + int total_cpu = 0; + while (struct dirent* ent = readdir(dir)) { + pid_t tid = internal::ProcDirSlotToPid(ent->d_name); + if (!tid) + continue; + + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + + std::string stat; + FilePath stat_path = + task_path.Append(ent->d_name).Append(internal::kStatFile); + if (file_util::ReadFileToString(stat_path, &stat)) { + int cpu = ParseProcStatCPU(stat); + if (cpu > 0) + total_cpu += cpu; + } + } + closedir(dir); + + return total_cpu; +} + +} // namespace + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +// On linux, we return vsize. +size_t ProcessMetrics::GetPagefileUsage() const { + return internal::ReadProcStatsAndGetFieldAsSizeT(process_, + internal::VM_VSIZE); +} + +// On linux, we return the high water mark of vsize. +size_t ProcessMetrics::GetPeakPagefileUsage() const { + return ReadProcStatusAndGetFieldAsSizeT(process_, "VmPeak") * 1024; +} + +// On linux, we return RSS. +size_t ProcessMetrics::GetWorkingSetSize() const { + return internal::ReadProcStatsAndGetFieldAsSizeT(process_, internal::VM_RSS) * + getpagesize(); +} + +// On linux, we return the high water mark of RSS. +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + return ReadProcStatusAndGetFieldAsSizeT(process_, "VmHWM") * 1024; +} + +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + WorkingSetKBytes ws_usage; + if (!GetWorkingSetKBytes(&ws_usage)) + return false; + + if (private_bytes) + *private_bytes = ws_usage.priv * 1024; + + if (shared_bytes) + *shared_bytes = ws_usage.shared * 1024; + + return true; +} + +// Private and Shared working set sizes are obtained from /proc/<pid>/statm. +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { + // Use statm instead of smaps because smaps is: + // a) Large and slow to parse. + // b) Unavailable in the SUID sandbox. + + // First we need to get the page size, since everything is measured in pages. + // For details, see: man 5 proc. + const int page_size_kb = getpagesize() / 1024; + if (page_size_kb <= 0) + return false; + + std::string statm; + { + FilePath statm_file = internal::GetProcPidDir(process_).Append("statm"); + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + bool ret = file_util::ReadFileToString(statm_file, &statm); + if (!ret || statm.length() == 0) + return false; + } + + std::vector<std::string> statm_vec; + SplitString(statm, ' ', &statm_vec); + if (statm_vec.size() != 7) + return false; // Not the format we expect. + + int statm_rss, statm_shared; + StringToInt(statm_vec[1], &statm_rss); + StringToInt(statm_vec[2], &statm_shared); + + ws_usage->priv = (statm_rss - statm_shared) * page_size_kb; + ws_usage->shared = statm_shared * page_size_kb; + + // Sharable is not calculated, as it does not provide interesting data. + ws_usage->shareable = 0; + + return true; +} + +double ProcessMetrics::GetCPUUsage() { + // This queries the /proc-specific scaling factor which is + // conceptually the system hertz. To dump this value on another + // system, try + // od -t dL /proc/self/auxv + // and look for the number after 17 in the output; mine is + // 0000040 17 100 3 134512692 + // which means the answer is 100. + // It may be the case that this value is always 100. + static const int kHertz = sysconf(_SC_CLK_TCK); + + struct timeval now; + int retval = gettimeofday(&now, NULL); + if (retval) + return 0; + int64 time = TimeValToMicroseconds(now); + + if (last_time_ == 0) { + // First call, just set the last values. + last_time_ = time; + last_cpu_ = GetProcessCPU(process_); + return 0; + } + + int64 time_delta = time - last_time_; + DCHECK_NE(time_delta, 0); + if (time_delta == 0) + return 0; + + int cpu = GetProcessCPU(process_); + + // We have the number of jiffies in the time period. Convert to percentage. + // Note this means we will go *over* 100 in the case where multiple threads + // are together adding to more than one CPU's worth. + int percentage = 100 * (cpu - last_cpu_) / + (kHertz * TimeDelta::FromMicroseconds(time_delta).InSecondsF()); + + last_time_ = time; + last_cpu_ = cpu; + + return percentage; +} + +// To have /proc/self/io file you must enable CONFIG_TASK_IO_ACCOUNTING +// in your kernel configuration. +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + + std::string proc_io_contents; + FilePath io_file = internal::GetProcPidDir(process_).Append("io"); + if (!file_util::ReadFileToString(io_file, &proc_io_contents)) + return false; + + (*io_counters).OtherOperationCount = 0; + (*io_counters).OtherTransferCount = 0; + + StringTokenizer tokenizer(proc_io_contents, ": \n"); + ParsingState state = KEY_NAME; + StringPiece last_key_name; + while (tokenizer.GetNext()) { + switch (state) { + case KEY_NAME: + last_key_name = tokenizer.token_piece(); + state = KEY_VALUE; + break; + case KEY_VALUE: + DCHECK(!last_key_name.empty()); + if (last_key_name == "syscr") { + StringToInt64(tokenizer.token_piece(), + reinterpret_cast<int64*>(&(*io_counters).ReadOperationCount)); + } else if (last_key_name == "syscw") { + StringToInt64(tokenizer.token_piece(), + reinterpret_cast<int64*>(&(*io_counters).WriteOperationCount)); + } else if (last_key_name == "rchar") { + StringToInt64(tokenizer.token_piece(), + reinterpret_cast<int64*>(&(*io_counters).ReadTransferCount)); + } else if (last_key_name == "wchar") { + StringToInt64(tokenizer.token_piece(), + reinterpret_cast<int64*>(&(*io_counters).WriteTransferCount)); + } + state = KEY_NAME; + break; + } + } + return true; +} + +ProcessMetrics::ProcessMetrics(ProcessHandle process) + : process_(process), + last_time_(0), + last_system_time_(0), + last_cpu_(0) { + processor_count_ = base::SysInfo::NumberOfProcessors(); +} + +size_t GetSystemCommitCharge() { + SystemMemoryInfoKB meminfo; + if (!GetSystemMemoryInfo(&meminfo)) + return 0; + return meminfo.total - meminfo.free - meminfo.buffers - meminfo.cached; +} + +// Exposed for testing. +int ParseProcStatCPU(const std::string& input) { + std::vector<std::string> proc_stats; + if (!internal::ParseProcStats(input, &proc_stats)) + return -1; + + if (proc_stats.size() <= internal::VM_STIME) + return -1; + int utime = GetProcStatsFieldAsInt(proc_stats, internal::VM_UTIME); + int stime = GetProcStatsFieldAsInt(proc_stats, internal::VM_STIME); + return utime + stime; +} + +namespace { + +// The format of /proc/meminfo is: +// +// MemTotal: 8235324 kB +// MemFree: 1628304 kB +// Buffers: 429596 kB +// Cached: 4728232 kB +// ... +const size_t kMemTotalIndex = 1; +const size_t kMemFreeIndex = 4; +const size_t kMemBuffersIndex = 7; +const size_t kMemCachedIndex = 10; +const size_t kMemActiveAnonIndex = 22; +const size_t kMemInactiveAnonIndex = 25; +const size_t kMemActiveFileIndex = 28; +const size_t kMemInactiveFileIndex = 31; + +} // namespace + +SystemMemoryInfoKB::SystemMemoryInfoKB() + : total(0), + free(0), + buffers(0), + cached(0), + active_anon(0), + inactive_anon(0), + active_file(0), + inactive_file(0), + shmem(0), + gem_objects(-1), + gem_size(-1) { +} + +bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) { + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + + // Used memory is: total - free - buffers - caches + FilePath meminfo_file("/proc/meminfo"); + std::string meminfo_data; + if (!file_util::ReadFileToString(meminfo_file, &meminfo_data)) { + DLOG(WARNING) << "Failed to open " << meminfo_file.value(); + return false; + } + std::vector<std::string> meminfo_fields; + SplitStringAlongWhitespace(meminfo_data, &meminfo_fields); + + if (meminfo_fields.size() < kMemCachedIndex) { + DLOG(WARNING) << "Failed to parse " << meminfo_file.value() + << ". Only found " << meminfo_fields.size() << " fields."; + return false; + } + + DCHECK_EQ(meminfo_fields[kMemTotalIndex-1], "MemTotal:"); + DCHECK_EQ(meminfo_fields[kMemFreeIndex-1], "MemFree:"); + DCHECK_EQ(meminfo_fields[kMemBuffersIndex-1], "Buffers:"); + DCHECK_EQ(meminfo_fields[kMemCachedIndex-1], "Cached:"); + DCHECK_EQ(meminfo_fields[kMemActiveAnonIndex-1], "Active(anon):"); + DCHECK_EQ(meminfo_fields[kMemInactiveAnonIndex-1], "Inactive(anon):"); + DCHECK_EQ(meminfo_fields[kMemActiveFileIndex-1], "Active(file):"); + DCHECK_EQ(meminfo_fields[kMemInactiveFileIndex-1], "Inactive(file):"); + + StringToInt(meminfo_fields[kMemTotalIndex], &meminfo->total); + StringToInt(meminfo_fields[kMemFreeIndex], &meminfo->free); + StringToInt(meminfo_fields[kMemBuffersIndex], &meminfo->buffers); + StringToInt(meminfo_fields[kMemCachedIndex], &meminfo->cached); + StringToInt(meminfo_fields[kMemActiveAnonIndex], &meminfo->active_anon); + StringToInt(meminfo_fields[kMemInactiveAnonIndex], + &meminfo->inactive_anon); + StringToInt(meminfo_fields[kMemActiveFileIndex], &meminfo->active_file); + StringToInt(meminfo_fields[kMemInactiveFileIndex], + &meminfo->inactive_file); +#if defined(OS_CHROMEOS) + // Chrome OS has a tweaked kernel that allows us to query Shmem, which is + // usually video memory otherwise invisible to the OS. Unfortunately, the + // meminfo format varies on different hardware so we have to search for the + // string. It always appears after "Cached:". + for (size_t i = kMemCachedIndex+2; i < meminfo_fields.size(); i += 3) { + if (meminfo_fields[i] == "Shmem:") { + StringToInt(meminfo_fields[i+1], &meminfo->shmem); + break; + } + } + + // Report on Chrome OS GEM object graphics memory. /var/run/debugfs_gpu is a + // bind mount into /sys/kernel/debug and synchronously reading the in-memory + // files in /sys is fast. +#if defined(ARCH_CPU_ARM_FAMILY) + FilePath geminfo_file("/var/run/debugfs_gpu/exynos_gem_objects"); +#else + FilePath geminfo_file("/var/run/debugfs_gpu/i915_gem_objects"); +#endif + std::string geminfo_data; + meminfo->gem_objects = -1; + meminfo->gem_size = -1; + if (file_util::ReadFileToString(geminfo_file, &geminfo_data)) { + int gem_objects = -1; + long long gem_size = -1; + int num_res = sscanf(geminfo_data.c_str(), + "%d objects, %lld bytes", + &gem_objects, &gem_size); + if (num_res == 2) { + meminfo->gem_objects = gem_objects; + meminfo->gem_size = gem_size; + } + } + +#if defined(ARCH_CPU_ARM_FAMILY) + // Incorporate Mali graphics memory if present. + FilePath mali_memory_file("/sys/devices/platform/mali.0/memory"); + std::string mali_memory_data; + if (file_util::ReadFileToString(mali_memory_file, &mali_memory_data)) { + long long mali_size = -1; + int num_res = sscanf(mali_memory_data.c_str(), "%lld bytes", &mali_size); + if (num_res == 1) + meminfo->gem_size += mali_size; + } +#endif // defined(ARCH_CPU_ARM_FAMILY) +#endif // defined(OS_CHROMEOS) + + return true; +} + +} // namespace base diff --git a/base/process/process_metrics_mac.cc b/base/process/process_metrics_mac.cc new file mode 100644 index 0000000..74da992 --- /dev/null +++ b/base/process/process_metrics_mac.cc @@ -0,0 +1,325 @@ +// Copyright (c) 2013 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 "base/process/process_metrics.h" + +#include <mach/mach.h> +#include <mach/mach_vm.h> +#include <mach/shared_region.h> +#include <sys/sysctl.h> + +#include "base/hash_tables.h" +#include "base/logging.h" +#include "base/mac/scoped_mach_port.h" +#include "base/sys_info.h" + +namespace base { + +namespace { + +bool GetTaskInfo(mach_port_t task, task_basic_info_64* task_info_data) { + if (task == MACH_PORT_NULL) + return false; + mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT; + kern_return_t kr = task_info(task, + TASK_BASIC_INFO_64, + reinterpret_cast<task_info_t>(task_info_data), + &count); + // Most likely cause for failure: |task| is a zombie. + return kr == KERN_SUCCESS; +} + +bool GetCPUTypeForProcess(pid_t pid, cpu_type_t* cpu_type) { + size_t len = sizeof(*cpu_type); + int result = sysctlbyname("sysctl.proc_cputype", + cpu_type, + &len, + NULL, + 0); + if (result != 0) { + DPLOG(ERROR) << "sysctlbyname(""sysctl.proc_cputype"")"; + return false; + } + + return true; +} + +bool IsAddressInSharedRegion(mach_vm_address_t addr, cpu_type_t type) { + if (type == CPU_TYPE_I386) { + return addr >= SHARED_REGION_BASE_I386 && + addr < (SHARED_REGION_BASE_I386 + SHARED_REGION_SIZE_I386); + } else if (type == CPU_TYPE_X86_64) { + return addr >= SHARED_REGION_BASE_X86_64 && + addr < (SHARED_REGION_BASE_X86_64 + SHARED_REGION_SIZE_X86_64); + } else { + return false; + } +} + +} // namespace + +// Getting a mach task from a pid for another process requires permissions in +// general, so there doesn't really seem to be a way to do these (and spinning +// up ps to fetch each stats seems dangerous to put in a base api for anyone to +// call). Child processes ipc their port, so return something if available, +// otherwise return 0. + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics( + ProcessHandle process, + ProcessMetrics::PortProvider* port_provider) { + return new ProcessMetrics(process, port_provider); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + task_basic_info_64 task_info_data; + if (!GetTaskInfo(TaskForPid(process_), &task_info_data)) + return 0; + return task_info_data.virtual_size; +} + +size_t ProcessMetrics::GetPeakPagefileUsage() const { + return 0; +} + +size_t ProcessMetrics::GetWorkingSetSize() const { + task_basic_info_64 task_info_data; + if (!GetTaskInfo(TaskForPid(process_), &task_info_data)) + return 0; + return task_info_data.resident_size; +} + +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + return 0; +} + +// This is a rough approximation of the algorithm that libtop uses. +// private_bytes is the size of private resident memory. +// shared_bytes is the size of shared resident memory. +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + kern_return_t kr; + size_t private_pages_count = 0; + size_t shared_pages_count = 0; + + if (!private_bytes && !shared_bytes) + return true; + + mach_port_t task = TaskForPid(process_); + if (task == MACH_PORT_NULL) { + DLOG(ERROR) << "Invalid process"; + return false; + } + + cpu_type_t cpu_type; + if (!GetCPUTypeForProcess(process_, &cpu_type)) + return false; + + // The same region can be referenced multiple times. To avoid double counting + // we need to keep track of which regions we've already counted. + base::hash_set<int> seen_objects; + + // We iterate through each VM region in the task's address map. For shared + // memory we add up all the pages that are marked as shared. Like libtop we + // try to avoid counting pages that are also referenced by other tasks. Since + // we don't have access to the VM regions of other tasks the only hint we have + // is if the address is in the shared region area. + // + // Private memory is much simpler. We simply count the pages that are marked + // as private or copy on write (COW). + // + // See libtop_update_vm_regions in + // http://www.opensource.apple.com/source/top/top-67/libtop.c + mach_vm_size_t size = 0; + for (mach_vm_address_t address = MACH_VM_MIN_ADDRESS;; address += size) { + vm_region_top_info_data_t info; + mach_msg_type_number_t info_count = VM_REGION_TOP_INFO_COUNT; + mach_port_t object_name; + kr = mach_vm_region(task, + &address, + &size, + VM_REGION_TOP_INFO, + (vm_region_info_t)&info, + &info_count, + &object_name); + if (kr == KERN_INVALID_ADDRESS) { + // We're at the end of the address space. + break; + } else if (kr != KERN_SUCCESS) { + DLOG(ERROR) << "Calling mach_vm_region failed with error: " + << mach_error_string(kr); + return false; + } + + if (IsAddressInSharedRegion(address, cpu_type) && + info.share_mode != SM_PRIVATE) + continue; + + if (info.share_mode == SM_COW && info.ref_count == 1) + info.share_mode = SM_PRIVATE; + + switch (info.share_mode) { + case SM_PRIVATE: + private_pages_count += info.private_pages_resident; + private_pages_count += info.shared_pages_resident; + break; + case SM_COW: + private_pages_count += info.private_pages_resident; + // Fall through + case SM_SHARED: + if (seen_objects.count(info.obj_id) == 0) { + // Only count the first reference to this region. + seen_objects.insert(info.obj_id); + shared_pages_count += info.shared_pages_resident; + } + break; + default: + break; + } + } + + vm_size_t page_size; + kr = host_page_size(task, &page_size); + if (kr != KERN_SUCCESS) { + DLOG(ERROR) << "Failed to fetch host page size, error: " + << mach_error_string(kr); + return false; + } + + if (private_bytes) + *private_bytes = private_pages_count * page_size; + if (shared_bytes) + *shared_bytes = shared_pages_count * page_size; + + return true; +} + +void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) const { +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { + size_t priv = GetWorkingSetSize(); + if (!priv) + return false; + ws_usage->priv = priv / 1024; + ws_usage->shareable = 0; + ws_usage->shared = 0; + return true; +} + +#define TIME_VALUE_TO_TIMEVAL(a, r) do { \ + (r)->tv_sec = (a)->seconds; \ + (r)->tv_usec = (a)->microseconds; \ +} while (0) + +double ProcessMetrics::GetCPUUsage() { + mach_port_t task = TaskForPid(process_); + if (task == MACH_PORT_NULL) + return 0; + + kern_return_t kr; + + // Libtop explicitly loops over the threads (libtop_pinfo_update_cpu_usage() + // in libtop.c), but this is more concise and gives the same results: + task_thread_times_info thread_info_data; + mach_msg_type_number_t thread_info_count = TASK_THREAD_TIMES_INFO_COUNT; + kr = task_info(task, + TASK_THREAD_TIMES_INFO, + reinterpret_cast<task_info_t>(&thread_info_data), + &thread_info_count); + if (kr != KERN_SUCCESS) { + // Most likely cause: |task| is a zombie. + return 0; + } + + task_basic_info_64 task_info_data; + if (!GetTaskInfo(task, &task_info_data)) + return 0; + + /* Set total_time. */ + // thread info contains live time... + struct timeval user_timeval, system_timeval, task_timeval; + TIME_VALUE_TO_TIMEVAL(&thread_info_data.user_time, &user_timeval); + TIME_VALUE_TO_TIMEVAL(&thread_info_data.system_time, &system_timeval); + timeradd(&user_timeval, &system_timeval, &task_timeval); + + // ... task info contains terminated time. + TIME_VALUE_TO_TIMEVAL(&task_info_data.user_time, &user_timeval); + TIME_VALUE_TO_TIMEVAL(&task_info_data.system_time, &system_timeval); + timeradd(&user_timeval, &task_timeval, &task_timeval); + timeradd(&system_timeval, &task_timeval, &task_timeval); + + struct timeval now; + int retval = gettimeofday(&now, NULL); + if (retval) + return 0; + + int64 time = TimeValToMicroseconds(now); + int64 task_time = TimeValToMicroseconds(task_timeval); + + if ((last_system_time_ == 0) || (last_time_ == 0)) { + // First call, just set the last values. + last_system_time_ = task_time; + last_time_ = time; + return 0; + } + + int64 system_time_delta = task_time - last_system_time_; + int64 time_delta = time - last_time_; + DCHECK_NE(0U, time_delta); + if (time_delta == 0) + return 0; + + last_system_time_ = task_time; + last_time_ = time; + + return static_cast<double>(system_time_delta * 100.0) / time_delta; +} + +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + return false; +} + +ProcessMetrics::ProcessMetrics(ProcessHandle process, + ProcessMetrics::PortProvider* port_provider) + : process_(process), + last_time_(0), + last_system_time_(0), + port_provider_(port_provider) { + processor_count_ = SysInfo::NumberOfProcessors(); +} + +mach_port_t ProcessMetrics::TaskForPid(ProcessHandle process) const { + mach_port_t task = MACH_PORT_NULL; + if (port_provider_) + task = port_provider_->TaskForPid(process_); + if (task == MACH_PORT_NULL && process_ == getpid()) + task = mach_task_self(); + return task; +} + +// Bytes committed by the system. +size_t GetSystemCommitCharge() { + base::mac::ScopedMachPort host(mach_host_self()); + mach_msg_type_number_t count = HOST_VM_INFO_COUNT; + vm_statistics_data_t data; + kern_return_t kr = host_statistics(host, HOST_VM_INFO, + reinterpret_cast<host_info_t>(&data), + &count); + if (kr) { + DLOG(WARNING) << "Failed to fetch host statistics."; + return 0; + } + + vm_size_t page_size; + kr = host_page_size(host, &page_size); + if (kr) { + DLOG(ERROR) << "Failed to fetch host page size."; + return 0; + } + + return (data.active_count * page_size) / 1024; +} + +} // namespace base diff --git a/base/process/process_metrics_openbsd.cc b/base/process/process_metrics_openbsd.cc new file mode 100644 index 0000000..36f607c --- /dev/null +++ b/base/process/process_metrics_openbsd.cc @@ -0,0 +1,168 @@ +// Copyright (c) 2013 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 "base/process/process_metrics.h" + +#include <sys/param.h> +#include <sys/sysctl.h> + +namespace base { + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + struct kinfo_proc info; + size_t length; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_, + sizeof(struct kinfo_proc), 0 }; + + if (sysctl(mib, arraysize(mib), NULL, &length, NULL, 0) < 0) + return -1; + + mib[5] = (length / sizeof(struct kinfo_proc)); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return -1; + + return (info.p_vm_tsize + info.p_vm_dsize + info.p_vm_ssize); +} + +size_t ProcessMetrics::GetPeakPagefileUsage() const { + return 0; +} + +size_t ProcessMetrics::GetWorkingSetSize() const { + struct kinfo_proc info; + size_t length; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_, + sizeof(struct kinfo_proc), 0 }; + + if (sysctl(mib, arraysize(mib), NULL, &length, NULL, 0) < 0) + return -1; + + mib[5] = (length / sizeof(struct kinfo_proc)); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return -1; + + return info.p_vm_rssize * getpagesize(); +} + +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + return 0; +} + +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + WorkingSetKBytes ws_usage; + + if (!GetWorkingSetKBytes(&ws_usage)) + return false; + + if (private_bytes) + *private_bytes = ws_usage.priv << 10; + + if (shared_bytes) + *shared_bytes = ws_usage.shared * 1024; + + return true; +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { + // TODO(bapt): be sure we can't be precise + size_t priv = GetWorkingSetSize(); + if (!priv) + return false; + ws_usage->priv = priv / 1024; + ws_usage->shareable = 0; + ws_usage->shared = 0; + + return true; +} + +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + return false; +} + +static int GetProcessCPU(pid_t pid) { + struct kinfo_proc info; + size_t length; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid, + sizeof(struct kinfo_proc), 0 }; + + if (sysctl(mib, arraysize(mib), NULL, &length, NULL, 0) < 0) + return -1; + + mib[5] = (length / sizeof(struct kinfo_proc)); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return 0; + + return info.p_pctcpu; +} + +double ProcessMetrics::GetCPUUsage() { + struct timeval now; + + int retval = gettimeofday(&now, NULL); + if (retval) + return 0; + + int64 time = TimeValToMicroseconds(now); + + if (last_time_ == 0) { + // First call, just set the last values. + last_time_ = time; + last_cpu_ = GetProcessCPU(process_); + return 0; + } + + int64 time_delta = time - last_time_; + DCHECK_NE(time_delta, 0); + + if (time_delta == 0) + return 0; + + int cpu = GetProcessCPU(process_); + + last_time_ = time; + last_cpu_ = cpu; + + double percentage = static_cast<double>((cpu * 100.0) / FSCALE); + + return percentage; +} + +ProcessMetrics::ProcessMetrics(ProcessHandle process) + : process_(process), + last_time_(0), + last_system_time_(0), + last_cpu_(0) { + + processor_count_ = base::SysInfo::NumberOfProcessors(); +} + +size_t GetSystemCommitCharge() { + int mib[] = { CTL_VM, VM_METER }; + int pagesize; + struct vmtotal vmtotal; + unsigned long mem_total, mem_free, mem_inactive; + size_t len = sizeof(vmtotal); + + if (sysctl(mib, arraysize(mib), &vmtotal, &len, NULL, 0) < 0) + return 0; + + mem_total = vmtotal.t_vm; + mem_free = vmtotal.t_free; + mem_inactive = vmtotal.t_vm - vmtotal.t_avm; + + pagesize = getpagesize(); + + return mem_total - (mem_free*pagesize) - (mem_inactive*pagesize); +} + +} // namespace base diff --git a/base/process/process_metrics_posix.cc b/base/process/process_metrics_posix.cc new file mode 100644 index 0000000..3422a73 --- /dev/null +++ b/base/process/process_metrics_posix.cc @@ -0,0 +1,21 @@ +// Copyright (c) 2013 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 "base/process/process_metrics.h" + +#include <sys/time.h> + +namespace base { + +int64 TimeValToMicroseconds(const struct timeval& tv) { + static const int kMicrosecondsPerSecond = 1000000; + int64 ret = tv.tv_sec; // Avoid (int * int) integer overflow. + ret *= kMicrosecondsPerSecond; + ret += tv.tv_usec; + return ret; +} + +ProcessMetrics::~ProcessMetrics() { } + +} // namespace base diff --git a/base/process/process_metrics_win.cc b/base/process/process_metrics_win.cc new file mode 100644 index 0000000..f42ea86 --- /dev/null +++ b/base/process/process_metrics_win.cc @@ -0,0 +1,315 @@ +// Copyright (c) 2013 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 "base/process/process_metrics.h" + +#include <windows.h> +#include <psapi.h> + +#include "base/logging.h" +#include "base/sys_info.h" + +namespace base { + +// System pagesize. This value remains constant on x86/64 architectures. +const int PAGESIZE_KB = 4; + +ProcessMetrics::~ProcessMetrics() { } + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.PagefileUsage; + } + return 0; +} + +// Returns the peak space allocated for the pagefile, in bytes. +size_t ProcessMetrics::GetPeakPagefileUsage() const { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.PeakPagefileUsage; + } + return 0; +} + +// Returns the current working set size, in bytes. +size_t ProcessMetrics::GetWorkingSetSize() const { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.WorkingSetSize; + } + return 0; +} + +// Returns the peak working set size, in bytes. +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.PeakWorkingSetSize; + } + return 0; +} + +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + // PROCESS_MEMORY_COUNTERS_EX is not supported until XP SP2. + // GetProcessMemoryInfo() will simply fail on prior OS. So the requested + // information is simply not available. Hence, we will return 0 on unsupported + // OSes. Unlike most Win32 API, we don't need to initialize the "cb" member. + PROCESS_MEMORY_COUNTERS_EX pmcx; + if (private_bytes && + GetProcessMemoryInfo(process_, + reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&pmcx), + sizeof(pmcx))) { + *private_bytes = pmcx.PrivateUsage; + } + + if (shared_bytes) { + WorkingSetKBytes ws_usage; + if (!GetWorkingSetKBytes(&ws_usage)) + return false; + + *shared_bytes = ws_usage.shared * 1024; + } + + return true; +} + +void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) const { + MEMORY_BASIC_INFORMATION mbi = {0}; + size_t committed_private = 0; + size_t committed_mapped = 0; + size_t committed_image = 0; + void* base_address = NULL; + while (VirtualQueryEx(process_, base_address, &mbi, sizeof(mbi)) == + sizeof(mbi)) { + if (mbi.State == MEM_COMMIT) { + if (mbi.Type == MEM_PRIVATE) { + committed_private += mbi.RegionSize; + } else if (mbi.Type == MEM_MAPPED) { + committed_mapped += mbi.RegionSize; + } else if (mbi.Type == MEM_IMAGE) { + committed_image += mbi.RegionSize; + } else { + NOTREACHED(); + } + } + void* new_base = (static_cast<BYTE*>(mbi.BaseAddress)) + mbi.RegionSize; + // Avoid infinite loop by weird MEMORY_BASIC_INFORMATION. + // If we query 64bit processes in a 32bit process, VirtualQueryEx() + // returns such data. + if (new_base <= base_address) { + usage->image = 0; + usage->mapped = 0; + usage->priv = 0; + return; + } + base_address = new_base; + } + usage->image = committed_image / 1024; + usage->mapped = committed_mapped / 1024; + usage->priv = committed_private / 1024; +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { + size_t ws_private = 0; + size_t ws_shareable = 0; + size_t ws_shared = 0; + + DCHECK(ws_usage); + memset(ws_usage, 0, sizeof(*ws_usage)); + + DWORD number_of_entries = 4096; // Just a guess. + PSAPI_WORKING_SET_INFORMATION* buffer = NULL; + int retries = 5; + for (;;) { + DWORD buffer_size = sizeof(PSAPI_WORKING_SET_INFORMATION) + + (number_of_entries * sizeof(PSAPI_WORKING_SET_BLOCK)); + + // if we can't expand the buffer, don't leak the previous + // contents or pass a NULL pointer to QueryWorkingSet + PSAPI_WORKING_SET_INFORMATION* new_buffer = + reinterpret_cast<PSAPI_WORKING_SET_INFORMATION*>( + realloc(buffer, buffer_size)); + if (!new_buffer) { + free(buffer); + return false; + } + buffer = new_buffer; + + // Call the function once to get number of items + if (QueryWorkingSet(process_, buffer, buffer_size)) + break; // Success + + if (GetLastError() != ERROR_BAD_LENGTH) { + free(buffer); + return false; + } + + number_of_entries = static_cast<DWORD>(buffer->NumberOfEntries); + + // Maybe some entries are being added right now. Increase the buffer to + // take that into account. + number_of_entries = static_cast<DWORD>(number_of_entries * 1.25); + + if (--retries == 0) { + free(buffer); // If we're looping, eventually fail. + return false; + } + } + + // On windows 2000 the function returns 1 even when the buffer is too small. + // The number of entries that we are going to parse is the minimum between the + // size we allocated and the real number of entries. + number_of_entries = + std::min(number_of_entries, static_cast<DWORD>(buffer->NumberOfEntries)); + for (unsigned int i = 0; i < number_of_entries; i++) { + if (buffer->WorkingSetInfo[i].Shared) { + ws_shareable++; + if (buffer->WorkingSetInfo[i].ShareCount > 1) + ws_shared++; + } else { + ws_private++; + } + } + + ws_usage->priv = ws_private * PAGESIZE_KB; + ws_usage->shareable = ws_shareable * PAGESIZE_KB; + ws_usage->shared = ws_shared * PAGESIZE_KB; + free(buffer); + return true; +} + +static uint64 FileTimeToUTC(const FILETIME& ftime) { + LARGE_INTEGER li; + li.LowPart = ftime.dwLowDateTime; + li.HighPart = ftime.dwHighDateTime; + return li.QuadPart; +} + +double ProcessMetrics::GetCPUUsage() { + FILETIME now; + FILETIME creation_time; + FILETIME exit_time; + FILETIME kernel_time; + FILETIME user_time; + + GetSystemTimeAsFileTime(&now); + + if (!GetProcessTimes(process_, &creation_time, &exit_time, + &kernel_time, &user_time)) { + // We don't assert here because in some cases (such as in the Task Manager) + // we may call this function on a process that has just exited but we have + // not yet received the notification. + return 0; + } + int64 system_time = (FileTimeToUTC(kernel_time) + FileTimeToUTC(user_time)) / + processor_count_; + int64 time = FileTimeToUTC(now); + + if ((last_system_time_ == 0) || (last_time_ == 0)) { + // First call, just set the last values. + last_system_time_ = system_time; + last_time_ = time; + return 0; + } + + int64 system_time_delta = system_time - last_system_time_; + int64 time_delta = time - last_time_; + DCHECK_NE(0U, time_delta); + if (time_delta == 0) + return 0; + + // We add time_delta / 2 so the result is rounded. + int cpu = static_cast<int>((system_time_delta * 100 + time_delta / 2) / + time_delta); + + last_system_time_ = system_time; + last_time_ = time; + + return cpu; +} + +bool ProcessMetrics::CalculateFreeMemory(FreeMBytes* free) const { + const SIZE_T kTopAddress = 0x7F000000; + const SIZE_T kMegabyte = 1024 * 1024; + SIZE_T accumulated = 0; + + MEMORY_BASIC_INFORMATION largest = {0}; + UINT_PTR scan = 0; + while (scan < kTopAddress) { + MEMORY_BASIC_INFORMATION info; + if (!::VirtualQueryEx(process_, reinterpret_cast<void*>(scan), + &info, sizeof(info))) + return false; + if (info.State == MEM_FREE) { + accumulated += info.RegionSize; + if (info.RegionSize > largest.RegionSize) + largest = info; + } + scan += info.RegionSize; + } + free->largest = largest.RegionSize / kMegabyte; + free->largest_ptr = largest.BaseAddress; + free->total = accumulated / kMegabyte; + return true; +} + +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + return GetProcessIoCounters(process_, io_counters) != FALSE; +} + +ProcessMetrics::ProcessMetrics(ProcessHandle process) + : process_(process), + processor_count_(base::SysInfo::NumberOfProcessors()), + last_time_(0), + last_system_time_(0) { +} + +// GetPerformanceInfo is not available on WIN2K. So we'll +// load it on-the-fly. +const wchar_t kPsapiDllName[] = L"psapi.dll"; +typedef BOOL (WINAPI *GetPerformanceInfoFunction) ( + PPERFORMANCE_INFORMATION pPerformanceInformation, + DWORD cb); + +// Beware of races if called concurrently from multiple threads. +static BOOL InternalGetPerformanceInfo( + PPERFORMANCE_INFORMATION pPerformanceInformation, DWORD cb) { + static GetPerformanceInfoFunction GetPerformanceInfo_func = NULL; + if (!GetPerformanceInfo_func) { + HMODULE psapi_dll = ::GetModuleHandle(kPsapiDllName); + if (psapi_dll) + GetPerformanceInfo_func = reinterpret_cast<GetPerformanceInfoFunction>( + GetProcAddress(psapi_dll, "GetPerformanceInfo")); + + if (!GetPerformanceInfo_func) { + // The function could be loaded! + memset(pPerformanceInformation, 0, cb); + return FALSE; + } + } + return GetPerformanceInfo_func(pPerformanceInformation, cb); +} + +size_t GetSystemCommitCharge() { + // Get the System Page Size. + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + + PERFORMANCE_INFORMATION info; + if (!InternalGetPerformanceInfo(&info, sizeof(info))) { + DLOG(ERROR) << "Failed to fetch internal performance info."; + return 0; + } + return (info.CommitTotal * system_info.dwPageSize) / 1024; +} + +} // namespace base |