• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <android-base/file.h>
18 #include <android-base/parseint.h>
19 #include <android-base/stringprintf.h>
20 #include <android-base/strings.h>
21 #include <dirent.h>
22 #include <errno.h>
23 #include <inttypes.h>
24 #include <linux/kernel-page-flags.h>
25 #include <linux/oom.h>
26 #include <meminfo/procmeminfo.h>
27 #include <meminfo/sysmeminfo.h>
28 #include <procinfo/process.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <sys/types.h>
32 #include <unistd.h>
33 
34 #include <iostream>
35 #include <memory>
36 #include <set>
37 #include <sstream>
38 #include <unordered_map>
39 #include <vector>
40 
41 using ::android::meminfo::MemUsage;
42 using ::android::meminfo::ProcMemInfo;
43 
44 struct ProcessRecord {
45   public:
ProcessRecordProcessRecord46     ProcessRecord(pid_t pid, bool get_wss = false, uint64_t pgflags = 0, uint64_t pgflags_mask = 0)
47         : pid_(-1),
48           oomadj_(OOM_SCORE_ADJ_MAX + 1),
49           cmdline_(""),
50           proportional_swap_(0),
51           unique_swap_(0),
52           zswap_(0) {
53         std::unique_ptr<ProcMemInfo> procmem =
54                 std::make_unique<ProcMemInfo>(pid, get_wss, pgflags, pgflags_mask);
55         if (procmem == nullptr) {
56             std::cerr << "Failed to create ProcMemInfo for: " << pid << std::endl;
57             return;
58         }
59 
60         std::string fname = ::android::base::StringPrintf("/proc/%d/oom_score_adj", pid);
61         auto oomscore_fp =
62                 std::unique_ptr<FILE, decltype(&fclose)>{fopen(fname.c_str(), "re"), fclose};
63         if (oomscore_fp == nullptr) {
64             std::cerr << "Failed to open oom_score_adj file: " << fname << std::endl;
65             return;
66         }
67 
68         if (fscanf(oomscore_fp.get(), "%d\n", &oomadj_) != 1) {
69             std::cerr << "Failed to read oomadj from: " << fname << std::endl;
70             return;
71         }
72 
73         fname = ::android::base::StringPrintf("/proc/%d/cmdline", pid);
74         if (!::android::base::ReadFileToString(fname, &cmdline_)) {
75             std::cerr << "Failed to read cmdline from: " << fname << std::endl;
76             cmdline_ = "<unknown>";
77         }
78         // We deliberately don't read the proc/<pid>cmdline file directly into 'cmdline_'
79         // because of some processes showing up cmdlines that end with "0x00 0x0A 0x00"
80         // e.g. xtra-daemon, lowi-server
81         // The .c_str() assignment below then takes care of trimming the cmdline at the first
82         // 0x00. This is how original procrank worked (luckily)
83         cmdline_.resize(strlen(cmdline_.c_str()));
84         usage_or_wss_ = get_wss ? procmem->Wss() : procmem->Usage();
85         swap_offsets_ = procmem->SwapOffsets();
86         pid_ = pid;
87     }
88 
validProcessRecord89     bool valid() const { return pid_ != -1; }
90 
CalculateSwapProcessRecord91     void CalculateSwap(const std::vector<uint16_t>& swap_offset_array,
92                        float zram_compression_ratio) {
93         for (auto& off : swap_offsets_) {
94             proportional_swap_ += getpagesize() / swap_offset_array[off];
95             unique_swap_ += swap_offset_array[off] == 1 ? getpagesize() : 0;
96             zswap_ = proportional_swap_ * zram_compression_ratio;
97         }
98     }
99 
100     // Getters
pidProcessRecord101     pid_t pid() const { return pid_; }
cmdlineProcessRecord102     const std::string& cmdline() const { return cmdline_; }
oomadjProcessRecord103     int32_t oomadj() const { return oomadj_; }
proportional_swapProcessRecord104     uint64_t proportional_swap() const { return proportional_swap_; }
unique_swapProcessRecord105     uint64_t unique_swap() const { return unique_swap_; }
zswapProcessRecord106     uint64_t zswap() const { return zswap_; }
107 
108     // Wrappers to ProcMemInfo
SwapOffsetsProcessRecord109     const std::vector<uint64_t>& SwapOffsets() const { return swap_offsets_; }
UsageProcessRecord110     const MemUsage& Usage() const { return usage_or_wss_; }
WssProcessRecord111     const MemUsage& Wss() const { return usage_or_wss_; }
112 
113   private:
114     pid_t pid_;
115     int32_t oomadj_;
116     std::string cmdline_;
117     uint64_t proportional_swap_;
118     uint64_t unique_swap_;
119     uint64_t zswap_;
120     MemUsage usage_or_wss_;
121     std::vector<uint64_t> swap_offsets_;
122 };
123 
124 // Show working set instead of memory consumption
125 bool show_wss = false;
126 // Reset working set of each process
127 bool reset_wss = false;
128 // Show per-process oom_score_adj column
129 bool show_oomadj = false;
130 // True if the device has swap enabled
131 bool has_swap = false;
132 // True, if device has zram enabled
133 bool has_zram = false;
134 // If zram is enabled, the compression ratio is zram used / swap used.
135 float zram_compression_ratio = 0.0;
136 // Sort process in reverse, default is descending
137 bool reverse_sort = false;
138 
139 // Calculated total memory usage across all processes in the system
140 uint64_t total_pss = 0;
141 uint64_t total_uss = 0;
142 uint64_t total_swap = 0;
143 uint64_t total_pswap = 0;
144 uint64_t total_uswap = 0;
145 uint64_t total_zswap = 0;
146 
usage(int exit_status)147 [[noreturn]] static void usage(int exit_status) {
148     std::cerr << "Usage: " << getprogname() << " [ -W ] [ -v | -r | -p | -u | -s | -h ] [-d PID]"
149               << std::endl
150               << "    -v  Sort by VSS." << std::endl
151               << "    -r  Sort by RSS." << std::endl
152               << "    -p  Sort by PSS." << std::endl
153               << "    -u  Sort by USS." << std::endl
154               << "    -s  Sort by swap." << std::endl
155               << "        (Default sort order is PSS.)" << std::endl
156               << "    -R  Reverse sort order (default is descending)." << std::endl
157               << "    -c  Only show cached (storage backed) pages" << std::endl
158               << "    -C  Only show non-cached (ram/swap backed) pages" << std::endl
159               << "    -k  Only show pages collapsed by KSM" << std::endl
160               << "    -w  Display statistics for working set only." << std::endl
161               << "    -W  Reset working set of processes." << std::endl
162               << "    -o  Show and sort by oom score against lowmemorykiller thresholds."
163               << std::endl
164               << "    -d  Filter to descendants of specified process (can be repeated)" << std::endl
165               << "    -h  Display this help screen." << std::endl;
166     exit(exit_status);
167 }
168 
read_all_pids(std::set<pid_t> * pids)169 static bool read_all_pids(std::set<pid_t>* pids) {
170     pids->clear();
171     std::unique_ptr<DIR, int (*)(DIR*)> procdir(opendir("/proc"), closedir);
172     if (!procdir) return false;
173 
174     struct dirent* dir;
175     pid_t pid;
176     while ((dir = readdir(procdir.get()))) {
177         if (!::android::base::ParseInt(dir->d_name, &pid)) continue;
178         pids->insert(pid);
179     }
180 
181     return true;
182 }
183 
count_swap_offsets(const ProcessRecord & proc,std::vector<uint16_t> & swap_offset_array)184 static bool count_swap_offsets(const ProcessRecord& proc,
185                                std::vector<uint16_t>& swap_offset_array) {
186     const std::vector<uint64_t>& swp_offs = proc.SwapOffsets();
187     for (auto& off : swp_offs) {
188         if (off >= swap_offset_array.size()) {
189             std::cerr << "swap offset " << off << " is out of bounds for process: " << proc.pid()
190                       << std::endl;
191             return false;
192         }
193 
194         if (swap_offset_array[off] == USHRT_MAX) {
195             std::cerr << "swap offset " << off << " ref count overflow in process: " << proc.pid()
196                       << std::endl;
197             return false;
198         }
199 
200         swap_offset_array[off]++;
201     }
202 
203     return true;
204 }
205 
print_header(std::stringstream & ss)206 static void print_header(std::stringstream& ss) {
207     ss.str("");
208     ss << ::android::base::StringPrintf("%5s  ", "PID");
209     if (show_oomadj) {
210         ss << ::android::base::StringPrintf("%5s  ", "oom");
211     }
212 
213     if (show_wss) {
214         ss << ::android::base::StringPrintf("%7s  %7s  %7s  ", "WRss", "WPss", "WUss");
215         // now swap statistics here, working set pages by definition shouldn't end up in swap.
216     } else {
217         ss << ::android::base::StringPrintf("%8s  %7s  %7s  %7s  ", "Vss", "Rss", "Pss", "Uss");
218         if (has_swap) {
219             ss << ::android::base::StringPrintf("%7s  %7s  %7s  ", "Swap", "PSwap", "USwap");
220             if (has_zram) {
221                 ss << ::android::base::StringPrintf("%7s  ", "ZSwap");
222             }
223         }
224     }
225 
226     ss << "cmdline";
227 }
228 
print_process_record(std::stringstream & ss,ProcessRecord & proc)229 static void print_process_record(std::stringstream& ss, ProcessRecord& proc) {
230     ss << ::android::base::StringPrintf("%5d  ", proc.pid());
231     if (show_oomadj) {
232         ss << ::android::base::StringPrintf("%5d  ", proc.oomadj());
233     }
234 
235     if (show_wss) {
236         ss << ::android::base::StringPrintf("%6" PRIu64 "K  %6" PRIu64 "K  %6" PRIu64 "K  ",
237                                             proc.Wss().rss / 1024, proc.Wss().pss / 1024,
238                                             proc.Wss().uss / 1024);
239     } else {
240         ss << ::android::base::StringPrintf("%7" PRIu64 "K  %6" PRIu64 "K  %6" PRIu64 "K  %6" PRIu64
241                                             "K  ",
242                                             proc.Usage().vss / 1024, proc.Usage().rss / 1024,
243                                             proc.Usage().pss / 1024, proc.Usage().uss / 1024);
244         if (has_swap) {
245             ss << ::android::base::StringPrintf("%6" PRIu64 "K  ", proc.Usage().swap / 1024);
246             ss << ::android::base::StringPrintf("%6" PRIu64 "K  ", proc.proportional_swap() / 1024);
247             ss << ::android::base::StringPrintf("%6" PRIu64 "K  ", proc.unique_swap() / 1024);
248             if (has_zram) {
249                 ss << ::android::base::StringPrintf("%6" PRIu64 "K  ", (proc.zswap() / 1024));
250             }
251         }
252     }
253 }
254 
print_processes(std::stringstream & ss,std::vector<ProcessRecord> & procs,const std::vector<uint16_t> & swap_offset_array)255 static void print_processes(std::stringstream& ss, std::vector<ProcessRecord>& procs,
256                             const std::vector<uint16_t>& swap_offset_array) {
257     for (auto& proc : procs) {
258         total_pss += show_wss ? proc.Wss().pss : proc.Usage().pss;
259         total_uss += show_wss ? proc.Wss().uss : proc.Usage().uss;
260         if (!show_wss && has_swap) {
261             proc.CalculateSwap(swap_offset_array, zram_compression_ratio);
262             total_swap += proc.Usage().swap;
263             total_pswap += proc.proportional_swap();
264             total_uswap += proc.unique_swap();
265             if (has_zram) {
266                 total_zswap += proc.zswap();
267             }
268         }
269 
270         print_process_record(ss, proc);
271         ss << proc.cmdline() << std::endl;
272     }
273 }
274 
print_separator(std::stringstream & ss)275 static void print_separator(std::stringstream& ss) {
276     ss << ::android::base::StringPrintf("%5s  ", "");
277     if (show_oomadj) {
278         ss << ::android::base::StringPrintf("%5s  ", "");
279     }
280 
281     if (show_wss) {
282         ss << ::android::base::StringPrintf("%7s  %7s  %7s  ", "", "------", "------");
283     } else {
284         ss << ::android::base::StringPrintf("%8s  %7s  %7s  %7s  ", "", "", "------", "------");
285         if (has_swap) {
286             ss << ::android::base::StringPrintf("%7s  %7s  %7s  ", "------", "------", "------");
287             if (has_zram) {
288                 ss << ::android::base::StringPrintf("%7s  ", "------");
289             }
290         }
291     }
292 
293     ss << ::android::base::StringPrintf("%s", "------");
294 }
295 
print_totals(std::stringstream & ss)296 static void print_totals(std::stringstream& ss) {
297     ss << ::android::base::StringPrintf("%5s  ", "");
298     if (show_oomadj) {
299         ss << ::android::base::StringPrintf("%5s  ", "");
300     }
301 
302     if (show_wss) {
303         ss << ::android::base::StringPrintf("%7s  %6" PRIu64 "K  %6" PRIu64 "K  ", "",
304                                             total_pss / 1024, total_uss / 1024);
305     } else {
306         ss << ::android::base::StringPrintf("%8s  %7s  %6" PRIu64 "K  %6" PRIu64 "K  ", "", "",
307                                             total_pss / 1024, total_uss / 1024);
308         if (has_swap) {
309             ss << ::android::base::StringPrintf("%6" PRIu64 "K  ", total_swap / 1024);
310             ss << ::android::base::StringPrintf("%6" PRIu64 "K  ", total_pswap / 1024);
311             ss << ::android::base::StringPrintf("%6" PRIu64 "K  ", total_uswap / 1024);
312             if (has_zram) {
313                 ss << ::android::base::StringPrintf("%6" PRIu64 "K  ", total_zswap / 1024);
314             }
315         }
316     }
317     ss << "TOTAL";
318 }
319 
print_sysmeminfo(std::stringstream & ss,::android::meminfo::SysMemInfo & smi)320 static void print_sysmeminfo(std::stringstream& ss, ::android::meminfo::SysMemInfo& smi) {
321     if (has_swap) {
322         ss << ::android::base::StringPrintf("ZRAM: %" PRIu64 "K physical used for %" PRIu64
323                                             "K in swap "
324                                             "(%" PRIu64 "K total swap)",
325                                             smi.mem_zram_kb(),
326                                             (smi.mem_swap_kb() - smi.mem_swap_free_kb()),
327                                             smi.mem_swap_kb())
328            << std::endl;
329     }
330 
331     ss << ::android::base::StringPrintf(" RAM: %" PRIu64 "K total, %" PRIu64 "K free, %" PRIu64
332                                         "K buffers, "
333                                         "%" PRIu64 "K cached, %" PRIu64 "K shmem, %" PRIu64
334                                         "K slab",
335                                         smi.mem_total_kb(), smi.mem_free_kb(), smi.mem_buffers_kb(),
336                                         smi.mem_cached_kb(), smi.mem_shmem_kb(), smi.mem_slab_kb());
337 }
338 
main(int argc,char * argv[])339 int main(int argc, char* argv[]) {
340     auto pss_sort = [](ProcessRecord& a, ProcessRecord& b) {
341         MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
342         MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
343         return reverse_sort ? stats_a.pss < stats_b.pss : stats_a.pss > stats_b.pss;
344     };
345 
346     auto uss_sort = [](ProcessRecord& a, ProcessRecord& b) {
347         MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
348         MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
349         return reverse_sort ? stats_a.uss < stats_b.uss : stats_a.uss > stats_b.uss;
350     };
351 
352     auto rss_sort = [](ProcessRecord& a, ProcessRecord& b) {
353         MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
354         MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
355         return reverse_sort ? stats_a.rss < stats_b.rss : stats_a.rss > stats_b.rss;
356     };
357 
358     auto vss_sort = [](ProcessRecord& a, ProcessRecord& b) {
359         MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
360         MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
361         return reverse_sort ? stats_a.vss < stats_b.vss : stats_a.vss > stats_b.vss;
362     };
363 
364     auto swap_sort = [](ProcessRecord& a, ProcessRecord& b) {
365         MemUsage stats_a = show_wss ? a.Wss() : a.Usage();
366         MemUsage stats_b = show_wss ? b.Wss() : b.Usage();
367         return reverse_sort ? stats_a.swap < stats_b.swap : stats_a.swap > stats_b.swap;
368     };
369 
370     auto oomadj_sort = [](ProcessRecord& a, ProcessRecord& b) {
371         return reverse_sort ? a.oomadj() < b.oomadj() : a.oomadj() > b.oomadj();
372     };
373 
374     // default PSS sort
375     std::function<bool(ProcessRecord & a, ProcessRecord & b)> proc_sort = pss_sort;
376 
377     // count all pages by default
378     uint64_t pgflags = 0;
379     uint64_t pgflags_mask = 0;
380 
381     std::vector<pid_t> descendant_filter;
382     int opt;
383     while ((opt = getopt(argc, argv, "cCd:hkoprRsuvwW")) != -1) {
384         switch (opt) {
385             case 'c':
386                 pgflags = 0;
387                 pgflags_mask = (1 << KPF_SWAPBACKED);
388                 break;
389             case 'C':
390                 pgflags = (1 << KPF_SWAPBACKED);
391                 pgflags_mask = (1 << KPF_SWAPBACKED);
392                 break;
393             case 'd': {
394                 pid_t p;
395                 if (!android::base::ParseInt(optarg, &p)) {
396                     std::cerr << "Failed to parse pid '" << optarg << "'" << std::endl;
397                     usage(EXIT_FAILURE);
398                 }
399                 descendant_filter.push_back(p);
400                 break;
401             }
402             case 'h':
403                 usage(EXIT_SUCCESS);
404             case 'k':
405                 pgflags = (1 << KPF_KSM);
406                 pgflags_mask = (1 << KPF_KSM);
407                 break;
408             case 'o':
409                 proc_sort = oomadj_sort;
410                 show_oomadj = true;
411                 break;
412             case 'p':
413                 proc_sort = pss_sort;
414                 break;
415             case 'r':
416                 proc_sort = rss_sort;
417                 break;
418             case 'R':
419                 reverse_sort = true;
420                 break;
421             case 's':
422                 proc_sort = swap_sort;
423                 break;
424             case 'u':
425                 proc_sort = uss_sort;
426                 break;
427             case 'v':
428                 proc_sort = vss_sort;
429                 break;
430             case 'w':
431                 show_wss = true;
432                 break;
433             case 'W':
434                 reset_wss = true;
435                 break;
436             default:
437                 usage(EXIT_FAILURE);
438         }
439     }
440 
441     std::set<pid_t> pids;
442     std::vector<ProcessRecord> procs;
443     if (!read_all_pids(&pids)) {
444         std::cerr << "Failed to read pids" << std::endl;
445         exit(EXIT_FAILURE);
446     }
447 
448     if (descendant_filter.size()) {
449         // Map from parent pid to all of its children.
450         std::unordered_map<pid_t, std::vector<pid_t>> pid_tree;
451 
452         for (pid_t pid : pids) {
453             android::procinfo::ProcessInfo info;
454             std::string error;
455             if (!android::procinfo::GetProcessInfo(pid, &info, &error)) {
456                 std::cerr << "warning: failed to get process info for: " << pid << ": " << error
457                           << std::endl;
458                 continue;
459             }
460 
461             pid_tree[info.ppid].push_back(pid);
462         }
463 
464         std::set<pid_t> final_pids;
465         std::vector<pid_t>& frontier = descendant_filter;
466 
467         // Do a breadth-first walk of the process tree, starting from the pids we were given.
468         while (!frontier.empty()) {
469             pid_t pid = frontier.back();
470             frontier.pop_back();
471 
472             // It's possible for the pid we're looking at to already be in our list if one of the
473             // passed in processes descends from another, or if the same pid is passed twice.
474             auto [it, inserted] = final_pids.insert(pid);
475             if (inserted) {
476                 auto it = pid_tree.find(pid);
477                 if (it != pid_tree.end()) {
478                     // Add all of the children of |pid| to the list of nodes to visit.
479                     frontier.insert(frontier.end(), it->second.begin(), it->second.end());
480                 }
481             }
482         }
483 
484         pids = std::move(final_pids);
485     }
486 
487     if (reset_wss) {
488         for (pid_t pid : pids) {
489             if (!ProcMemInfo::ResetWorkingSet(pid)) {
490                 std::cerr << "Failed to reset working set of all processes" << std::endl;
491                 exit(EXIT_FAILURE);
492             }
493         }
494         // we are done, all other options passed to procrank are ignored in the presence of '-W'
495         return 0;
496     }
497 
498     ::android::meminfo::SysMemInfo smi;
499     if (!smi.ReadMemInfo()) {
500         std::cerr << "Failed to get system memory info" << std::endl;
501         exit(EXIT_FAILURE);
502     }
503 
504     // Figure out swap and zram
505     uint64_t swap_total = smi.mem_swap_kb() * 1024;
506     has_swap = swap_total > 0;
507     // Allocate the swap array
508     std::vector<uint16_t> swap_offset_array(swap_total / getpagesize() + 1, 0);
509     if (has_swap) {
510         has_zram = smi.mem_zram_kb() > 0;
511         if (has_zram) {
512             zram_compression_ratio = static_cast<float>(smi.mem_zram_kb()) /
513                                      (smi.mem_swap_kb() - smi.mem_swap_free_kb());
514         }
515     }
516 
517     // Mark each swap offset used by the process as we find them for calculating proportional
518     // swap usage later.
519     for (pid_t pid : pids) {
520         ProcessRecord proc(pid, show_wss, pgflags, pgflags_mask);
521         if (!proc.valid()) {
522             // Check to see if the process is still around, skip the process if the proc
523             // directory is inaccessible. It was most likely killed while creating the process
524             // record
525             std::string procdir = ::android::base::StringPrintf("/proc/%d", pid);
526             if (access(procdir.c_str(), F_OK | R_OK)) continue;
527 
528             // Warn if we failed to gather process stats even while it is still alive.
529             // Return success here, so we continue to print stats for other processes.
530             std::cerr << "warning: failed to create process record for: " << pid << std::endl;
531             continue;
532         }
533 
534         // Skip processes with no memory mappings
535         uint64_t vss = show_wss ? proc.Wss().vss : proc.Usage().vss;
536         if (vss == 0) continue;
537 
538         // collect swap_offset counts from all processes in 1st pass
539         if (!show_wss && has_swap && !count_swap_offsets(proc, swap_offset_array)) {
540             std::cerr << "Failed to count swap offsets for process: " << pid << std::endl;
541             std::cerr << "Failed to read all pids from the system" << std::endl;
542             exit(EXIT_FAILURE);
543         }
544 
545         procs.emplace_back(std::move(proc));
546     }
547 
548     std::stringstream ss;
549     if (procs.empty()) {
550         // This would happen in corner cases where procrank is being run to find KSM usage on a
551         // system with no KSM and combined with working set determination as follows
552         //   procrank -w -u -k
553         //   procrank -w -s -k
554         //   procrank -w -o -k
555         ss << "<empty>" << std::endl << std::endl;
556         print_sysmeminfo(ss, smi);
557         ss << std::endl;
558         std::cout << ss.str();
559         return 0;
560     }
561 
562     // Sort all process records, default is PSS descending
563     std::sort(procs.begin(), procs.end(), proc_sort);
564 
565     // start dumping output in string stream
566     print_header(ss);
567     ss << std::endl;
568 
569     // 2nd pass to calculate and get per process stats to add them up
570     print_processes(ss, procs, swap_offset_array);
571 
572     // Add separator to output
573     print_separator(ss);
574     ss << std::endl;
575 
576     // Add totals to output
577     print_totals(ss);
578     ss << std::endl << std::endl;
579 
580     // Add system information at the end
581     print_sysmeminfo(ss, smi);
582     ss << std::endl;
583 
584     // dump on the screen
585     std::cout << ss.str();
586 
587     return 0;
588 }
589