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