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