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