1 /*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 use std::env;
17 extern crate getopts;
18 use getopts::Options;
19
20 use std::collections::HashMap;
21
22 use regex::RegexBuilder;
23
24 use prettytable::{Table, Row, Cell, format};
25 use prettytable::format::*;
26
27 struct VmStruct {
28 name: String,
29 start: u64,
30 end: u64,
31 off: u64,
32 perm: String,
33 dev: String,
34 inode: u64,
35 counts: usize,
36 value: HashMap<String, u64>
37 }
38 impl VmStruct {
addnull39 fn add(&mut self, key: &String, val: u64) {
40 let v = self.value.entry(key.clone()).or_insert(0);
41 *v += val;
42 }
incress_countsnull43 fn incress_counts(&mut self) {
44 self.counts += 1;
45 }
46 }
47
mainnull48 fn main() {
49 let args: Vec<String> = env::args().collect();
50 let program = args[0].clone();
51
52 let mut opts = Options::new();
53 opts.optopt("p", "pid", "parse target pid smaps", "PID");
54 opts.optopt("f", "file", "print target file", "FILE");
55 opts.optflag("v", "verbose", "verbose mode, not combine");
56 opts.optflag("h", "help", "print this help menu");
57 let matches = match opts.parse(&args[1..]) {
58 Ok(m) => { m }
59 Err(_f) => { return print_usage(&program, opts) }
60 };
61 if matches.opt_present("h") {
62 print_usage(&program, opts);
63 return;
64 }
65 if matches.opt_present("p") && matches.opt_present("f") {
66 print_usage(&program, opts);
67 return;
68 }
69 let need_combine = if matches.opt_present("v") { false } else { true };
70
71 let pid = match matches.opt_str("p") {
72 Some(s) => s.parse().unwrap_or(-1),
73 None => -1,
74 };
75 let mut file_path = match matches.opt_str("f") {
76 Some(s) => s,
77 None => String::from(""),
78 };
79
80 if pid == -1 && &file_path == "" {
81 print_usage(&program, opts);
82 return;
83 }
84
85 if pid != -1 {
86 file_path = format!("/proc/{:?}/smaps", pid);
87 }
88
89 let smaps_info = read_smaps(&file_path, need_combine);
90
91 if need_combine {
92 print_smaps_combined(smaps_info);
93 } else {
94 print_smaps_verbose(smaps_info);
95 }
96
97 std::process::exit(0);
98 }
99
print_smaps_combinednull100 fn print_smaps_combined(infos: Vec<VmStruct>) {
101 let value_keys = vec![
102 ("Size", "d"),
103 ("Rss", "d"),
104 ("Pss", "d"),
105 ("Shared\nClean", "d"),
106 ("Shared\nDirty", "d"),
107 ("Private\nClean","d"),
108 ("Private\nDirty","d"),
109 ("Swap", "d"),
110 ("SwapPss", "d")
111 ];
112 let info_keys = vec![
113 ("Counts", ""),
114 ("Name", ""),
115 ];
116 print_smaps_core(infos, &value_keys, &info_keys);
117 }
118
print_smaps_verbosenull119 fn print_smaps_verbose(infos: Vec<VmStruct>) {
120 let value_keys = vec![
121 ("Size", "d"),
122 ("Rss", "d"),
123 ("Pss", "d"),
124 ("Shared\nClean", "d"),
125 ("Shared\nDirty", "d"),
126 ("Private\nClean","d"),
127 ("Private\nDirty","d"),
128 ("Swap", "d"),
129 ("SwapPss", "d")
130 ];
131 let info_keys = vec![
132 ("Start", ""),
133 ("End", ""),
134 ("Perm", ""),
135 ("Name", "")
136 ];
137 print_smaps_core(infos, &value_keys, &info_keys);
138 }
139
print_smaps_corenull140 fn print_smaps_core(infos: Vec<VmStruct>, value_keys: &Vec<(&str, &str)>, info_keys: &Vec<(&str, &str)>) {
141 let mut summary = VmStruct {
142 name: String::from("Summary"),
143 start: 0,
144 end: 0,
145 off: 0,
146 perm: String::from(""),
147 dev: String::from(""),
148 inode: 0,
149 counts: 0,
150 value: HashMap::from([])
151 };
152
153 let mut table = Table::new();
154 table.set_format(*format::consts::FORMAT_NO_LINESEP_WITH_TITLE);
155
156 table.set_titles(Row::new(value_keys.into_iter().chain(info_keys.into_iter()).map(|&x| Cell::new(x.0)).collect()));
157 for i in infos {
158 // make a row
159 let mut r = Row::new(value_keys.into_iter().map(|&x| value_to_cell(&i, x.0, x.1)).collect());
160 for ik in info_keys {
161 r.add_cell(info_to_cell(&i, ik.0, false));
162 }
163 table.add_row(r);
164
165 // calculate summary
166 for (n, v) in i.value {
167 summary.add(&n, v);
168 }
169 summary.counts += i.counts;
170 }
171
172 table.add_empty_row();
173
174 // add summary row
175 let mut rsum = Row::new(value_keys.into_iter().map(|&x| value_to_cell(&summary, x.0, x.1)).collect());
176 for ik in info_keys {
177 rsum.add_cell(info_to_cell(&summary, ik.0, true));
178 }
179 table.add_row(rsum);
180
181 // Print the table to stdout
182 table.printstd();
183 }
184
185
value_to_cellnull186 fn value_to_cell(i: &VmStruct, k: &str, t: &str) -> Cell {
187 if i.value.contains_key(k) {
188 match t {
189 "x" => Cell::new_align(format!("{:x}", i.value.get(k).unwrap()).as_str(), Alignment::RIGHT),
190 "d" => Cell::new_align(format!("{}", i.value.get(k).unwrap()).as_str(), Alignment::RIGHT),
191 "s" => Cell::new(format!("{}", i.value.get(k).unwrap()).as_str()),
192 _ => Cell::new(""),
193 }
194 } else {
195 Cell::new("")
196 }
197 }
198
info_to_cellnull199 fn info_to_cell(i: &VmStruct, k: &str, is_summary: bool) -> Cell {
200 if is_summary {
201 match k {
202 "Counts" => Cell::new_align(format!("{}", i.counts).as_str(), Alignment::RIGHT),
203 "Name" => Cell::new(format!("{}", i.name).as_str()),
204 _ => Cell::new("")
205 }
206 } else {
207 match k {
208 "Name" => Cell::new(format!("{}", i.name).as_str()),
209 "Start" => Cell::new(format!("{:x}", i.start).as_str()),
210 "End" => Cell::new(format!("{:x}", i.end).as_str()),
211 "Off" => Cell::new(format!("{:x}", i.off).as_str()),
212 "Perm" => Cell::new(format!("{}", i.perm).as_str()),
213 "Dev" => Cell::new(format!("{}", i.dev).as_str()),
214 "Inode" => Cell::new(format!("{}", i.inode).as_str()),
215 "Counts" => Cell::new_align(format!("{}", i.counts).as_str(), Alignment::RIGHT),
216 _ => Cell::new("")
217 }
218 }
219 }
220
read_smapsnull221 fn read_smaps(file_path: &String, need_combine: bool) -> Vec<VmStruct> {
222 let mut output : Vec<VmStruct> = Vec::new();
223
224 // 55de5fa6e000-55de5fad3000 r--p 0011e000 103:03 5908202 /usr/lib/systemd/systemd
225 let regex_head = RegexBuilder::new("^(?P<start>[0-9a-f]+)-(?P<end>[0-9a-f]+)[ \t]+(?P<perm>[^ ]+)[ \t]+(?P<off>[0-9a-f]+)[ \t]+(?P<dev>[0-9a-f:]+)[ \t]+(?P<inode>[0-9a-z]+)[ \t]*(?P<name>.*)")
226 .multi_line(true)
227 .build()
228 .unwrap();
229 let regex_val = RegexBuilder::new("^(?P<key>[a-zA-Z_]+):[ \t]+(?P<val>[0-9]+)[ \t]+kB")
230 .multi_line(true)
231 .build()
232 .unwrap();
233
234 let file_context = std::fs::read_to_string(file_path)
235 .unwrap();
236
237 let vms: Vec<_> = regex_head
238 .find_iter(&file_context)
239 .map(|matched| matched.start())
240 .chain(std::iter::once(file_context.len()))
241 .collect();
242
243 let mut vm_map = HashMap::new();
244 for vm in vms.windows(2) {
245 let caps = regex_head.captures(&file_context[vm[0]..vm[1]]).unwrap();
246 let start = u64::from_str_radix(caps.name("start").unwrap().as_str(), 16).unwrap();
247 let end = u64::from_str_radix(caps.name("end").unwrap().as_str(), 16).unwrap();
248 let off = u64::from_str_radix(caps.name("off").unwrap().as_str(), 16).unwrap();
249 let perm = caps.name("perm").unwrap().as_str();
250 let dev = caps.name("dev").unwrap().as_str();
251 let inode = u64::from_str_radix(caps.name("inode").unwrap().as_str(), 10).unwrap();
252
253 let mut name = String::from("[anon]");
254 if caps.name("name").unwrap().as_str() != "" {
255 name = caps.name("name").unwrap().as_str().to_string();
256 }
257
258 let value_map: HashMap<String, u64> = regex_val
259 .captures_iter(&file_context[vm[0]..vm[1]])
260 .map(|cap| {
261 let key = cap.get(1).unwrap().as_str().to_owned();
262 let value = cap.get(2).unwrap().as_str().parse().unwrap();
263 (key.clone().replace("_", "\n"), value)
264 })
265 .collect();
266
267 if need_combine && vm_map.contains_key(&name) {
268 let &idx: &usize = vm_map.get(&name).unwrap();
269 for (n, v) in value_map {
270 output.get_mut(idx).unwrap().add(&n, v);
271 }
272 output.get_mut(idx).unwrap().incress_counts();
273 } else {
274 let mut vms = VmStruct{
275 name: name.clone(),
276 start: start,
277 end: end,
278 off: off,
279 perm: perm.to_string(),
280 dev: dev.to_string(),
281 inode: inode,
282 counts: 1,
283 value: HashMap::from([])};
284
285 for (n, v) in value_map {
286 vms.add(&n, v);
287 }
288 output.push(vms);
289 if need_combine {
290 vm_map.insert(name.clone(), output.len() - 1);
291 }
292 }
293 }
294 output
295 }
296
print_usagenull297 fn print_usage(program: &str, opts: Options) {
298 let brief = format!("Usage: {} [options] PID/FILE ", program);
299 print!("{}", opts.usage(&brief));
300 }
301