281 lines
8.8 KiB
C++
281 lines
8.8 KiB
C++
// Copyright (C) 2015 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 <getopt.h>
|
|
#include <inttypes.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#include <android-base/logging.h>
|
|
|
|
#include "tasklist.h"
|
|
#include "taskstats.h"
|
|
|
|
constexpr uint64_t NSEC_PER_SEC = 1000000000;
|
|
|
|
static uint64_t BytesToKB(uint64_t bytes) {
|
|
return (bytes + 1024 - 1) / 1024;
|
|
}
|
|
|
|
static float TimeToTgidPercent(uint64_t ns, int time, const TaskStatistics& stats) {
|
|
float percent = ns / stats.threads() / (time * NSEC_PER_SEC / 100.0f);
|
|
return std::min(percent, 99.99f);
|
|
}
|
|
|
|
static void usage(char* myname) {
|
|
printf(
|
|
"Usage: %s [-h] [-P] [-d <delay>] [-n <cycles>] [-s <column>]\n"
|
|
" -a Show byte count instead of rate\n"
|
|
" -d Set the delay between refreshes in seconds.\n"
|
|
" -h Display this help screen.\n"
|
|
" -m Set the number of processes or threads to show\n"
|
|
" -n Set the number of refreshes before exiting.\n"
|
|
" -P Show processes instead of the default threads.\n"
|
|
" -s Set the column to sort by:\n"
|
|
" pid, read, write, total, io, swap, faults, sched, mem or delay.\n",
|
|
myname);
|
|
}
|
|
|
|
using Sorter = std::function<void(std::vector<TaskStatistics>&)>;
|
|
static Sorter GetSorter(const std::string& field) {
|
|
// Generic comparator
|
|
static auto comparator = [](auto& lhs, auto& rhs, auto field, bool ascending) -> bool {
|
|
auto a = (lhs.*field)();
|
|
auto b = (rhs.*field)();
|
|
if (a != b) {
|
|
// Sort by selected field
|
|
return ascending ^ (a < b);
|
|
} else {
|
|
// And then fall back to sorting by pid
|
|
return lhs.pid() < rhs.pid();
|
|
}
|
|
};
|
|
|
|
auto make_sorter = [](auto field, bool ascending) {
|
|
// Make closure for comparator on a specific field
|
|
using namespace std::placeholders;
|
|
auto bound_comparator = std::bind(comparator, _1, _2, field, ascending);
|
|
|
|
// Return closure to std::sort with specialized comparator
|
|
return [bound_comparator](auto& vector) {
|
|
return std::sort(vector.begin(), vector.end(), bound_comparator);
|
|
};
|
|
};
|
|
|
|
static const std::map<std::string, Sorter> sorters{
|
|
{"pid", make_sorter(&TaskStatistics::pid, false)},
|
|
{"read", make_sorter(&TaskStatistics::read, true)},
|
|
{"write", make_sorter(&TaskStatistics::write, true)},
|
|
{"total", make_sorter(&TaskStatistics::read_write, true)},
|
|
{"io", make_sorter(&TaskStatistics::delay_io, true)},
|
|
{"swap", make_sorter(&TaskStatistics::delay_swap, true)},
|
|
{"faults", make_sorter(&TaskStatistics::faults, true)},
|
|
{"sched", make_sorter(&TaskStatistics::delay_sched, true)},
|
|
{"mem", make_sorter(&TaskStatistics::delay_mem, true)},
|
|
{"delay", make_sorter(&TaskStatistics::delay_total, true)},
|
|
};
|
|
|
|
auto it = sorters.find(field);
|
|
if (it == sorters.end()) {
|
|
return nullptr;
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
bool accumulated = false;
|
|
bool processes = false;
|
|
int delay = 1;
|
|
int cycles = -1;
|
|
int limit = -1;
|
|
Sorter sorter = GetSorter("total");
|
|
|
|
android::base::InitLogging(argv, android::base::StderrLogger);
|
|
|
|
while (1) {
|
|
int c;
|
|
static const option longopts[] = {
|
|
{"accumulated", 0, 0, 'a'},
|
|
{"delay", required_argument, 0, 'd'},
|
|
{"help", 0, 0, 'h'},
|
|
{"limit", required_argument, 0, 'm'},
|
|
{"iter", required_argument, 0, 'n'},
|
|
{"sort", required_argument, 0, 's'},
|
|
{"processes", 0, 0, 'P'},
|
|
{0, 0, 0, 0},
|
|
};
|
|
c = getopt_long(argc, argv, "ad:hm:n:Ps:", longopts, NULL);
|
|
if (c < 0) {
|
|
break;
|
|
}
|
|
switch (c) {
|
|
case 'a':
|
|
accumulated = true;
|
|
break;
|
|
case 'd':
|
|
delay = atoi(optarg);
|
|
break;
|
|
case 'h':
|
|
usage(argv[0]);
|
|
return (EXIT_SUCCESS);
|
|
case 'm':
|
|
limit = atoi(optarg);
|
|
break;
|
|
case 'n':
|
|
cycles = atoi(optarg);
|
|
break;
|
|
case 's': {
|
|
sorter = GetSorter(optarg);
|
|
if (sorter == nullptr) {
|
|
LOG(ERROR) << "Invalid sort column \"" << optarg << "\"";
|
|
usage(argv[0]);
|
|
return EXIT_FAILURE;
|
|
}
|
|
break;
|
|
}
|
|
case 'P':
|
|
processes = true;
|
|
break;
|
|
case '?':
|
|
usage(argv[0]);
|
|
return EXIT_FAILURE;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
std::map<pid_t, std::vector<pid_t>> tgid_map;
|
|
|
|
TaskstatsSocket taskstats_socket;
|
|
if (!taskstats_socket.Open()) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
std::unordered_map<pid_t, TaskStatistics> pid_stats;
|
|
std::unordered_map<pid_t, TaskStatistics> tgid_stats;
|
|
std::vector<TaskStatistics> stats;
|
|
|
|
bool first = true;
|
|
bool second = true;
|
|
|
|
while (true) {
|
|
stats.clear();
|
|
if (!TaskList::Scan(tgid_map)) {
|
|
LOG(ERROR) << "failed to scan tasks";
|
|
return EXIT_FAILURE;
|
|
}
|
|
for (auto& tgid_it : tgid_map) {
|
|
pid_t tgid = tgid_it.first;
|
|
std::vector<pid_t>& pid_list = tgid_it.second;
|
|
|
|
TaskStatistics tgid_stats_new;
|
|
TaskStatistics tgid_stats_delta;
|
|
|
|
if (processes) {
|
|
// If printing processes, collect stats for the tgid which will
|
|
// hold delay accounting data across all threads, including
|
|
// ones that have exited.
|
|
if (!taskstats_socket.GetTgidStats(tgid, tgid_stats_new)) {
|
|
continue;
|
|
}
|
|
tgid_stats_delta = tgid_stats[tgid].Update(tgid_stats_new);
|
|
}
|
|
|
|
// Collect per-thread stats
|
|
for (pid_t pid : pid_list) {
|
|
TaskStatistics pid_stats_new;
|
|
if (!taskstats_socket.GetPidStats(pid, pid_stats_new)) {
|
|
continue;
|
|
}
|
|
|
|
TaskStatistics pid_stats_delta = pid_stats[pid].Update(pid_stats_new);
|
|
|
|
if (processes) {
|
|
tgid_stats_delta.AddPidToTgid(pid_stats_delta);
|
|
} else {
|
|
stats.push_back(pid_stats_delta);
|
|
}
|
|
}
|
|
|
|
if (processes) {
|
|
stats.push_back(tgid_stats_delta);
|
|
}
|
|
}
|
|
|
|
if (!first) {
|
|
sorter(stats);
|
|
if (!second) {
|
|
printf("\n");
|
|
}
|
|
if (accumulated) {
|
|
printf("%6s %-16s %20s %14s %34s\n", "", "", "---- IO (KiB) ----", "--- faults ---",
|
|
"----------- delayed on ----------");
|
|
} else {
|
|
printf("%6s %-16s %20s %14s %34s\n", "", "", "--- IO (KiB/s) ---", "--- faults ---",
|
|
"----------- delayed on ----------");
|
|
}
|
|
printf("%6s %-16s %6s %6s %6s %6s %6s %-5s %-5s %-5s %-5s %-5s\n", "PID", "Command",
|
|
"read", "write", "total", "major", "minor", "IO", "swap", "sched", "mem", "total");
|
|
int n = limit;
|
|
const int delay_div = accumulated ? 1 : delay;
|
|
uint64_t total_read = 0;
|
|
uint64_t total_write = 0;
|
|
uint64_t total_read_write = 0;
|
|
uint64_t total_minflt = 0;
|
|
uint64_t total_majflt = 0;
|
|
for (const TaskStatistics& statistics : stats) {
|
|
total_read += statistics.read();
|
|
total_write += statistics.write();
|
|
total_read_write += statistics.read_write();
|
|
total_minflt += statistics.minflt();
|
|
total_majflt += statistics.majflt();
|
|
|
|
if (n == 0) {
|
|
continue;
|
|
} else if (n > 0) {
|
|
n--;
|
|
}
|
|
|
|
printf("%6d %-16s %6" PRIu64 " %6" PRIu64 " %6" PRIu64 " %6" PRIu64 " %6" PRIu64
|
|
" %5.2f%% %5.2f%% %5.2f%% %5.2f%% %5.2f%%\n",
|
|
statistics.pid(), statistics.comm().c_str(),
|
|
BytesToKB(statistics.read()) / delay_div, BytesToKB(statistics.write()) / delay_div,
|
|
BytesToKB(statistics.read_write()) / delay_div, statistics.majflt(),
|
|
statistics.minflt(), TimeToTgidPercent(statistics.delay_io(), delay, statistics),
|
|
TimeToTgidPercent(statistics.delay_swap(), delay, statistics),
|
|
TimeToTgidPercent(statistics.delay_sched(), delay, statistics),
|
|
TimeToTgidPercent(statistics.delay_mem(), delay, statistics),
|
|
TimeToTgidPercent(statistics.delay_total(), delay, statistics));
|
|
}
|
|
printf("%6s %-16s %6" PRIu64 " %6" PRIu64 " %6" PRIu64 " %6" PRIu64 " %6" PRIu64 "\n", "",
|
|
"TOTAL", BytesToKB(total_read) / delay_div, BytesToKB(total_write) / delay_div,
|
|
BytesToKB(total_read_write) / delay_div, total_majflt / delay_div,
|
|
total_minflt / delay_div);
|
|
|
|
second = false;
|
|
|
|
if (cycles > 0 && --cycles == 0) break;
|
|
}
|
|
first = false;
|
|
sleep(delay);
|
|
}
|
|
|
|
return 0;
|
|
}
|