1#[cfg(not(any(target_os = "windows", target_env = "ohos")))]
6use std::ffi::CString;
7#[cfg(not(any(target_os = "windows", target_env = "ohos")))]
8use std::mem::size_of;
9#[cfg(not(any(target_os = "windows", target_env = "ohos")))]
10use std::ptr::null_mut;
11
12#[cfg(all(target_os = "linux", target_env = "gnu"))]
13use libc::c_int;
14#[cfg(not(any(target_os = "windows", target_env = "ohos")))]
15use libc::{c_void, size_t};
16use profile_traits::mem::{ProcessReports, Report, ReportKind, ReporterRequest};
17use profile_traits::path;
18
19const JEMALLOC_HEAP_ALLOCATED_STR: &str = "jemalloc-heap-allocated";
20const SYSTEM_HEAP_ALLOCATED_STR: &str = "system-heap-allocated";
21const SYSTEM_HEAP_RESERVED_STR: &str = "system-heap-reserved";
22
23struct SystemHeapInfo {
24 allocated: Option<usize>,
25 reserved: Option<usize>,
26}
27
28pub fn collect_reports(request: ReporterRequest) {
30 let mut reports = vec![];
31 {
32 let mut report = |path, size| {
33 if let Some(size) = size {
34 reports.push(Report {
35 path,
36 kind: ReportKind::NonExplicitSize,
37 size,
38 });
39 }
40 };
41
42 report(path!["vsize"], vsize());
44 report(path!["resident"], resident());
45 report(path!["pss"], proportional_set_size());
46
47 for seg in resident_segments() {
51 report(path!["resident-according-to-smaps", seg.0], Some(seg.1));
52 }
53
54 let system_heap = system_heap_info();
57 report(path![SYSTEM_HEAP_ALLOCATED_STR], system_heap.allocated);
58 report(path![SYSTEM_HEAP_RESERVED_STR], system_heap.reserved);
59
60 report(
65 path![JEMALLOC_HEAP_ALLOCATED_STR],
66 jemalloc_stat("stats.allocated"),
67 );
68
69 report(path!["jemalloc-heap-active"], jemalloc_stat("stats.active"));
73
74 report(path!["jemalloc-heap-mapped"], jemalloc_stat("stats.mapped"));
78 }
79
80 request.reports_channel.send(ProcessReports::new(reports));
81}
82
83#[cfg(all(target_os = "linux", target_env = "gnu"))]
84unsafe extern "C" {
85 fn mallinfo() -> struct_mallinfo;
86}
87
88#[cfg(all(target_os = "linux", target_env = "gnu"))]
89#[repr(C)]
90pub struct struct_mallinfo {
91 arena: c_int,
92 ordblks: c_int,
93 smblks: c_int,
94 hblks: c_int,
95 hblkhd: c_int,
96 usmblks: c_int,
97 fsmblks: c_int,
98 uordblks: c_int,
99 fordblks: c_int,
100 keepcost: c_int,
101}
102
103#[cfg(all(target_os = "linux", target_env = "gnu"))]
104fn system_heap_info() -> SystemHeapInfo {
105 let info: struct_mallinfo = unsafe { mallinfo() };
106
107 let allocated = if info.hblkhd >= 0 && info.uordblks >= 0 {
118 Some(info.hblkhd as usize + info.uordblks as usize)
119 } else {
120 None
121 };
122
123 let reserved = if info.arena >= 0 && info.hblkhd >= 0 {
124 Some(info.arena as usize + info.hblkhd as usize)
125 } else {
126 None
127 };
128
129 SystemHeapInfo {
130 allocated,
131 reserved,
132 }
133}
134
135#[cfg(target_os = "macos")]
136fn macos_malloc_statistics() -> libc::malloc_statistics_t {
137 let mut stats = libc::malloc_statistics_t {
138 blocks_in_use: 0,
139 size_in_use: 0,
140 max_size_in_use: 0,
141 size_allocated: 0,
142 };
143 unsafe {
144 libc::malloc_zone_statistics(null_mut(), &mut stats);
146 }
147 stats
148}
149
150#[cfg(target_os = "macos")]
151fn system_heap_info() -> SystemHeapInfo {
152 let stats = macos_malloc_statistics();
153 SystemHeapInfo {
154 allocated: Some(stats.size_in_use),
155 reserved: Some(stats.size_allocated),
156 }
157}
158
159#[cfg(not(any(all(target_os = "linux", target_env = "gnu"), target_os = "macos")))]
160fn system_heap_info() -> SystemHeapInfo {
161 SystemHeapInfo {
162 allocated: None,
163 reserved: None,
164 }
165}
166
167#[cfg(not(any(target_os = "windows", target_env = "ohos")))]
168use tikv_jemalloc_sys::mallctl;
169
170#[cfg(not(any(target_os = "windows", target_env = "ohos")))]
171fn jemalloc_stat(value_name: &str) -> Option<usize> {
172 let epoch_name = "epoch";
176 let epoch_c_name = CString::new(epoch_name).unwrap();
177 let mut epoch: u64 = 0;
178 let epoch_ptr = &mut epoch as *mut _ as *mut c_void;
179 let mut epoch_len = size_of::<u64>() as size_t;
180
181 let value_c_name = CString::new(value_name).unwrap();
182 let mut value: size_t = 0;
183 let value_ptr = &mut value as *mut _ as *mut c_void;
184 let mut value_len = size_of::<size_t>() as size_t;
185
186 let rv = unsafe {
189 mallctl(
190 epoch_c_name.as_ptr(),
191 epoch_ptr,
192 &mut epoch_len,
193 epoch_ptr,
194 epoch_len,
195 )
196 };
197 if rv != 0 {
198 return None;
199 }
200
201 let rv = unsafe {
202 mallctl(
203 value_c_name.as_ptr(),
204 value_ptr,
205 &mut value_len,
206 null_mut(),
207 0,
208 )
209 };
210 if rv != 0 {
211 return None;
212 }
213
214 Some(value as usize)
215}
216
217#[cfg(any(target_os = "windows", target_env = "ohos"))]
218fn jemalloc_stat(_value_name: &str) -> Option<usize> {
219 None
220}
221
222#[cfg(target_os = "linux")]
223fn page_size() -> usize {
224 unsafe { ::libc::sysconf(::libc::_SC_PAGESIZE) as usize }
225}
226
227#[cfg(target_os = "linux")]
228fn proc_self_statm_field(field: usize) -> Option<usize> {
229 use std::fs::File;
230 use std::io::Read;
231
232 let mut f = File::open("/proc/self/statm").ok()?;
233 let mut contents = String::new();
234 f.read_to_string(&mut contents).ok()?;
235 let s = contents.split_whitespace().nth(field)?;
236 let npages = s.parse::<usize>().ok()?;
237 Some(npages * page_size())
238}
239
240#[cfg(target_os = "linux")]
241fn vsize() -> Option<usize> {
242 proc_self_statm_field(0)
243}
244
245#[cfg(target_os = "linux")]
246fn resident() -> Option<usize> {
247 proc_self_statm_field(1)
248}
249#[cfg(target_os = "linux")]
250fn proportional_set_size() -> Option<usize> {
251 use std::fs::File;
252 use std::io::Read;
253 let mut file = File::open("/proc/self/smaps_rollup").ok()?;
254 let mut contents = String::new();
255 file.read_to_string(&mut contents).ok()?;
256 let pss_line = contents
257 .split("\n")
258 .find(|string| string.contains("Pss:"))?;
259
260 let pss_str = pss_line.split_whitespace().nth(1)?;
262 pss_str.parse().ok()
263}
264
265#[cfg(not(target_os = "linux"))]
266fn proportional_set_size() -> Option<usize> {
267 None
268}
269
270#[cfg(target_os = "macos")]
271fn task_basic_info() -> Option<mach2::task_info::task_basic_info> {
272 use mach2::kern_return::KERN_SUCCESS;
273 use mach2::task::task_info;
274 use mach2::task_info::{TASK_BASIC_INFO, TASK_BASIC_INFO_COUNT, task_basic_info};
275 use mach2::traps::mach_task_self;
276
277 let mut info = task_basic_info::default();
278 let mut count = TASK_BASIC_INFO_COUNT;
279 if unsafe {
280 task_info(
281 mach_task_self(),
282 TASK_BASIC_INFO,
283 std::ptr::from_mut(&mut info).cast(),
284 std::ptr::from_mut(&mut count),
285 )
286 } != KERN_SUCCESS
287 {
288 return None;
289 }
290 Some(info)
291}
292
293#[cfg(target_os = "macos")]
294fn vsize() -> Option<usize> {
295 task_basic_info().map(|task_basic_info| task_basic_info.virtual_size)
296}
297
298#[cfg(target_os = "macos")]
299fn resident() -> Option<usize> {
300 task_basic_info().map(|task_basic_info| task_basic_info.resident_size)
301}
302
303#[cfg(not(any(target_os = "linux", target_os = "macos")))]
304fn vsize() -> Option<usize> {
305 None
306}
307
308#[cfg(not(any(target_os = "linux", target_os = "macos")))]
309fn resident() -> Option<usize> {
310 None
311}
312
313#[cfg(target_os = "linux")]
314fn resident_segments() -> Vec<(String, usize)> {
315 use std::collections::HashMap;
316 use std::collections::hash_map::Entry;
317 use std::fs::File;
318 use std::io::{BufRead, BufReader};
319
320 use regex::Regex;
321
322 let f = match File::open("/proc/self/smaps") {
336 Ok(f) => BufReader::new(f),
337 Err(_) => return vec![],
338 };
339
340 let seg_re = Regex::new(
341 r"^[[:xdigit:]]+-[[:xdigit:]]+ (....) [[:xdigit:]]+ [[:xdigit:]]+:[[:xdigit:]]+ \d+ +(.*)",
342 )
343 .unwrap();
344 let rss_re = Regex::new(r"^Rss: +(\d+) kB").unwrap();
345
346 let mut seg_map: HashMap<String, usize> = HashMap::new();
348
349 #[derive(PartialEq)]
350 enum LookingFor {
351 Segment,
352 Rss,
353 }
354 let mut looking_for = LookingFor::Segment;
355
356 let mut curr_seg_name = String::new();
357
358 for line in f.lines() {
360 let line = match line {
361 Ok(line) => line,
362 Err(_) => continue,
363 };
364 if looking_for == LookingFor::Segment {
365 let cap = match seg_re.captures(&line) {
367 Some(cap) => cap,
368 None => continue,
369 };
370 let perms = cap.get(1).unwrap().as_str();
371 let pathname = cap.get(2).unwrap().as_str();
372
373 curr_seg_name.clear();
375 if pathname.is_empty() || pathname.starts_with("[stack:") {
376 curr_seg_name.push_str("anonymous");
381 } else {
382 curr_seg_name.push_str(pathname);
383 }
384 curr_seg_name.push_str(" (");
385 curr_seg_name.push_str(perms);
386 curr_seg_name.push(')');
387
388 looking_for = LookingFor::Rss;
389 } else {
390 let cap = match rss_re.captures(&line) {
392 Some(cap) => cap,
393 None => continue,
394 };
395 let rss = cap.get(1).unwrap().as_str().parse::<usize>().unwrap() * 1024;
396
397 if rss > 0 {
398 let seg_name = if rss < 512 * 1024 {
400 "other".to_owned()
401 } else {
402 curr_seg_name.clone()
403 };
404 match seg_map.entry(seg_name) {
405 Entry::Vacant(entry) => {
406 entry.insert(rss);
407 },
408 Entry::Occupied(mut entry) => *entry.get_mut() += rss,
409 }
410 }
411
412 looking_for = LookingFor::Segment;
413 }
414 }
415
416 seg_map.into_iter().collect()
420}
421
422#[cfg(not(target_os = "linux"))]
423fn resident_segments() -> Vec<(String, usize)> {
424 vec![]
425}