blob: 1d405207b5ef53858e4d0e9bf8bab2383ce003c7 [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 <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.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 "resources.h"
// TODO: dynamically compute this based on what it returns
#define MAX_CPUS 32
static zx_status_t gueststats(zx_handle_t root_resource, zx_duration_t delay) {
static zx_info_guest_stats_t old_stats[MAX_CPUS];
zx_info_guest_stats_t stats[MAX_CPUS];
size_t actual, avail;
zx_status_t err = zx_object_get_info(root_resource, ZX_INFO_GUEST_STATS, &stats, sizeof(stats),
&actual, &avail);
if (err != ZX_OK) {
fprintf(stderr, "ZX_INFO_GUEST_STATS returns %d (%s)\n", err, zx_status_get_string(err));
return err;
}
if (actual < avail) {
fprintf(stderr, "WARNING: actual cpus reported %zu less than available cpus %zu\n", actual,
avail);
}
#ifdef __aarch64__
printf(
"cpu "
" vm_entry"
" vm_exit"
" inst_abt"
" data_abt"
" wfx_inst"
" sys_inst"
" smc_inst"
" ints\n");
for (size_t i = 0; i < actual; i++) {
printf(
"%3zu"
" %9lu"
" %7lu"
" %8lu"
" %8lu"
" %7lu"
" %7lu"
" %7lu"
" %7lu"
"\n",
i, stats[i].vm_entries - old_stats[i].vm_entries, stats[i].vm_exits - old_stats[i].vm_exits,
stats[i].instruction_aborts - old_stats[i].instruction_aborts,
stats[i].data_aborts - old_stats[i].data_aborts,
stats[i].wfi_wfe_instructions - old_stats[i].wfi_wfe_instructions,
stats[i].system_instructions - old_stats[i].system_instructions,
stats[i].smc_instructions - old_stats[i].smc_instructions,
stats[i].interrupts - old_stats[i].interrupts);
old_stats[i] = stats[i];
}
#else
printf(
"cpu "
" vm_entry"
" vm_exit"
" ints"
" ints_win"
" ept"
" ctrl_reg"
" msr(rd wr)"
" inst(io hlt cpuid ple vmcall xsetbv)\n");
for (size_t i = 0; i < actual; i++) {
printf(
"%3zu"
" %7lu"
" %7lu"
" %7lu"
" %5lu"
" %6lu"
" %6lu"
" %8lu %3lu"
" %6lu %5lu %5lu %4lu %4lu %6lu\n",
i, stats[i].vm_entries - old_stats[i].vm_entries, stats[i].vm_exits - old_stats[i].vm_exits,
stats[i].interrupts - old_stats[i].interrupts,
stats[i].interrupt_windows - old_stats[i].interrupt_windows,
stats[i].ept_violations - old_stats[i].ept_violations,
stats[i].control_register_accesses - old_stats[i].control_register_accesses,
stats[i].wrmsr_instructions - old_stats[i].wrmsr_instructions,
stats[i].rdmsr_instructions - old_stats[i].rdmsr_instructions,
stats[i].io_instructions - old_stats[i].io_instructions,
stats[i].hlt_instructions - old_stats[i].hlt_instructions,
stats[i].cpuid_instructions - old_stats[i].cpuid_instructions,
stats[i].pause_instructions - old_stats[i].pause_instructions,
stats[i].vmcall_instructions - old_stats[i].vmcall_instructions,
stats[i].xsetbv_instructions - old_stats[i].xsetbv_instructions);
old_stats[i] = stats[i];
}
#endif
return ZX_OK;
}
static zx_status_t cpustats(zx_handle_t root_resource, zx_duration_t delay) {
static zx_duration_t last_idle_time[MAX_CPUS];
static zx_info_cpu_stats_t old_stats[MAX_CPUS];
zx_info_cpu_stats_t stats[MAX_CPUS];
// retrieve the system stats
size_t actual, avail;
zx_status_t err =
zx_object_get_info(root_resource, ZX_INFO_CPU_STATS, &stats, sizeof(stats), &actual, &avail);
if (err != ZX_OK) {
fprintf(stderr, "ZX_INFO_CPU_STATS returns %d (%s)\n", err, zx_status_get_string(err));
return err;
}
if (actual < avail) {
fprintf(stderr, "WARNING: actual cpus reported %zu less than available cpus %zu\n", actual,
avail);
}
printf(
"cpu load"
" sched (cs ylds pmpts irq_pmpts)"
" excep"
" pagef"
" sysc"
" ints (hw tmr tmr_cb)"
" ipi (rs gen)\n");
for (size_t i = 0; i < actual; i++) {
zx_duration_t idle_time = stats[i].idle_time;
zx_duration_t delta_time = zx_duration_sub_duration(idle_time, last_idle_time[i]);
zx_duration_t busy_time;
if (delay > delta_time) {
busy_time = zx_duration_sub_duration(delay, delta_time);
} else {
busy_time = 0;
}
unsigned int busypercent = zx_duration_mul_int64(busy_time, 10000) / delay;
printf(
"%3zu"
" %3u.%02u%%"
" %9lu %4lu %5lu %9lu"
" %6lu"
" %5lu"
" %5lu"
" %8lu %4lu %6lu"
" %8lu %4lu"
"\n",
i, busypercent / 100, busypercent % 100,
stats[i].context_switches - old_stats[i].context_switches,
stats[i].yields - old_stats[i].yields, stats[i].preempts - old_stats[i].preempts,
stats[i].irq_preempts - old_stats[i].irq_preempts,
stats[i].exceptions - old_stats[i].exceptions,
stats[i].page_faults - old_stats[i].page_faults, stats[i].syscalls - old_stats[i].syscalls,
stats[i].ints - old_stats[i].ints, stats[i].timer_ints - old_stats[i].timer_ints,
stats[i].timers - old_stats[i].timers,
stats[i].reschedule_ipis - old_stats[i].reschedule_ipis,
stats[i].generic_ipis - old_stats[i].generic_ipis);
old_stats[i] = stats[i];
last_idle_time[i] = idle_time;
}
return ZX_OK;
}
static zx_status_t cpuload(zx_handle_t root_resource, zx_duration_t delay) {
static zx_duration_t last_idle_time[MAX_CPUS];
zx_info_cpu_stats_t stats[MAX_CPUS];
// retrieve the system stats
size_t actual, avail;
zx_status_t err =
zx_object_get_info(root_resource, ZX_INFO_CPU_STATS, &stats, sizeof(stats), &actual, &avail);
if (err != ZX_OK) {
fprintf(stderr, "ZX_INFO_CPU_STATS returns %d (%s)\n", err, zx_status_get_string(err));
return err;
}
if (actual < avail) {
fprintf(stderr, "WARNING: actual cpus reported %zu less than available cpus %zu\n", actual,
avail);
}
for (size_t i = 0; i < actual; i++) {
zx_duration_t idle_time = stats[i].idle_time;
zx_duration_t delta_time = zx_duration_sub_duration(idle_time, last_idle_time[i]);
zx_duration_t busy_time;
if (delay > delta_time) {
busy_time = zx_duration_sub_duration(delay, delta_time);
} else {
busy_time = 0;
}
const double busypercent = (double)busy_time / delay;
static const char kBar[] = "||||||||||||||||||||";
static const int kBarLength = sizeof(kBar);
static const char* default_color = "\033[0;0m";
static const char* cpu_num_color = "\033[1;34m";
const char* color = busypercent < .9 ? "\033[1;34m" : "\033[1;31m";
printf("%s%2zu%s-[%s%-20.*s%s] ", cpu_num_color, i, default_color, color,
(int)(busypercent * kBarLength), kBar, default_color);
if ((i % 4) == 3) {
printf("\n");
}
last_idle_time[i] = idle_time;
}
if (actual % 4 != 3) {
printf("\n");
}
return ZX_OK;
}
static zx_status_t memstats(zx_handle_t root_resource) {
zx_info_kmem_stats_t stats;
zx_status_t err =
zx_object_get_info(root_resource, ZX_INFO_KMEM_STATS, &stats, sizeof(stats), NULL, NULL);
if (err != ZX_OK) {
fprintf(stderr, "ZX_INFO_KMEM_STATS returns %d (%s)\n", err, zx_status_get_string(err));
return err;
}
const int width = 80 / 8 - 1;
printf("%*s %*s %*s %*s %*s %*s %*s %*s %*s\n", width, "mem total", width, "free", width, "VMOs",
width, "kheap", width, "kfree", width, "wired", width, "mmu", width, "ipc", width,
"other");
const size_t fields[] = {
stats.total_bytes, stats.free_bytes,
stats.vmo_bytes, stats.total_heap_bytes - stats.free_heap_bytes,
stats.free_heap_bytes, stats.wired_bytes,
stats.mmu_overhead_bytes, stats.ipc_bytes,
stats.other_bytes,
};
char line[128] = {};
for (unsigned int i = 0; i < countof(fields); i++) {
const char unit = 'M';
char buf[MAX_FORMAT_SIZE_LEN];
format_size_fixed(buf, sizeof(buf), fields[i], unit);
char stage[MAX_FORMAT_SIZE_LEN + 8];
snprintf(stage, sizeof(stage), "%*s ", width, buf);
strlcat(line, stage, sizeof(line));
// TODO(dbort): Save some history so we can show deltas over time.
// Maybe have a few buckets like 1s, 10s, 1m.
}
printf("%s\n", line);
return ZX_OK;
}
static void print_help(FILE* f) {
fprintf(f, "Usage: kstats [options]\n");
fprintf(f, "Options:\n");
fprintf(f, " -v Print guest vm_entry/vm_exit stats\n");
fprintf(f, " -c Print system CPU stats\n");
fprintf(f, " -l Print system CPU load as bars\n");
fprintf(f, " -m Print system memory stats\n");
fprintf(f, " -d <delay> Delay in seconds (default 1 second)\n");
fprintf(f, " -n <times> Run this many times and then exit\n");
fprintf(f, " -t Print timestamp for each report\n");
fprintf(f, "\nCPU stats columns:\n");
fprintf(f, "\tcpu: cpu #\n");
fprintf(f, "\tload: percentage load\n");
fprintf(f, "\tsched (cs ylds pmpts irq_pmpts): scheduler statistics\n");
fprintf(f, "\t\tcs: context switches\n");
fprintf(f, "\t\tylds: explicit thread yields\n");
fprintf(f, "\t\tpmpts: thread preemption events\n");
fprintf(f, "\t\tirq_pmpts: thread preemption events from interrupt\n");
fprintf(f, "\texcep: exceptions (undefined instruction, bad memory access, etc)\n");
fprintf(f, "\tpagef: page faults\n");
fprintf(f, "\tsysc: syscalls\n");
fprintf(f, "\tints (hw tmr tmr_cb): interrupt statistics\n");
fprintf(f, "\t\thw: hardware interrupts\n");
fprintf(f, "\t\ttmr: timer interrupts\n");
fprintf(f, "\t\ttmr_cb: kernel timer events\n");
fprintf(f, "\tipi (rs gen): inter-processor-interrupts\n");
fprintf(f, "\t\trs: reschedule events\n");
fprintf(f, "\t\tgen: generic interprocessor interrupts\n");
}
int main(int argc, char** argv) {
bool cpu_stats = false;
bool guest_stats = false;
bool cpu_load = false;
bool mem_stats = false;
zx_duration_t delay = ZX_SEC(1);
int num_loops = -1;
bool timestamp = false;
int c;
while ((c = getopt(argc, argv, "cvd:n:hlmt")) > 0) {
switch (c) {
case 'v':
guest_stats = true;
break;
case 'c':
cpu_stats = true;
break;
case 'd':
delay = ZX_SEC(atoi(optarg));
if (delay == 0) {
fprintf(stderr, "Bad -d value '%s'\n", optarg);
print_help(stderr);
return 1;
}
break;
case 'l':
cpu_load = true;
break;
case 'n':
num_loops = atoi(optarg);
if (num_loops == 0) {
fprintf(stderr, "Bad -n value '%s'\n", optarg);
print_help(stderr);
return 1;
}
break;
case 'h':
print_help(stdout);
return 0;
case 'm':
mem_stats = true;
break;
case 't':
timestamp = true;
break;
default:
fprintf(stderr, "Unknown option\n");
print_help(stderr);
return 1;
}
}
if (!cpu_stats && !mem_stats && !cpu_load && !guest_stats) {
fprintf(stderr, "No statistics selected\n");
print_help(stderr);
return 1;
}
zx_handle_t root_resource;
zx_status_t ret = get_root_resource(&root_resource);
if (ret != ZX_OK) {
return ret;
}
// set stdin to non blocking so we can intercept ctrl-c.
// TODO: remove once ctrl-c works in the shell
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
for (;;) {
zx_time_t next_deadline = zx_deadline_after(delay);
// Print the current UTC time with milliseconds as
// an ISO 8601 string.
if (timestamp) {
struct timespec now;
timespec_get(&now, TIME_UTC);
struct tm nowtm;
gmtime_r(&now.tv_sec, &nowtm);
char tbuf[40];
strftime(tbuf, sizeof(tbuf), "%FT%T", &nowtm);
printf("\n--- %s.%03ldZ ---\n", tbuf, now.tv_nsec / (1000 * 1000));
}
if (cpu_load) {
ret |= cpuload(root_resource, delay);
}
if (guest_stats) {
ret |= gueststats(root_resource, delay);
}
if (cpu_stats) {
ret |= cpustats(root_resource, delay);
}
if (mem_stats) {
ret |= memstats(root_resource);
}
// Separate multiple runs with a blank line.
printf("\n");
if (ret != ZX_OK)
break;
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)
return 0;
}
}
zx_nanosleep(next_deadline);
}
zx_handle_close(root_resource);
return ret;
}