profile/
system_reporter.rs1#[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#[cfg(target_os = "macos")]
19use task_info::task_basic_info::{resident_size, virtual_size};
20
21const JEMALLOC_HEAP_ALLOCATED_STR: &str = "jemalloc-heap-allocated";
22const SYSTEM_HEAP_ALLOCATED_STR: &str = "system-heap-allocated";
23
24pub fn collect_reports(request: ReporterRequest) {
26 let mut reports = vec![];
27 {
28 let mut report = |path, size| {
29 if let Some(size) = size {
30 reports.push(Report {
31 path,
32 kind: ReportKind::NonExplicitSize,
33 size,
34 });
35 }
36 };
37
38 report(path!["vsize"], vsize());
40 report(path!["resident"], resident());
41 report(path!["pss"], proportional_set_size());
42
43 for seg in resident_segments() {
47 report(path!["resident-according-to-smaps", seg.0], Some(seg.1));
48 }
49
50 report(path![SYSTEM_HEAP_ALLOCATED_STR], system_heap_allocated());
53
54 report(
59 path![JEMALLOC_HEAP_ALLOCATED_STR],
60 jemalloc_stat("stats.allocated"),
61 );
62
63 report(path!["jemalloc-heap-active"], jemalloc_stat("stats.active"));
67
68 report(path!["jemalloc-heap-mapped"], jemalloc_stat("stats.mapped"));
72 }
73
74 request.reports_channel.send(ProcessReports::new(reports));
75}
76
77#[cfg(all(target_os = "linux", target_env = "gnu"))]
78unsafe extern "C" {
79 fn mallinfo() -> struct_mallinfo;
80}
81
82#[cfg(all(target_os = "linux", target_env = "gnu"))]
83#[repr(C)]
84pub struct struct_mallinfo {
85 arena: c_int,
86 ordblks: c_int,
87 smblks: c_int,
88 hblks: c_int,
89 hblkhd: c_int,
90 usmblks: c_int,
91 fsmblks: c_int,
92 uordblks: c_int,
93 fordblks: c_int,
94 keepcost: c_int,
95}
96
97#[cfg(all(target_os = "linux", target_env = "gnu"))]
98fn system_heap_allocated() -> Option<usize> {
99 let info: struct_mallinfo = unsafe { mallinfo() };
100
101 if info.hblkhd < 0 || info.uordblks < 0 {
110 None
111 } else {
112 Some(info.hblkhd as usize + info.uordblks as usize)
113 }
114}
115
116#[cfg(not(all(target_os = "linux", target_env = "gnu")))]
117fn system_heap_allocated() -> Option<usize> {
118 None
119}
120
121#[cfg(not(any(target_os = "windows", target_env = "ohos")))]
122use tikv_jemalloc_sys::mallctl;
123
124#[cfg(not(any(target_os = "windows", target_env = "ohos")))]
125fn jemalloc_stat(value_name: &str) -> Option<usize> {
126 let epoch_name = "epoch";
130 let epoch_c_name = CString::new(epoch_name).unwrap();
131 let mut epoch: u64 = 0;
132 let epoch_ptr = &mut epoch as *mut _ as *mut c_void;
133 let mut epoch_len = size_of::<u64>() as size_t;
134
135 let value_c_name = CString::new(value_name).unwrap();
136 let mut value: size_t = 0;
137 let value_ptr = &mut value as *mut _ as *mut c_void;
138 let mut value_len = size_of::<size_t>() as size_t;
139
140 let rv = unsafe {
143 mallctl(
144 epoch_c_name.as_ptr(),
145 epoch_ptr,
146 &mut epoch_len,
147 epoch_ptr,
148 epoch_len,
149 )
150 };
151 if rv != 0 {
152 return None;
153 }
154
155 let rv = unsafe {
156 mallctl(
157 value_c_name.as_ptr(),
158 value_ptr,
159 &mut value_len,
160 null_mut(),
161 0,
162 )
163 };
164 if rv != 0 {
165 return None;
166 }
167
168 Some(value as usize)
169}
170
171#[cfg(any(target_os = "windows", target_env = "ohos"))]
172fn jemalloc_stat(_value_name: &str) -> Option<usize> {
173 None
174}
175
176#[cfg(target_os = "linux")]
177fn page_size() -> usize {
178 unsafe { ::libc::sysconf(::libc::_SC_PAGESIZE) as usize }
179}
180
181#[cfg(target_os = "linux")]
182fn proc_self_statm_field(field: usize) -> Option<usize> {
183 use std::fs::File;
184 use std::io::Read;
185
186 let mut f = File::open("/proc/self/statm").ok()?;
187 let mut contents = String::new();
188 f.read_to_string(&mut contents).ok()?;
189 let s = contents.split_whitespace().nth(field)?;
190 let npages = s.parse::<usize>().ok()?;
191 Some(npages * page_size())
192}
193
194#[cfg(target_os = "linux")]
195fn vsize() -> Option<usize> {
196 proc_self_statm_field(0)
197}
198
199#[cfg(target_os = "linux")]
200fn resident() -> Option<usize> {
201 proc_self_statm_field(1)
202}
203#[cfg(target_os = "linux")]
204fn proportional_set_size() -> Option<usize> {
205 use std::fs::File;
206 use std::io::Read;
207 let mut file = File::open("/proc/self/smaps_rollup").ok()?;
208 let mut contents = String::new();
209 file.read_to_string(&mut contents).ok()?;
210 let pss_line = contents
211 .split("\n")
212 .find(|string| string.contains("Pss:"))?;
213
214 let pss_str = pss_line.split_whitespace().nth(1)?;
216 pss_str.parse().ok()
217}
218
219#[cfg(not(target_os = "linux"))]
220fn proportional_set_size() -> Option<usize> {
221 None
222}
223
224#[cfg(target_os = "macos")]
225fn vsize() -> Option<usize> {
226 virtual_size()
227}
228
229#[cfg(target_os = "macos")]
230fn resident() -> Option<usize> {
231 resident_size()
232}
233
234#[cfg(not(any(target_os = "linux", target_os = "macos")))]
235fn vsize() -> Option<usize> {
236 None
237}
238
239#[cfg(not(any(target_os = "linux", target_os = "macos")))]
240fn resident() -> Option<usize> {
241 None
242}
243
244#[cfg(target_os = "linux")]
245fn resident_segments() -> Vec<(String, usize)> {
246 use std::collections::HashMap;
247 use std::collections::hash_map::Entry;
248 use std::fs::File;
249 use std::io::{BufRead, BufReader};
250
251 use regex::Regex;
252
253 let f = match File::open("/proc/self/smaps") {
267 Ok(f) => BufReader::new(f),
268 Err(_) => return vec![],
269 };
270
271 let seg_re = Regex::new(
272 r"^[[:xdigit:]]+-[[:xdigit:]]+ (....) [[:xdigit:]]+ [[:xdigit:]]+:[[:xdigit:]]+ \d+ +(.*)",
273 )
274 .unwrap();
275 let rss_re = Regex::new(r"^Rss: +(\d+) kB").unwrap();
276
277 let mut seg_map: HashMap<String, usize> = HashMap::new();
279
280 #[derive(PartialEq)]
281 enum LookingFor {
282 Segment,
283 Rss,
284 }
285 let mut looking_for = LookingFor::Segment;
286
287 let mut curr_seg_name = String::new();
288
289 for line in f.lines() {
291 let line = match line {
292 Ok(line) => line,
293 Err(_) => continue,
294 };
295 if looking_for == LookingFor::Segment {
296 let cap = match seg_re.captures(&line) {
298 Some(cap) => cap,
299 None => continue,
300 };
301 let perms = cap.get(1).unwrap().as_str();
302 let pathname = cap.get(2).unwrap().as_str();
303
304 curr_seg_name.clear();
306 if pathname.is_empty() || pathname.starts_with("[stack:") {
307 curr_seg_name.push_str("anonymous");
312 } else {
313 curr_seg_name.push_str(pathname);
314 }
315 curr_seg_name.push_str(" (");
316 curr_seg_name.push_str(perms);
317 curr_seg_name.push(')');
318
319 looking_for = LookingFor::Rss;
320 } else {
321 let cap = match rss_re.captures(&line) {
323 Some(cap) => cap,
324 None => continue,
325 };
326 let rss = cap.get(1).unwrap().as_str().parse::<usize>().unwrap() * 1024;
327
328 if rss > 0 {
329 let seg_name = if rss < 512 * 1024 {
331 "other".to_owned()
332 } else {
333 curr_seg_name.clone()
334 };
335 match seg_map.entry(seg_name) {
336 Entry::Vacant(entry) => {
337 entry.insert(rss);
338 },
339 Entry::Occupied(mut entry) => *entry.get_mut() += rss,
340 }
341 }
342
343 looking_for = LookingFor::Segment;
344 }
345 }
346
347 seg_map.into_iter().collect()
351}
352
353#[cfg(not(target_os = "linux"))]
354fn resident_segments() -> Vec<(String, usize)> {
355 vec![]
356}