Skip to main content

pprof_alloc/stats/
smaps.rs

1use prometheus_client::collector::Collector;
2use prometheus_client::encoding::DescriptorEncoder;
3use prometheus_client::metrics::gauge::ConstGauge;
4use serde::Serialize;
5use std::fmt::Error;
6use std::fs::File;
7use std::io::Read;
8
9#[derive(Debug, Default, PartialEq, Clone, Serialize)]
10pub struct ProcessStats {
11	pub size: u64,
12	pub rss: u64,
13	pub pss: u64,
14	pub pss_dirty: u64,
15	pub shared_clean: u64,
16	pub shared_dirty: u64,
17	pub private_clean: u64,
18	pub private_dirty: u64,
19	pub referenced: u64,
20	pub anonymous: u64,
21	pub lazy_free: u64,
22	pub anon_huge_pages: u64,
23	pub shmem_huge_pages: u64,
24	pub shmem_pmd_mapped: u64,
25	pub file_pmd_mapped: u64,
26	pub shared_hugetlb: u64,
27	pub private_hugetlb: u64,
28	pub swap: u64,
29	pub swap_pss: u64,
30	pub locked: u64,
31}
32
33pub fn rollup() -> anyhow::Result<ProcessStats> {
34	let path = "/proc/self/smaps_rollup";
35	let mut file = File::open(path)?;
36	let mut input = String::new();
37	file.read_to_string(&mut input)?;
38	parse_rollup(&input)
39}
40
41fn parse_rollup(input: &str) -> anyhow::Result<ProcessStats> {
42	let smaps = super::procmaps::from_str(input).expect("library never returns None");
43	if smaps.len() != 1 {
44		return Err(anyhow::anyhow!(
45			"Expected 1 smaps entry, got {}",
46			smaps.len()
47		));
48	}
49	let smap = smaps.into_iter().next().unwrap();
50	Ok(ProcessStats {
51		size: smap.size,
52		rss: smap.rss,
53		pss: smap.pss,
54		pss_dirty: smap.pss_dirty,
55		shared_clean: smap.shared_clean,
56		shared_dirty: smap.shared_dirty,
57		private_clean: smap.private_clean,
58		private_dirty: smap.private_dirty,
59		referenced: smap.referenced,
60		anonymous: smap.anonymous,
61		lazy_free: smap.lazy_free,
62		anon_huge_pages: smap.anon_huge_pages,
63		shmem_huge_pages: smap.shmem_huge_pages,
64		shmem_pmd_mapped: smap.shmem_pmd_mapped,
65		file_pmd_mapped: smap.file_pmd_mapped,
66		shared_hugetlb: smap.shared_hugetlb,
67		private_hugetlb: smap.private_hugetlb,
68		swap: smap.swap,
69		swap_pss: smap.swap_pss,
70		locked: smap.locked,
71	})
72}
73
74#[derive(Debug, Clone)]
75pub struct PrometheusCollector {}
76
77impl PrometheusCollector {
78	pub fn register(registry: &mut prometheus_client::registry::Registry) {
79		registry.register_collector(Box::new(Self {}))
80	}
81}
82
83impl Collector for PrometheusCollector {
84	fn encode(&self, mut encoder: DescriptorEncoder) -> Result<(), Error> {
85		use prometheus_client::encoding::EncodeMetric;
86		let Ok(s) = rollup() else {
87			return Ok(());
88		};
89		let mut encode = |v: u64, n: &'static str, d: &str| {
90			let metric = ConstGauge::new(v);
91			let metric_encoder = encoder.encode_descriptor(n, d, None, metric.metric_type())?;
92			metric.encode(metric_encoder)?;
93			Ok(())
94		};
95		encode(s.size, "process_size", "size memory usage")?;
96		encode(s.rss, "process_rss", "rss memory usage")?;
97		encode(s.pss, "process_pss", "pss memory usage")?;
98		encode(s.pss_dirty, "process_pss_dirty", "pss_dirty memory usage")?;
99		encode(
100			s.shared_clean,
101			"process_shared_clean",
102			"shared_clean memory usage",
103		)?;
104		encode(
105			s.shared_dirty,
106			"process_shared_dirty",
107			"shared_dirty memory usage",
108		)?;
109		encode(
110			s.private_clean,
111			"process_private_clean",
112			"private_clean memory usage",
113		)?;
114		encode(
115			s.private_dirty,
116			"process_private_dirty",
117			"private_dirty memory usage",
118		)?;
119		encode(
120			s.referenced,
121			"process_referenced",
122			"referenced memory usage",
123		)?;
124		encode(s.anonymous, "process_anonymous", "anonymous memory usage")?;
125		encode(s.lazy_free, "process_lazy_free", "lazy free memory usage")?;
126		encode(
127			s.anon_huge_pages,
128			"process_anon_huge_pages",
129			"anonymous huge pages usage",
130		)?;
131		encode(
132			s.shmem_huge_pages,
133			"process_shmem_huge_pages",
134			"shared memory huge pages usage",
135		)?;
136		encode(
137			s.shmem_pmd_mapped,
138			"process_shmem_pmd_mapped",
139			"shared memory pmd mapped usage",
140		)?;
141		encode(
142			s.file_pmd_mapped,
143			"process_file_pmd_mapped",
144			"file pmd mapped usage",
145		)?;
146		encode(
147			s.shared_hugetlb,
148			"process_shared_hugetlb",
149			"shared hugetlb usage",
150		)?;
151		encode(
152			s.private_hugetlb,
153			"process_private_hugetlb",
154			"private hugetlb usage",
155		)?;
156		encode(s.swap, "process_swap", "process swap usage")?;
157		encode(
158			s.swap_pss,
159			"process_swap_pss",
160			"process proportional swap usage",
161		)?;
162		encode(s.locked, "process_locked", "process locked memory usage")?;
163		Ok(())
164	}
165}
166
167#[cfg(test)]
168mod tests {
169	use super::{ProcessStats, parse_rollup};
170
171	#[test]
172	fn parse_rollup_includes_non_heap_process_signals() {
173		let input = "\
174638000000000-638000001000 ---p 00000000 00:00 0                          [rollup]
175Rss:                8192 kB
176Pss:                6144 kB
177Pss_Dirty:          2048 kB
178Shared_Clean:       1024 kB
179Shared_Dirty:        512 kB
180Private_Clean:      1536 kB
181Private_Dirty:      5120 kB
182Referenced:         7168 kB
183Anonymous:          4096 kB
184LazyFree:            256 kB
185AnonHugePages:      2048 kB
186ShmemHugePages:      128 kB
187ShmemPmdMapped:      256 kB
188FilePmdMapped:       512 kB
189Shared_Hugetlb:       64 kB
190Private_Hugetlb:      32 kB
191Swap:               1024 kB
192SwapPss:             768 kB
193Locked:               16 kB
194Size:              16384 kB
195";
196
197		assert_eq!(
198			parse_rollup(input).expect("smaps rollup should parse"),
199			ProcessStats {
200				size: 16384 * 1024,
201				rss: 8192 * 1024,
202				pss: 6144 * 1024,
203				pss_dirty: 2048 * 1024,
204				shared_clean: 1024 * 1024,
205				shared_dirty: 512 * 1024,
206				private_clean: 1536 * 1024,
207				private_dirty: 5120 * 1024,
208				referenced: 7168 * 1024,
209				anonymous: 4096 * 1024,
210				lazy_free: 256 * 1024,
211				anon_huge_pages: 2048 * 1024,
212				shmem_huge_pages: 128 * 1024,
213				shmem_pmd_mapped: 256 * 1024,
214				file_pmd_mapped: 512 * 1024,
215				shared_hugetlb: 64 * 1024,
216				private_hugetlb: 32 * 1024,
217				swap: 1024 * 1024,
218				swap_pss: 768 * 1024,
219				locked: 16 * 1024,
220			}
221		);
222	}
223}