profile/
system_reporter.rs1#[cfg(target_os = "macos")]
6use std::ptr;
7
8#[cfg(all(target_os = "linux", target_env = "gnu"))]
9use libc::c_int;
10use profile_traits::mem::{ProcessReports, Report, ReportKind, ReporterRequest};
11use profile_traits::path;
12
13const SYSTEM_HEAP_ALLOCATED_STR: &str = "system-heap-allocated";
14const SYSTEM_HEAP_RESERVED_STR: &str = "system-heap-reserved";
15
16struct SystemHeapInfo {
17 allocated: Option<usize>,
18 reserved: Option<usize>,
19}
20
21pub fn collect_reports(request: ReporterRequest) {
23 let mut reports = vec![];
24 {
25 let mut report = |path, size| {
26 if let Some(size) = size {
27 reports.push(Report {
28 path,
29 kind: ReportKind::NonExplicitSize,
30 size,
31 });
32 }
33 };
34
35 report(path!["vsize"], vsize());
37 report(path!["resident"], resident());
38 report(path!["pss"], proportional_set_size());
39
40 for seg in resident_segments() {
44 report(path!["resident-according-to-smaps", seg.0], Some(seg.1));
45 }
46
47 let system_heap = system_heap_info();
54 report(path![SYSTEM_HEAP_ALLOCATED_STR], system_heap.allocated);
55 report(path![SYSTEM_HEAP_RESERVED_STR], system_heap.reserved);
56
57 for heap_report in servo_allocator::heap_reports() {
58 report(path![heap_report.path], heap_report.size);
59 }
60 }
61
62 request.reports_channel.send(ProcessReports::new(reports));
63}
64
65#[cfg(all(target_os = "linux", target_env = "gnu"))]
66unsafe extern "C" {
67 fn mallinfo() -> struct_mallinfo;
68}
69
70#[cfg(all(target_os = "linux", target_env = "gnu"))]
71#[repr(C)]
72pub struct struct_mallinfo {
73 arena: c_int,
74 ordblks: c_int,
75 smblks: c_int,
76 hblks: c_int,
77 hblkhd: c_int,
78 usmblks: c_int,
79 fsmblks: c_int,
80 uordblks: c_int,
81 fordblks: c_int,
82 keepcost: c_int,
83}
84
85#[cfg(all(target_os = "linux", target_env = "gnu"))]
86fn system_heap_info() -> SystemHeapInfo {
87 let info: struct_mallinfo = unsafe { mallinfo() };
88
89 let allocated = if info.hblkhd >= 0 && info.uordblks >= 0 {
100 Some(info.hblkhd as usize + info.uordblks as usize)
101 } else {
102 None
103 };
104
105 let reserved = if info.arena >= 0 && info.hblkhd >= 0 {
106 Some(info.arena as usize + info.hblkhd as usize)
107 } else {
108 None
109 };
110
111 SystemHeapInfo {
112 allocated,
113 reserved,
114 }
115}
116
117#[cfg(target_os = "macos")]
118fn macos_malloc_statistics() -> libc::malloc_statistics_t {
119 let mut stats = libc::malloc_statistics_t {
120 blocks_in_use: 0,
121 size_in_use: 0,
122 max_size_in_use: 0,
123 size_allocated: 0,
124 };
125 unsafe {
126 libc::malloc_zone_statistics(ptr::null_mut(), &mut stats);
128 }
129 stats
130}
131
132#[cfg(target_os = "macos")]
133fn system_heap_info() -> SystemHeapInfo {
134 let stats = macos_malloc_statistics();
135 SystemHeapInfo {
136 allocated: Some(stats.size_in_use),
137 reserved: Some(stats.size_allocated),
138 }
139}
140
141#[cfg(not(any(all(target_os = "linux", target_env = "gnu"), target_os = "macos")))]
142fn system_heap_info() -> SystemHeapInfo {
143 SystemHeapInfo {
144 allocated: None,
145 reserved: None,
146 }
147}
148
149#[cfg(target_os = "linux")]
150fn page_size() -> usize {
151 unsafe { ::libc::sysconf(::libc::_SC_PAGESIZE) as usize }
152}
153
154#[cfg(target_os = "linux")]
155fn proc_self_statm_field(field: usize) -> Option<usize> {
156 use std::fs::File;
157 use std::io::Read;
158
159 let mut f = File::open("/proc/self/statm").ok()?;
160 let mut contents = String::new();
161 f.read_to_string(&mut contents).ok()?;
162 let s = contents.split_whitespace().nth(field)?;
163 let npages = s.parse::<usize>().ok()?;
164 Some(npages * page_size())
165}
166
167#[cfg(target_os = "linux")]
168fn vsize() -> Option<usize> {
169 proc_self_statm_field(0)
170}
171
172#[cfg(target_os = "linux")]
173fn resident() -> Option<usize> {
174 proc_self_statm_field(1)
175}
176#[cfg(target_os = "linux")]
177fn proportional_set_size() -> Option<usize> {
178 use std::fs::File;
179 use std::io::Read;
180 let mut file = File::open("/proc/self/smaps_rollup").ok()?;
181 let mut contents = String::new();
182 file.read_to_string(&mut contents).ok()?;
183 let pss_line = contents
184 .split("\n")
185 .find(|string| string.contains("Pss:"))?;
186
187 let pss_str = pss_line.split_whitespace().nth(1)?;
189 pss_str.parse().ok()
190}
191
192#[cfg(not(target_os = "linux"))]
193fn proportional_set_size() -> Option<usize> {
194 None
195}
196
197#[cfg(target_os = "macos")]
198fn task_basic_info() -> Option<mach2::task_info::task_basic_info> {
199 use mach2::kern_return::KERN_SUCCESS;
200 use mach2::task::task_info;
201 use mach2::task_info::{TASK_BASIC_INFO, TASK_BASIC_INFO_COUNT, task_basic_info};
202 use mach2::traps::mach_task_self;
203
204 let mut info = task_basic_info::default();
205 let mut count = TASK_BASIC_INFO_COUNT;
206 if unsafe {
207 task_info(
208 mach_task_self(),
209 TASK_BASIC_INFO,
210 std::ptr::from_mut(&mut info).cast(),
211 std::ptr::from_mut(&mut count),
212 )
213 } != KERN_SUCCESS
214 {
215 return None;
216 }
217 Some(info)
218}
219
220#[cfg(target_os = "macos")]
221fn vsize() -> Option<usize> {
222 task_basic_info().map(|task_basic_info| task_basic_info.virtual_size)
223}
224
225#[cfg(target_os = "macos")]
226fn resident() -> Option<usize> {
227 task_basic_info().map(|task_basic_info| task_basic_info.resident_size)
228}
229
230#[cfg(not(any(target_os = "linux", target_os = "macos")))]
231fn vsize() -> Option<usize> {
232 None
233}
234
235#[cfg(not(any(target_os = "linux", target_os = "macos")))]
236fn resident() -> Option<usize> {
237 None
238}
239
240#[cfg(target_os = "linux")]
241fn resident_segments() -> Vec<(String, usize)> {
242 use std::collections::HashMap;
243 use std::collections::hash_map::Entry;
244 use std::fs::File;
245 use std::io::{BufRead, BufReader};
246
247 use regex::Regex;
248
249 let f = match File::open("/proc/self/smaps") {
263 Ok(f) => BufReader::new(f),
264 Err(_) => return vec![],
265 };
266
267 let seg_re = Regex::new(
268 r"^[[:xdigit:]]+-[[:xdigit:]]+ (....) [[:xdigit:]]+ [[:xdigit:]]+:[[:xdigit:]]+ \d+ +(.*)",
269 )
270 .unwrap();
271 let rss_re = Regex::new(r"^Rss: +(\d+) kB").unwrap();
272
273 let mut seg_map: HashMap<String, usize> = HashMap::new();
275
276 #[derive(PartialEq)]
277 enum LookingFor {
278 Segment,
279 Rss,
280 }
281 let mut looking_for = LookingFor::Segment;
282
283 let mut curr_seg_name = String::new();
284
285 for line in f.lines() {
287 let line = match line {
288 Ok(line) => line,
289 Err(_) => continue,
290 };
291 if looking_for == LookingFor::Segment {
292 let cap = match seg_re.captures(&line) {
294 Some(cap) => cap,
295 None => continue,
296 };
297 let perms = cap.get(1).unwrap().as_str();
298 let pathname = cap.get(2).unwrap().as_str();
299
300 curr_seg_name.clear();
302 if pathname.is_empty() || pathname.starts_with("[stack:") {
303 curr_seg_name.push_str("anonymous");
308 } else {
309 curr_seg_name.push_str(pathname);
310 }
311 curr_seg_name.push_str(" (");
312 curr_seg_name.push_str(perms);
313 curr_seg_name.push(')');
314
315 looking_for = LookingFor::Rss;
316 } else {
317 let cap = match rss_re.captures(&line) {
319 Some(cap) => cap,
320 None => continue,
321 };
322 let rss = cap.get(1).unwrap().as_str().parse::<usize>().unwrap() * 1024;
323
324 if rss > 0 {
325 let seg_name = if rss < 512 * 1024 {
327 "other".to_owned()
328 } else {
329 curr_seg_name.clone()
330 };
331 match seg_map.entry(seg_name) {
332 Entry::Vacant(entry) => {
333 entry.insert(rss);
334 },
335 Entry::Occupied(mut entry) => *entry.get_mut() += rss,
336 }
337 }
338
339 looking_for = LookingFor::Segment;
340 }
341 }
342
343 seg_map.into_iter().collect()
347}
348
349#[cfg(not(target_os = "linux"))]
350fn resident_segments() -> Vec<(String, usize)> {
351 vec![]
352}