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
25#include <android-base/logging.h>
26
27#include "tasklist.h"
28#include "taskstats.h"
29
30constexpr uint64_t NSEC_PER_SEC = 1000000000;
31
32static uint64_t BytesToKB(uint64_t bytes) {
33  return (bytes + 1024-1) / 1024;
34}
35
36static 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
41static void usage(char* myname) {
42  printf(
43      "Usage: %s [-h] [-P] [-d <delay>] [-n <cycles>] [-s <column>]\n"
44      "   -a  Show byte count instead of rate\n"
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
55using Sorter = std::function<void(std::vector<TaskStatistics>&)>;
56static Sorter GetSorter(const std::string& field) {
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
100int main(int argc, char* argv[]) {
101  bool accumulated = false;
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[] = {
113        {"accumulated", 0, 0, 'a'},
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    };
122    c = getopt_long(argc, argv, "ad:hm:n:Ps:", longopts, NULL);
123    if (c < 0) {
124      break;
125    }
126    switch (c) {
127    case 'a':
128      accumulated = true;
129      break;
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  if (!taskstats_socket.Open()) {
166    return EXIT_FAILURE;
167  }
168
169  std::unordered_map<pid_t, TaskStatistics> pid_stats;
170  std::unordered_map<pid_t, TaskStatistics> tgid_stats;
171  std::vector<TaskStatistics> stats;
172
173  bool first = true;
174  bool second = true;
175
176  while (true) {
177    stats.clear();
178    if (!TaskList::Scan(tgid_map)) {
179      LOG(ERROR) << "failed to scan tasks";
180      return EXIT_FAILURE;
181    }
182    for (auto& tgid_it : tgid_map) {
183      pid_t tgid = tgid_it.first;
184      std::vector<pid_t>& pid_list = tgid_it.second;
185
186      TaskStatistics tgid_stats_new;
187      TaskStatistics tgid_stats_delta;
188
189      if (processes) {
190        // If printing processes, collect stats for the tgid which will
191        // hold delay accounting data across all threads, including
192        // ones that have exited.
193        if (!taskstats_socket.GetTgidStats(tgid, tgid_stats_new)) {
194          continue;
195        }
196        tgid_stats_delta = tgid_stats[tgid].Update(tgid_stats_new);
197      }
198
199      // Collect per-thread stats
200      for (pid_t pid : pid_list) {
201        TaskStatistics pid_stats_new;
202        if (!taskstats_socket.GetPidStats(pid, pid_stats_new)) {
203          continue;
204        }
205
206        TaskStatistics pid_stats_delta = pid_stats[pid].Update(pid_stats_new);
207
208        if (processes) {
209          tgid_stats_delta.AddPidToTgid(pid_stats_delta);
210        } else {
211          stats.push_back(pid_stats_delta);
212        }
213      }
214
215      if (processes) {
216        stats.push_back(tgid_stats_delta);
217      }
218    }
219
220    if (!first) {
221      sorter(stats);
222      if (!second) {
223        printf("\n");
224      }
225      if (accumulated) {
226        printf("%6s %-16s %20s %34s\n", "", "",
227            "---- IO (KiB) ----", "----------- delayed on ----------");
228      } else {
229        printf("%6s %-16s %20s %34s\n", "", "",
230            "--- IO (KiB/s) ---", "----------- delayed on ----------");
231      }
232      printf("%6s %-16s %6s %6s %6s  %-5s  %-5s  %-5s  %-5s  %-5s\n",
233          "PID",
234          "Command",
235          "read",
236          "write",
237          "total",
238          "IO",
239          "swap",
240          "sched",
241          "mem",
242          "total");
243      int n = limit;
244      const int delay_div = accumulated ? 1 : delay;
245      uint64_t total_read = 0;
246      uint64_t total_write = 0;
247      uint64_t total_read_write = 0;
248      for (const TaskStatistics& statistics : stats) {
249        total_read += statistics.read();
250        total_write += statistics.write();
251        total_read_write += statistics.read_write();
252
253        if (n == 0) {
254          continue;
255        } else if (n > 0) {
256          n--;
257        }
258
259        printf("%6d %-16s %6" PRIu64 " %6" PRIu64 " %6" PRIu64 " %5.2f%% %5.2f%% %5.2f%% %5.2f%% %5.2f%%\n",
260            statistics.pid(),
261            statistics.comm().c_str(),
262            BytesToKB(statistics.read()) / delay_div,
263            BytesToKB(statistics.write()) / delay_div,
264            BytesToKB(statistics.read_write()) / delay_div,
265            TimeToTgidPercent(statistics.delay_io(), delay, statistics),
266            TimeToTgidPercent(statistics.delay_swap(), delay, statistics),
267            TimeToTgidPercent(statistics.delay_sched(), delay, statistics),
268            TimeToTgidPercent(statistics.delay_mem(), delay, statistics),
269            TimeToTgidPercent(statistics.delay_total(), delay, statistics));
270      }
271      printf("%6s %-16s %6" PRIu64 " %6" PRIu64 " %6" PRIu64 "\n", "", "TOTAL",
272          BytesToKB(total_read) / delay_div,
273          BytesToKB(total_write) / delay_div,
274          BytesToKB(total_read_write) / delay_div);
275
276      second = false;
277
278      if (cycles > 0 && --cycles == 0) break;
279    }
280    first = false;
281    sleep(delay);
282  }
283
284  return 0;
285}
286