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 | |
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
-rw-r--r-- | base/base.gypi | 12 | ||||
-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 | ||||
-rw-r--r-- | base/process_util.h | 207 | ||||
-rw-r--r-- | base/process_util_freebsd.cc | 114 | ||||
-rw-r--r-- | base/process_util_ios.mm | 37 | ||||
-rw-r--r-- | base/process_util_linux.cc | 590 | ||||
-rw-r--r-- | base/process_util_mac.mm | 307 | ||||
-rw-r--r-- | base/process_util_openbsd.cc | 156 | ||||
-rw-r--r-- | base/process_util_posix.cc | 10 | ||||
-rw-r--r-- | base/process_util_win.cc | 303 |
19 files changed, 1922 insertions, 1705 deletions
diff --git a/base/base.gypi b/base/base.gypi index acdd7ba..abfe1f5 100644 --- a/base/base.gypi +++ b/base/base.gypi @@ -376,6 +376,16 @@ 'process_util_posix.cc', 'process_util_win.cc', 'process_win.cc', + 'process/process_metrics.h', + 'process/process_metrics_freebsd.cc', + 'process/process_metrics_ios.cc', + 'process/process_metrics_linux.cc', + 'process/process_metrics_mac.cc', + 'process/process_metrics_openbsd.cc', + 'process/process_metrics_posix.cc', + 'process/process_metrics_win.cc', + 'process/internal_linux.cc', + 'process/internal_linux.h', 'profiler/scoped_profile.cc', 'profiler/scoped_profile.h', 'profiler/alternate_timer.cc', @@ -675,6 +685,8 @@ 'sources/': [ ['include', '^files/file_path_watcher_linux\\.cc$'], ['include', '^process_util_linux\\.cc$'], + ['include', '^process/internal_linux\\.cc$'], + ['include', '^process/process_metrics_linux\\.cc$'], ['include', '^posix/unix_domain_socket_linux\\.cc$'], ['include', '^strings/sys_string_conversions_posix\\.cc$'], ['include', '^sys_info_linux\\.cc$'], 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 diff --git a/base/process_util.h b/base/process_util.h index 6efc70c..2036ca8 100644 --- a/base/process_util.h +++ b/base/process_util.h @@ -39,6 +39,7 @@ typedef struct _malloc_zone_t malloc_zone_t; #include "base/base_export.h" #include "base/files/file_path.h" #include "base/process.h" +#include "base/process/process_metrics.h" #if defined(OS_POSIX) #include "base/posix/file_descriptor_shuffle.h" @@ -55,9 +56,6 @@ struct ProcessEntry : public PROCESSENTRY32 { const wchar_t* exe_file() const { return szExeFile; } }; -struct IoCounters : public IO_COUNTERS { -}; - // Process access masks. These constants provide platform-independent // definitions for the standard Windows access masks. // See http://msdn.microsoft.com/en-us/library/ms684880(VS.85).aspx for @@ -98,15 +96,6 @@ struct BASE_EXPORT ProcessEntry { std::vector<std::string> cmd_line_args_; }; -struct IoCounters { - uint64_t ReadOperationCount; - uint64_t WriteOperationCount; - uint64_t OtherOperationCount; - uint64_t ReadTransferCount; - uint64_t WriteTransferCount; - uint64_t OtherTransferCount; -}; - // Process access masks. They are not used on Posix because access checking // does not happen during handle creation. const uint32 kProcessAccessTerminate = 0; @@ -192,11 +181,6 @@ BASE_EXPORT FilePath GetProcessExecutablePath(ProcessHandle process); #endif #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); - // Get the number of threads of |process| as available in /proc/<pid>/stat. // This should be used with care as no synchronization with running threads is // done. This is mostly useful to guarantee being single-threaded. @@ -644,195 +628,6 @@ class BASE_EXPORT NamedProcessIterator : public ProcessIterator { DISALLOW_COPY_AND_ASSIGN(NamedProcessIterator); }; -// 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); -}; - -#if defined(OS_LINUX) || defined(OS_ANDROID) -// 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) - -// Returns the memory committed by the system in KBytes. -// Returns 0 if it can't compute the commit charge. -BASE_EXPORT size_t GetSystemCommitCharge(); - // Enables low fragmentation heap (LFH) for every heaps of this process. This // won't have any effect on heaps created after this function call. It will not // modify data allocated in the heaps before calling this function. So it is diff --git a/base/process_util_freebsd.cc b/base/process_util_freebsd.cc index cb5a2db..bcf629f 100644 --- a/base/process_util_freebsd.cc +++ b/base/process_util_freebsd.cc @@ -162,120 +162,6 @@ bool NamedProcessIterator::IncludeEntry() { return ProcessIterator::IncludeEntry(); } - -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; -} - -bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { - return false; -} - -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; -} - -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); -} - void EnableTerminationOnOutOfMemory() { DLOG(WARNING) << "Not feasible."; } diff --git a/base/process_util_ios.mm b/base/process_util_ios.mm index 7239ed2..4d95a7e 100644 --- a/base/process_util_ios.mm +++ b/base/process_util_ios.mm @@ -5,7 +5,6 @@ #include "base/process_util.h" #import <Foundation/Foundation.h> -#include <mach/task.h> #include <stdio.h> #include <sys/resource.h> @@ -16,19 +15,6 @@ 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 - ProcessId GetCurrentProcId() { return getpid(); } @@ -66,27 +52,4 @@ size_t GetMaxFds() { return static_cast<size_t>(max_fds); } -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_util_linux.cc b/base/process_util_linux.cc index 898c172..4f510d7 100644 --- a/base/process_util_linux.cc +++ b/base/process_util_linux.cc @@ -12,10 +12,10 @@ #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" @@ -23,143 +23,14 @@ namespace base { namespace { -enum ParsingState { - KEY_NAME, - KEY_VALUE -}; - -const char kProcDir[] = "/proc"; -const char kStatFile[] = "stat"; - -// Returns a FilePath to "/proc/pid". -FilePath GetProcPidDir(pid_t pid) { - return FilePath(kProcDir).Append(IntToString(pid)); -} - -// 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 /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) { - buffer->clear(); - // Synchronously reading files in /proc is safe. - ThreadRestrictions::ScopedAllowIO allow_io; - - FilePath stat_file = GetProcPidDir(pid).Append(kStatFile); - if (!file_util::ReadFileToString(stat_file, buffer)) { - DLOG(WARNING) << "Failed to get process stats."; - return false; - } - return !buffer->empty(); -} - -// 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) { - // |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; -} - -// 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) { - 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; -} - -// Same as GetProcStatsFieldAsInt(), but for size_t values. -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; -} - -// Convenience wrapper around GetProcStatsFieldAsInt(), ParseProcStats() and -// ReadProcStats(). See GetProcStatsFieldAsInt() for details. -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); -} - -// Same as ReadProcStatsAndGetFieldAsInt() but for size_t values. -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); -} - // Reads the |field_num|th field from |proc_stats|. // Returns an empty string on failure. // This version only handles VM_COMM and VM_STATE, which are the only fields // that are strings. std::string GetProcStatsFieldAsString( const std::vector<std::string>& proc_stats, - ProcStatsFields field_num) { - if (field_num < VM_COMM || field_num > VM_STATE) { + internal::ProcStatsFields field_num) { + if (field_num < internal::VM_COMM || field_num > internal::VM_STATE) { NOTREACHED(); return std::string(); } @@ -180,7 +51,7 @@ bool GetProcCmdline(pid_t pid, std::vector<std::string>* proc_cmd_line_args) { // Synchronously reading files in /proc is safe. ThreadRestrictions::ScopedAllowIO allow_io; - FilePath cmd_line_file = GetProcPidDir(pid).Append("cmdline"); + FilePath cmd_line_file = internal::GetProcPidDir(pid).Append("cmdline"); std::string cmd_line; if (!file_util::ReadFileToString(cmd_line_file, &cmd_line)) return false; @@ -190,113 +61,6 @@ bool GetProcCmdline(pid_t pid, std::vector<std::string>* proc_cmd_line_args) { return true; } -// 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) { - 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; -} - -// 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 = 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 = 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(kStatFile); - if (file_util::ReadFileToString(stat_path, &stat)) { - int cpu = ParseProcStatCPU(stat); - if (cpu > 0) - total_cpu += cpu; - } - } - closedir(dir); - - return total_cpu; -} - -// 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 = 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; -} - } // namespace #if defined(USE_LINUX_BREAKPAD) @@ -306,14 +70,15 @@ size_t g_oom_size = 0U; const char kProcSelfExe[] = "/proc/self/exe"; ProcessId GetParentProcessId(ProcessHandle process) { - ProcessId pid = ReadProcStatsAndGetFieldAsInt(process, VM_PPID); + ProcessId pid = + internal::ReadProcStatsAndGetFieldAsInt(process, internal::VM_PPID); if (pid) return pid; return -1; } FilePath GetProcessExecutablePath(ProcessHandle process) { - FilePath stat_file = GetProcPidDir(process).Append("exe"); + FilePath stat_file = internal::GetProcPidDir(process).Append("exe"); FilePath exe_name; if (!file_util::ReadSymbolicLink(stat_file, &exe_name)) { // No such process. Happens frequently in e.g. TerminateAllChromeProcesses @@ -324,7 +89,7 @@ FilePath GetProcessExecutablePath(ProcessHandle process) { ProcessIterator::ProcessIterator(const ProcessFilter* filter) : filter_(filter) { - procfs_dir_ = opendir(kProcDir); + procfs_dir_ = opendir(internal::kProcDir); } ProcessIterator::~ProcessIterator() { @@ -353,7 +118,7 @@ bool ProcessIterator::CheckForNextProcess() { return false; // If not a process, keep looking for one. - pid = ProcDirSlotToPid(slot->d_name); + pid = internal::ProcDirSlotToPid(slot->d_name); if (!pid) { skipped++; continue; @@ -362,12 +127,13 @@ bool ProcessIterator::CheckForNextProcess() { if (!GetProcCmdline(pid, &cmd_line_args)) continue; - if (!ReadProcStats(pid, &stats_data)) + if (!internal::ReadProcStats(pid, &stats_data)) continue; - if (!ParseProcStats(stats_data, &proc_stats)) + if (!internal::ParseProcStats(stats_data, &proc_stats)) continue; - std::string runstate = GetProcStatsFieldAsString(proc_stats, VM_STATE); + std::string runstate = + GetProcStatsFieldAsString(proc_stats, internal::VM_STATE); if (runstate.size() != 1) { NOTREACHED(); continue; @@ -388,8 +154,8 @@ bool ProcessIterator::CheckForNextProcess() { } entry_.pid_ = pid; - entry_.ppid_ = GetProcStatsFieldAsInt(proc_stats, VM_PPID); - entry_.gid_ = GetProcStatsFieldAsInt(proc_stats, VM_PGRP); + entry_.ppid_ = GetProcStatsFieldAsInt(proc_stats, internal::VM_PPID); + entry_.gid_ = GetProcStatsFieldAsInt(proc_stats, internal::VM_PGRP); entry_.cmd_line_args_.assign(cmd_line_args.begin(), cmd_line_args.end()); entry_.exe_file_ = GetProcessExecutablePath(pid).BaseName().value(); return true; @@ -402,329 +168,9 @@ bool NamedProcessIterator::IncludeEntry() { } -// static -ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { - return new ProcessMetrics(process); -} - -// On linux, we return vsize. -size_t ProcessMetrics::GetPagefileUsage() const { - return ReadProcStatsAndGetFieldAsSizeT(process_, 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 ReadProcStatsAndGetFieldAsSizeT(process_, 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 = 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 = 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_ = SysInfo::NumberOfProcessors(); -} - - -// Exposed for testing. -int ParseProcStatCPU(const std::string& input) { - std::vector<std::string> proc_stats; - if (!ParseProcStats(input, &proc_stats)) - return -1; - - if (proc_stats.size() <= VM_STIME) - return -1; - int utime = GetProcStatsFieldAsInt(proc_stats, VM_UTIME); - int stime = GetProcStatsFieldAsInt(proc_stats, VM_STIME); - return utime + stime; -} - int GetNumberOfThreads(ProcessHandle process) { - return ReadProcStatsAndGetFieldAsInt(process, VM_NUMTHREADS); -} - -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; -} - -size_t GetSystemCommitCharge() { - SystemMemoryInfoKB meminfo; - if (!GetSystemMemoryInfo(&meminfo)) - return 0; - return meminfo.total - meminfo.free - meminfo.buffers - meminfo.cached; + return internal::ReadProcStatsAndGetFieldAsInt(process, + internal::VM_NUMTHREADS); } namespace { @@ -859,7 +305,7 @@ bool AdjustOOMScore(ProcessId process, int score) { if (score < 0 || score > kMaxOomScore) return false; - FilePath oom_path(GetProcPidDir(process)); + FilePath oom_path(internal::GetProcPidDir(process)); // Attempt to write the newer oom_score_adj file first. FilePath oom_file = oom_path.AppendASCII("oom_score_adj"); diff --git a/base/process_util_mac.mm b/base/process_util_mac.mm index e0174b4..c9f0ef7 100644 --- a/base/process_util_mac.mm +++ b/base/process_util_mac.mm @@ -184,313 +184,6 @@ bool NamedProcessIterator::IncludeEntry() { } -// ------------------------------------------------------------------------ -// NOTE: about ProcessMetrics -// -// 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. -// - -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(); -} - -// static -ProcessMetrics* ProcessMetrics::CreateProcessMetrics( - ProcessHandle process, - ProcessMetrics::PortProvider* port_provider) { - return new ProcessMetrics(process, port_provider); -} - -bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { - return false; -} - -static 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; -} - -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; -} - -static 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; -} - -static 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; -} - -// 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; -} - -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; -} - // These are helpers for EnableTerminationOnHeapCorruption, which is a no-op // on 64 bit Macs. #if ARCH_CPU_32_BITS diff --git a/base/process_util_openbsd.cc b/base/process_util_openbsd.cc index 099c2b4..de16909 100644 --- a/base/process_util_openbsd.cc +++ b/base/process_util_openbsd.cc @@ -178,162 +178,6 @@ bool NamedProcessIterator::IncludeEntry() { } -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; - 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; -} - -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); -} - void EnableTerminationOnOutOfMemory() { } diff --git a/base/process_util_posix.cc b/base/process_util_posix.cc index 6f15130..9b05b98 100644 --- a/base/process_util_posix.cc +++ b/base/process_util_posix.cc @@ -771,8 +771,6 @@ bool LaunchProcess(const CommandLine& cmdline, return LaunchProcess(cmdline.argv(), options, process_handle); } -ProcessMetrics::~ProcessMetrics() { } - void RaiseProcessToHighPriority() { // On POSIX, we don't actually do anything here. We could try to nice() or // setpriority() or sched_getscheduler, but these all require extra rights. @@ -946,14 +944,6 @@ bool WaitForSingleProcess(ProcessHandle handle, base::TimeDelta wait) { } } -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; -} - // Return value used by GetAppOutputInternal to encapsulate the various exit // scenarios from the function. enum GetAppOutputInternalResult { diff --git a/base/process_util_win.cc b/base/process_util_win.cc index 0dd679b..1a30d0e 100644 --- a/base/process_util_win.cc +++ b/base/process_util_win.cc @@ -33,9 +33,6 @@ namespace base { namespace { -// System pagesize. This value remains constant on x86/64 architectures. -const int PAGESIZE_KB = 4; - // Exit codes with special meanings on Windows. const DWORD kNormalTerminationExitCode = 0; const DWORD kDebuggerInactiveExitCode = 0xC0000354; @@ -679,267 +676,6 @@ void EnsureProcessTerminated(ProcessHandle process) { base::TimeDelta::FromMilliseconds(kWaitInterval)); } -/////////////////////////////////////////////////////////////////////////////// -// ProcesMetrics - -ProcessMetrics::ProcessMetrics(ProcessHandle process) - : process_(process), - processor_count_(base::SysInfo::NumberOfProcessors()), - last_time_(0), - last_system_time_(0) { -} - -// static -ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { - return new ProcessMetrics(process); -} - -ProcessMetrics::~ProcessMetrics() { } - -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::GetIOCounters(IoCounters* io_counters) const { - return GetProcessIoCounters(process_, io_counters) != FALSE; -} - -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 EnableLowFragmentationHeap() { HMODULE kernel32 = GetModuleHandle(L"kernel32.dll"); HeapSetFn heap_set = reinterpret_cast<HeapSetFn>(GetProcAddress( @@ -988,43 +724,4 @@ void RaiseProcessToHighPriority() { SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); } -// 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 |