| /* |
| * Copyright (C) 2022 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 <inttypes.h> |
| #include <linux/oom.h> |
| #include <stdlib.h> |
| #include <sys/mman.h> |
| #include <unistd.h> |
| |
| #include <chrono> |
| #include <iomanip> |
| #include <iostream> |
| #include <set> |
| #include <string> |
| #include <vector> |
| |
| #include <android-base/file.h> |
| #include <android-base/parseint.h> |
| #include <android-base/stringprintf.h> |
| #include <android-base/strings.h> |
| #include <meminfo/sysmeminfo.h> |
| |
| #include <processrecord.h> |
| #include <smapinfo.h> |
| |
| namespace android { |
| namespace smapinfo { |
| |
| using ::android::base::StringPrintf; |
| using ::android::meminfo::EscapeCsvString; |
| using ::android::meminfo::EscapeJsonString; |
| using ::android::meminfo::Format; |
| using ::android::meminfo::MemUsage; |
| using ::android::meminfo::Vma; |
| |
| bool get_all_pids(std::set<pid_t>* pids) { |
| pids->clear(); |
| std::unique_ptr<DIR, int (*)(DIR*)> procdir(opendir("/proc"), closedir); |
| if (!procdir) return false; |
| |
| struct dirent* dir; |
| pid_t pid; |
| while ((dir = readdir(procdir.get()))) { |
| if (!::android::base::ParseInt(dir->d_name, &pid)) continue; |
| pids->insert(pid); |
| } |
| return true; |
| } |
| |
| namespace procrank { |
| |
| static bool count_swap_offsets(const ProcessRecord& proc, std::vector<uint16_t>& swap_offset_array, |
| std::ostream& err) { |
| const std::vector<uint64_t>& swp_offs = proc.SwapOffsets(); |
| for (auto& off : swp_offs) { |
| if (off >= swap_offset_array.size()) { |
| err << "swap offset " << off << " is out of bounds for process: " << proc.pid() << "\n"; |
| return false; |
| } |
| if (swap_offset_array[off] == USHRT_MAX) { |
| err << "swap offset " << off << " ref count overflow in process: " << proc.pid() |
| << "\n"; |
| return false; |
| } |
| swap_offset_array[off]++; |
| } |
| return true; |
| } |
| |
| struct params { |
| // Calculated total memory usage across all processes in the system. |
| uint64_t total_pss; |
| uint64_t total_uss; |
| uint64_t total_swap; |
| uint64_t total_pswap; |
| uint64_t total_uswap; |
| uint64_t total_zswap; |
| |
| // Print options. |
| bool show_oomadj; |
| bool show_wss; |
| bool swap_enabled; |
| bool zram_enabled; |
| |
| // If zram is enabled, the compression ratio is zram used / swap used. |
| float zram_compression_ratio; |
| }; |
| |
| static std::function<bool(ProcessRecord& a, ProcessRecord& b)> select_sort(struct params* params, |
| SortOrder sort_order) { |
| // Create sort function based on sort_order. |
| std::function<bool(ProcessRecord & a, ProcessRecord & b)> proc_sort; |
| switch (sort_order) { |
| case (SortOrder::BY_OOMADJ): |
| proc_sort = [](ProcessRecord& a, ProcessRecord& b) { return a.oomadj() > b.oomadj(); }; |
| break; |
| case (SortOrder::BY_RSS): |
| proc_sort = [=](ProcessRecord& a, ProcessRecord& b) { |
| return a.Usage(params->show_wss).rss > b.Usage(params->show_wss).rss; |
| }; |
| break; |
| case (SortOrder::BY_SWAP): |
| proc_sort = [=](ProcessRecord& a, ProcessRecord& b) { |
| return a.Usage(params->show_wss).swap > b.Usage(params->show_wss).swap; |
| }; |
| break; |
| case (SortOrder::BY_USS): |
| proc_sort = [=](ProcessRecord& a, ProcessRecord& b) { |
| return a.Usage(params->show_wss).uss > b.Usage(params->show_wss).uss; |
| }; |
| break; |
| case (SortOrder::BY_VSS): |
| proc_sort = [=](ProcessRecord& a, ProcessRecord& b) { |
| return a.Usage(params->show_wss).vss > b.Usage(params->show_wss).vss; |
| }; |
| break; |
| case (SortOrder::BY_PSS): |
| default: |
| proc_sort = [=](ProcessRecord& a, ProcessRecord& b) { |
| return a.Usage(params->show_wss).pss > b.Usage(params->show_wss).pss; |
| }; |
| break; |
| } |
| return proc_sort; |
| } |
| |
| static bool populate_procs(struct params* params, uint64_t pgflags, uint64_t pgflags_mask, |
| std::vector<uint16_t>& swap_offset_array, const std::set<pid_t>& pids, |
| std::vector<ProcessRecord>* procs, |
| std::map<pid_t, ProcessRecord>* processrecords_ptr, std::ostream& err) { |
| // Fall back to using an empty map of ProcessRecords if nullptr was passed in. |
| std::map<pid_t, ProcessRecord> processrecords; |
| if (!processrecords_ptr) { |
| processrecords_ptr = &processrecords; |
| } |
| // Mark each swap offset used by the process as we find them for calculating |
| // proportional swap usage later. |
| for (pid_t pid : pids) { |
| // Check if a ProcessRecord already exists for this pid, create one if one does not exist. |
| auto iter = processrecords_ptr->find(pid); |
| ProcessRecord& proc = |
| (iter != processrecords_ptr->end()) |
| ? iter->second |
| : processrecords_ptr |
| ->emplace(pid, ProcessRecord(pid, params->show_wss, pgflags, |
| pgflags_mask, true, |
| params->show_oomadj, err)) |
| .first->second; |
| |
| if (!proc.valid()) { |
| // Check to see if the process is still around, skip the process if the proc |
| // directory is inaccessible. It was most likely killed while creating the process |
| // record. |
| std::string procdir = StringPrintf("/proc/%d", pid); |
| if (access(procdir.c_str(), F_OK | R_OK)) continue; |
| |
| // Warn if we failed to gather process stats even while it is still alive. |
| // Return success here, so we continue to print stats for other processes. |
| err << "warning: failed to create process record for: " << pid << "\n"; |
| continue; |
| } |
| |
| // Skip processes with no memory mappings. |
| uint64_t vss = proc.Usage(params->show_wss).vss; |
| if (vss == 0) continue; |
| |
| // Collect swap_offset counts from all processes in 1st pass. |
| if (!params->show_wss && params->swap_enabled && |
| !count_swap_offsets(proc, swap_offset_array, err)) { |
| err << "Failed to count swap offsets for process: " << pid << "\n"; |
| err << "Failed to read all pids from the system\n"; |
| return false; |
| } |
| |
| procs->push_back(proc); |
| } |
| return true; |
| } |
| |
| static void print_header(struct params* params, std::ostream& out) { |
| out << StringPrintf("%5s ", "PID"); |
| if (params->show_oomadj) { |
| out << StringPrintf("%5s ", "oom"); |
| } |
| |
| if (params->show_wss) { |
| out << StringPrintf("%7s %7s %7s ", "WRss", "WPss", "WUss"); |
| } else { |
| // Swap statistics here, as working set pages by definition shouldn't end up in swap. |
| out << StringPrintf("%8s %7s %7s %7s ", "Vss", "Rss", "Pss", "Uss"); |
| if (params->swap_enabled) { |
| out << StringPrintf("%7s %7s %7s ", "Swap", "PSwap", "USwap"); |
| if (params->zram_enabled) { |
| out << StringPrintf("%7s ", "ZSwap"); |
| } |
| } |
| } |
| |
| out << "cmdline\n"; |
| } |
| |
| static void print_divider(struct params* params, std::ostream& out) { |
| out << StringPrintf("%5s ", ""); |
| if (params->show_oomadj) { |
| out << StringPrintf("%5s ", ""); |
| } |
| |
| if (params->show_wss) { |
| out << StringPrintf("%7s %7s %7s ", "", "------", "------"); |
| } else { |
| out << StringPrintf("%8s %7s %7s %7s ", "", "", "------", "------"); |
| if (params->swap_enabled) { |
| out << StringPrintf("%7s %7s %7s ", "------", "------", "------"); |
| if (params->zram_enabled) { |
| out << StringPrintf("%7s ", "------"); |
| } |
| } |
| } |
| |
| out << StringPrintf("%s\n", "------"); |
| } |
| |
| static void print_processrecord(struct params* params, ProcessRecord& proc, std::ostream& out) { |
| out << StringPrintf("%5d ", proc.pid()); |
| if (params->show_oomadj) { |
| out << StringPrintf("%5d ", proc.oomadj()); |
| } |
| |
| if (params->show_wss) { |
| out << StringPrintf("%6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K ", |
| proc.Usage(params->show_wss).rss, proc.Usage(params->show_wss).pss, |
| proc.Usage(params->show_wss).uss); |
| } else { |
| out << StringPrintf("%7" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K %6" PRIu64 "K ", |
| proc.Usage(params->show_wss).vss, proc.Usage(params->show_wss).rss, |
| proc.Usage(params->show_wss).pss, proc.Usage(params->show_wss).uss); |
| if (params->swap_enabled) { |
| out << StringPrintf("%6" PRIu64 "K ", proc.Usage(params->show_wss).swap); |
| out << StringPrintf("%6" PRIu64 "K ", proc.proportional_swap()); |
| out << StringPrintf("%6" PRIu64 "K ", proc.unique_swap()); |
| if (params->zram_enabled) { |
| out << StringPrintf("%6" PRIu64 "K ", proc.zswap()); |
| } |
| } |
| } |
| out << proc.cmdline() << "\n"; |
| } |
| |
| static void print_totals(struct params* params, std::ostream& out) { |
| out << StringPrintf("%5s ", ""); |
| if (params->show_oomadj) { |
| out << StringPrintf("%5s ", ""); |
| } |
| |
| if (params->show_wss) { |
| out << StringPrintf("%7s %6" PRIu64 "K %6" PRIu64 "K ", "", params->total_pss, |
| params->total_uss); |
| } else { |
| out << StringPrintf("%8s %7s %6" PRIu64 "K %6" PRIu64 "K ", "", "", params->total_pss, |
| params->total_uss); |
| if (params->swap_enabled) { |
| out << StringPrintf("%6" PRIu64 "K ", params->total_swap); |
| out << StringPrintf("%6" PRIu64 "K ", params->total_pswap); |
| out << StringPrintf("%6" PRIu64 "K ", params->total_uswap); |
| if (params->zram_enabled) { |
| out << StringPrintf("%6" PRIu64 "K ", params->total_zswap); |
| } |
| } |
| } |
| out << "TOTAL\n\n"; |
| } |
| |
| static void print_sysmeminfo(struct params* params, const ::android::meminfo::SysMemInfo& smi, |
| std::ostream& out) { |
| if (params->swap_enabled) { |
| out << StringPrintf("ZRAM: %" PRIu64 "K physical used for %" PRIu64 "K in swap (%" PRIu64 |
| "K total swap)\n", |
| smi.mem_zram_kb(), (smi.mem_swap_kb() - smi.mem_swap_free_kb()), |
| smi.mem_swap_kb()); |
| } |
| |
| out << StringPrintf(" RAM: %" PRIu64 "K total, %" PRIu64 "K free, %" PRIu64 |
| "K buffers, %" PRIu64 "K cached, %" PRIu64 "K shmem, %" PRIu64 "K slab\n", |
| smi.mem_total_kb(), smi.mem_free_kb(), smi.mem_buffers_kb(), |
| smi.mem_cached_kb(), smi.mem_shmem_kb(), smi.mem_slab_kb()); |
| } |
| |
| static void add_to_totals(struct params* params, ProcessRecord& proc, |
| const std::vector<uint16_t>& swap_offset_array) { |
| params->total_pss += proc.Usage(params->show_wss).pss; |
| params->total_uss += proc.Usage(params->show_wss).uss; |
| if (!params->show_wss && params->swap_enabled) { |
| proc.CalculateSwap(swap_offset_array, params->zram_compression_ratio); |
| params->total_swap += proc.Usage(params->show_wss).swap; |
| params->total_pswap += proc.proportional_swap(); |
| params->total_uswap += proc.unique_swap(); |
| if (params->zram_enabled) { |
| params->total_zswap += proc.zswap(); |
| } |
| } |
| } |
| |
| } // namespace procrank |
| |
| bool run_procrank(uint64_t pgflags, uint64_t pgflags_mask, const std::set<pid_t>& pids, |
| bool get_oomadj, bool get_wss, SortOrder sort_order, bool reverse_sort, |
| std::map<pid_t, ProcessRecord>* processrecords_ptr, std::ostream& out, |
| std::ostream& err) { |
| ::android::meminfo::SysMemInfo smi; |
| if (!smi.ReadMemInfo()) { |
| err << "Failed to get system memory info\n"; |
| return false; |
| } |
| |
| struct procrank::params params = { |
| .total_pss = 0, |
| .total_uss = 0, |
| .total_swap = 0, |
| .total_pswap = 0, |
| .total_uswap = 0, |
| .total_zswap = 0, |
| .show_oomadj = get_oomadj, |
| .show_wss = get_wss, |
| .swap_enabled = false, |
| .zram_enabled = false, |
| .zram_compression_ratio = 0.0, |
| }; |
| |
| // Figure out swap and zram. |
| uint64_t swap_total = smi.mem_swap_kb() * 1024; |
| params.swap_enabled = swap_total > 0; |
| // Allocate the swap array. |
| std::vector<uint16_t> swap_offset_array(swap_total / getpagesize() + 1, 0); |
| if (params.swap_enabled) { |
| params.zram_enabled = smi.mem_zram_kb() > 0; |
| if (params.zram_enabled) { |
| params.zram_compression_ratio = static_cast<float>(smi.mem_zram_kb()) / |
| (smi.mem_swap_kb() - smi.mem_swap_free_kb()); |
| } |
| } |
| |
| std::vector<ProcessRecord> procs; |
| if (!procrank::populate_procs(¶ms, pgflags, pgflags_mask, swap_offset_array, pids, &procs, |
| processrecords_ptr, err)) { |
| return false; |
| } |
| |
| if (procs.empty()) { |
| // This would happen in corner cases where procrank is being run to find KSM usage on a |
| // system with no KSM and combined with working set determination as follows |
| // procrank -w -u -k |
| // procrank -w -s -k |
| // procrank -w -o -k |
| out << "<empty>\n\n"; |
| procrank::print_sysmeminfo(¶ms, smi, out); |
| return true; |
| } |
| |
| // Create sort function based on sort_order, default is PSS descending. |
| std::function<bool(ProcessRecord & a, ProcessRecord & b)> proc_sort = |
| procrank::select_sort(¶ms, sort_order); |
| |
| // Sort all process records, default is PSS descending. |
| if (reverse_sort) { |
| std::sort(procs.rbegin(), procs.rend(), proc_sort); |
| } else { |
| std::sort(procs.begin(), procs.end(), proc_sort); |
| } |
| |
| procrank::print_header(¶ms, out); |
| |
| for (auto& proc : procs) { |
| procrank::add_to_totals(¶ms, proc, swap_offset_array); |
| procrank::print_processrecord(¶ms, proc, out); |
| } |
| |
| procrank::print_divider(¶ms, out); |
| procrank::print_totals(¶ms, out); |
| procrank::print_sysmeminfo(¶ms, smi, out); |
| |
| return true; |
| } |
| |
| namespace librank { |
| |
| static void add_mem_usage(MemUsage* to, const MemUsage& from) { |
| to->vss += from.vss; |
| to->rss += from.rss; |
| to->pss += from.pss; |
| to->uss += from.uss; |
| |
| to->swap += from.swap; |
| |
| to->private_clean += from.private_clean; |
| to->private_dirty += from.private_dirty; |
| to->shared_clean += from.shared_clean; |
| to->shared_dirty += from.shared_dirty; |
| } |
| |
| // Represents a specific process's usage of a library. |
| struct LibProcRecord { |
| public: |
| LibProcRecord(ProcessRecord& proc) : pid_(-1), oomadj_(OOM_SCORE_ADJ_MAX + 1) { |
| pid_ = proc.pid(); |
| cmdline_ = proc.cmdline(); |
| oomadj_ = proc.oomadj(); |
| usage_.clear(); |
| } |
| |
| bool valid() const { return pid_ != -1; } |
| void AddUsage(const MemUsage& mem_usage) { add_mem_usage(&usage_, mem_usage); } |
| |
| // Getters |
| pid_t pid() const { return pid_; } |
| const std::string& cmdline() const { return cmdline_; } |
| int32_t oomadj() const { return oomadj_; } |
| const MemUsage& usage() const { return usage_; } |
| |
| private: |
| pid_t pid_; |
| std::string cmdline_; |
| int32_t oomadj_; |
| MemUsage usage_; |
| }; |
| |
| // Represents all processes' usage of a specific library. |
| struct LibRecord { |
| public: |
| LibRecord(const std::string& name) : name_(name) {} |
| |
| void AddUsage(const LibProcRecord& proc, const MemUsage& mem_usage) { |
| auto [it, inserted] = procs_.insert(std::pair<pid_t, LibProcRecord>(proc.pid(), proc)); |
| // Adds to proc's PID's contribution to usage of this lib, as well as total lib usage. |
| it->second.AddUsage(mem_usage); |
| add_mem_usage(&usage_, mem_usage); |
| } |
| uint64_t pss() const { return usage_.pss; } |
| |
| // Getters |
| const std::string& name() const { return name_; } |
| const std::map<pid_t, LibProcRecord>& processes() const { return procs_; } |
| |
| private: |
| std::string name_; |
| MemUsage usage_; |
| std::map<pid_t, LibProcRecord> procs_; |
| }; |
| |
| static std::function<bool(LibProcRecord& a, LibProcRecord& b)> select_sort(SortOrder sort_order) { |
| // Create sort function based on sort_order. |
| std::function<bool(LibProcRecord & a, LibProcRecord & b)> proc_sort; |
| switch (sort_order) { |
| case (SortOrder::BY_RSS): |
| proc_sort = [](LibProcRecord& a, LibProcRecord& b) { |
| return a.usage().rss > b.usage().rss; |
| }; |
| break; |
| case (SortOrder::BY_USS): |
| proc_sort = [](LibProcRecord& a, LibProcRecord& b) { |
| return a.usage().uss > b.usage().uss; |
| }; |
| break; |
| case (SortOrder::BY_VSS): |
| proc_sort = [](LibProcRecord& a, LibProcRecord& b) { |
| return a.usage().vss > b.usage().vss; |
| }; |
| break; |
| case (SortOrder::BY_OOMADJ): |
| proc_sort = [](LibProcRecord& a, LibProcRecord& b) { return a.oomadj() > b.oomadj(); }; |
| break; |
| case (SortOrder::BY_PSS): |
| default: |
| proc_sort = [](LibProcRecord& a, LibProcRecord& b) { |
| return a.usage().pss > b.usage().pss; |
| }; |
| break; |
| } |
| return proc_sort; |
| } |
| |
| struct params { |
| // Filtering options. |
| std::string lib_prefix; |
| bool all_libs; |
| const std::vector<std::string>& excluded_libs; |
| uint16_t mapflags_mask; |
| |
| // Print options. |
| Format format; |
| bool swap_enabled; |
| bool show_oomadj; |
| }; |
| |
| static bool populate_libs(struct params* params, uint64_t pgflags, uint64_t pgflags_mask, |
| const std::set<pid_t>& pids, |
| std::map<std::string, LibRecord>& lib_name_map, |
| std::map<pid_t, ProcessRecord>* processrecords_ptr, std::ostream& err) { |
| // Fall back to using an empty map of ProcessRecords if nullptr was passed in. |
| std::map<pid_t, ProcessRecord> processrecords; |
| if (!processrecords_ptr) { |
| processrecords_ptr = &processrecords; |
| } |
| for (pid_t pid : pids) { |
| // Check if a ProcessRecord already exists for this pid, create one if one does not exist. |
| auto iter = processrecords_ptr->find(pid); |
| ProcessRecord& proc = |
| (iter != processrecords_ptr->end()) |
| ? iter->second |
| : processrecords_ptr |
| ->emplace(pid, ProcessRecord(pid, false, pgflags, pgflags_mask, |
| true, params->show_oomadj, err)) |
| .first->second; |
| |
| if (!proc.valid()) { |
| err << "error: failed to create process record for: " << pid << "\n"; |
| return false; |
| } |
| |
| const std::vector<Vma>& maps = proc.Smaps(); |
| if (maps.size() == 0) { |
| continue; |
| } |
| |
| LibProcRecord record(proc); |
| for (const Vma& map : maps) { |
| // Skip library/map if the prefix for the path doesn't match. |
| if (!params->lib_prefix.empty() && |
| !::android::base::StartsWith(map.name, params->lib_prefix)) { |
| continue; |
| } |
| // Skip excluded library/map names. |
| if (!params->all_libs && |
| (std::find(params->excluded_libs.begin(), params->excluded_libs.end(), map.name) != |
| params->excluded_libs.end())) { |
| continue; |
| } |
| // Skip maps based on map permissions. |
| if (params->mapflags_mask && |
| ((map.flags & (PROT_READ | PROT_WRITE | PROT_EXEC)) != params->mapflags_mask)) { |
| continue; |
| } |
| |
| // Add memory for lib usage. |
| auto [it, inserted] = lib_name_map.emplace(map.name, LibRecord(map.name)); |
| it->second.AddUsage(record, map.usage); |
| |
| if (!params->swap_enabled && map.usage.swap) { |
| params->swap_enabled = true; |
| } |
| } |
| } |
| return true; |
| } |
| |
| static void print_header(struct params* params, std::ostream& out) { |
| switch (params->format) { |
| case Format::RAW: |
| // clang-format off |
| out << std::setw(7) << "RSStot" |
| << std::setw(10) << "VSS" |
| << std::setw(9) << "RSS" |
| << std::setw(9) << "PSS" |
| << std::setw(9) << "USS" |
| << " "; |
| //clang-format on |
| if (params->swap_enabled) { |
| out << std::setw(7) << "Swap" |
| << " "; |
| } |
| if (params->show_oomadj) { |
| out << std::setw(7) << "Oom" |
| << " "; |
| } |
| out << "Name/PID\n"; |
| break; |
| case Format::CSV: |
| out << "\"Library\",\"Total_RSS\",\"Process\",\"PID\",\"VSS\",\"RSS\",\"PSS\",\"USS\""; |
| if (params->swap_enabled) { |
| out << ",\"Swap\""; |
| } |
| if (params->show_oomadj) { |
| out << ",\"Oomadj\""; |
| } |
| out << "\n"; |
| break; |
| case Format::JSON: |
| default: |
| break; |
| } |
| } |
| |
| static void print_library(struct params* params, const LibRecord& lib, |
| std::ostream& out) { |
| if (params->format == Format::RAW) { |
| // clang-format off |
| out << std::setw(6) << lib.pss() << "K" |
| << std::setw(10) << "" |
| << std::setw(9) << "" |
| << std::setw(9) << "" |
| << std::setw(9) << "" |
| << " "; |
| // clang-format on |
| if (params->swap_enabled) { |
| out << std::setw(7) << "" |
| << " "; |
| } |
| if (params->show_oomadj) { |
| out << std::setw(7) << "" |
| << " "; |
| } |
| out << lib.name() << "\n"; |
| } |
| } |
| |
| static void print_proc_as_raw(struct params* params, const LibProcRecord& p, std::ostream& out) { |
| const MemUsage& usage = p.usage(); |
| // clang-format off |
| out << std::setw(7) << "" |
| << std::setw(9) << usage.vss << "K " |
| << std::setw(6) << usage.rss << "K " |
| << std::setw(6) << usage.pss << "K " |
| << std::setw(6) << usage.uss << "K "; |
| // clang-format on |
| if (params->swap_enabled) { |
| out << std::setw(6) << usage.swap << "K "; |
| } |
| if (params->show_oomadj) { |
| out << std::setw(7) << p.oomadj() << " "; |
| } |
| out << " " << p.cmdline() << " [" << p.pid() << "]\n"; |
| } |
| |
| static void print_proc_as_json(struct params* params, const LibRecord& l, const LibProcRecord& p, |
| std::ostream& out) { |
| const MemUsage& usage = p.usage(); |
| // clang-format off |
| out << "{\"Library\":" << EscapeJsonString(l.name()) |
| << ",\"Total_RSS\":" << l.pss() |
| << ",\"Process\":" << EscapeJsonString(p.cmdline()) |
| << ",\"PID\":\"" << p.pid() << "\"" |
| << ",\"VSS\":" << usage.vss |
| << ",\"RSS\":" << usage.rss |
| << ",\"PSS\":" << usage.pss |
| << ",\"USS\":" << usage.uss; |
| // clang-format on |
| if (params->swap_enabled) { |
| out << ",\"Swap\":" << usage.swap; |
| } |
| if (params->show_oomadj) { |
| out << ",\"Oom\":" << p.oomadj(); |
| } |
| out << "}\n"; |
| } |
| |
| static void print_proc_as_csv(struct params* params, const LibRecord& l, const LibProcRecord& p, |
| std::ostream& out) { |
| const MemUsage& usage = p.usage(); |
| // clang-format off |
| out << EscapeCsvString(l.name()) |
| << "," << l.pss() |
| << "," << EscapeCsvString(p.cmdline()) |
| << ",\"[" << p.pid() << "]\"" |
| << "," << usage.vss |
| << "," << usage.rss |
| << "," << usage.pss |
| << "," << usage.uss; |
| // clang-format on |
| if (params->swap_enabled) { |
| out << "," << usage.swap; |
| } |
| if (params->show_oomadj) { |
| out << "," << p.oomadj(); |
| } |
| out << "\n"; |
| } |
| |
| static void print_procs(struct params* params, const LibRecord& lib, |
| const std::vector<LibProcRecord>& procs, std::ostream& out) { |
| for (const LibProcRecord& p : procs) { |
| switch (params->format) { |
| case Format::RAW: |
| print_proc_as_raw(params, p, out); |
| break; |
| case Format::JSON: |
| print_proc_as_json(params, lib, p, out); |
| break; |
| case Format::CSV: |
| print_proc_as_csv(params, lib, p, out); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| } // namespace librank |
| |
| bool run_librank(uint64_t pgflags, uint64_t pgflags_mask, const std::set<pid_t>& pids, |
| const std::string& lib_prefix, bool all_libs, |
| const std::vector<std::string>& excluded_libs, uint16_t mapflags_mask, |
| Format format, SortOrder sort_order, bool reverse_sort, |
| std::map<pid_t, ProcessRecord>* processrecords_ptr, std::ostream& out, |
| std::ostream& err) { |
| struct librank::params params = { |
| .lib_prefix = lib_prefix, |
| .all_libs = all_libs, |
| .excluded_libs = excluded_libs, |
| .mapflags_mask = mapflags_mask, |
| .format = format, |
| .swap_enabled = false, |
| .show_oomadj = (sort_order == SortOrder::BY_OOMADJ), |
| }; |
| |
| // Fills in usage info for each LibRecord. |
| std::map<std::string, librank::LibRecord> lib_name_map; |
| if (!librank::populate_libs(¶ms, pgflags, pgflags_mask, pids, lib_name_map, |
| processrecords_ptr, err)) { |
| return false; |
| } |
| |
| librank::print_header(¶ms, out); |
| |
| // Create vector of all LibRecords, sorted by descending PSS. |
| std::vector<librank::LibRecord> libs; |
| libs.reserve(lib_name_map.size()); |
| for (const auto& [k, v] : lib_name_map) { |
| libs.push_back(v); |
| } |
| std::sort(libs.begin(), libs.end(), |
| [](const librank::LibRecord& l1, const librank::LibRecord& l2) { |
| return l1.pss() > l2.pss(); |
| }); |
| |
| std::function<bool(librank::LibProcRecord & a, librank::LibProcRecord & b)> libproc_sort = |
| librank::select_sort(sort_order); |
| for (librank::LibRecord& lib : libs) { |
| // Sort all processes for this library, default is PSS-descending. |
| std::vector<librank::LibProcRecord> procs; |
| procs.reserve(lib.processes().size()); |
| for (const auto& [k, v] : lib.processes()) { |
| procs.push_back(v); |
| } |
| if (reverse_sort) { |
| std::sort(procs.rbegin(), procs.rend(), libproc_sort); |
| } else { |
| std::sort(procs.begin(), procs.end(), libproc_sort); |
| } |
| |
| librank::print_library(¶ms, lib, out); |
| librank::print_procs(¶ms, lib, procs, out); |
| } |
| |
| return true; |
| } |
| |
| namespace showmap { |
| |
| // These are defined as static variables instead of a struct (as in procrank::params and |
| // librank::params) because the collect_vma callback references them. |
| static bool show_addr; |
| static bool verbose; |
| |
| static std::string get_vma_name(const Vma& vma, bool total, bool is_bss) { |
| if (total) { |
| return "TOTAL"; |
| } |
| std::string vma_name = vma.name; |
| if (is_bss) { |
| vma_name.append(" [bss]"); |
| } |
| return vma_name; |
| } |
| |
| static std::string get_flags(const Vma& vma, bool total) { |
| std::string flags_str("---"); |
| if (verbose && !total) { |
| if (vma.flags & PROT_READ) flags_str[0] = 'r'; |
| if (vma.flags & PROT_WRITE) flags_str[1] = 'w'; |
| if (vma.flags & PROT_EXEC) flags_str[2] = 'x'; |
| } |
| return flags_str; |
| } |
| |
| struct VmaInfo { |
| Vma vma; |
| bool is_bss; |
| uint32_t count; |
| |
| VmaInfo() = default; |
| VmaInfo(const Vma& v) : vma(v), is_bss(false), count(1) {} |
| VmaInfo(const Vma& v, bool bss) : vma(v), is_bss(bss), count(1) {} |
| VmaInfo(const Vma& v, const std::string& name, bool bss) : vma(v), is_bss(bss), count(1) { |
| vma.name = name; |
| } |
| |
| void to_raw(bool total, std::ostream& out) const; |
| void to_csv(bool total, std::ostream& out) const; |
| void to_json(bool total, std::ostream& out) const; |
| }; |
| |
| void VmaInfo::to_raw(bool total, std::ostream& out) const { |
| if (show_addr) { |
| if (total) { |
| out << " "; |
| } else { |
| out << std::hex << std::setw(16) << vma.start << " " << std::setw(16) << vma.end << " " |
| << std::dec; |
| } |
| } |
| // clang-format off |
| out << std::setw(8) << vma.usage.vss << " " |
| << std::setw(8) << vma.usage.rss << " " |
| << std::setw(8) << vma.usage.pss << " " |
| << std::setw(8) << vma.usage.shared_clean << " " |
| << std::setw(8) << vma.usage.shared_dirty << " " |
| << std::setw(8) << vma.usage.private_clean << " " |
| << std::setw(8) << vma.usage.private_dirty << " " |
| << std::setw(8) << vma.usage.swap << " " |
| << std::setw(8) << vma.usage.swap_pss << " " |
| << std::setw(9) << vma.usage.anon_huge_pages << " " |
| << std::setw(9) << vma.usage.shmem_pmd_mapped << " " |
| << std::setw(9) << vma.usage.file_pmd_mapped << " " |
| << std::setw(8) << vma.usage.shared_hugetlb << " " |
| << std::setw(8) << vma.usage.private_hugetlb << " " |
| << std::setw(8) << vma.usage.locked << " "; |
| // clang-format on |
| if (!verbose && !show_addr) { |
| out << std::setw(4) << count << " "; |
| } |
| if (verbose) { |
| if (total) { |
| out << " "; |
| } else { |
| out << std::setw(5) << get_flags(vma, total) << " "; |
| } |
| } |
| out << get_vma_name(vma, total, is_bss) << "\n"; |
| } |
| |
| void VmaInfo::to_csv(bool total, std::ostream& out) const { |
| // clang-format off |
| out << vma.usage.vss |
| << "," << vma.usage.rss |
| << "," << vma.usage.pss |
| << "," << vma.usage.shared_clean |
| << "," << vma.usage.shared_dirty |
| << "," << vma.usage.private_clean |
| << "," << vma.usage.private_dirty |
| << "," << vma.usage.swap |
| << "," << vma.usage.swap_pss |
| << "," << vma.usage.anon_huge_pages |
| << "," << vma.usage.shmem_pmd_mapped |
| << "," << vma.usage.file_pmd_mapped |
| << "," << vma.usage.shared_hugetlb |
| << "," << vma.usage.private_hugetlb |
| << "," << vma.usage.locked; |
| // clang-format on |
| if (show_addr) { |
| out << ","; |
| if (total) { |
| out << ","; |
| } else { |
| out << std::hex << vma.start << "," << vma.end << std::dec; |
| } |
| } |
| if (!verbose && !show_addr) { |
| out << "," << count; |
| } |
| if (verbose) { |
| out << ","; |
| if (!total) { |
| out << EscapeCsvString(get_flags(vma, total)); |
| } |
| } |
| out << "," << EscapeCsvString(get_vma_name(vma, total, is_bss)) << "\n"; |
| } |
| |
| void VmaInfo::to_json(bool total, std::ostream& out) const { |
| // clang-format off |
| out << "{\"virtual size\":" << vma.usage.vss |
| << ",\"RSS\":" << vma.usage.rss |
| << ",\"PSS\":" << vma.usage.pss |
| << ",\"shared clean\":" << vma.usage.shared_clean |
| << ",\"shared dirty\":" << vma.usage.shared_dirty |
| << ",\"private clean\":" << vma.usage.private_clean |
| << ",\"private dirty\":" << vma.usage.private_dirty |
| << ",\"swap\":" << vma.usage.swap |
| << ",\"swapPSS\":" << vma.usage.swap_pss |
| << ",\"Anon HugePages\":" << vma.usage.anon_huge_pages |
| << ",\"Shmem PmdMapped\":" << vma.usage.shmem_pmd_mapped |
| << ",\"File PmdMapped\":" << vma.usage.file_pmd_mapped |
| << ",\"Shared Hugetlb\":" << vma.usage.shared_hugetlb |
| << ",\"Private Hugetlb\":" << vma.usage.private_hugetlb |
| << ",\"Locked\":" << vma.usage.locked; |
| // clang-format on |
| if (show_addr) { |
| if (total) { |
| out << ",\"start addr\":\"\",\"end addr\":\"\""; |
| } else { |
| out << ",\"start addr\":\"" << std::hex << vma.start << "\",\"end addr\":\"" << vma.end |
| << "\"" << std::dec; |
| } |
| } |
| if (!verbose && !show_addr) { |
| out << ",\"#\":" << count; |
| } |
| if (verbose) { |
| out << ",\"flags\":" << EscapeJsonString(get_flags(vma, total)); |
| } |
| out << ",\"object\":" << EscapeJsonString(get_vma_name(vma, total, is_bss)) << "}"; |
| } |
| |
| static bool is_library(const std::string& name) { |
| return (name.size() > 4) && (name[0] == '/') && ::android::base::EndsWith(name, ".so"); |
| } |
| |
| static void infer_vma_name(VmaInfo& current, const VmaInfo& recent) { |
| if (current.vma.name.empty()) { |
| if (recent.vma.end == current.vma.start && is_library(recent.vma.name)) { |
| current.vma.name = recent.vma.name; |
| current.is_bss = true; |
| } else { |
| current.vma.name = "[anon]"; |
| } |
| } |
| } |
| |
| static void add_mem_usage(MemUsage* to, const MemUsage& from) { |
| to->vss += from.vss; |
| to->rss += from.rss; |
| to->pss += from.pss; |
| |
| to->swap += from.swap; |
| to->swap_pss += from.swap_pss; |
| |
| to->private_clean += from.private_clean; |
| to->private_dirty += from.private_dirty; |
| to->shared_clean += from.shared_clean; |
| to->shared_dirty += from.shared_dirty; |
| |
| to->anon_huge_pages += from.anon_huge_pages; |
| to->shmem_pmd_mapped += from.shmem_pmd_mapped; |
| to->file_pmd_mapped += from.file_pmd_mapped; |
| to->shared_hugetlb += from.shared_hugetlb; |
| to->private_hugetlb += from.private_hugetlb; |
| } |
| |
| // A multimap is used instead of a map to allow for duplicate keys in case verbose output is used. |
| static std::multimap<std::string, VmaInfo> vmas; |
| |
| static void collect_vma(const Vma& vma) { |
| static VmaInfo recent; |
| VmaInfo current(vma); |
| |
| std::string key; |
| if (show_addr) { |
| // vma.end is included in case vma.start is identical for two VMAs. |
| key = StringPrintf("%16" PRIx64 "%16" PRIx64, vma.start, vma.end); |
| } else { |
| key = vma.name; |
| } |
| |
| if (vmas.empty()) { |
| vmas.emplace(key, current); |
| recent = current; |
| return; |
| } |
| |
| infer_vma_name(current, recent); |
| recent = current; |
| |
| // If sorting by address, the VMA can be placed into the map as-is. |
| if (show_addr) { |
| vmas.emplace(key, current); |
| return; |
| } |
| |
| // infer_vma_name() may have changed current.vma.name, so this key needs to be set again before |
| // using it to sort by name. For verbose output, the VMA can immediately be placed into the map. |
| key = current.vma.name; |
| if (verbose) { |
| vmas.emplace(key, current); |
| return; |
| } |
| |
| // Coalesces VMAs' usage by name, if !show_addr && !verbose. |
| auto iter = vmas.find(key); |
| if (iter == vmas.end()) { |
| vmas.emplace(key, current); |
| return; |
| } |
| |
| VmaInfo& match = iter->second; |
| add_mem_usage(&match.vma.usage, current.vma.usage); |
| match.is_bss &= current.is_bss; |
| } |
| |
| static void print_text_header(std::ostream& out) { |
| if (show_addr) { |
| out << " start end "; |
| } |
| out << " virtual shared shared private private " |
| "Anon Shmem File Shared Private\n"; |
| if (show_addr) { |
| out << " addr addr "; |
| } |
| out << " size RSS PSS clean dirty clean dirty swap swapPSS " |
| "HugePages PmdMapped PmdMapped Hugetlb Hugetlb Locked "; |
| if (!verbose && !show_addr) { |
| out << " # "; |
| } |
| if (verbose) { |
| out << "flags "; |
| } |
| out << "object\n"; |
| } |
| |
| static void print_text_divider(std::ostream& out) { |
| if (show_addr) { |
| out << "---------------- ---------------- "; |
| } |
| out << "-------- -------- -------- -------- -------- -------- -------- -------- -------- " |
| "--------- --------- --------- -------- -------- -------- "; |
| if (!verbose && !show_addr) { |
| out << "---- "; |
| } |
| if (verbose) { |
| out << "----- "; |
| } |
| out << "------------------------------\n"; |
| } |
| |
| static void print_csv_header(std::ostream& out) { |
| out << "\"virtual size\",\"RSS\",\"PSS\",\"shared clean\",\"shared dirty\",\"private clean\"," |
| "\"private dirty\",\"swap\",\"swapPSS\",\"Anon HugePages\",\"Shmem PmdMapped\"," |
| "\"File PmdMapped\",\"Shared Hugetlb\",\"Private Hugetlb\",\"Locked\""; |
| if (show_addr) { |
| out << ",\"start addr\",\"end addr\""; |
| } |
| if (!verbose && !show_addr) { |
| out << ",\"#\""; |
| } |
| if (verbose) { |
| out << ",\"flags\""; |
| } |
| out << ",\"object\"\n"; |
| } |
| |
| static void print_header(Format format, std::ostream& out) { |
| switch (format) { |
| case Format::RAW: |
| print_text_header(out); |
| print_text_divider(out); |
| break; |
| case Format::CSV: |
| print_csv_header(out); |
| break; |
| case Format::JSON: |
| out << "["; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void print_vmainfo(const VmaInfo& v, Format format, std::ostream& out) { |
| switch (format) { |
| case Format::RAW: |
| v.to_raw(false, out); |
| break; |
| case Format::CSV: |
| v.to_csv(false, out); |
| break; |
| case Format::JSON: |
| v.to_json(false, out); |
| out << ","; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void print_vmainfo_totals(const VmaInfo& total_usage, Format format, std::ostream& out) { |
| switch (format) { |
| case Format::RAW: |
| print_text_divider(out); |
| print_text_header(out); |
| print_text_divider(out); |
| total_usage.to_raw(true, out); |
| break; |
| case Format::CSV: |
| total_usage.to_csv(true, out); |
| break; |
| case Format::JSON: |
| total_usage.to_json(true, out); |
| out << "]\n"; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| } // namespace showmap |
| |
| bool run_showmap(pid_t pid, const std::string& filename, bool terse, bool verbose, bool show_addr, |
| bool quiet, Format format, std::map<pid_t, ProcessRecord>* processrecords_ptr, |
| std::ostream& out, std::ostream& err) { |
| // Accumulated vmas are cleared to account for sequential showmap calls by bugreport_procdump. |
| showmap::vmas.clear(); |
| |
| showmap::show_addr = show_addr; |
| showmap::verbose = verbose; |
| |
| bool success; |
| if (!filename.empty()) { |
| success = ::android::meminfo::ForEachVmaFromFile(filename, showmap::collect_vma); |
| } else if (!processrecords_ptr) { |
| ProcessRecord proc(pid, false, 0, 0, false, false, err); |
| success = proc.ForEachExistingVma(showmap::collect_vma); |
| } else { |
| // Check if a ProcessRecord already exists for this pid, create one if one does not exist. |
| auto iter = processrecords_ptr->find(pid); |
| ProcessRecord& proc = |
| (iter != processrecords_ptr->end()) |
| ? iter->second |
| : processrecords_ptr |
| ->emplace(pid, ProcessRecord(pid, false, 0, 0, false, false, err)) |
| .first->second; |
| success = proc.ForEachExistingVma(showmap::collect_vma); |
| } |
| |
| if (!success) { |
| if (!quiet) { |
| if (!filename.empty()) { |
| err << "Failed to parse file " << filename << "\n"; |
| } else { |
| err << "No maps for pid " << pid << "\n"; |
| } |
| } |
| return false; |
| } |
| |
| showmap::print_header(format, out); |
| |
| showmap::VmaInfo total_usage; |
| for (const auto& entry : showmap::vmas) { |
| const showmap::VmaInfo& v = entry.second; |
| showmap::add_mem_usage(&total_usage.vma.usage, v.vma.usage); |
| total_usage.count += v.count; |
| if (terse && !(v.vma.usage.private_dirty || v.vma.usage.private_clean)) { |
| continue; |
| } |
| showmap::print_vmainfo(v, format, out); |
| } |
| showmap::print_vmainfo_totals(total_usage, format, out); |
| |
| return true; |
| } |
| |
| namespace bugreport_procdump { |
| |
| static void create_processrecords(const std::set<pid_t>& pids, |
| std::map<pid_t, ProcessRecord>& processrecords, |
| std::ostream& err) { |
| for (pid_t pid : pids) { |
| ProcessRecord proc(pid, false, 0, 0, true, false, err); |
| if (!proc.valid()) { |
| err << "Could not create a ProcessRecord for pid " << pid << "\n"; |
| continue; |
| } |
| processrecords.emplace(pid, std::move(proc)); |
| } |
| } |
| |
| static void print_section_start(const std::string& name, std::ostream& out) { |
| out << "------ " << name << " ------\n"; |
| } |
| |
| static void print_section_end(const std::string& name, |
| const std::chrono::time_point<std::chrono::steady_clock>& start, |
| std::ostream& out) { |
| // std::ratio<1> represents the period for one second. |
| using floatsecs = std::chrono::duration<float, std::ratio<1>>; |
| auto end = std::chrono::steady_clock::now(); |
| std::streamsize precision = out.precision(); |
| out << "------ " << std::setprecision(3) << std::fixed << floatsecs(end - start).count() |
| << " was the duration of '" << name << "' ------\n"; |
| out << std::setprecision(precision) << std::defaultfloat; |
| } |
| |
| static void call_smaps_of_all_processes(const std::string& filename, bool terse, bool verbose, |
| bool show_addr, bool quiet, Format format, |
| std::map<pid_t, ProcessRecord>& processrecords, |
| std::ostream& out, std::ostream& err) { |
| for (const auto& [pid, record] : processrecords) { |
| std::string showmap_title = StringPrintf("SHOW MAP %d: %s", pid, record.cmdline().c_str()); |
| |
| auto showmap_start = std::chrono::steady_clock::now(); |
| print_section_start(showmap_title, out); |
| run_showmap(pid, filename, terse, verbose, show_addr, quiet, format, &processrecords, out, |
| err); |
| print_section_end(showmap_title, showmap_start, out); |
| } |
| } |
| |
| static void call_librank(const std::set<pid_t>& pids, |
| std::map<pid_t, ProcessRecord>& processrecords, std::ostream& out, |
| std::ostream& err) { |
| auto librank_start = std::chrono::steady_clock::now(); |
| print_section_start("LIBRANK", out); |
| run_librank(0, 0, pids, "", false, {"[heap]", "[stack]"}, 0, Format::RAW, SortOrder::BY_PSS, |
| false, &processrecords, out, err); |
| print_section_end("LIBRANK", librank_start, out); |
| } |
| |
| static void call_procrank(const std::set<pid_t>& pids, |
| std::map<pid_t, ProcessRecord>& processrecords, std::ostream& out, |
| std::ostream& err) { |
| auto procrank_start = std::chrono::steady_clock::now(); |
| print_section_start("PROCRANK", out); |
| run_procrank(0, 0, pids, false, false, SortOrder::BY_PSS, false, &processrecords, out, err); |
| print_section_end("PROCRANK", procrank_start, out); |
| } |
| |
| } // namespace bugreport_procdump |
| |
| bool run_bugreport_procdump(std::ostream& out, std::ostream& err) { |
| std::set<pid_t> pids; |
| if (!::android::smapinfo::get_all_pids(&pids)) { |
| err << "Failed to get all pids.\n"; |
| return false; |
| } |
| |
| // create_processrecords is the only expensive call in this function, as showmap, librank, and |
| // procrank will only print already-collected information. This duration is captured by |
| // dumpstate in the BUGREPORT PROCDUMP section. |
| std::map<pid_t, ProcessRecord> processrecords; |
| bugreport_procdump::create_processrecords(pids, processrecords, err); |
| |
| // pids without associated ProcessRecords are removed so that librank/procrank do not fall back |
| // to creating new ProcessRecords for them. |
| for (pid_t pid : pids) { |
| if (processrecords.find(pid) == processrecords.end()) { |
| pids.erase(pid); |
| } |
| } |
| |
| auto all_smaps_start = std::chrono::steady_clock::now(); |
| bugreport_procdump::print_section_start("SMAPS OF ALL PROCESSES", out); |
| bugreport_procdump::call_smaps_of_all_processes("", false, false, false, true, Format::RAW, |
| processrecords, out, err); |
| bugreport_procdump::print_section_end("SMAPS OF ALL PROCESSES", all_smaps_start, out); |
| |
| bugreport_procdump::call_librank(pids, processrecords, out, err); |
| bugreport_procdump::call_procrank(pids, processrecords, out, err); |
| |
| return true; |
| } |
| |
| } // namespace smapinfo |
| } // namespace android |