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