Colin Cross | 646d001 | 2015-09-03 17:56:39 -0700 | [diff] [blame] | 1 | // Copyright (C) 2015 The Android Open Source Project |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | #include <getopt.h> |
| 16 | #include <inttypes.h> |
| 17 | #include <stdint.h> |
| 18 | #include <stdlib.h> |
| 19 | |
| 20 | #include <algorithm> |
| 21 | #include <map> |
| 22 | #include <unordered_map> |
| 23 | #include <vector> |
| 24 | |
Elliott Hughes | 66dd09e | 2015-12-04 14:00:57 -0800 | [diff] [blame] | 25 | #include <android-base/logging.h> |
Colin Cross | 646d001 | 2015-09-03 17:56:39 -0700 | [diff] [blame] | 26 | |
| 27 | #include "tasklist.h" |
| 28 | #include "taskstats.h" |
| 29 | |
| 30 | constexpr uint64_t NSEC_PER_SEC = 1000000000; |
| 31 | |
| 32 | static uint64_t BytesToKB(uint64_t bytes) { |
| 33 | return (bytes + 1024-1) / 1024; |
| 34 | } |
| 35 | |
| 36 | static float TimeToTgidPercent(uint64_t ns, int time, const TaskStatistics& stats) { |
| 37 | float percent = ns / stats.threads() / (time * NSEC_PER_SEC / 100.0f); |
| 38 | return std::min(percent, 99.99f); |
| 39 | } |
| 40 | |
| 41 | static void usage(char* myname) { |
| 42 | printf( |
| 43 | "Usage: %s [-h] [-P] [-d <delay>] [-n <cycles>] [-s <column>]\n" |
Colin Cross | a1bc8d7 | 2015-09-08 15:04:22 -0700 | [diff] [blame] | 44 | " -a Show byte count instead of rate\n" |
Colin Cross | 646d001 | 2015-09-03 17:56:39 -0700 | [diff] [blame] | 45 | " -d Set the delay between refreshes in seconds.\n" |
| 46 | " -h Display this help screen.\n" |
| 47 | " -m Set the number of processes or threads to show\n" |
| 48 | " -n Set the number of refreshes before exiting.\n" |
| 49 | " -P Show processes instead of the default threads.\n" |
| 50 | " -s Set the column to sort by:\n" |
| 51 | " pid, read, write, total, io, swap, sched, mem or delay.\n", |
| 52 | myname); |
| 53 | } |
| 54 | |
| 55 | using Sorter = std::function<void(std::vector<TaskStatistics>&)>; |
Chih-Hung Hsieh | 9065380 | 2016-08-02 11:19:23 -0700 | [diff] [blame] | 56 | static Sorter GetSorter(const std::string& field) { |
Colin Cross | 646d001 | 2015-09-03 17:56:39 -0700 | [diff] [blame] | 57 | // Generic comparator |
| 58 | static auto comparator = [](auto& lhs, auto& rhs, auto field, bool ascending) -> bool { |
| 59 | auto a = (lhs.*field)(); |
| 60 | auto b = (rhs.*field)(); |
| 61 | if (a != b) { |
| 62 | // Sort by selected field |
| 63 | return ascending ^ (a < b); |
| 64 | } else { |
| 65 | // And then fall back to sorting by pid |
| 66 | return lhs.pid() < rhs.pid(); |
| 67 | } |
| 68 | }; |
| 69 | |
| 70 | auto make_sorter = [](auto field, bool ascending) { |
| 71 | // Make closure for comparator on a specific field |
| 72 | using namespace std::placeholders; |
| 73 | auto bound_comparator = std::bind(comparator, _1, _2, field, ascending); |
| 74 | |
| 75 | // Return closure to std::sort with specialized comparator |
| 76 | return [bound_comparator](auto& vector) { |
| 77 | return std::sort(vector.begin(), vector.end(), bound_comparator); |
| 78 | }; |
| 79 | }; |
| 80 | |
| 81 | static const std::map<std::string, Sorter> sorters{ |
| 82 | {"pid", make_sorter(&TaskStatistics::pid, false)}, |
| 83 | {"read", make_sorter(&TaskStatistics::read, true)}, |
| 84 | {"write", make_sorter(&TaskStatistics::write, true)}, |
| 85 | {"total", make_sorter(&TaskStatistics::read_write, true)}, |
| 86 | {"io", make_sorter(&TaskStatistics::delay_io, true)}, |
| 87 | {"swap", make_sorter(&TaskStatistics::delay_swap, true)}, |
| 88 | {"sched", make_sorter(&TaskStatistics::delay_sched, true)}, |
| 89 | {"mem", make_sorter(&TaskStatistics::delay_mem, true)}, |
| 90 | {"delay", make_sorter(&TaskStatistics::delay_total, true)}, |
| 91 | }; |
| 92 | |
| 93 | auto it = sorters.find(field); |
| 94 | if (it == sorters.end()) { |
| 95 | return nullptr; |
| 96 | } |
| 97 | return it->second; |
| 98 | } |
| 99 | |
| 100 | int main(int argc, char* argv[]) { |
Colin Cross | a1bc8d7 | 2015-09-08 15:04:22 -0700 | [diff] [blame] | 101 | bool accumulated = false; |
Colin Cross | 646d001 | 2015-09-03 17:56:39 -0700 | [diff] [blame] | 102 | bool processes = false; |
| 103 | int delay = 1; |
| 104 | int cycles = -1; |
| 105 | int limit = -1; |
| 106 | Sorter sorter = GetSorter("total"); |
| 107 | |
| 108 | android::base::InitLogging(argv, android::base::StderrLogger); |
| 109 | |
| 110 | while (1) { |
| 111 | int c; |
| 112 | static const option longopts[] = { |
Colin Cross | a1bc8d7 | 2015-09-08 15:04:22 -0700 | [diff] [blame] | 113 | {"accumulated", 0, 0, 'a'}, |
Colin Cross | 646d001 | 2015-09-03 17:56:39 -0700 | [diff] [blame] | 114 | {"delay", required_argument, 0, 'd'}, |
| 115 | {"help", 0, 0, 'h'}, |
| 116 | {"limit", required_argument, 0, 'm'}, |
| 117 | {"iter", required_argument, 0, 'n'}, |
| 118 | {"sort", required_argument, 0, 's'}, |
| 119 | {"processes", 0, 0, 'P'}, |
| 120 | {0, 0, 0, 0}, |
| 121 | }; |
Colin Cross | a1bc8d7 | 2015-09-08 15:04:22 -0700 | [diff] [blame] | 122 | c = getopt_long(argc, argv, "ad:hm:n:Ps:", longopts, NULL); |
Colin Cross | 646d001 | 2015-09-03 17:56:39 -0700 | [diff] [blame] | 123 | if (c < 0) { |
| 124 | break; |
| 125 | } |
| 126 | switch (c) { |
Colin Cross | a1bc8d7 | 2015-09-08 15:04:22 -0700 | [diff] [blame] | 127 | case 'a': |
| 128 | accumulated = true; |
| 129 | break; |
Colin Cross | 646d001 | 2015-09-03 17:56:39 -0700 | [diff] [blame] | 130 | case 'd': |
| 131 | delay = atoi(optarg); |
| 132 | break; |
| 133 | case 'h': |
| 134 | usage(argv[0]); |
| 135 | return(EXIT_SUCCESS); |
| 136 | case 'm': |
| 137 | limit = atoi(optarg); |
| 138 | break; |
| 139 | case 'n': |
| 140 | cycles = atoi(optarg); |
| 141 | break; |
| 142 | case 's': { |
| 143 | sorter = GetSorter(optarg); |
| 144 | if (sorter == nullptr) { |
| 145 | LOG(ERROR) << "Invalid sort column \"" << optarg << "\""; |
| 146 | usage(argv[0]); |
| 147 | return(EXIT_FAILURE); |
| 148 | } |
| 149 | break; |
| 150 | } |
| 151 | case 'P': |
| 152 | processes = true; |
| 153 | break; |
| 154 | case '?': |
| 155 | usage(argv[0]); |
| 156 | return(EXIT_FAILURE); |
| 157 | default: |
| 158 | abort(); |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | std::map<pid_t, std::vector<pid_t>> tgid_map; |
| 163 | |
| 164 | TaskstatsSocket taskstats_socket; |
| 165 | taskstats_socket.Open(); |
| 166 | |
| 167 | std::unordered_map<pid_t, TaskStatistics> pid_stats; |
| 168 | std::unordered_map<pid_t, TaskStatistics> tgid_stats; |
| 169 | std::vector<TaskStatistics> stats; |
| 170 | |
| 171 | bool first = true; |
| 172 | bool second = true; |
| 173 | |
| 174 | while (true) { |
| 175 | stats.clear(); |
| 176 | if (!TaskList::Scan(tgid_map)) { |
| 177 | LOG(FATAL) << "failed to scan tasks"; |
| 178 | } |
| 179 | for (auto& tgid_it : tgid_map) { |
| 180 | pid_t tgid = tgid_it.first; |
| 181 | std::vector<pid_t>& pid_list = tgid_it.second; |
| 182 | |
| 183 | TaskStatistics tgid_stats_new; |
| 184 | TaskStatistics tgid_stats_delta; |
| 185 | |
| 186 | if (processes) { |
| 187 | // If printing processes, collect stats for the tgid which will |
| 188 | // hold delay accounting data across all threads, including |
| 189 | // ones that have exited. |
| 190 | if (!taskstats_socket.GetTgidStats(tgid, tgid_stats_new)) { |
| 191 | continue; |
| 192 | } |
| 193 | tgid_stats_delta = tgid_stats[tgid].Update(tgid_stats_new); |
| 194 | } |
| 195 | |
| 196 | // Collect per-thread stats |
| 197 | for (pid_t pid : pid_list) { |
| 198 | TaskStatistics pid_stats_new; |
| 199 | if (!taskstats_socket.GetPidStats(pid, pid_stats_new)) { |
| 200 | continue; |
| 201 | } |
| 202 | |
| 203 | TaskStatistics pid_stats_delta = pid_stats[pid].Update(pid_stats_new); |
| 204 | |
| 205 | if (processes) { |
| 206 | tgid_stats_delta.AddPidToTgid(pid_stats_delta); |
| 207 | } else { |
| 208 | stats.push_back(pid_stats_delta); |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | if (processes) { |
| 213 | stats.push_back(tgid_stats_delta); |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | if (!first) { |
| 218 | sorter(stats); |
| 219 | if (!second) { |
| 220 | printf("\n"); |
| 221 | } |
Colin Cross | a1bc8d7 | 2015-09-08 15:04:22 -0700 | [diff] [blame] | 222 | if (accumulated) { |
| 223 | printf("%6s %-16s %20s %34s\n", "", "", |
| 224 | "---- IO (KiB) ----", "----------- delayed on ----------"); |
| 225 | } else { |
| 226 | printf("%6s %-16s %20s %34s\n", "", "", |
| 227 | "--- IO (KiB/s) ---", "----------- delayed on ----------"); |
| 228 | } |
Colin Cross | 646d001 | 2015-09-03 17:56:39 -0700 | [diff] [blame] | 229 | printf("%6s %-16s %6s %6s %6s %-5s %-5s %-5s %-5s %-5s\n", |
| 230 | "PID", |
| 231 | "Command", |
| 232 | "read", |
| 233 | "write", |
| 234 | "total", |
| 235 | "IO", |
| 236 | "swap", |
| 237 | "sched", |
| 238 | "mem", |
| 239 | "total"); |
| 240 | int n = limit; |
Colin Cross | c5cacc1 | 2015-09-08 17:09:22 -0700 | [diff] [blame] | 241 | const int delay_div = accumulated ? 1 : delay; |
| 242 | uint64_t total_read = 0; |
| 243 | uint64_t total_write = 0; |
| 244 | uint64_t total_read_write = 0; |
Colin Cross | 646d001 | 2015-09-03 17:56:39 -0700 | [diff] [blame] | 245 | for (const TaskStatistics& statistics : stats) { |
Colin Cross | c5cacc1 | 2015-09-08 17:09:22 -0700 | [diff] [blame] | 246 | total_read += statistics.read(); |
| 247 | total_write += statistics.write(); |
| 248 | total_read_write += statistics.read_write(); |
| 249 | |
Colin Cross | 42a0753 | 2015-09-16 16:31:31 -0700 | [diff] [blame] | 250 | if (n == 0) { |
| 251 | continue; |
| 252 | } else if (n > 0) { |
Colin Cross | c5cacc1 | 2015-09-08 17:09:22 -0700 | [diff] [blame] | 253 | n--; |
Colin Cross | c5cacc1 | 2015-09-08 17:09:22 -0700 | [diff] [blame] | 254 | } |
Colin Cross | 42a0753 | 2015-09-16 16:31:31 -0700 | [diff] [blame] | 255 | |
| 256 | printf("%6d %-16s %6" PRIu64 " %6" PRIu64 " %6" PRIu64 " %5.2f%% %5.2f%% %5.2f%% %5.2f%% %5.2f%%\n", |
| 257 | statistics.pid(), |
| 258 | statistics.comm().c_str(), |
| 259 | BytesToKB(statistics.read()) / delay_div, |
| 260 | BytesToKB(statistics.write()) / delay_div, |
| 261 | BytesToKB(statistics.read_write()) / delay_div, |
| 262 | TimeToTgidPercent(statistics.delay_io(), delay, statistics), |
| 263 | TimeToTgidPercent(statistics.delay_swap(), delay, statistics), |
| 264 | TimeToTgidPercent(statistics.delay_sched(), delay, statistics), |
| 265 | TimeToTgidPercent(statistics.delay_mem(), delay, statistics), |
| 266 | TimeToTgidPercent(statistics.delay_total(), delay, statistics)); |
Colin Cross | 646d001 | 2015-09-03 17:56:39 -0700 | [diff] [blame] | 267 | } |
Colin Cross | c5cacc1 | 2015-09-08 17:09:22 -0700 | [diff] [blame] | 268 | printf("%6s %-16s %6" PRIu64 " %6" PRIu64 " %6" PRIu64 "\n", "", "TOTAL", |
| 269 | BytesToKB(total_read) / delay_div, |
| 270 | BytesToKB(total_write) / delay_div, |
| 271 | BytesToKB(total_read_write) / delay_div); |
| 272 | |
Colin Cross | 646d001 | 2015-09-03 17:56:39 -0700 | [diff] [blame] | 273 | second = false; |
| 274 | |
| 275 | if (cycles > 0 && --cycles == 0) break; |
| 276 | } |
| 277 | first = false; |
| 278 | sleep(delay); |
| 279 | } |
| 280 | |
| 281 | return 0; |
| 282 | } |