trapstat.c revision 1e49577a7fcde812700ded04431b49d67cc57d6d
/*
* 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
*/
/*
*/
#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>
#include "_trapstat.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 a dummy g_traps reader to establish a symbol capabilities lead.
* This routine should never be called, as the sun4u and sun4v variants
* will be used as appropriate.
*/
/* ARGSUSED0 */
get_trap_ent(int ndx)
{
return (NULL);
}
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++) {
continue;
}
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;
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;
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++) {
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);
}