blob: 9a3fa30616ed44dd5ac15269063a1fc039aa2433 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fcntl.h>
#include <inttypes.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/exception.h>
#include <zircon/syscalls/object.h>
#include <zircon/time.h>
#include <zircon/types.h>
#include <pretty/sizes.h>
#include <task-utils/get.h>
#include <task-utils/walker.h>
#include "src/lib/listnode/listnode.h"
enum sort_order { UNSORTED, SORT_TIME_DELTA };
typedef struct {
struct list_node node;
// has it been seen this pass?
bool scanned;
zx_duration_t delta_time;
// information about the thread
zx_koid_t proc_koid;
zx_koid_t koid;
zx_info_thread_t info;
zx_info_thread_stats_t stats;
char name[ZX_MAX_NAME_LEN];
char proc_name[ZX_MAX_NAME_LEN];
} thread_info_t;
// arguments
static zx_duration_t delay = ZX_SEC(1);
static int count = -1;
static bool print_all = false;
static bool raw_time = false;
static enum sort_order sort_order = SORT_TIME_DELTA;
// active locals
static struct list_node thread_list = LIST_INITIAL_VALUE(thread_list);
static char last_process_name[ZX_MAX_NAME_LEN];
static zx_koid_t last_process_scanned;
// Return text representation of thread state.
static const char* state_string(const zx_info_thread_t* info) {
if (info->wait_exception_channel_type != ZX_EXCEPTION_CHANNEL_TYPE_NONE) {
return "excp";
} else {
switch (ZX_THREAD_STATE_BASIC(info->state)) {
case ZX_THREAD_STATE_NEW:
return "new";
case ZX_THREAD_STATE_RUNNING:
return "run";
case ZX_THREAD_STATE_SUSPENDED:
return "susp";
case ZX_THREAD_STATE_BLOCKED:
return "block";
case ZX_THREAD_STATE_DYING:
return "dying";
case ZX_THREAD_STATE_DEAD:
return "dead";
default:
return "???";
}
}
}
static zx_status_t process_callback(void* unused_ctx, int depth, zx_handle_t proc, zx_koid_t koid,
zx_koid_t parent_koid) {
last_process_scanned = koid;
zx_status_t status =
zx_object_get_property(proc, ZX_PROP_NAME, &last_process_name, sizeof(last_process_name));
return status;
}
// Adds a thread's information to the thread_list
static zx_status_t thread_callback(void* unused_ctx, int depth, zx_handle_t thread, zx_koid_t koid,
zx_koid_t parent_koid) {
thread_info_t e = {};
e.koid = koid;
e.scanned = true;
e.proc_koid = last_process_scanned;
strlcpy(e.proc_name, last_process_name, sizeof(e.proc_name));
zx_status_t status = zx_object_get_property(thread, ZX_PROP_NAME, e.name, sizeof(e.name));
if (status != ZX_OK) {
return status;
}
status = zx_object_get_info(thread, ZX_INFO_THREAD, &e.info, sizeof(e.info), NULL, NULL);
if (status != ZX_OK) {
return status;
}
status = zx_object_get_info(thread, ZX_INFO_THREAD_STATS, &e.stats, sizeof(e.stats), NULL, NULL);
if (status != ZX_OK) {
return status;
}
// see if this thread is in the list
thread_info_t* temp;
list_for_every_entry (&thread_list, temp, thread_info_t, node) {
if (e.koid == temp->koid) {
// mark it scanned, compute the delta time,
// and copy the new state over
temp->scanned = true;
temp->delta_time = zx_duration_sub_duration(e.stats.total_runtime, temp->stats.total_runtime);
temp->info = e.info;
temp->stats = e.stats;
return ZX_OK;
}
}
// it wasn't in the list, add it
thread_info_t* new_entry = malloc(sizeof(thread_info_t));
*new_entry = e;
list_add_tail(&thread_list, &new_entry->node);
return ZX_OK;
}
static void sort_threads(enum sort_order order) {
if (order == UNSORTED)
return;
struct list_node new_list = LIST_INITIAL_VALUE(new_list);
// cheezy sort into second list, then swap back to first
thread_info_t* e;
while ((e = list_remove_head_type(&thread_list, thread_info_t, node))) {
thread_info_t* t;
bool found = false;
list_for_every_entry (&new_list, t, thread_info_t, node) {
if (order == SORT_TIME_DELTA) {
if (e->delta_time > t->delta_time) {
list_add_before(&t->node, &e->node);
found = true;
break;
}
}
}
// walked off the end
if (!found)
list_add_tail(&new_list, &e->node);
}
list_move(&new_list, &thread_list);
}
static void print_threads(void) {
thread_info_t* e;
printf("%8s %8s %10s %4s %5s %s\n", "PID", "TID", raw_time ? "TIME_NS" : "TIME%", "CPU", "STATE",
"NAME");
int i = 0;
list_for_every_entry (&thread_list, e, thread_info_t, node) {
// only print threads that are active
if (!print_all && e->delta_time == 0)
continue;
if (!raw_time) {
double percent = 0;
if (e->delta_time > 0)
percent = e->delta_time / (double)delay * 100;
printf("%8lu %8lu %10.2f %4u %5s %s:%s\n", e->proc_koid, e->koid, percent,
e->stats.last_scheduled_cpu, state_string(&e->info), e->proc_name, e->name);
} else {
printf("%8lu %8lu %10lu %4u %5s %s:%s\n", e->proc_koid, e->koid, e->delta_time,
e->stats.last_scheduled_cpu, state_string(&e->info), e->proc_name, e->name);
}
// only print the first count items (or all, if count < 0)
if (++i == count)
break;
}
}
static void print_help(FILE* f) {
fprintf(f, "Usage: top [options]\n");
fprintf(f, "Options:\n");
fprintf(f, " -a Print all threads, even if inactive\n");
fprintf(f, " -c <count> Print the first count threads (default infinity)\n");
fprintf(f, " -d <delay> Delay in seconds (default 1 second)\n");
fprintf(f, " -j <jobid> Show only threads that belong to a given jobid\n");
fprintf(f, " -n <times> Run this many times and then exit\n");
fprintf(f, " -o <sort field> Sort by different fields (default is time)\n");
fprintf(f, " -r Print raw time in nanoseconds\n");
fprintf(f, "\nSupported sort fields:\n");
fprintf(f, "\tnone : no sorting, in job order\n");
fprintf(f, "\ttime : sort by delta time between scans\n");
}
int main(int argc, char** argv) {
zx_handle_t target_job = ZX_HANDLE_INVALID;
int num_loops = -1;
for (int i = 1; i < argc; ++i) {
const char* arg = argv[i];
if (!strcmp(arg, "--help") || !strcmp(arg, "-h")) {
print_help(stdout);
return 0;
}
if (!strcmp(arg, "-a")) {
print_all = true;
} else if (!strcmp(arg, "-d")) {
delay = 0;
if (i + 1 < argc) {
delay = ZX_SEC(atoi(argv[i + 1]));
}
if (delay == 0) {
fprintf(stderr, "Bad -d value '%s'\n", argv[i + 1]);
print_help(stderr);
return 1;
}
i++;
} else if (!strcmp(arg, "-n")) {
num_loops = 0;
if (i + 1 < argc) {
num_loops = atoi(argv[i + 1]);
}
if (num_loops == 0) {
fprintf(stderr, "Bad -n value '%s'\n", argv[i + 1]);
print_help(stderr);
return 1;
}
i++;
} else if (!strcmp(arg, "-c")) {
count = 0;
if (i + 1 < argc) {
count = atoi(argv[i + 1]);
}
if (count == 0) {
fprintf(stderr, "Bad count\n");
print_help(stderr);
return 1;
}
i++;
} else if (!strcmp(arg, "-o")) {
if (i + 1 >= argc) {
fprintf(stderr, "Bad sort field\n");
print_help(stderr);
return 1;
} else if (!strcmp(argv[i + 1], "none")) {
sort_order = UNSORTED;
} else if (!strcmp(argv[i + 1], "time")) {
sort_order = SORT_TIME_DELTA;
} else {
fprintf(stderr, "Bad sort field\n");
print_help(stderr);
return 1;
}
i++;
} else if (!strcmp(arg, "-r")) {
raw_time = true;
} else if (!strcmp(arg, "-j")) {
if (i + 1 >= argc) {
fprintf(stderr, "Bad job field\n");
print_help(stderr);
return 1;
}
int jobid = atoi(argv[i + 1]);
zx_obj_type_t type;
zx_status_t status = get_task_by_koid(jobid, &type, &target_job);
if (status != ZX_OK) {
fprintf(stderr, "ERROR: get_task_by_koid failed: %s (%d)\n", zx_status_get_string(status),
status);
return 1;
}
if (type != ZX_OBJ_TYPE_JOB) {
fprintf(stderr, "ERROR: object with koid %d is not a job\n", jobid);
return 1;
}
i++;
} else {
fprintf(stderr, "Unknown option: %s\n", arg);
print_help(stderr);
return 1;
}
}
bool is_term = false;
const char* term = getenv("TERM");
if (term != NULL) {
if (!strncmp(term, "screen", 6)) {
is_term = true;
}
if (!strncmp(term, "xterm", 5)) {
is_term = true;
}
}
// set stdin to non blocking
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
int ret = 0;
bool first_run = true;
for (;;) {
zx_time_t next_deadline = zx_deadline_after(delay);
// mark all active threads as not scanned
thread_info_t* e;
list_for_every_entry (&thread_list, e, thread_info_t, node) { e->scanned = false; }
// If we have a target job, only walk the target subtree. Otherwise walk from root.
zx_status_t status;
if (target_job != ZX_HANDLE_INVALID) {
status = walk_job_tree(target_job, NULL, process_callback, thread_callback, NULL);
} else {
status = walk_root_job_tree(NULL, process_callback, thread_callback, NULL);
}
if (status != ZX_OK) {
fprintf(stderr, "WARNING: walking the job tree failed: %s (%d)\n",
zx_status_get_string(status), status);
ret = 1;
goto finish;
}
// remove every entry that hasn't been scanned this pass
thread_info_t* temp;
list_for_every_entry_safe (&thread_list, e, temp, thread_info_t, node) {
if (!e->scanned) {
list_delete(&e->node);
free(e);
}
}
if (first_run) {
// We don't have data until after we scan twice, since we're
// computing deltas.
first_run = false;
continue;
}
// sort the list
sort_threads(sort_order);
// dump the list of threads
if (is_term) {
printf("\E[H\E[J");
}
print_threads();
if (num_loops > 0) {
if (--num_loops == 0) {
break;
}
} else {
// TODO: replace once ctrl-c works in the shell
char c;
int err;
while ((err = read(STDIN_FILENO, &c, 1)) > 0) {
if (c == 0x3) {
ret = 0;
goto finish;
}
}
}
zx_nanosleep(next_deadline);
}
finish:
if (target_job != ZX_HANDLE_INVALID) {
zx_handle_close(target_job);
}
return ret;
}