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