Home | History | Annotate | Download | only in process
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "base/process/process_metrics.h"
      6 
      7 #include <mach/mach.h>
      8 #include <mach/mach_vm.h>
      9 #include <mach/shared_region.h>
     10 #include <stddef.h>
     11 #include <stdint.h>
     12 #include <sys/sysctl.h>
     13 
     14 #include "base/containers/hash_tables.h"
     15 #include "base/logging.h"
     16 #include "base/mac/mach_logging.h"
     17 #include "base/mac/scoped_mach_port.h"
     18 #include "base/sys_info.h"
     19 
     20 #if !defined(TASK_POWER_INFO)
     21 // Doesn't exist in the 10.6 or 10.7 SDKs.
     22 #define TASK_POWER_INFO        21
     23 struct task_power_info {
     24         uint64_t                total_user;
     25         uint64_t                total_system;
     26         uint64_t                task_interrupt_wakeups;
     27         uint64_t                task_platform_idle_wakeups;
     28         uint64_t                task_timer_wakeups_bin_1;
     29         uint64_t                task_timer_wakeups_bin_2;
     30 };
     31 typedef struct task_power_info        task_power_info_data_t;
     32 typedef struct task_power_info        *task_power_info_t;
     33 #define TASK_POWER_INFO_COUNT        ((mach_msg_type_number_t) \
     34                 (sizeof (task_power_info_data_t) / sizeof (natural_t)))
     35 #endif
     36 
     37 namespace base {
     38 
     39 namespace {
     40 
     41 bool GetTaskInfo(mach_port_t task, task_basic_info_64* task_info_data) {
     42   if (task == MACH_PORT_NULL)
     43     return false;
     44   mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT;
     45   kern_return_t kr = task_info(task,
     46                                TASK_BASIC_INFO_64,
     47                                reinterpret_cast<task_info_t>(task_info_data),
     48                                &count);
     49   // Most likely cause for failure: |task| is a zombie.
     50   return kr == KERN_SUCCESS;
     51 }
     52 
     53 bool GetCPUTypeForProcess(pid_t /* pid */, cpu_type_t* cpu_type) {
     54   size_t len = sizeof(*cpu_type);
     55   int result = sysctlbyname("sysctl.proc_cputype",
     56                             cpu_type,
     57                             &len,
     58                             NULL,
     59                             0);
     60   if (result != 0) {
     61     DPLOG(ERROR) << "sysctlbyname(""sysctl.proc_cputype"")";
     62     return false;
     63   }
     64 
     65   return true;
     66 }
     67 
     68 bool IsAddressInSharedRegion(mach_vm_address_t addr, cpu_type_t type) {
     69   if (type == CPU_TYPE_I386) {
     70     return addr >= SHARED_REGION_BASE_I386 &&
     71            addr < (SHARED_REGION_BASE_I386 + SHARED_REGION_SIZE_I386);
     72   } else if (type == CPU_TYPE_X86_64) {
     73     return addr >= SHARED_REGION_BASE_X86_64 &&
     74            addr < (SHARED_REGION_BASE_X86_64 + SHARED_REGION_SIZE_X86_64);
     75   } else {
     76     return false;
     77   }
     78 }
     79 
     80 }  // namespace
     81 
     82 SystemMemoryInfoKB::SystemMemoryInfoKB() {
     83   total = 0;
     84   free = 0;
     85 }
     86 
     87 SystemMemoryInfoKB::SystemMemoryInfoKB(const SystemMemoryInfoKB& other) =
     88     default;
     89 
     90 // Getting a mach task from a pid for another process requires permissions in
     91 // general, so there doesn't really seem to be a way to do these (and spinning
     92 // up ps to fetch each stats seems dangerous to put in a base api for anyone to
     93 // call). Child processes ipc their port, so return something if available,
     94 // otherwise return 0.
     95 
     96 // static
     97 ProcessMetrics* ProcessMetrics::CreateProcessMetrics(
     98     ProcessHandle process,
     99     PortProvider* port_provider) {
    100   return new ProcessMetrics(process, port_provider);
    101 }
    102 
    103 size_t ProcessMetrics::GetPagefileUsage() const {
    104   task_basic_info_64 task_info_data;
    105   if (!GetTaskInfo(TaskForPid(process_), &task_info_data))
    106     return 0;
    107   return task_info_data.virtual_size;
    108 }
    109 
    110 size_t ProcessMetrics::GetPeakPagefileUsage() const {
    111   return 0;
    112 }
    113 
    114 size_t ProcessMetrics::GetWorkingSetSize() const {
    115   task_basic_info_64 task_info_data;
    116   if (!GetTaskInfo(TaskForPid(process_), &task_info_data))
    117     return 0;
    118   return task_info_data.resident_size;
    119 }
    120 
    121 size_t ProcessMetrics::GetPeakWorkingSetSize() const {
    122   return 0;
    123 }
    124 
    125 // This is a rough approximation of the algorithm that libtop uses.
    126 // private_bytes is the size of private resident memory.
    127 // shared_bytes is the size of shared resident memory.
    128 bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes,
    129                                     size_t* shared_bytes) {
    130   size_t private_pages_count = 0;
    131   size_t shared_pages_count = 0;
    132 
    133   if (!private_bytes && !shared_bytes)
    134     return true;
    135 
    136   mach_port_t task = TaskForPid(process_);
    137   if (task == MACH_PORT_NULL) {
    138     DLOG(ERROR) << "Invalid process";
    139     return false;
    140   }
    141 
    142   cpu_type_t cpu_type;
    143   if (!GetCPUTypeForProcess(process_, &cpu_type))
    144     return false;
    145 
    146   // The same region can be referenced multiple times. To avoid double counting
    147   // we need to keep track of which regions we've already counted.
    148   base::hash_set<int> seen_objects;
    149 
    150   // We iterate through each VM region in the task's address map. For shared
    151   // memory we add up all the pages that are marked as shared. Like libtop we
    152   // try to avoid counting pages that are also referenced by other tasks. Since
    153   // we don't have access to the VM regions of other tasks the only hint we have
    154   // is if the address is in the shared region area.
    155   //
    156   // Private memory is much simpler. We simply count the pages that are marked
    157   // as private or copy on write (COW).
    158   //
    159   // See libtop_update_vm_regions in
    160   // http://www.opensource.apple.com/source/top/top-67/libtop.c
    161   mach_vm_size_t size = 0;
    162   for (mach_vm_address_t address = MACH_VM_MIN_ADDRESS;; address += size) {
    163     vm_region_top_info_data_t info;
    164     mach_msg_type_number_t info_count = VM_REGION_TOP_INFO_COUNT;
    165     mach_port_t object_name;
    166     kern_return_t kr = mach_vm_region(task,
    167                                       &address,
    168                                       &size,
    169                                       VM_REGION_TOP_INFO,
    170                                       reinterpret_cast<vm_region_info_t>(&info),
    171                                       &info_count,
    172                                       &object_name);
    173     if (kr == KERN_INVALID_ADDRESS) {
    174       // We're at the end of the address space.
    175       break;
    176     } else if (kr != KERN_SUCCESS) {
    177       MACH_DLOG(ERROR, kr) << "mach_vm_region";
    178       return false;
    179     }
    180 
    181     // The kernel always returns a null object for VM_REGION_TOP_INFO, but
    182     // balance it with a deallocate in case this ever changes. See 10.9.2
    183     // xnu-2422.90.20/osfmk/vm/vm_map.c vm_map_region.
    184     mach_port_deallocate(mach_task_self(), object_name);
    185 
    186     if (IsAddressInSharedRegion(address, cpu_type) &&
    187         info.share_mode != SM_PRIVATE)
    188       continue;
    189 
    190     if (info.share_mode == SM_COW && info.ref_count == 1)
    191       info.share_mode = SM_PRIVATE;
    192 
    193     switch (info.share_mode) {
    194       case SM_PRIVATE:
    195         private_pages_count += info.private_pages_resident;
    196         private_pages_count += info.shared_pages_resident;
    197         break;
    198       case SM_COW:
    199         private_pages_count += info.private_pages_resident;
    200         // Fall through
    201       case SM_SHARED:
    202         if (seen_objects.count(info.obj_id) == 0) {
    203           // Only count the first reference to this region.
    204           seen_objects.insert(info.obj_id);
    205           shared_pages_count += info.shared_pages_resident;
    206         }
    207         break;
    208       default:
    209         break;
    210     }
    211   }
    212 
    213   if (private_bytes)
    214     *private_bytes = private_pages_count * PAGE_SIZE;
    215   if (shared_bytes)
    216     *shared_bytes = shared_pages_count * PAGE_SIZE;
    217 
    218   return true;
    219 }
    220 
    221 void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) const {
    222   WorkingSetKBytes unused;
    223   if (!GetCommittedAndWorkingSetKBytes(usage, &unused)) {
    224     *usage = CommittedKBytes();
    225   }
    226 }
    227 
    228 bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const {
    229   CommittedKBytes unused;
    230   return GetCommittedAndWorkingSetKBytes(&unused, ws_usage);
    231 }
    232 
    233 bool ProcessMetrics::GetCommittedAndWorkingSetKBytes(
    234     CommittedKBytes* usage,
    235     WorkingSetKBytes* ws_usage) const {
    236   task_basic_info_64 task_info_data;
    237   if (!GetTaskInfo(TaskForPid(process_), &task_info_data))
    238     return false;
    239 
    240   usage->priv = task_info_data.virtual_size / 1024;
    241   usage->mapped = 0;
    242   usage->image = 0;
    243 
    244   ws_usage->priv = task_info_data.resident_size / 1024;
    245   ws_usage->shareable = 0;
    246   ws_usage->shared = 0;
    247 
    248   return true;
    249 }
    250 
    251 #define TIME_VALUE_TO_TIMEVAL(a, r) do {  \
    252   (r)->tv_sec = (a)->seconds;             \
    253   (r)->tv_usec = (a)->microseconds;       \
    254 } while (0)
    255 
    256 double ProcessMetrics::GetCPUUsage() {
    257   mach_port_t task = TaskForPid(process_);
    258   if (task == MACH_PORT_NULL)
    259     return 0;
    260 
    261   // Libtop explicitly loops over the threads (libtop_pinfo_update_cpu_usage()
    262   // in libtop.c), but this is more concise and gives the same results:
    263   task_thread_times_info thread_info_data;
    264   mach_msg_type_number_t thread_info_count = TASK_THREAD_TIMES_INFO_COUNT;
    265   kern_return_t kr = task_info(task,
    266                                TASK_THREAD_TIMES_INFO,
    267                                reinterpret_cast<task_info_t>(&thread_info_data),
    268                                &thread_info_count);
    269   if (kr != KERN_SUCCESS) {
    270     // Most likely cause: |task| is a zombie.
    271     return 0;
    272   }
    273 
    274   task_basic_info_64 task_info_data;
    275   if (!GetTaskInfo(task, &task_info_data))
    276     return 0;
    277 
    278   /* Set total_time. */
    279   // thread info contains live time...
    280   struct timeval user_timeval, system_timeval, task_timeval;
    281   TIME_VALUE_TO_TIMEVAL(&thread_info_data.user_time, &user_timeval);
    282   TIME_VALUE_TO_TIMEVAL(&thread_info_data.system_time, &system_timeval);
    283   timeradd(&user_timeval, &system_timeval, &task_timeval);
    284 
    285   // ... task info contains terminated time.
    286   TIME_VALUE_TO_TIMEVAL(&task_info_data.user_time, &user_timeval);
    287   TIME_VALUE_TO_TIMEVAL(&task_info_data.system_time, &system_timeval);
    288   timeradd(&user_timeval, &task_timeval, &task_timeval);
    289   timeradd(&system_timeval, &task_timeval, &task_timeval);
    290 
    291   TimeTicks time = TimeTicks::Now();
    292   int64_t task_time = TimeValToMicroseconds(task_timeval);
    293 
    294   if (last_system_time_ == 0) {
    295     // First call, just set the last values.
    296     last_cpu_time_ = time;
    297     last_system_time_ = task_time;
    298     return 0;
    299   }
    300 
    301   int64_t system_time_delta = task_time - last_system_time_;
    302   int64_t time_delta = (time - last_cpu_time_).InMicroseconds();
    303   DCHECK_NE(0U, time_delta);
    304   if (time_delta == 0)
    305     return 0;
    306 
    307   last_cpu_time_ = time;
    308   last_system_time_ = task_time;
    309 
    310   return static_cast<double>(system_time_delta * 100.0) / time_delta;
    311 }
    312 
    313 int ProcessMetrics::GetIdleWakeupsPerSecond() {
    314   mach_port_t task = TaskForPid(process_);
    315   if (task == MACH_PORT_NULL)
    316     return 0;
    317 
    318   task_power_info power_info_data;
    319   mach_msg_type_number_t power_info_count = TASK_POWER_INFO_COUNT;
    320   kern_return_t kr = task_info(task,
    321                                TASK_POWER_INFO,
    322                                reinterpret_cast<task_info_t>(&power_info_data),
    323                                &power_info_count);
    324   if (kr != KERN_SUCCESS) {
    325     // Most likely cause: |task| is a zombie, or this is on a pre-10.8.4 system
    326     // where TASK_POWER_INFO isn't supported yet.
    327     return 0;
    328   }
    329   return CalculateIdleWakeupsPerSecond(
    330       power_info_data.task_platform_idle_wakeups);
    331 }
    332 
    333 bool ProcessMetrics::GetIOCounters(IoCounters* /* io_counters */) const {
    334   return false;
    335 }
    336 
    337 ProcessMetrics::ProcessMetrics(ProcessHandle process,
    338                                PortProvider* port_provider)
    339     : process_(process),
    340       last_system_time_(0),
    341       last_absolute_idle_wakeups_(0),
    342       port_provider_(port_provider) {
    343   processor_count_ = SysInfo::NumberOfProcessors();
    344 }
    345 
    346 mach_port_t ProcessMetrics::TaskForPid(ProcessHandle /* process */) const {
    347   mach_port_t task = MACH_PORT_NULL;
    348   if (port_provider_)
    349     task = port_provider_->TaskForPid(process_);
    350   if (task == MACH_PORT_NULL && process_ == getpid())
    351     task = mach_task_self();
    352   return task;
    353 }
    354 
    355 // Bytes committed by the system.
    356 size_t GetSystemCommitCharge() {
    357   base::mac::ScopedMachSendRight host(mach_host_self());
    358   mach_msg_type_number_t count = HOST_VM_INFO_COUNT;
    359   vm_statistics_data_t data;
    360   kern_return_t kr = host_statistics(host.get(), HOST_VM_INFO,
    361                                      reinterpret_cast<host_info_t>(&data),
    362                                      &count);
    363   if (kr != KERN_SUCCESS) {
    364     MACH_DLOG(WARNING, kr) << "host_statistics";
    365     return 0;
    366   }
    367 
    368   return (data.active_count * PAGE_SIZE) / 1024;
    369 }
    370 
    371 // On Mac, We only get total memory and free memory from the system.
    372 bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) {
    373   struct host_basic_info hostinfo;
    374   mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
    375   base::mac::ScopedMachSendRight host(mach_host_self());
    376   int result = host_info(host.get(), HOST_BASIC_INFO,
    377                          reinterpret_cast<host_info_t>(&hostinfo), &count);
    378   if (result != KERN_SUCCESS)
    379     return false;
    380 
    381   DCHECK_EQ(HOST_BASIC_INFO_COUNT, count);
    382   meminfo->total = static_cast<int>(hostinfo.max_mem / 1024);
    383 
    384   vm_statistics_data_t vm_info;
    385   count = HOST_VM_INFO_COUNT;
    386 
    387   if (host_statistics(host.get(), HOST_VM_INFO,
    388                       reinterpret_cast<host_info_t>(&vm_info),
    389                       &count) != KERN_SUCCESS) {
    390     return false;
    391   }
    392 
    393   meminfo->free = static_cast<int>(
    394       (vm_info.free_count - vm_info.speculative_count) * PAGE_SIZE / 1024);
    395 
    396   return true;
    397 }
    398 
    399 }  // namespace base
    400