OLD | NEW |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "base/process/process_metrics.h" | 5 #include "base/process/process_metrics.h" |
6 | 6 |
7 #include <mach/mach.h> | 7 #include <mach/mach.h> |
8 #include <mach/mach_vm.h> | 8 #include <mach/mach_vm.h> |
9 #include <mach/shared_region.h> | 9 #include <mach/shared_region.h> |
10 #include <stddef.h> | 10 #include <stddef.h> |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
72 return addr >= SHARED_REGION_BASE_I386 && | 72 return addr >= SHARED_REGION_BASE_I386 && |
73 addr < (SHARED_REGION_BASE_I386 + SHARED_REGION_SIZE_I386); | 73 addr < (SHARED_REGION_BASE_I386 + SHARED_REGION_SIZE_I386); |
74 } else if (type == CPU_TYPE_X86_64) { | 74 } else if (type == CPU_TYPE_X86_64) { |
75 return addr >= SHARED_REGION_BASE_X86_64 && | 75 return addr >= SHARED_REGION_BASE_X86_64 && |
76 addr < (SHARED_REGION_BASE_X86_64 + SHARED_REGION_SIZE_X86_64); | 76 addr < (SHARED_REGION_BASE_X86_64 + SHARED_REGION_SIZE_X86_64); |
77 } else { | 77 } else { |
78 return false; | 78 return false; |
79 } | 79 } |
80 } | 80 } |
81 | 81 |
82 enum MachVMRegionResult { Finished, Error, Success }; | |
83 | |
84 // Both |size| and |address| are in-out parameters. | |
85 // |info| is an output parameter, only valid on Success. | |
86 MachVMRegionResult GetTopInfo(mach_port_t task, | |
87 mach_vm_size_t* size, | |
88 mach_vm_address_t* address, | |
89 vm_region_top_info_data_t* info) { | |
90 mach_msg_type_number_t info_count = VM_REGION_TOP_INFO_COUNT; | |
91 mach_port_t object_name; | |
92 kern_return_t kr = mach_vm_region(task, address, size, VM_REGION_TOP_INFO, | |
93 reinterpret_cast<vm_region_info_t>(info), | |
94 &info_count, &object_name); | |
95 // We're at the end of the address space. | |
96 if (kr == KERN_INVALID_ADDRESS) | |
97 return Finished; | |
98 | |
99 if (kr != KERN_SUCCESS) | |
100 return Error; | |
101 | |
102 // The kernel always returns a null object for VM_REGION_TOP_INFO, but | |
103 // balance it with a deallocate in case this ever changes. See 10.9.2 | |
104 // xnu-2422.90.20/osfmk/vm/vm_map.c vm_map_region. | |
105 mach_port_deallocate(task, object_name); | |
106 return Success; | |
107 } | |
108 | |
109 MachVMRegionResult GetBasicInfo(mach_port_t task, | |
110 mach_vm_size_t* size, | |
111 mach_vm_address_t* address, | |
112 vm_region_basic_info_64* info) { | |
113 mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64; | |
114 mach_port_t object_name; | |
115 kern_return_t kr = mach_vm_region( | |
116 task, address, size, VM_REGION_BASIC_INFO_64, | |
117 reinterpret_cast<vm_region_info_t>(info), &info_count, &object_name); | |
118 if (kr == KERN_INVALID_ADDRESS) { | |
119 // We're at the end of the address space. | |
120 return Finished; | |
121 } else if (kr != KERN_SUCCESS) { | |
122 return Error; | |
123 } | |
124 | |
125 // The kernel always returns a null object for VM_REGION_BASIC_INFO_64, but | |
126 // balance it with a deallocate in case this ever changes. See 10.9.2 | |
127 // xnu-2422.90.20/osfmk/vm/vm_map.c vm_map_region. | |
128 mach_port_deallocate(task, object_name); | |
129 return Success; | |
130 } | |
131 | |
132 } // namespace | 82 } // namespace |
133 | 83 |
134 // Getting a mach task from a pid for another process requires permissions in | 84 // Getting a mach task from a pid for another process requires permissions in |
135 // general, so there doesn't really seem to be a way to do these (and spinning | 85 // general, so there doesn't really seem to be a way to do these (and spinning |
136 // up ps to fetch each stats seems dangerous to put in a base api for anyone to | 86 // up ps to fetch each stats seems dangerous to put in a base api for anyone to |
137 // call). Child processes ipc their port, so return something if available, | 87 // call). Child processes ipc their port, so return something if available, |
138 // otherwise return 0. | 88 // otherwise return 0. |
139 | 89 |
140 // static | 90 // static |
141 std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateProcessMetrics( | 91 std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateProcessMetrics( |
142 ProcessHandle process, | 92 ProcessHandle process, |
143 PortProvider* port_provider) { | 93 PortProvider* port_provider) { |
144 return WrapUnique(new ProcessMetrics(process, port_provider)); | 94 return WrapUnique(new ProcessMetrics(process, port_provider)); |
145 } | 95 } |
146 | 96 |
147 size_t ProcessMetrics::GetPagefileUsage() const { | 97 size_t ProcessMetrics::GetPagefileUsage() const { |
148 task_basic_info_64 task_info_data; | 98 task_basic_info_64 task_info_data; |
149 if (!GetTaskInfo(TaskForPid(process_), &task_info_data)) | 99 if (!GetTaskInfo(TaskForPid(process_), &task_info_data)) |
150 return 0; | 100 return 0; |
151 return task_info_data.virtual_size; | 101 return task_info_data.virtual_size; |
152 } | 102 } |
153 | 103 |
154 size_t ProcessMetrics::GetPeakPagefileUsage() const { | 104 size_t ProcessMetrics::GetPeakPagefileUsage() const { |
155 return 0; | 105 return 0; |
156 } | 106 } |
157 | 107 |
158 size_t ProcessMetrics::GetWorkingSetSize() const { | 108 size_t ProcessMetrics::GetWorkingSetSize() const { |
| 109 size_t private_bytes = 0; |
| 110 size_t shared_bytes = 0; |
159 size_t resident_bytes = 0; | 111 size_t resident_bytes = 0; |
160 if (!GetMemoryBytes(nullptr, nullptr, &resident_bytes, nullptr)) | 112 if (!GetMemoryBytes(&private_bytes, &shared_bytes, &resident_bytes)) |
161 return 0; | 113 return 0; |
162 return resident_bytes; | 114 return resident_bytes; |
163 } | 115 } |
164 | 116 |
165 size_t ProcessMetrics::GetPeakWorkingSetSize() const { | 117 size_t ProcessMetrics::GetPeakWorkingSetSize() const { |
166 return 0; | 118 return 0; |
167 } | 119 } |
168 | 120 |
169 bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, | |
170 size_t* shared_bytes) const { | |
171 return GetMemoryBytes(private_bytes, shared_bytes, nullptr, nullptr); | |
172 } | |
173 | |
174 // This is a rough approximation of the algorithm that libtop uses. | 121 // This is a rough approximation of the algorithm that libtop uses. |
175 // private_bytes is the size of private resident memory. | 122 // private_bytes is the size of private resident memory. |
176 // shared_bytes is the size of shared resident memory. | 123 // shared_bytes is the size of shared resident memory. |
177 bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, | 124 bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, |
178 size_t* shared_bytes, | 125 size_t* shared_bytes) const { |
179 size_t* resident_bytes, | |
180 size_t* locked_bytes) const { | |
181 size_t private_pages_count = 0; | 126 size_t private_pages_count = 0; |
182 size_t shared_pages_count = 0; | 127 size_t shared_pages_count = 0; |
183 size_t wired_pages_count = 0; | 128 |
| 129 if (!private_bytes && !shared_bytes) |
| 130 return true; |
184 | 131 |
185 mach_port_t task = TaskForPid(process_); | 132 mach_port_t task = TaskForPid(process_); |
186 if (task == MACH_PORT_NULL) { | 133 if (task == MACH_PORT_NULL) { |
187 DLOG(ERROR) << "Invalid process"; | 134 DLOG(ERROR) << "Invalid process"; |
188 return false; | 135 return false; |
189 } | 136 } |
190 | 137 |
191 cpu_type_t cpu_type; | 138 cpu_type_t cpu_type; |
192 if (!GetCPUTypeForProcess(process_, &cpu_type)) | 139 if (!GetCPUTypeForProcess(process_, &cpu_type)) |
193 return false; | 140 return false; |
194 | 141 |
195 // The same region can be referenced multiple times. To avoid double counting | 142 // The same region can be referenced multiple times. To avoid double counting |
196 // we need to keep track of which regions we've already counted. | 143 // we need to keep track of which regions we've already counted. |
197 hash_set<int> seen_objects; | 144 hash_set<int> seen_objects; |
198 | 145 |
199 // We iterate through each VM region in the task's address map. For shared | 146 // We iterate through each VM region in the task's address map. For shared |
200 // memory we add up all the pages that are marked as shared. Like libtop we | 147 // memory we add up all the pages that are marked as shared. Like libtop we |
201 // try to avoid counting pages that are also referenced by other tasks. Since | 148 // try to avoid counting pages that are also referenced by other tasks. Since |
202 // we don't have access to the VM regions of other tasks the only hint we have | 149 // we don't have access to the VM regions of other tasks the only hint we have |
203 // is if the address is in the shared region area. | 150 // is if the address is in the shared region area. |
204 // | 151 // |
205 // Private memory is much simpler. We simply count the pages that are marked | 152 // Private memory is much simpler. We simply count the pages that are marked |
206 // as private or copy on write (COW). | 153 // as private or copy on write (COW). |
207 // | 154 // |
208 // See libtop_update_vm_regions in | 155 // See libtop_update_vm_regions in |
209 // http://www.opensource.apple.com/source/top/top-67/libtop.c | 156 // http://www.opensource.apple.com/source/top/top-67/libtop.c |
210 mach_vm_size_t size = 0; | 157 mach_vm_size_t size = 0; |
211 for (mach_vm_address_t address = MACH_VM_MIN_ADDRESS;; address += size) { | 158 for (mach_vm_address_t address = MACH_VM_MIN_ADDRESS;; address += size) { |
212 mach_vm_size_t size_copy = size; | 159 vm_region_top_info_data_t info; |
213 mach_vm_address_t address_copy = address; | 160 mach_msg_type_number_t info_count = VM_REGION_TOP_INFO_COUNT; |
| 161 mach_port_t object_name; |
| 162 kern_return_t kr = mach_vm_region(task, |
| 163 &address, |
| 164 &size, |
| 165 VM_REGION_TOP_INFO, |
| 166 reinterpret_cast<vm_region_info_t>(&info), |
| 167 &info_count, |
| 168 &object_name); |
| 169 if (kr == KERN_INVALID_ADDRESS) { |
| 170 // We're at the end of the address space. |
| 171 break; |
| 172 } else if (kr != KERN_SUCCESS) { |
| 173 MACH_DLOG(ERROR, kr) << "mach_vm_region"; |
| 174 return false; |
| 175 } |
214 | 176 |
215 vm_region_top_info_data_t info; | 177 // The kernel always returns a null object for VM_REGION_TOP_INFO, but |
216 MachVMRegionResult result = GetTopInfo(task, &size, &address, &info); | 178 // balance it with a deallocate in case this ever changes. See 10.9.2 |
217 if (result == Error) | 179 // xnu-2422.90.20/osfmk/vm/vm_map.c vm_map_region. |
218 return false; | 180 mach_port_deallocate(mach_task_self(), object_name); |
219 if (result == Finished) | |
220 break; | |
221 | |
222 vm_region_basic_info_64 basic_info; | |
223 result = GetBasicInfo(task, &size_copy, &address_copy, &basic_info); | |
224 switch (result) { | |
225 case Finished: | |
226 case Error: | |
227 return false; | |
228 case Success: | |
229 break; | |
230 } | |
231 bool is_wired = basic_info.user_wired_count > 0; | |
232 | 181 |
233 if (IsAddressInSharedRegion(address, cpu_type) && | 182 if (IsAddressInSharedRegion(address, cpu_type) && |
234 info.share_mode != SM_PRIVATE) | 183 info.share_mode != SM_PRIVATE) |
235 continue; | 184 continue; |
236 | 185 |
237 if (info.share_mode == SM_COW && info.ref_count == 1) | 186 if (info.share_mode == SM_COW && info.ref_count == 1) |
238 info.share_mode = SM_PRIVATE; | 187 info.share_mode = SM_PRIVATE; |
239 | 188 |
240 switch (info.share_mode) { | 189 switch (info.share_mode) { |
241 case SM_LARGE_PAGE: | 190 case SM_LARGE_PAGE: |
(...skipping 10 matching lines...) Expand all Loading... |
252 case SM_SHARED_ALIASED: | 201 case SM_SHARED_ALIASED: |
253 if (seen_objects.count(info.obj_id) == 0) { | 202 if (seen_objects.count(info.obj_id) == 0) { |
254 // Only count the first reference to this region. | 203 // Only count the first reference to this region. |
255 seen_objects.insert(info.obj_id); | 204 seen_objects.insert(info.obj_id); |
256 shared_pages_count += info.shared_pages_resident; | 205 shared_pages_count += info.shared_pages_resident; |
257 } | 206 } |
258 break; | 207 break; |
259 default: | 208 default: |
260 break; | 209 break; |
261 } | 210 } |
262 if (is_wired) { | |
263 wired_pages_count += | |
264 info.private_pages_resident + info.shared_pages_resident; | |
265 } | |
266 } | 211 } |
267 | 212 |
268 if (private_bytes) | 213 if (private_bytes) |
269 *private_bytes = private_pages_count * PAGE_SIZE; | 214 *private_bytes = private_pages_count * PAGE_SIZE; |
270 if (shared_bytes) | 215 if (shared_bytes) |
271 *shared_bytes = shared_pages_count * PAGE_SIZE; | 216 *shared_bytes = shared_pages_count * PAGE_SIZE; |
272 if (resident_bytes) | |
273 *resident_bytes = (private_pages_count + shared_pages_count) * PAGE_SIZE; | |
274 if (locked_bytes) | |
275 *locked_bytes = wired_pages_count * PAGE_SIZE; | |
276 | 217 |
277 return true; | 218 return true; |
278 } | 219 } |
279 | 220 |
280 void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) const { | 221 void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) const { |
281 WorkingSetKBytes unused; | 222 WorkingSetKBytes unused; |
282 if (!GetCommittedAndWorkingSetKBytes(usage, &unused)) { | 223 if (!GetCommittedAndWorkingSetKBytes(usage, &unused)) { |
283 *usage = CommittedKBytes(); | 224 *usage = CommittedKBytes(); |
284 } | 225 } |
285 } | 226 } |
(...skipping 14 matching lines...) Expand all Loading... |
300 usage->mapped = 0; | 241 usage->mapped = 0; |
301 usage->image = 0; | 242 usage->image = 0; |
302 | 243 |
303 ws_usage->priv = task_info_data.resident_size / 1024; | 244 ws_usage->priv = task_info_data.resident_size / 1024; |
304 ws_usage->shareable = 0; | 245 ws_usage->shareable = 0; |
305 ws_usage->shared = 0; | 246 ws_usage->shared = 0; |
306 | 247 |
307 return true; | 248 return true; |
308 } | 249 } |
309 | 250 |
| 251 bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, |
| 252 size_t* shared_bytes, |
| 253 size_t* resident_bytes) const { |
| 254 if (!GetMemoryBytes(private_bytes, shared_bytes)) |
| 255 return false; |
| 256 *resident_bytes = *private_bytes + *shared_bytes; |
| 257 return true; |
| 258 } |
| 259 |
310 #define TIME_VALUE_TO_TIMEVAL(a, r) do { \ | 260 #define TIME_VALUE_TO_TIMEVAL(a, r) do { \ |
311 (r)->tv_sec = (a)->seconds; \ | 261 (r)->tv_sec = (a)->seconds; \ |
312 (r)->tv_usec = (a)->microseconds; \ | 262 (r)->tv_usec = (a)->microseconds; \ |
313 } while (0) | 263 } while (0) |
314 | 264 |
315 double ProcessMetrics::GetCPUUsage() { | 265 double ProcessMetrics::GetCPUUsage() { |
316 mach_port_t task = TaskForPid(process_); | 266 mach_port_t task = TaskForPid(process_); |
317 if (task == MACH_PORT_NULL) | 267 if (task == MACH_PORT_NULL) |
318 return 0; | 268 return 0; |
319 | 269 |
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
467 saturated_cast<int>(PAGE_SIZE / 1024 * vm_info.speculative_count); | 417 saturated_cast<int>(PAGE_SIZE / 1024 * vm_info.speculative_count); |
468 meminfo->file_backed = | 418 meminfo->file_backed = |
469 saturated_cast<int>(PAGE_SIZE / 1024 * vm_info.external_page_count); | 419 saturated_cast<int>(PAGE_SIZE / 1024 * vm_info.external_page_count); |
470 meminfo->purgeable = | 420 meminfo->purgeable = |
471 saturated_cast<int>(PAGE_SIZE / 1024 * vm_info.purgeable_count); | 421 saturated_cast<int>(PAGE_SIZE / 1024 * vm_info.purgeable_count); |
472 | 422 |
473 return true; | 423 return true; |
474 } | 424 } |
475 | 425 |
476 } // namespace base | 426 } // namespace base |
OLD | NEW |