trapstat.c revision f498645a3eecf2ddd304b4ea9c7f1b4c155ff79e
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <strings.h>
#include <limits.h>
#include <sys/trapstat.h>
#include <stddef.h>
#include <termio.h>
#define TSTAT_DEVICE "/dev/trapstat"
#define TSTAT_COMMAND "trapstat"
#define TSTAT_PAGESIZE_MODIFIERS " kmgtp"
#define TSTAT_PAGESIZE_STRLEN 10
#define TSTAT_MAX_RATE 5000
#define TSTAT_COLUMN_OFFS 26
#define TSTAT_COLUMNS_PER_CPU 9
static processorid_t g_max_cpus;
static int8_t *g_selected;
static int g_interval = NANOSEC;
static int g_peffect = 1;
static int g_absolute = 0;
static processorid_t *g_pset_cpus;
static uint_t g_pset_ncpus;
static int g_winch;
static int g_pgsizes;
static char **g_pgnames;
static size_t g_datasize;
static int g_gen;
static int g_fd;
static int g_exec_errno;
static int g_child_exited;
static int g_child_status;
static void *g_arg;
typedef struct tstat_sum {
double tsum_time;
} tstat_sum_t;
#define TSTAT_ENT_USED 0
#define TSTAT_ENT_RESERVED 1
#define TSTAT_ENT_UNUSED 2
#define TSTAT_ENT_CONTINUED 3
typedef struct tstat_ent {
char *tent_name;
char *tent_descr;
int tent_type;
} tstat_ent_t;
static tstat_ent_t g_traps[] = {
#ifndef sun4v
{ "power-on", "power on reset" },
{ "watchdog", "watchdog reset" },
{ "xir", "externally initiated reset" },
{ "sir", "software initiated reset" },
{ "red", "RED state exception" },
{ "immu-xcp", "instruction access exception" },
{ "immu-miss", "instruction access MMU miss" },
{ "immu-err", "instruction access error" },
{ "ill-inst", "illegal instruction" },
{ "priv-inst", "privileged opcode" },
{ "unimp-ldd", "unimplemented LDD" },
{ "unimp-std", "unimplemented STD" },
{ "fp-disabled", "fp disabled" },
{ "fp-ieee754", "fp exception ieee754" },
{ "fp-xcp-other", "fp exception other" },
{ "tag-oflow", "tag overflow" },
{ "cleanwin", "clean window" },
{ "div-zero", "division by zero" },
{ "internal-err", "internal processor error" },
{ "dmmu-xcp", "data access exception" },
{ "dmmu-miss", "data access MMU miss" },
{ "dmmu-err", "data access error" },
{ "dmmu-prot", "data access protection" },
{ "unalign", "mem address not aligned" },
{ "lddf-unalign", "LDDF mem address not aligned" },
{ "stdf-unalign", "STDF mem address not aligned" },
{ "priv-act", "privileged action" },
{ "ldqf-unalign", "LDQF mem address not aligned" },
{ "stqf-unalign", "STQF mem address not aligned" },
{ "async-d-err", "async data error" },
{ "level-1", "interrupt level 1" },
{ "level-2", "interrupt level 2" },
{ "level-3", "interrupt level 3" },
{ "level-4", "interrupt level 4" },
{ "level-5", "interrupt level 5" },
{ "level-6", "interrupt level 6" },
{ "level-7", "interrupt level 7" },
{ "level-8", "interrupt level 8" },
{ "level-9", "interrupt level 9" },
{ "level-10", "interrupt level 10" },
{ "level-11", "interrupt level 11" },
{ "level-12", "interrupt level 12" },
{ "level-13", "interrupt level 13" },
{ "level-14", "interrupt level 14" },
{ "level-15", "interrupt level 15" },
{ "int-vec", "interrupt vector" },
{ "pa-watch", "PA watchpoint" },
{ "va-watch", "VA watchpoint" },
{ "ecc-err", "corrected ECC error" },
{ "itlb-miss", "instruction access MMU miss" },
{ "dtlb-miss", "data access MMU miss" },
{ "dtlb-prot", "data access protection" },
{ "fast-ecc", "fast ECC error" },
{ "dcache-parity", "D-cache parity error" },
{ "icache-parity", "I-cache parity error" },
#else /* sun4v */
{ "watchdog", "watchdog reset" },
{ "immu-xcp", "instruction access exception" },
{ "immu-miss", "instruction access MMU miss" },
{ "ill-inst", "illegal instruction" },
{ "priv-inst", "privileged opcode" },
{ "unimp-ldd", "unimplemented LDD" },
{ "unimp-std", "unimplemented STD" },
{ "fp-disabled", "fp disabled" },
{ "fp-ieee754", "fp exception ieee754" },
{ "fp-xcp-other", "fp exception other" },
{ "tag-oflow", "tag overflow" },
{ "cleanwin", "clean window" },
{ "div-zero", "division by zero" },
{ "dmmu-xcp", "data access exception" },
{ "dmmu-miss", "data access MMU miss" },
{ "dmmu-prot", "data access protection" },
{ "unalign", "mem address not aligned" },
{ "lddf-unalign", "LDDF mem address not aligned" },
{ "stdf-unalign", "STDF mem address not aligned" },
{ "priv-act", "privileged action" },
{ "ldqf-unalign", "LDQF mem address not aligned" },
{ "stqf-unalign", "STQF mem address not aligned" },
{ "level-1", "interrupt level 1" },
{ "level-2", "interrupt level 2" },
{ "level-3", "interrupt level 3" },
{ "level-4", "interrupt level 4" },
{ "level-5", "interrupt level 5" },
{ "level-6", "interrupt level 6" },
{ "level-7", "interrupt level 7" },
{ "level-8", "interrupt level 8" },
{ "level-9", "interrupt level 9" },
{ "level-10", "interrupt level 10" },
{ "level-11", "interrupt level 11" },
{ "level-12", "interrupt level 12" },
{ "level-13", "interrupt level 13" },
{ "level-14", "interrupt level 14" },
{ "level-15", "interrupt level 15" },
{ "pa-watch", "PA watchpoint" },
{ "va-watch", "VA watchpoint" },
{ "itlb-miss", "instruction access MMU miss" },
{ "dtlb-miss", "data access MMU miss" },
{ "dtlb-prot", "data access protection" },
{ "ctl-xfer", "control transfer" },
{ "instr-brkpt", "instruction breakpoint" },
{ "hw-changed", "hardware changed" },
{ "cpu_mondo", "cpu mondo trap" },
{ "dev_mondo", "device mondo trap" },
{ "res-err", "resumable error" },
{ "nonres-err", "non-resumable error" },
#endif /* sun4v */
{ "spill-0-normal", "spill 0 normal" },
{ "spill-user-32", "spill user window, 32-bit" },
{ "spill-user-64", "spill user window, 64-bit" },
{ "spill-user-32-cln", "spill, clean user window, 32-bit" },
{ "spill-user-64-cln", "spill, clean user window, 64-bit" },
{ "spill-kern-32", "spill kernel window, 32-bit" },
{ "spill-kern-64", "spill kernel window, 64-bit" },
{ "spill-mixed", "spill window, mixed 32-bit/64-bit" },
{ "spill-0-other", "spill 0 other" },
{ "spill-asuser-32", "spill user window as kernel, 32-bit" },
{ "spill-asuser-64", "spill user window as kernel, 64-bit" },
{ "spill-asuser-32-cln", "spill, clean user window as kernel, 32-bit" },
{ "spill-asuser-64-cln", "spill, clean user window as kernel, 64-bit" },
{ "spill-5-other", "spill 5 other" },
{ "spill-6-other", "spill 6 other" },
{ "spill-7-other", "spill 7 other" },
{ "fill-0-normal", "fill 0 normal" },
{ "fill-user-32", "fill user window, 32-bit" },
{ "fill-user-64", "fill user window, 64-bit" },
{ "fill-user-32-cln", "fill user window, 32-bit" },
{ "fill-user-64-cln", "fill user window, 64-bit" },
{ "fill-kern-32", "fill kernel window, 32-bit" },
{ "fill-kern-64", "fill kernel window, 64-bit" },
{ "fill-mixed", "fill window, mixed 32-bit/64-bit" },
{ "fill-0-other", "fill 0 other" },
{ "fill-asuser-32", "fill user window as kernel, 32-bit" },
{ "fill-asuser-64", "fill user window as kernel, 64-bit" },
{ "fill-asuser-32-cln", "fill user window as kernel, 32-bit" },
{ "fill-asuser-64-cln", "fill user window as kernel, 64-bit" },
{ "fill-5-other", "fill 5 other" },
{ "fill-6-other", "fill 6 other" },
{ "fill-7-other", "fill 7 other" },
{ "syscall-4x", "old system call" },
{ "usr-brkpt", "user breakpoint" },
{ "usr-div-zero", "user divide by zero" },
{ "flush-wins", "flush windows" },
{ "clean-wins", "clean windows" },
{ "fix-align", "fix unaligned references" },
{ "syscall-32", "ILP32 system call" },
{ "set-t0-addr", "set trap0 address" },
{ "trap-inst-16", "trap instruction 16", },
{ "trap-inst-17", "trap instruction 17", },
{ "trap-inst-18", "trap instruction 18", },
{ "trap-inst-19", "trap instruction 19", },
{ "trap-inst-20", "trap instruction 20", },
{ "trap-inst-21", "trap instruction 21", },
{ "trap-inst-22", "trap instruction 22", },
{ "trap-inst-23", "trap instruction 23", },
{ "trap-inst-24", "trap instruction 24", },
{ "trap-inst-25", "trap instruction 25", },
{ "trap-inst-26", "trap instruction 26", },
{ "trap-inst-27", "trap instruction 27", },
{ "trap-inst-28", "trap instruction 28", },
{ "trap-inst-29", "trap instruction 29", },
{ "trap-inst-30", "trap instruction 30", },
{ "trap-inst-31", "trap instruction 31", },
{ "get-cc", "get condition codes" },
{ "set-cc", "set condition codes" },
{ "get-psr", "get psr" },
{ "set-psr", "set psr (some fields)" },
{ "getts", "get timestamp" },
{ "gethrvtime", "get lwp virtual time" },
{ "self-xcall", "self xcall" },
{ "gethrtime", "get hrestime" },
{ "getlgrp", "get lgrpid" },
{ "dtrace-pid", "DTrace pid provider" },
{ "dtrace-return", "DTrace pid provider return" },
{ "syscall-64", "LP64 system call" },
{ "tt-freeze", "freeze traptrace" },
{ "tt-unfreeze", "unfreeze traptrace" },
{ "ptl1-panic", "test ptl1-panic" },
{ "kmdb-enter", "kmdb enter (L1-A)" },
{ "kmdb-brkpt", "kmdb breakpoint" },
{ "obp-brkpt", "obp breakpoint" },
};
static void
usage(void)
{
"\nusage: trapstat [ -t | -T | -e entrylist ]\n"
" [ -C psrset | -c cpulist ]\n"
" [ -P ] [ -a ] [ -r rate ] [[ interval [ count ] ] | "
"command [ args ] ]\n\n"
"Trap selection options:\n\n"
" -t TLB statistics\n"
" -T TLB statistics, with pagesize information\n"
" -e entrylist Enable statistics only for entries specified "
"by entrylist\n\n"
"CPU selection options:\n\n"
" -c cpulist Enable statistics only for specified CPU list\n"
" -C psrset Enable statistics only for specified processor "
"set\n\n"
"Other options:\n\n"
" -a Display trap values as accumulating values "
"instead of rates\n"
" -l List trap table entries and exit\n"
" -P Display output in parsable format\n"
" -r hz Set sampling rate to be hz samples "
"per second\n\n");
}
static void
{
}
static void
set_width(void)
{
return;
return;
/*
* If TIOCGWINSZ returned 0 for the columns, just return --
* thereby using the default value of g_cpus_per_line. (This
* happens, e.g., when running over a tip line.)
*/
return;
}
if (g_cpus_per_line < 1)
g_cpus_per_line = 1;
}
static void
{
switch (signo) {
case SIGWINCH:
g_winch = 1;
set_width();
break;
case SIGCHLD:
g_child_exited = 1;
continue;
break;
default:
break;
}
}
static void
setup(void)
{
int i;
for (i = 0; i < TSTAT_NENT; i++) {
}
fatal("could not allocate g_selected");
if (g_pset_cpus == NULL)
fatal("could not allocate g_pset_cpus");
fatal("getpagesizes()");
fatal("could not allocate g_pgsize array");
fatal("could not allocate g_pgnames");
for (i = 0; i < g_pgsizes; i++) {
fatal("could not allocate g_pgnames[%d]", i);
continue;
}
fatal("could not allocate data buffer 0");
fatal("could not allocate data buffer 1");
set_width();
(void) sigemptyset(&set);
fatal("cannot create CLOCK_HIGHRES timer");
}
static void
{
struct itimerspec ts;
/*
* If the interval is less than one second, we'll report the
* numbers in terms of rate-per-interval. If the interval is
* greater than one second, we'll report numbers in terms of
* rate-per-second.
*/
fatal("cannot set time on CLOCK_HIGHRES timer");
}
static void
{
int entno;
if (!parsable) {
"dec", "entry name", "description");
"+-----------------------\n");
}
continue;
}
}
static void
select_entry(char *entry)
{
char *end;
/*
* The entry may be specified as a number (e.g., "0x68", "104") or
* as a name ("dtlb-miss").
*/
if (*end == '\0') {
if (entno >= TSTAT_NENT)
goto bad_entry;
} else {
continue;
break;
}
if (entno == TSTAT_NENT)
goto bad_entry;
}
return;
print_entries(stderr, 0);
}
static void
{
fatal("cannot specify both a processor set and a processor\n");
}
}
static void
{
fatal("cannot specify both a processor set and processors\n");
do {
}
static void
{
if (pset < 0)
/*
* Only one processor set can be specified.
*/
fatal("at most one processor set may be specified\n");
/*
* One cannot select processors _and_ a processor set.
*/
for (i = 0; i < g_max_cpus; i++)
if (g_selected[i])
break;
if (i != g_max_cpus)
fatal("cannot specify both a processor and a processor set\n");
if (g_pset_ncpus == 0)
fatal("TSTATIOC_NOCPU failed");
for (i = 0; i < g_pset_ncpus; i++)
}
static void
check_pset(void)
{
return;
}
if (ncpus == 0)
if (ncpus == g_pset_ncpus) {
for (i = 0; i < g_pset_ncpus; i++) {
if (!g_selected[g_pset_cpus[i]])
break;
}
/*
* If the number of CPUs hasn't changed, and every CPU
* in the processor set is also selected, we know that the
* processor set itself hasn't changed.
*/
if (i == g_pset_ncpus)
return;
}
/*
* If we're here, we have a new processor set. First, we need
* to zero out the selection array.
*/
fatal("TSTATIOC_STOP failed");
fatal("TSATIOC_NOCPU failed");
for (i = 0; i < g_pset_ncpus; i++) {
fatal("TSTATIOC_CPU failed for cpu %d", i);
}
/*
* Now that we have selected the CPUs, we're going to reenable
* trapstat, and reread the data for the current generation.
*/
fatal("TSTATIOC_GO failed");
fatal("TSTATIOC_READ failed");
}
static void
{
double p;
/*
* Now we need to account for the trapstat probe effect. Take
* the amount of time spent in the handler, and add the
* amount of time known to be due to the trapstat probe effect.
*/
/*
* This really shouldn't happen unless our calculation of
* the probe effect was vastly incorrect. In any case,
* print 99.9 for the time instead of printing negative
* values...
*/
}
}
static void
{
}
static void
{
TSTAT_PRINT_MISSDATA(diff, p);
*ttl += p;
}
static void
{
int ps;
double ttl = 0.0;
(void) printf(" |");
}
}
static void
{
int ps;
(void) printf("\n");
}
}
static void
{
(*(tstat_sum_t **)sump)++;
}
static void
{
}
}
static void
{
int i;
double ttl = 0.0;
for (i = 0; i < 4; i++) {
if (i == 2)
(void) printf(" |");
}
}
static void
{
char pre[12];
(void) printf("cpu m size| %9s %4s %9s %4s | %9s %4s %9s %4s |%4s\n"
"----------+-------------------------------+-----------------------"
"--------+----\n", "itlb-miss", "%tim", "itsb-miss", "%tim",
"dtlb-miss", "%tim", "dtsb-miss", "%tim", "%tim");
for (i = 0; i < g_max_cpus; i++) {
break;
if (i != 0)
(void) printf("----------+-----------------------------"
"--+-------------------------------+----\n");
(void) printf("- - - - - + - - - - - - - - - - - - - -"
" - + - - - - - - - - - - - - - - - + - -\n");
ncpus++;
}
(void) printf("==========+===============================+========="
"======================+====\n");
(void) printf(" ttl |");
(void) printf("\n");
}
static void
{
int i, cpu;
char pre[30];
for (i = 0; i < g_max_cpus; i++) {
break;
&opgsz->tpgsz_kernel);
}
}
static void
{
int ps, i;
double ttl = 0.0;
}
for (i = 0; i < 4; i++) {
if (i == 2 && !parsable)
(void) printf(" |");
}
if (parsable) {
(void) printf("\n");
return;
}
}
static void
{
(void) printf("cpu m| %9s %4s %9s %4s | %9s %4s %9s %4s |%4s\n"
"-----+-------------------------------+-----------------------"
"--------+----\n", "itlb-miss", "%tim", "itsb-miss", "%tim",
"dtlb-miss", "%tim", "dtsb-miss", "%tim", "%tim");
for (i = 0; i < g_max_cpus; i++) {
break;
if (i != 0)
(void) printf("-----+-------------------------------+-"
"------------------------------+----\n");
ncpus++;
}
(void) printf("=====+===============================+========="
"======================+====\n");
(void) printf(" ttl |");
(void) printf("\n");
}
static void
{
int i, cpu;
for (i = 0; i < g_max_cpus; i++) {
break;
}
}
static void
{
int i, j, k, done;
/*
* First, blast through all of the data updating our array
* of active traps. We keep an array of active traps to prevent
* printing lines for traps that are never seen -- while still printing
* lines for traps that have been seen only once on some CPU.
*/
for (i = 0; i < g_max_cpus; i++) {
break;
for (j = 0; j < TSTAT_NENT; j++) {
continue;
g_active[j] = 1;
}
}
for (i = 0; i < g_cpus_per_line; i++) {
break;
break;
if (i == 0)
(void) printf("vct name |");
}
if (i == 0)
break;
if (i != g_cpus_per_line)
done = 1;
(void) printf("\n------------------------+");
for (j = 0; j < i; j++)
(void) printf("---------");
(void) printf("\n");
for (j = 0; j < TSTAT_NENT; j++) {
if (!g_active[j])
continue;
for (k = 0; k < i; k++) {
}
(void) printf("\n");
}
(void) printf("\n");
}
}
static void
{
int i;
break;
for (i = 0; i < TSTAT_NENT; i++) {
continue;
(void) printf("%lld %d %x %s ",
tdata_traps[i]));
}
}
}
static void
{
int i;
/*
* The last CPU we were watching must have been DR'd out
* of the system. Print a vaguely useful message and exit.
*/
fatal("all initially selected CPUs have been unconfigured\n");
}
/*
* If a CPU is DR'd out of the system, we'll stop receiving data
* for it. CPUs are never added, however (that is, if a CPU is
* DR'd into the system, we won't automatically start receiving
* data for it). We check for this by making sure that all of
* the CPUs present in the old data are present in the new data.
* If we find one missing in the new data, we correct the old data
* by removing the old CPU. This assures that delta are printed
* correctly.
*/
for (i = 0; i < g_max_cpus; i++) {
return;
break;
}
if (i == g_max_cpus)
return;
/*
* If we're here, we know that the odata is a CPU which has been
* DR'd out. We'll now smoosh it out of the old data.
*/
}
/*
* There may be other CPUs DR'd out; tail-call recurse.
*/
}
int
{
char c, *end;
struct {
char opt;
int repeat;
} tab[] = {
/*
* If argv[0] is non-NULL, set argv[0] to keep any getopt(3C) output
* consistent with other error output.
*/
argv[0] = TSTAT_COMMAND;
setup();
/*
* First, check to see if this option changes our printing
* function.
*/
continue;
/*
* This option is allowed to
* have repeats; break out.
*/
break;
}
fatal("expected -%c at most once\n", c);
}
fatal("only one of -%c, -%c expected\n",
}
break;
}
switch (c) {
case 'a':
g_absolute = 1;
break;
case 'e': {
while (s != NULL) {
select_entry(s);
}
break;
}
case 'l':
list = 1;
break;
case 'n':
/*
* This undocumented option prevents trapstat from
* actually switching the %tba to point to the
* interposing trap table. It's very useful when
* debugging trapstat bugs: one can specify "-n"
* and then examine the would-be interposing trap
* table without running the risk of RED stating.
*/
fatal("TSTATIOC_NOGO");
break;
case 'N':
/*
* This undocumented option forces trapstat to ignore
* its determined probe effect. This may be useful
* if it is believed that the probe effect has been
* grossly overestimated.
*/
g_peffect = 0;
break;
case 't':
case 'T':
/*
* When running with TLB statistics, we want to
* minimize probe effect by running with all other
* entries explicitly disabled.
*/
fatal("TSTATIOC_NOENTRY");
fatal("TSTATIOC_TLBDATA");
break;
case 'c': {
/*
* We allow CPUs to be specified as an optionally
* comma separated list of either CPU IDs or ranges
* of CPU IDs.
*/
while (s != NULL) {
*end = '\0';
fatal("invalid cpu '%s'\n", s);
}
if (*(s = end) != '\0') {
if (*s != '-')
fatal("invalid cpu '%s'\n", s);
if (*end != '\0' ||
fatal("invalid cpu '%s'\n", s);
select_cpus(id, p);
} else {
select_cpu(id);
}
}
break;
}
case 'C': {
if (*end != '\0' ||
break;
}
case 'r': {
if (*end != '\0' ||
if (rate <= 0)
fatal("rate must be greater than zero\n");
if (rate > TSTAT_MAX_RATE)
fatal("rate may not exceed %d\n",
break;
}
case 'P':
parsable = 1;
break;
default:
usage();
}
}
if (list) {
}
if (*end != '\0') {
/*
* That wasn't a valid number. It must be that we're
* to execute this command.
*/
switch (vfork()) {
case 0:
/*
* No luck. Set errno.
*/
/*NOTREACHED*/
case -1:
fatal("cannot fork");
/*NOTREACHED*/
default:
break;
}
} else {
if (interval <= 0)
fatal("interval must be greater than zero.\n");
fatal("invalid count '%s'\n", s);
}
}
} else {
if (!rate)
}
if (!g_selected[id])
continue;
}
fatal("TSTATIOC_GO failed");
fatal("initial TSTATIOC_READ failed");
(void) sigemptyset(&set);
(void) sigsuspend(&set);
if (g_winch) {
g_winch = 0;
continue;
}
if (g_child_exited && g_exec_errno != 0) {
}
fatal("TSTATIOC_READ failed");
/*
* Before we blithely print the data, we need to
* make sure that we haven't lost a CPU.
*/
if (g_child_exited) {
if (WIFEXITED(g_child_status)) {
if (WEXITSTATUS(g_child_status) == 0)
break;
"warning: %s exited with code %d\n",
} else {
"warning: %s died on signal %d\n",
}
break;
}
check_pset();
g_gen ^= 1;
}
return (0);
}