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