Sandeep Patil | 2aeaaeb | 2018-11-23 00:13:16 -0800 | [diff] [blame^] | 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 <dirent.h> |
| 18 | #include <errno.h> |
| 19 | #include <error.h> |
| 20 | #include <inttypes.h> |
| 21 | #include <linux/kernel-page-flags.h> |
| 22 | #include <stdio.h> |
| 23 | #include <stdlib.h> |
| 24 | #include <sys/mman.h> |
| 25 | #include <sys/types.h> |
| 26 | #include <unistd.h> |
| 27 | |
| 28 | #include <algorithm> |
| 29 | #include <memory> |
| 30 | #include <vector> |
| 31 | |
| 32 | #include <android-base/file.h> |
| 33 | #include <android-base/parseint.h> |
| 34 | #include <android-base/stringprintf.h> |
| 35 | #include <android-base/strings.h> |
| 36 | |
| 37 | #include <meminfo/procmeminfo.h> |
| 38 | |
| 39 | using ::android::meminfo::MemUsage; |
| 40 | using ::android::meminfo::ProcMemInfo; |
| 41 | using ::android::meminfo::Vma; |
| 42 | |
| 43 | [[noreturn]] static void usage(const char* myname, int exit_status) { |
| 44 | fprintf(stderr, |
| 45 | "Usage: %s [ -P | -L ] [ -v | -r | -p | -u | -s | -h ]\n" |
| 46 | "\n" |
| 47 | "Sort options:\n" |
| 48 | " -v Sort processes by VSS.\n" |
| 49 | " -r Sort processes by RSS.\n" |
| 50 | " -p Sort processes by PSS.\n" |
| 51 | " -u Sort processes by USS.\n" |
| 52 | " -s Sort processes by swap.\n" |
| 53 | " (Default sort order is PSS.)\n" |
| 54 | " -a Show all mappings, including stack, heap and anon.\n" |
| 55 | " -P /path Limit libraries displayed to those in path.\n" |
| 56 | " -R Reverse sort order (default is descending).\n" |
| 57 | " -m [r][w][x] Only list pages that exactly match permissions\n" |
| 58 | " -c Only show cached (storage backed) pages\n" |
| 59 | " -C Only show non-cached (ram/swap backed) pages\n" |
| 60 | " -k Only show pages collapsed by KSM\n" |
| 61 | " -h Display this help screen.\n", |
| 62 | myname); |
| 63 | exit(exit_status); |
| 64 | } |
| 65 | |
| 66 | static void add_mem_usage(MemUsage* to, const MemUsage& from) { |
| 67 | to->vss += from.vss; |
| 68 | to->rss += from.rss; |
| 69 | to->pss += from.pss; |
| 70 | to->uss += from.uss; |
| 71 | |
| 72 | to->swap += from.swap; |
| 73 | |
| 74 | to->private_clean += from.private_clean; |
| 75 | to->private_dirty += from.private_dirty; |
| 76 | |
| 77 | to->shared_clean += from.shared_clean; |
| 78 | to->shared_dirty += from.shared_dirty; |
| 79 | } |
| 80 | |
| 81 | struct ProcessRecord { |
| 82 | public: |
| 83 | ProcessRecord(pid_t pid) : pid_(-1), cmdline_("") { |
| 84 | std::string fname = ::android::base::StringPrintf("/proc/%d/cmdline", pid); |
| 85 | std::string cmdline; |
| 86 | if (!::android::base::ReadFileToString(fname, &cmdline)) { |
| 87 | fprintf(stderr, "Failed to read cmdline from: %s\n", fname.c_str()); |
| 88 | return; |
| 89 | } |
| 90 | // We deliberately don't read the proc/<pid>cmdline file directly into 'cmdline_' |
| 91 | // because of some processes showing up cmdlines that end with "0x00 0x0A 0x00" |
| 92 | // e.g. xtra-daemon, lowi-server |
| 93 | // The .c_str() assignment below then takes care of trimming the cmdline at the first |
| 94 | // 0x00. This is how original procrank worked (luckily) |
| 95 | cmdline_ = cmdline.c_str(); |
| 96 | pid_ = pid; |
| 97 | usage_.clear(); |
| 98 | } |
| 99 | |
| 100 | ~ProcessRecord() = default; |
| 101 | |
| 102 | bool valid() const { return pid_ != -1; } |
| 103 | |
| 104 | // Getters |
| 105 | pid_t pid() const { return pid_; } |
| 106 | const std::string& cmdline() const { return cmdline_; } |
| 107 | const MemUsage& usage() const { return usage_; } |
| 108 | |
| 109 | // Add to the usage |
| 110 | void AddUsage(const MemUsage& mem_usage) { add_mem_usage(&usage_, mem_usage); } |
| 111 | |
| 112 | private: |
| 113 | pid_t pid_; |
| 114 | std::string cmdline_; |
| 115 | MemUsage usage_; |
| 116 | }; |
| 117 | |
| 118 | struct LibRecord { |
| 119 | public: |
| 120 | LibRecord(const std::string& name) : name_(name) {} |
| 121 | ~LibRecord() = default; |
| 122 | |
| 123 | const std::string& name() const { return name_; } |
| 124 | const MemUsage& usage() const { return usage_; } |
| 125 | const std::vector<ProcessRecord>& processes() const { return procs_; } |
| 126 | uint64_t pss() const { return usage_.pss; } |
| 127 | void AddUsage(const ProcessRecord& proc, const MemUsage& mem_usage) { |
| 128 | auto process = std::find_if(procs_.begin(), procs_.end(), |
| 129 | [&](auto p) -> bool { return p.pid() == proc.pid(); }); |
| 130 | if (process == procs_.end()) { |
| 131 | process = procs_.emplace(procs_.end(), proc.pid()); |
| 132 | } |
| 133 | process->AddUsage(mem_usage); |
| 134 | add_mem_usage(&usage_, mem_usage); |
| 135 | } |
| 136 | |
| 137 | void Sort(std::function<bool(const ProcessRecord&, const ProcessRecord&)>& sorter) { |
| 138 | std::sort(procs_.begin(), procs_.end(), sorter); |
| 139 | } |
| 140 | |
| 141 | private: |
| 142 | std::string name_; |
| 143 | MemUsage usage_; |
| 144 | std::vector<ProcessRecord> procs_; |
| 145 | }; |
| 146 | |
| 147 | // List of every library / map |
| 148 | static std::vector<LibRecord> g_libs; |
| 149 | |
| 150 | // List of library/map names that we don't want to show by default |
| 151 | static const std::vector<std::string> g_blacklisted_libs = {"[heap]", "[stack]"}; |
| 152 | |
| 153 | // Global flags affected by command line |
| 154 | static uint64_t g_pgflags = 0; |
| 155 | static uint64_t g_pgflags_mask = 0; |
| 156 | static uint16_t g_mapflags_mask = 0; |
| 157 | static bool g_all_libs = false; |
| 158 | static bool g_has_swap = false; |
| 159 | static bool g_reverse_sort = false; |
| 160 | static std::string g_prefix_filter = ""; |
| 161 | |
| 162 | static bool read_all_pids(std::function<bool(pid_t pid)> for_each_pid) { |
| 163 | std::unique_ptr<DIR, int (*)(DIR*)> procdir(opendir("/proc"), closedir); |
| 164 | if (!procdir) return false; |
| 165 | |
| 166 | struct dirent* dir; |
| 167 | pid_t pid; |
| 168 | while ((dir = readdir(procdir.get()))) { |
| 169 | if (!::android::base::ParseInt(dir->d_name, &pid)) continue; |
| 170 | if (!for_each_pid(pid)) return false; |
| 171 | } |
| 172 | |
| 173 | return true; |
| 174 | } |
| 175 | |
| 176 | static bool scan_libs_per_process(pid_t pid) { |
| 177 | ProcMemInfo pmem(pid, false, g_pgflags, g_pgflags_mask); |
| 178 | const std::vector<Vma> maps = pmem.Maps(); |
| 179 | if (maps.size() == 0) { |
| 180 | // nothing to do here, continue |
| 181 | return true; |
| 182 | } |
| 183 | |
| 184 | ProcessRecord proc(pid); |
| 185 | if (!proc.valid()) { |
| 186 | fprintf(stderr, "Failed to create process record for process: %d\n", pid); |
| 187 | return false; |
| 188 | } |
| 189 | |
| 190 | for (auto& map : maps) { |
| 191 | // skip library / map if prefix for the path doesn't match |
| 192 | if (!g_prefix_filter.empty() && !::android::base::StartsWith(map.name, g_prefix_filter)) { |
| 193 | continue; |
| 194 | } |
| 195 | // Skip maps based on map permissions |
| 196 | if (g_mapflags_mask && |
| 197 | ((map.flags & (PROT_READ | PROT_WRITE | PROT_EXEC)) != g_mapflags_mask)) { |
| 198 | continue; |
| 199 | } |
| 200 | |
| 201 | // skip blacklisted library / map names |
| 202 | if (!g_all_libs && (std::find(g_blacklisted_libs.begin(), g_blacklisted_libs.end(), |
| 203 | map.name) != g_blacklisted_libs.end())) { |
| 204 | continue; |
| 205 | } |
| 206 | |
| 207 | auto lib = std::find_if(g_libs.begin(), g_libs.end(), |
| 208 | [&](auto l) -> bool { return map.name == l.name(); }); |
| 209 | if (lib == g_libs.end()) { |
| 210 | lib = g_libs.emplace(g_libs.end(), map.name); |
| 211 | } |
| 212 | |
| 213 | lib->AddUsage(proc, map.usage); |
| 214 | if (!g_has_swap && map.usage.swap) { |
| 215 | g_has_swap = true; |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | return true; |
| 220 | } |
| 221 | |
| 222 | static uint16_t parse_mapflags(const char* mapflags) { |
| 223 | uint16_t ret = 0; |
| 224 | for (const char* p = mapflags; *p; p++) { |
| 225 | switch (*p) { |
| 226 | case 'r': |
| 227 | ret |= PROT_READ; |
| 228 | break; |
| 229 | case 'w': |
| 230 | ret |= PROT_WRITE; |
| 231 | break; |
| 232 | case 'x': |
| 233 | ret |= PROT_EXEC; |
| 234 | break; |
| 235 | default: |
| 236 | error(EXIT_FAILURE, 0, "Invalid permissions string: %s, %s", mapflags, p); |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | return ret; |
| 241 | } |
| 242 | |
| 243 | int main(int argc, char* argv[]) { |
| 244 | int opt; |
| 245 | |
| 246 | auto pss_sort = [](const ProcessRecord& a, const ProcessRecord& b) { |
| 247 | return g_reverse_sort ? a.usage().pss < b.usage().pss : a.usage().pss > b.usage().pss; |
| 248 | }; |
| 249 | |
| 250 | auto uss_sort = [](const ProcessRecord& a, const ProcessRecord& b) { |
| 251 | return g_reverse_sort ? a.usage().uss < b.usage().uss : a.usage().uss > b.usage().uss; |
| 252 | }; |
| 253 | |
| 254 | auto vss_sort = [](const ProcessRecord& a, const ProcessRecord& b) { |
| 255 | return g_reverse_sort ? a.usage().vss < b.usage().vss : a.usage().vss > b.usage().vss; |
| 256 | }; |
| 257 | |
| 258 | auto rss_sort = [](const ProcessRecord& a, const ProcessRecord& b) { |
| 259 | return g_reverse_sort ? a.usage().rss < b.usage().rss : a.usage().rss > b.usage().rss; |
| 260 | }; |
| 261 | |
| 262 | auto swap_sort = [](const ProcessRecord& a, const ProcessRecord& b) { |
| 263 | return g_reverse_sort ? a.usage().swap < b.usage().swap : a.usage().swap > b.usage().swap; |
| 264 | }; |
| 265 | |
| 266 | std::function<bool(const ProcessRecord&, const ProcessRecord&)> sort_func = pss_sort; |
| 267 | |
| 268 | while ((opt = getopt(argc, argv, "acChkm:pP:uvrsR")) != -1) { |
| 269 | switch (opt) { |
| 270 | case 'a': |
| 271 | g_all_libs = true; |
| 272 | break; |
| 273 | case 'c': |
| 274 | g_pgflags = 0; |
| 275 | g_pgflags_mask = (1 << KPF_SWAPBACKED); |
| 276 | break; |
| 277 | case 'C': |
| 278 | g_pgflags = g_pgflags_mask = (1 << KPF_SWAPBACKED); |
| 279 | break; |
| 280 | case 'h': |
| 281 | usage(argv[0], EXIT_SUCCESS); |
| 282 | case 'k': |
| 283 | g_pgflags = g_pgflags_mask = (1 << KPF_KSM); |
| 284 | break; |
| 285 | case 'm': |
| 286 | g_mapflags_mask = parse_mapflags(optarg); |
| 287 | break; |
| 288 | case 'p': |
| 289 | sort_func = pss_sort; |
| 290 | break; |
| 291 | case 'P': |
| 292 | g_prefix_filter = optarg; |
| 293 | break; |
| 294 | case 'u': |
| 295 | sort_func = uss_sort; |
| 296 | break; |
| 297 | case 'v': |
| 298 | sort_func = vss_sort; |
| 299 | break; |
| 300 | case 'r': |
| 301 | sort_func = rss_sort; |
| 302 | break; |
| 303 | case 's': |
| 304 | sort_func = swap_sort; |
| 305 | break; |
| 306 | case 'R': |
| 307 | g_reverse_sort = true; |
| 308 | break; |
| 309 | default: |
| 310 | usage(argv[0], EXIT_FAILURE); |
| 311 | } |
| 312 | } |
| 313 | |
| 314 | if (!read_all_pids(scan_libs_per_process)) { |
| 315 | error(EXIT_FAILURE, 0, "Failed to read all pids from the system"); |
| 316 | } |
| 317 | |
| 318 | printf(" %6s %7s %6s %6s %6s ", "RSStot", "VSS", "RSS", "PSS", "USS"); |
| 319 | if (g_has_swap) { |
| 320 | printf(" %6s ", "Swap"); |
| 321 | } |
| 322 | printf("Name/PID\n"); |
| 323 | |
| 324 | // sort the libraries by their pss |
| 325 | std::sort(g_libs.begin(), g_libs.end(), |
| 326 | [](const LibRecord& l1, const LibRecord& l2) { return l1.pss() > l2.pss(); }); |
| 327 | |
| 328 | for (auto& lib : g_libs) { |
| 329 | printf("%6" PRIu64 "K %7s %6s %6s %6s ", lib.pss() / 1024, "", "", "", ""); |
| 330 | if (g_has_swap) { |
| 331 | printf(" %6s ", ""); |
| 332 | } |
| 333 | printf("%s\n", lib.name().c_str()); |
| 334 | |
| 335 | // sort all mappings first |
| 336 | lib.Sort(sort_func); |
| 337 | |
| 338 | for (auto& p : lib.processes()) { |
| 339 | const MemUsage& usage = p.usage(); |
| 340 | printf(" %6s %7" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K ", "", |
| 341 | usage.vss / 1024, usage.rss / 1024, usage.pss / 1024, usage.uss / 1024); |
| 342 | if (g_has_swap) { |
| 343 | printf("%6" PRIu64 "K ", usage.swap / 1024); |
| 344 | } |
| 345 | printf(" %s [%d]\n", p.cmdline().c_str(), p.pid()); |
| 346 | } |
| 347 | } |
| 348 | |
| 349 | return 0; |
| 350 | } |