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}