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 {
add(&mut self, key: &String, val: u64)39 fn add(&mut self, key: &String, val: u64) {
40 let v = self.value.entry(key.clone()).or_insert(0);
41 *v += val;
42 }
incress_counts(&mut self)43 fn incress_counts(&mut self) {
44 self.counts += 1;
45 }
46 }
47
main()48 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_combined(infos: Vec<VmStruct>)100 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_verbose(infos: Vec<VmStruct>)119 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 ("Name", ""),
135 ];
136 print_smaps_core(infos, &value_keys, &info_keys);
137 }
138
print_smaps_core(infos: Vec<VmStruct>, value_keys: &Vec<(&str, &str)>, info_keys: &Vec<(&str, &str)>)139 fn print_smaps_core(infos: Vec<VmStruct>, value_keys: &Vec<(&str, &str)>, info_keys: &Vec<(&str, &str)>) {
140 let mut summary = VmStruct {
141 name: String::from("Summary"),
142 start: 0,
143 end: 0,
144 off: 0,
145 perm: String::from(""),
146 dev: String::from(""),
147 inode: 0,
148 counts: 0,
149 value: HashMap::from([])
150 };
151
152 let mut table = Table::new();
153 table.set_format(*format::consts::FORMAT_NO_LINESEP_WITH_TITLE);
154
155 table.set_titles(Row::new(value_keys.into_iter().chain(info_keys.into_iter()).map(|&x| Cell::new(x.0)).collect()));
156 for i in infos {
157 // make a row
158 let mut r = Row::new(value_keys.into_iter().map(|&x| value_to_cell(&i, x.0, x.1)).collect());
159 for ik in info_keys {
160 r.add_cell(info_to_cell(&i, ik.0, false));
161 }
162 table.add_row(r);
163
164 // calculate summary
165 for (n, v) in i.value {
166 summary.add(&n, v);
167 }
168 summary.counts += i.counts;
169 }
170
171 table.add_empty_row();
172
173 // add summary row
174 let mut rsum = Row::new(value_keys.into_iter().map(|&x| value_to_cell(&summary, x.0, x.1)).collect());
175 for ik in info_keys {
176 rsum.add_cell(info_to_cell(&summary, ik.0, true));
177 }
178 table.add_row(rsum);
179
180 // Print the table to stdout
181 table.printstd();
182 }
183
184
value_to_cell(i: &VmStruct, k: &str, t: &str) -> Cell185 fn value_to_cell(i: &VmStruct, k: &str, t: &str) -> Cell {
186 if i.value.contains_key(k) {
187 match t {
188 "x" => Cell::new_align(format!("{:x}", i.value.get(k).unwrap()).as_str(), Alignment::RIGHT),
189 "d" => Cell::new_align(format!("{}", i.value.get(k).unwrap()).as_str(), Alignment::RIGHT),
190 "s" => Cell::new(format!("{}", i.value.get(k).unwrap()).as_str()),
191 _ => Cell::new(""),
192 }
193 } else {
194 Cell::new("")
195 }
196 }
197
info_to_cell(i: &VmStruct, k: &str, is_summary: bool) -> Cell198 fn info_to_cell(i: &VmStruct, k: &str, is_summary: bool) -> Cell {
199 if is_summary {
200 match k {
201 "Counts" => Cell::new_align(format!("{}", i.counts).as_str(), Alignment::RIGHT),
202 "Name" => Cell::new(format!("{}", i.name).as_str()),
203 _ => Cell::new("")
204 }
205 } else {
206 match k {
207 "Name" => Cell::new(format!("{}", i.name).as_str()),
208 "Start" => Cell::new(format!("{:x}", i.start).as_str()),
209 "End" => Cell::new(format!("{:x}", i.end).as_str()),
210 "Off" => Cell::new(format!("{:x}", i.off).as_str()),
211 "Perm" => Cell::new(format!("{}", i.perm).as_str()),
212 "Dev" => Cell::new(format!("{}", i.dev).as_str()),
213 "Inode" => Cell::new(format!("{}", i.inode).as_str()),
214 "Counts" => Cell::new_align(format!("{}", i.counts).as_str(), Alignment::RIGHT),
215 _ => Cell::new("")
216 }
217 }
218 }
219
read_smaps(file_path: &String, need_combine: bool) -> Vec<VmStruct>220 fn read_smaps(file_path: &String, need_combine: bool) -> Vec<VmStruct> {
221 let mut output : Vec<VmStruct> = Vec::new();
222
223 // 55de5fa6e000-55de5fad3000 r--p 0011e000 103:03 5908202 /usr/lib/systemd/systemd
224 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>.*)")
225 .multi_line(true)
226 .build()
227 .unwrap();
228 let regex_val = RegexBuilder::new("^(?P<key>[a-zA-Z_]+):[ \t]+(?P<val>[0-9]+)[ \t]+kB")
229 .multi_line(true)
230 .build()
231 .unwrap();
232
233 let file_context = std::fs::read_to_string(file_path)
234 .unwrap();
235
236 let vms: Vec<_> = regex_head
237 .find_iter(&file_context)
238 .map(|matched| matched.start())
239 .chain(std::iter::once(file_context.len()))
240 .collect();
241
242 let mut vm_map = HashMap::new();
243 for vm in vms.windows(2) {
244 let caps = regex_head.captures(&file_context[vm[0]..vm[1]]).unwrap();
245 let start = u64::from_str_radix(caps.name("start").unwrap().as_str(), 16).unwrap();
246 let end = u64::from_str_radix(caps.name("end").unwrap().as_str(), 16).unwrap();
247 let off = u64::from_str_radix(caps.name("off").unwrap().as_str(), 16).unwrap();
248 let perm = caps.name("perm").unwrap().as_str();
249 let dev = caps.name("dev").unwrap().as_str();
250 let inode = u64::from_str_radix(caps.name("inode").unwrap().as_str(), 10).unwrap();
251
252 let mut name = String::from("[anon]");
253 if caps.name("name").unwrap().as_str() != "" {
254 name = caps.name("name").unwrap().as_str().to_string();
255 }
256
257 let value_map: HashMap<String, u64> = regex_val
258 .captures_iter(&file_context[vm[0]..vm[1]])
259 .map(|cap| {
260 let key = cap.get(1).unwrap().as_str().to_owned();
261 let value = cap.get(2).unwrap().as_str().parse().unwrap();
262 (key.clone().replace("_", "\n"), value)
263 })
264 .collect();
265
266 if need_combine && vm_map.contains_key(&name) {
267 let &idx: &usize = vm_map.get(&name).unwrap();
268 for (n, v) in value_map {
269 output.get_mut(idx).unwrap().add(&n, v);
270 }
271 output.get_mut(idx).unwrap().incress_counts();
272 } else {
273 let mut vms = VmStruct{
274 name: name.clone(),
275 start: start,
276 end: end,
277 off: off,
278 perm: perm.to_string(),
279 dev: dev.to_string(),
280 inode: inode,
281 counts: 1,
282 value: HashMap::from([])};
283
284 for (n, v) in value_map {
285 vms.add(&n, v);
286 }
287 output.push(vms);
288 if need_combine {
289 vm_map.insert(name.clone(), output.len() - 1);
290 }
291 }
292 }
293 output
294 }
295
print_usage(program: &str, opts: Options)296 fn print_usage(program: &str, opts: Options) {
297 let brief = format!("Usage: {} [options] PID/FILE ", program);
298 print!("{}", opts.usage(&brief));
299 }
300