Sandeep Patil | 061b713 | 2019-01-19 21:11:01 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2019 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 <getopt.h> |
| 18 | #include <inttypes.h> |
| 19 | #include <stdio.h> |
| 20 | #include <stdlib.h> |
| 21 | #include <sys/types.h> |
| 22 | #include <unistd.h> |
| 23 | |
| 24 | #include <algorithm> |
| 25 | #include <memory> |
| 26 | #include <string> |
| 27 | #include <vector> |
| 28 | |
| 29 | #include <meminfo/pageacct.h> |
| 30 | #include <meminfo/procmeminfo.h> |
| 31 | |
| 32 | using ::android::meminfo::ProcMemInfo; |
| 33 | using ::android::meminfo::Vma; |
| 34 | |
| 35 | // Global options |
| 36 | static int32_t g_delay = 0; |
| 37 | static int32_t g_total = 2; |
| 38 | static pid_t g_pid = -1; |
| 39 | |
| 40 | [[noreturn]] static void usage(int exit_status) { |
| 41 | fprintf(stderr, |
| 42 | "%s [-d DELAY_BETWEEN_EACH_SAMPLE] [-n REFRESH_TOTAL] PID\n" |
| 43 | "-d\tdelay between each working set sample (default 0)\n" |
| 44 | "-n\ttotal number of refreshes before we exit (default 2)\n", |
| 45 | getprogname()); |
| 46 | |
| 47 | exit(exit_status); |
| 48 | } |
| 49 | |
| 50 | static void print_header() { |
| 51 | const char* addr1 = " start end "; |
| 52 | const char* addr2 = " addr addr "; |
| 53 | |
| 54 | printf("%s virtual shared shared private private\n", addr1); |
| 55 | printf("%s size RSS PSS clean dirty clean dirty swap " |
| 56 | "swapPSS", |
| 57 | addr2); |
| 58 | printf(" object\n"); |
| 59 | } |
| 60 | |
| 61 | static void print_divider() { |
| 62 | printf("---------------- ---------------- "); |
| 63 | printf("--------- --------- --------- --------- --------- --------- --------- --------- " |
| 64 | "--------- "); |
| 65 | printf("------------------------------\n"); |
| 66 | } |
| 67 | |
| 68 | static void print_vma(const Vma& v) { |
| 69 | printf("%16" PRIx64 " %16" PRIx64 " ", v.start, v.end); |
| 70 | printf("%8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 |
| 71 | "K %8" PRIu64 "K %8" PRIu64 "K %8" PRIu64 "K ", |
| 72 | v.usage.vss / 1024, v.usage.rss / 1024, v.usage.pss / 1024, v.usage.shared_clean / 1024, |
| 73 | v.usage.shared_dirty / 1024, v.usage.private_clean / 1024, v.usage.private_dirty / 1024, |
| 74 | v.usage.swap / 1024, v.usage.swap_pss / 1024); |
| 75 | printf("%s\n", v.name.c_str()); |
| 76 | } |
| 77 | |
| 78 | static bool same_vma(const Vma& cur, const Vma& last) { |
| 79 | return (cur.start == last.start && cur.end == last.end && cur.name == last.name && |
| 80 | cur.flags == last.flags && cur.offset == last.offset); |
| 81 | } |
| 82 | |
| 83 | static Vma diff_vma_params(const Vma& cur, const Vma& last) { |
| 84 | Vma res; |
| 85 | res.usage.shared_clean = cur.usage.shared_clean > last.usage.shared_clean |
| 86 | ? cur.usage.shared_clean - last.usage.shared_clean |
| 87 | : 0; |
| 88 | res.usage.shared_dirty = cur.usage.shared_dirty > last.usage.shared_dirty |
| 89 | ? cur.usage.shared_dirty - last.usage.shared_dirty |
| 90 | : 0; |
| 91 | res.usage.private_clean = cur.usage.private_clean > last.usage.private_clean |
| 92 | ? cur.usage.private_clean - last.usage.private_clean |
| 93 | : 0; |
| 94 | res.usage.private_dirty = cur.usage.private_dirty > last.usage.private_dirty |
| 95 | ? cur.usage.private_dirty - last.usage.private_dirty |
| 96 | : 0; |
| 97 | |
| 98 | res.usage.rss = cur.usage.rss > last.usage.rss ? cur.usage.rss - last.usage.rss : 0; |
| 99 | res.usage.pss = cur.usage.pss > last.usage.pss ? cur.usage.pss - last.usage.pss : 0; |
| 100 | res.usage.uss = cur.usage.uss > last.usage.uss ? cur.usage.uss - last.usage.uss : 0; |
| 101 | res.usage.swap = cur.usage.swap > last.usage.swap ? cur.usage.swap - last.usage.swap : 0; |
| 102 | res.usage.swap_pss = |
| 103 | cur.usage.swap_pss > last.usage.swap_pss ? cur.usage.swap_pss - last.usage.swap_pss : 0; |
| 104 | |
| 105 | // set vma properties to the same as the current one. |
| 106 | res.start = cur.start; |
| 107 | res.end = cur.end; |
| 108 | res.offset = cur.offset; |
| 109 | res.flags = cur.flags; |
| 110 | res.name = cur.name; |
| 111 | return res; |
| 112 | } |
| 113 | |
| 114 | static void diff_workingset(std::vector<Vma>& wss, std::vector<Vma>& old, std::vector<Vma>* res) { |
| 115 | res->clear(); |
| 116 | auto vma_sorter = [](const Vma& a, const Vma& b) { return a.start < b.start; }; |
| 117 | std::sort(wss.begin(), wss.end(), vma_sorter); |
| 118 | std::sort(old.begin(), old.end(), vma_sorter); |
| 119 | if (old.empty()) { |
| 120 | *res = wss; |
| 121 | return; |
| 122 | } |
| 123 | |
| 124 | for (auto& i : wss) { |
| 125 | bool found_same_vma = false; |
| 126 | // TODO: This is highly inefficient, fix it if it takes |
| 127 | // too long. Worst case will be system_server |
| 128 | for (auto& j : old) { |
| 129 | if (same_vma(i, j)) { |
| 130 | res->emplace_back(diff_vma_params(i, j)); |
| 131 | found_same_vma = true; |
| 132 | break; |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | if (!found_same_vma) { |
| 137 | res->emplace_back(i); |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | std::sort(res->begin(), res->end(), vma_sorter); |
| 142 | return; |
| 143 | } |
| 144 | |
| 145 | static int workingset() { |
| 146 | std::vector<Vma> last_wss = {}; |
| 147 | std::vector<Vma> diff_wss = {}; |
| 148 | uint32_t nr_refresh = 0; |
| 149 | |
| 150 | while (true) { |
| 151 | std::unique_ptr<ProcMemInfo> proc_mem = std::make_unique<ProcMemInfo>(g_pid, true); |
| 152 | std::vector<Vma> wss = proc_mem->MapsWithPageIdle(); |
| 153 | |
| 154 | diff_workingset(wss, last_wss, &diff_wss); |
| 155 | diff_wss.erase(std::remove_if(diff_wss.begin(), diff_wss.end(), |
| 156 | [](const auto& v) { return v.usage.rss == 0; }), |
| 157 | diff_wss.end()); |
| 158 | if ((nr_refresh % 5) == 0) { |
| 159 | print_header(); |
| 160 | print_divider(); |
| 161 | } |
| 162 | |
| 163 | for (const auto& v : diff_wss) { |
| 164 | print_vma(v); |
| 165 | } |
| 166 | |
| 167 | nr_refresh++; |
| 168 | if (nr_refresh == g_total) { |
| 169 | break; |
| 170 | } |
| 171 | |
| 172 | last_wss = wss; |
| 173 | sleep(g_delay); |
| 174 | print_divider(); |
| 175 | } |
| 176 | |
| 177 | return 0; |
| 178 | } |
| 179 | |
| 180 | int main(int argc, char* argv[]) { |
| 181 | struct option longopts[] = { |
| 182 | {"help", no_argument, nullptr, 'h'}, |
| 183 | {0, 0, nullptr, 0}, |
| 184 | }; |
| 185 | |
| 186 | int opt; |
| 187 | while ((opt = getopt_long(argc, argv, "d:n:h", longopts, nullptr)) != -1) { |
| 188 | switch (opt) { |
| 189 | case 'd': |
| 190 | g_delay = atoi(optarg); |
| 191 | break; |
| 192 | case 'n': |
| 193 | g_total = atoi(optarg); |
| 194 | break; |
| 195 | case 'h': |
| 196 | usage(EXIT_SUCCESS); |
| 197 | default: |
| 198 | usage(EXIT_FAILURE); |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | if ((argc - 1) < optind) { |
| 203 | fprintf(stderr, "Invalid arguments: Must provide <pid> at the end\n"); |
| 204 | usage(EXIT_FAILURE); |
| 205 | } |
| 206 | |
| 207 | g_pid = atoi(argv[optind]); |
| 208 | if (g_pid <= 0) { |
| 209 | fprintf(stderr, "Invalid process id %s\n", argv[optind]); |
| 210 | usage(EXIT_FAILURE); |
| 211 | } |
| 212 | |
| 213 | if (!::android::meminfo::PageAcct::KernelHasPageIdle()) { |
| 214 | fprintf(stderr, "Missing support for Idle page tracking in the kernel\n"); |
| 215 | return 0; |
| 216 | } |
| 217 | |
| 218 | return workingset(); |
| 219 | } |