| /* | |
| * Copyright (C) 2018 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include <errno.h> | |
| #include <inttypes.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include <iostream> | |
| #include <sstream> | |
| #include <string> | |
| #include <vector> | |
| #include <android-base/stringprintf.h> | |
| #include <meminfo/procmeminfo.h> | |
| using Vma = ::android::meminfo::Vma; | |
| using ProcMemInfo = ::android::meminfo::ProcMemInfo; | |
| using MemUsage = ::android::meminfo::MemUsage; | |
| // Global flags to control procmem output | |
| // Set to use page idle bits for working set detection | |
| bool use_pageidle = false; | |
| // hides map entries with zero rss | |
| bool hide_zeroes = false; | |
| // Reset working set and exit | |
| bool reset_wss = false; | |
| // Show working set, mutually exclusive with reset_wss; | |
| bool show_wss = false; | |
| [[noreturn]] static void usage(int exit_status) { | |
| fprintf(stderr, | |
| "Usage: %s [-i] [ -w | -W ] [ -p | -m ] [ -h ] pid\n" | |
| " -i Uses idle page tracking for working set statistics.\n" | |
| " -w Displays statistics for the working set only.\n" | |
| " -W Resets the working set of the process.\n" | |
| " -p Sort by PSS.\n" | |
| " -u Sort by USS.\n" | |
| " -m Sort by mapping order (as read from /proc).\n" | |
| " -h Hide maps with no RSS.\n", | |
| getprogname()); | |
| exit(exit_status); | |
| } | |
| static void print_separator(std::stringstream& ss) { | |
| if (show_wss) { | |
| ss << ::android::base::StringPrintf("%7s %7s %7s %7s %7s %7s %7s %s\n", "-------", | |
| "-------", "-------", "-------", "-------", "-------", | |
| "-------", ""); | |
| return; | |
| } | |
| ss << ::android::base::StringPrintf("%7s %7s %7s %7s %7s %7s %7s %7s %s\n", "-------", | |
| "-------", "-------", "-------", "-------", "-------", | |
| "-------", "-------", ""); | |
| } | |
| static void print_header(std::stringstream& ss) { | |
| if (show_wss) { | |
| ss << ::android::base::StringPrintf("%7s %7s %7s %7s %7s %7s %7s %s\n", "WRss", | |
| "WPss", "WUss", "WShCl", "WShDi", "WPrCl", "WPrDi", | |
| "Name"); | |
| } else { | |
| ss << ::android::base::StringPrintf("%7s %7s %7s %7s %7s %7s %7s %7s %s\n", "Vss", | |
| "Rss", "Pss", "Uss", "ShCl", "ShDi", "PrCl", "PrDi", | |
| "Name"); | |
| } | |
| print_separator(ss); | |
| } | |
| static void print_stats(std::stringstream& ss, const MemUsage& stats) { | |
| if (!show_wss) { | |
| ss << ::android::base::StringPrintf("%6" PRIu64 "K ", stats.vss / 1024); | |
| } | |
| ss << ::android::base::StringPrintf("%6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 | |
| "K %6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K ", | |
| stats.rss / 1024, stats.pss / 1024, stats.uss / 1024, | |
| stats.shared_clean / 1024, stats.shared_dirty / 1024, | |
| stats.private_clean / 1024, stats.private_dirty / 1024); | |
| } | |
| static int show(const MemUsage& proc_stats, const std::vector<Vma>& maps) { | |
| std::stringstream ss; | |
| print_header(ss); | |
| for (auto& vma : maps) { | |
| const MemUsage& vma_stats = vma.usage; | |
| if (hide_zeroes && vma_stats.rss == 0) { | |
| continue; | |
| } | |
| print_stats(ss, vma_stats); | |
| ss << vma.name << std::endl; | |
| } | |
| print_separator(ss); | |
| print_stats(ss, proc_stats); | |
| ss << "TOTAL" << std::endl; | |
| std::cout << ss.str(); | |
| return 0; | |
| } | |
| int main(int argc, char* argv[]) { | |
| int opt; | |
| auto pss_sort = [](const Vma& a, const Vma& b) { | |
| uint64_t pss_a = a.usage.pss; | |
| uint64_t pss_b = b.usage.pss; | |
| return pss_a > pss_b; | |
| }; | |
| auto uss_sort = [](const Vma& a, const Vma& b) { | |
| uint64_t uss_a = a.usage.uss; | |
| uint64_t uss_b = b.usage.uss; | |
| return uss_a > uss_b; | |
| }; | |
| std::function<bool(const Vma& a, const Vma& b)> sort_func = nullptr; | |
| while ((opt = getopt(argc, argv, "himpuWw")) != -1) { | |
| switch (opt) { | |
| case 'h': | |
| hide_zeroes = true; | |
| break; | |
| case 'i': | |
| // TODO: libmeminfo doesn't support the flag to chose | |
| // between idle page tracking vs clear_refs. So for now, | |
| // this flag is unused and the library defaults to using | |
| // /proc/<pid>/clear_refs for finding the working set. | |
| use_pageidle = true; | |
| break; | |
| case 'm': | |
| // this is the default | |
| break; | |
| case 'p': | |
| sort_func = pss_sort; | |
| break; | |
| case 'u': | |
| sort_func = uss_sort; | |
| break; | |
| case 'W': | |
| reset_wss = true; | |
| break; | |
| case 'w': | |
| show_wss = true; | |
| break; | |
| case '?': | |
| usage(EXIT_SUCCESS); | |
| default: | |
| usage(EXIT_FAILURE); | |
| } | |
| } | |
| if (optind != (argc - 1)) { | |
| fprintf(stderr, "Need exactly one pid at the end\n"); | |
| usage(EXIT_FAILURE); | |
| } | |
| pid_t pid = atoi(argv[optind]); | |
| if (pid == 0) { | |
| std::cerr << "Invalid process id" << std::endl; | |
| exit(EXIT_FAILURE); | |
| } | |
| if (reset_wss) { | |
| if (!ProcMemInfo::ResetWorkingSet(pid)) { | |
| std::cerr << "Failed to reset working set of pid : " << pid << std::endl; | |
| exit(EXIT_FAILURE); | |
| } | |
| return 0; | |
| } | |
| ProcMemInfo proc(pid, show_wss); | |
| const MemUsage& proc_stats = proc.Usage(); | |
| std::vector<Vma> maps(proc.Maps()); | |
| if (sort_func != nullptr) { | |
| std::sort(maps.begin(), maps.end(), sort_func); | |
| } | |
| return show(proc_stats, maps); | |
| } |