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
42 for seg in resident_segments() {
46 report(path!["resident-according-to-smaps", seg.0], Some(seg.1));
47 }
48
49 report(path![SYSTEM_HEAP_ALLOCATED_STR], system_heap_allocated());
52
53 report(
58 path![JEMALLOC_HEAP_ALLOCATED_STR],
59 jemalloc_stat("stats.allocated"),
60 );
61
62 report(path!["jemalloc-heap-active"], jemalloc_stat("stats.active"));
66
67 report(path!["jemalloc-heap-mapped"], jemalloc_stat("stats.mapped"));
71 }
72
73 request.reports_channel.send(ProcessReports::new(reports));
74}
75
76#[cfg(all(target_os = "linux", target_env = "gnu"))]
77unsafe extern "C" {
78 fn mallinfo() -> struct_mallinfo;
79}
80
81#[cfg(all(target_os = "linux", target_env = "gnu"))]
82#[repr(C)]
83pub struct struct_mallinfo {
84 arena: c_int,
85 ordblks: c_int,
86 smblks: c_int,
87 hblks: c_int,
88 hblkhd: c_int,
89 usmblks: c_int,
90 fsmblks: c_int,
91 uordblks: c_int,
92 fordblks: c_int,
93 keepcost: c_int,
94}
95
96#[cfg(all(target_os = "linux", target_env = "gnu"))]
97fn system_heap_allocated() -> Option<usize> {
98 let info: struct_mallinfo = unsafe { mallinfo() };
99
100 if info.hblkhd < 0 || info.uordblks < 0 {
109 None
110 } else {
111 Some(info.hblkhd as usize + info.uordblks as usize)
112 }
113}
114
115#[cfg(not(all(target_os = "linux", target_env = "gnu")))]
116fn system_heap_allocated() -> Option<usize> {
117 None
118}
119
120#[cfg(not(any(target_os = "windows", target_env = "ohos")))]
121use tikv_jemalloc_sys::mallctl;
122
123#[cfg(not(any(target_os = "windows", target_env = "ohos")))]
124fn jemalloc_stat(value_name: &str) -> Option<usize> {
125 let epoch_name = "epoch";
129 let epoch_c_name = CString::new(epoch_name).unwrap();
130 let mut epoch: u64 = 0;
131 let epoch_ptr = &mut epoch as *mut _ as *mut c_void;
132 let mut epoch_len = size_of::<u64>() as size_t;
133
134 let value_c_name = CString::new(value_name).unwrap();
135 let mut value: size_t = 0;
136 let value_ptr = &mut value as *mut _ as *mut c_void;
137 let mut value_len = size_of::<size_t>() as size_t;
138
139 let rv = unsafe {
142 mallctl(
143 epoch_c_name.as_ptr(),
144 epoch_ptr,
145 &mut epoch_len,
146 epoch_ptr,
147 epoch_len,
148 )
149 };
150 if rv != 0 {
151 return None;
152 }
153
154 let rv = unsafe {
155 mallctl(
156 value_c_name.as_ptr(),
157 value_ptr,
158 &mut value_len,
159 null_mut(),
160 0,
161 )
162 };
163 if rv != 0 {
164 return None;
165 }
166
167 Some(value as usize)
168}
169
170#[cfg(any(target_os = "windows", target_env = "ohos"))]
171fn jemalloc_stat(_value_name: &str) -> Option<usize> {
172 None
173}
174
175#[cfg(target_os = "linux")]
176fn page_size() -> usize {
177 unsafe { ::libc::sysconf(::libc::_SC_PAGESIZE) as usize }
178}
179
180#[cfg(target_os = "linux")]
181fn proc_self_statm_field(field: usize) -> Option<usize> {
182 use std::fs::File;
183 use std::io::Read;
184
185 let mut f = File::open("/proc/self/statm").ok()?;
186 let mut contents = String::new();
187 f.read_to_string(&mut contents).ok()?;
188 let s = contents.split_whitespace().nth(field)?;
189 let npages = s.parse::<usize>().ok()?;
190 Some(npages * page_size())
191}
192
193#[cfg(target_os = "linux")]
194fn vsize() -> Option<usize> {
195 proc_self_statm_field(0)
196}
197
198#[cfg(target_os = "linux")]
199fn resident() -> Option<usize> {
200 proc_self_statm_field(1)
201}
202
203#[cfg(target_os = "macos")]
204fn vsize() -> Option<usize> {
205 virtual_size()
206}
207
208#[cfg(target_os = "macos")]
209fn resident() -> Option<usize> {
210 resident_size()
211}
212
213#[cfg(not(any(target_os = "linux", target_os = "macos")))]
214fn vsize() -> Option<usize> {
215 None
216}
217
218#[cfg(not(any(target_os = "linux", target_os = "macos")))]
219fn resident() -> Option<usize> {
220 None
221}
222
223#[cfg(target_os = "linux")]
224fn resident_segments() -> Vec<(String, usize)> {
225 use std::collections::HashMap;
226 use std::collections::hash_map::Entry;
227 use std::fs::File;
228 use std::io::{BufRead, BufReader};
229
230 use regex::Regex;
231
232 let f = match File::open("/proc/self/smaps") {
246 Ok(f) => BufReader::new(f),
247 Err(_) => return vec![],
248 };
249
250 let seg_re = Regex::new(
251 r"^[[:xdigit:]]+-[[:xdigit:]]+ (....) [[:xdigit:]]+ [[:xdigit:]]+:[[:xdigit:]]+ \d+ +(.*)",
252 )
253 .unwrap();
254 let rss_re = Regex::new(r"^Rss: +(\d+) kB").unwrap();
255
256 let mut seg_map: HashMap<String, usize> = HashMap::new();
258
259 #[derive(PartialEq)]
260 enum LookingFor {
261 Segment,
262 Rss,
263 }
264 let mut looking_for = LookingFor::Segment;
265
266 let mut curr_seg_name = String::new();
267
268 for line in f.lines() {
270 let line = match line {
271 Ok(line) => line,
272 Err(_) => continue,
273 };
274 if looking_for == LookingFor::Segment {
275 let cap = match seg_re.captures(&line) {
277 Some(cap) => cap,
278 None => continue,
279 };
280 let perms = cap.get(1).unwrap().as_str();
281 let pathname = cap.get(2).unwrap().as_str();
282
283 curr_seg_name.clear();
285 if pathname.is_empty() || pathname.starts_with("[stack:") {
286 curr_seg_name.push_str("anonymous");
291 } else {
292 curr_seg_name.push_str(pathname);
293 }
294 curr_seg_name.push_str(" (");
295 curr_seg_name.push_str(perms);
296 curr_seg_name.push(')');
297
298 looking_for = LookingFor::Rss;
299 } else {
300 let cap = match rss_re.captures(&line) {
302 Some(cap) => cap,
303 None => continue,
304 };
305 let rss = cap.get(1).unwrap().as_str().parse::<usize>().unwrap() * 1024;
306
307 if rss > 0 {
308 let seg_name = if rss < 512 * 1024 {
310 "other".to_owned()
311 } else {
312 curr_seg_name.clone()
313 };
314 match seg_map.entry(seg_name) {
315 Entry::Vacant(entry) => {
316 entry.insert(rss);
317 },
318 Entry::Occupied(mut entry) => *entry.get_mut() += rss,
319 }
320 }
321
322 looking_for = LookingFor::Segment;
323 }
324 }
325
326 seg_map.into_iter().collect()
330}
331
332#[cfg(not(target_os = "linux"))]
333fn resident_segments() -> Vec<(String, usize)> {
334 vec![]
335}