iostat.c revision 00c76d6fcc0e3d5821ed5ac5165f1835f8151454
/*
* 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 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* rewritten from UCB 4.13 83/09/25
* rewritten from SunOS 4.1 SID 1.18 89/10/06
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <unistd.h>
#include <memory.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <inttypes.h>
#include <strings.h>
#include <sys/systeminfo.h>
#include <kstat.h>
#include "dsr.h"
#include "statcommon.h"
#define DISK_OLD 0x0001
#define DISK_NEW 0x0002
#define DISK_EXTENDED 0x0004
#define DISK_ERRORS 0x0008
#define DISK_EXTENDED_ERRORS 0x0010
#define REPRINT 19
/*
* It's really a pseudo-gigabyte. We use 1000000000 bytes so that the disk
* labels don't look bad. 1GB is really 1073741824 bytes.
*/
#define DISK_GIGABYTE 1000000000.0
/*
* Function desciptor to be called when extended
* headers are used.
*/
typedef struct formatter {
void (*nfunc)(void);
} format_t;
/*
* data to the right of disk data
*/
enum show_disk_mode {
};
char *cmdname = "iostat";
int caught_cont = 0;
static char one_blank[] = " ";
static char two_blanks[] = " ";
/*
* count for number of lines to be emitted before a header is
* shown again. Only used for the basic format.
*/
/*
* If we're in raw format, have we printed a header? We only do it
* once for raw but we emit it every REPRINT lines in non-raw format.
* This applies only for the basic header. The extended header is
* done only once in both formats.
*/
/*
* Flags representing arguments from command line
*/
/* format (-d, -D, -e, -E, -x -X -Y) */
static int do_partitions; /* per-partition stats (-p) */
static int do_partitions_only; /* per-partition stats only (-P) */
/* no per-device stats for disks */
/*
* Definition of allowable types of timestamps
*/
#define CDATE 1
#define UDATE 2
/*
* Default number of disk drives to be displayed in basic format
*/
#define DEFAULT_LIMIT 4
struct iodev_filter df;
static int interval; /* interval (seconds) to output */
static int iter; /* iterations from command line */
#define SMALL_SCRATCH_BUFLEN MAXNAMELEN
static int iodevs_nl; /* name field width */
static char disk_header[132];
static int lineout; /* data waiting to be printed? */
static double getime; /* elapsed time */
static double percent; /* 100 / etime */
/*
* List of functions to be called which will construct the desired output
*/
static format_t *formatter_list;
static format_t *formatter_end;
static void print_timestamp(void);
static void print_tty_hdr1(void);
static void print_tty_hdr2(void);
static void print_cpu_hdr1(void);
static void print_cpu_hdr2(void);
static void print_tty_data(void);
static void print_cpu_data(void);
static void print_err_hdr(void);
static void print_disk_header(void);
static void hdrout(void);
static void disk_errors(void);
static void do_newline(void);
static void push_out(const char *, ...);
static void printhdr(int);
static void printxhdr(void);
static void usage(void);
static void do_args(int, char **);
static void do_format(void);
static void show_all_disks(void);
static void show_first_disk(void);
static void show_other_disks(void);
static void show_disk_errors(void *, void *, void *);
static void write_core_header(void);
int
{
long hz;
int iiter;
/*
* iostat historically showed CPU changes, even though
* it doesn't provide much useful information
*/
if (do_disk)
types |= SNAP_IODEVS;
if (do_disk && !do_partitions_only)
if (do_disk & DISK_IOPATH_LI) {
types |= SNAP_IOPATHS_LI;
}
if (do_disk & DISK_IOPATH_LTI) {
}
if (do_disk & DISK_ERROR_MASK)
if (do_partitions || do_partitions_only)
if (do_conversions)
if (do_devid)
if (do_controller) {
if (!(do_disk & PRINT_VERTICAL) ||
fail(0, "-C can only be used with -e or -x.");
}
/*
* Undocumented behavior - sending a SIGCONT will result
* in a new header being emitted. Used only if we're not
* doing extended headers. This is a historical
* artifact.
*/
if (!(do_disk & PRINT_VERTICAL))
if (interval)
kc = open_kstat();
if (interval)
/* compute width of "device" field */
do_format();
do {
if (getime == 0.0)
getime = 1.0;
}
if (formatter_list) {
while (tmp) {
}
}
/* only doing a single iteration, we are done */
if (iiter == 1)
continue;
/* Have a kip */
&caught_cont);
if (!suppress_state)
/* if config changed, show stats from boot */
}
} while (--iter);
(void) kstat_close(kc);
return (0);
}
/*
* Some magic numbers used in header formatting.
*
* DISK_LEN = length of either "kps tps serv" or "wps rps util"
* using 0 as the first position
*
* DISK_ERROR_LEN = length of "s/w h/w trn tot" with one space on
* either side. Does not use zero as first pos.
*
* DEVICE_LEN = length of "device" + 1 character.
*/
#define DISK_LEN 11
#define DISK_ERROR_LEN 16
#define DEVICE_LEN 7
/*ARGSUSED*/
static void
{
char *name;
char fbuf[SMALL_SCRATCH_BUFLEN];
return;
if (!do_raw) {
/*
* The length is less
* than the section
* which will be displayed
* on the next line.
* Center the entry.
*/
} else {
}
}
/*ARGSUSED*/
static void
{
}
/*
* Write out a two line header. What is written out depends on the flags
* selected but in the worst case consists of a tty header, a disk header
* providing information for 4 disks and a cpu header.
*
* The tty header consists of the word "tty" on the first line above the
* words "tin tout" on the next line. If present the tty portion consumes
* the first 10 characters of each line since "tin tout" is surrounded
* by single spaces.
*
* Each of the disk sections is a 14 character "block" in which the name of
* the disk is centered in the first 12 characters of the first line.
*
* The cpu section is an 11 character block with "cpu" centered over the
* section.
*
* The worst case should look as follows:
*
* 0---------1--------2---------3---------4---------5---------6---------7-------
* tty sd0 sd1 sd2 sd3 cpu
* tin tout kps tps serv kps tps serv kps tps serv kps tps serv us sy wt id
* NNN NNNN NNN NNN NNNN NNN NNN NNNN NNN NNN NNNN NNN NNN NNNN NN NN NN NN
*
* When -D is specified, the disk header looks as follows (worst case):
*
* 0---------1--------2---------3---------4---------5---------6---------7-------
* tty sd0 sd1 sd2 sd3 cpu
* tin tout rps wps util rps wps util rps wps util rps wps util us sy wt id
* NNN NNNN NNN NNN NNNN NNN NNN NNNN NNN NNN NNNN NNN NNN NNNN NN NN NN NN
*/
static void
{
/*
* If we're here because a signal fired, reenable the
* signal.
*/
if (sig)
caught_cont = 1;
/*
* Horizontal mode headers
*
* First line
*/
if (do_tty)
if (do_disk & DISK_NORMAL) {
}
if (do_cpu)
do_newline();
/*
* Second line
*/
if (do_tty)
if (do_disk & DISK_NORMAL) {
}
if (do_cpu)
do_newline();
}
/*
* Write out the extended header centered over the core information.
*/
static void
write_core_header(void)
{
char *edev = "extended device statistics";
if (do_raw == 0) {
/*
* The things we do to look nice...
*
* Center the core output header. Make sure we have the
* right number of trailing spaces for follow-on headers
*/
lead_space_ct /= 2;
if (lead_space_ct > 0) {
if (do_disk & DISK_ERRORS)
} else
} else
}
/*
* In extended mode headers, we don't want to reprint the header on
* signals as they are printed every time anyways.
*/
static void
printxhdr(void)
{
/*
* Vertical mode headers
*/
if (do_disk & DISK_EXTENDED)
if (do_disk & DISK_ERRORS)
if (do_conversions) {
} else {
if (do_tty)
if (do_cpu)
if (do_tty)
if (do_cpu)
}
}
/*
* Write out a line for this disk - note that show_disk writes out
* full lines or blocks for each selected disk.
*/
static void
{
char *disk_name;
int doit = 1;
int i;
char *fstr;
return;
switch (show_disk_mode) {
case SHOW_FIRST_ONLY:
return;
break;
case SHOW_SECOND_ONWARDS:
(*count)++;
return;
}
break;
default:
break;
}
/*
* Only do if we want IO stats - Avoids errors traveling this
* section if that's all we want to see.
*/
if (do_disk & DISK_IO_MASK) {
if (old) {
new->is_snaptime);
} else {
new->is_snaptime);
}
if (new->is_nr_children) {
/* synthetic path */
if (!old) {
}
}
}
if (hr_etime == 0.0)
/* reads per second */
/* writes per second */
/* transactions per second */
/*
*/
if (!do_megabytes)
iosize = 1024.0;
else
iosize = 1048576.0;
if (ldeltas) {
} else
krps = 0.0;
if (ldeltas) {
} else
kwps = 0.0;
/*
* Blocks transferred per second
*/
/*
* Average number of wait transactions waiting
*/
if (w_delta) {
} else
avw = 0.0;
/*
* Average number of run transactions waiting
*/
if (r_delta) {
} else
avr = 0.0;
/*
* Average wait service time in milliseconds
*/
if (avw != 0.0)
else
wserv = 0.0;
if (avr != 0.0)
else
rserv = 0.0;
} else {
rserv = 0.0;
wserv = 0.0;
serv = 0.0;
}
/* % of time there is a transaction waiting for service */
if (t_delta) {
w_pct *= 100.0;
/*
* Average the wait queue utilization over the
* the controller's devices, if this is a controller.
*/
} else
w_pct = 0.0;
/* % of time there is a transaction running */
if (t_delta) {
r_pct *= 100.0;
/*
* Average the percent busy over the controller's
* devices, if this is a controller.
*/
} else {
r_pct = 0.0;
}
/* % of time there is a transaction running */
if (do_interval) {
}
}
if ((!do_conversions) && ((suppress_zero == 0) ||
((do_disk & DISK_EXTENDED) == 0))) {
if (do_raw == 0) {
push_out("%-*.*s",
} else {
}
}
}
switch (do_disk & DISK_IO_MASK) {
case DISK_OLD:
if (do_raw == 0)
fstr = "%3.0f %3.0f %4.0f ";
else
fstr = "%.0f,%.0f,%.0f";
break;
case DISK_NEW:
if (do_raw == 0)
fstr = "%3.0f %3.0f %4.1f ";
else
fstr = "%.0f,%.0f,%.1f";
break;
case DISK_EXTENDED:
if (suppress_zero) {
doit = 0;
} else if (do_conversions == 0) {
if (do_raw == 0) {
push_out("%-*.*s",
} else {
}
}
}
if (doit) {
if (!do_conversions) {
if (do_raw == 0) {
fstr = " %6.1f %6.1f %6.1f %6.1f "
"%4.1f %4.1f %6.1f %3.0f "
"%3.0f ";
} else {
fstr = "%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,"
"%.1f,%.0f,%.0f";
}
} else {
if (do_raw == 0) {
fstr = " %6.1f %6.1f %6.1f %6.1f "
"%4.1f %4.1f %6.1f %6.1f "
"%3.0f %3.0f ";
} else {
fstr = "%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,"
"%.1f,%.1f,%.0f,%.0f";
}
}
}
break;
}
if (do_disk & DISK_ERRORS) {
if ((do_disk == DISK_ERRORS)) {
if (do_raw == 0)
}
char *efstr;
if (do_raw == 0)
efstr = "%3u ";
else
efstr = "%u";
toterrs = 0;
for (i = 0; i < 3; i++) {
case KSTAT_DATA_ULONG:
break;
case KSTAT_DATA_ULONGLONG:
/*
* We're only set up to
* write out the low
* order 32-bits so
* just grab that.
*/
break;
default:
break;
}
}
} else {
if (do_raw == 0)
push_out(" 0 0 0 0 ");
else
push_out("0,0,0,0");
}
}
char *lu;
char lub[SMALL_SCRATCH_BUFLEN];
if (lu) {
else {
*lu = 0;
*lu = '/';
}
} else
if (mount_pt) {
if (do_raw == 0)
push_out(" (%s)",
else
push_out("(%s)",
}
}
}
}
do_newline();
(*count)++;
}
static void
usage(void)
{
"Usage: iostat [-cCdDeEiImMnpPrstxXYz] "
" [-l n] [-T d|u] [disk ...] [interval [count]]\n"
"\t\t-c: report percentage of time system has spent\n"
"\t\t-C: report disk statistics by controller\n"
"\t\t\tservice time in milliseconds \n"
"\t\t\tpercentage disk utilization \n"
"\t\t-e: report device error summary statistics\n"
"\t\t-E: report extended device error statistics\n"
"\t\t-i: show device IDs for -E output\n"
"\t\t-I: report the counts in each interval,\n"
"\t\t\tinstead of rates, where applicable\n"
"\t\t-l n: Limit the number of disks to n\n"
"\t\t-m: Display mount points (most useful with -p)\n"
"\t\t-n: convert device names to cXdYtZ format\n"
"\t\t-p: report per-partition disk statistics\n"
"\t\t-P: report per-partition disk statistics only,\n"
"\t\t\tno per-device disk statistics\n"
"\t\t-r: Display data in comma separated format\n"
"\t\t-s: Suppress state change messages\n"
"\t\t-T d|u Display a timestamp in date (d) or unix "
"time_t (u)\n"
"\t\t-x: display extended disk statistics\n"
"\t\t-X: display I/O path statistics\n"
"\t\t-Y: display I/O path (I/T/L) statistics\n"
"\t\t-z: Suppress entries with all zero values\n");
exit(1);
}
/*ARGSUSED*/
static void
{
int i, len;
char *dev_name;
return;
return;
if (len > 20)
else if (len > 16)
else {
if (do_conversions)
else
}
col = 0;
/* skip kstats that the driver did not kstat_named_init */
continue;
case KSTAT_DATA_CHAR:
do_devid) {
push_out("Device Id: %s ",
} else
push_out("Device Id: ");
} else {
}
break;
case KSTAT_DATA_ULONG:
col += 4;
break;
case KSTAT_DATA_ULONGLONG:
push_out("%s: %2.2fGB <%llu bytes>\n",
col = 0;
break;
}
col += 4;
break;
}
do_newline();
col = 0;
}
}
if (col > 0) {
do_newline();
}
do_newline();
}
void
{
int c;
int errflg = 0;
extern char *optarg;
extern int optind;
switch (c) {
case 't':
do_tty++;
break;
case 'd':
break;
case 'D':
break;
case 'x':
do_disk |= DISK_EXTENDED;
break;
case 'X':
if (do_disk & DISK_IOPATH_LTI)
errflg++; /* -Y already used */
else
break;
case 'Y':
if (do_disk & DISK_IOPATH_LI)
errflg++; /* -X already used */
else
break;
case 'C':
break;
case 'c':
do_cpu++;
break;
case 'I':
do_interval++;
break;
case 'p':
break;
case 'P':
break;
case 'n':
break;
case 'M':
do_megabytes++;
break;
case 'e':
do_disk |= DISK_ERRORS;
break;
case 'E':
break;
case 'i':
do_devid = 1;
break;
case 's':
suppress_state = 1;
break;
case 'z':
suppress_zero = 1;
break;
case 'm':
show_mountpts = 1;
break;
case 'T':
if (optarg) {
if (*optarg == 'u')
else if (*optarg == 'd')
else
errflg++;
} else
errflg++;
break;
case 'r':
do_raw = 1;
break;
case 'l':
usage();
break;
case '?':
errflg++;
}
usage();
}
if (errflg) {
usage();
}
/* if no output classes explicity specified, use defaults */
/*
* multi-path options (-X, -Y) without a specific vertical
* output format (-x, -e, -E) imply extended -x format
*/
!(do_disk & PRINT_VERTICAL))
do_disk |= DISK_EXTENDED;
/*
* If conflicting options take the preferred
* -D and -x result in -x
* -d or -D and -e or -E gives only whatever -d or -D was specified
*/
do_disk &= ~DISK_NORMAL;
do_disk &= ~DISK_ERROR_MASK;
/* nfs, tape, always shown */
/*
* If limit == 0 then no command line limit was set, else if any of
* the flags that cause unlimited disks were not set,
* use the default of 4
*/
if (df.if_max_iodevs == 0) {
df.if_skip_floppy = 0;
}
}
if (do_disk) {
count++;
i++;
}
/*
* "Note: disks explicitly requested
* are not subject to this disk limit"
*/
df.if_nr_names = 0;
}
if (interval < 1)
fail(0, "invalid interval");
optind++;
if (iter < 1)
fail(0, "invalid count");
optind++;
}
}
if (interval == 0)
iter = 1;
usage();
}
/*
* Driver for doing the extended header formatting. Will produce
* the function stack needed to output an extended header based
* on the options selected.
*/
void
do_format(void)
{
char header[SMALL_SCRATCH_BUFLEN];
char ch;
char iosz;
const char *fstr;
disk_header[0] = 0;
if (do_disk & DISK_ERRORS) {
if (do_raw == 0) {
} else
} else
switch (do_disk & DISK_IO_MASK) {
case DISK_OLD:
if (do_raw == 0)
fstr = "%cp%c tp%c serv ";
else
fstr = "%cp%c,tp%c,serv";
break;
case DISK_NEW:
if (do_raw == 0)
fstr = "rp%c wp%c util ";
else
fstr = "%rp%c,wp%c,util";
break;
case DISK_EXTENDED:
/* This is -x option */
if (!do_conversions) {
/* without -n option */
if (do_raw == 0) {
/* without -r option */
(void) snprintf(disk_header,
sizeof (disk_header),
"%-*.*s r/%c w/%c "
"%cr/%c %cw/%c wait actv "
"svc_t %%%%w %%%%b %s",
} else {
/* with -r option */
(void) snprintf(disk_header,
sizeof (disk_header),
"device,r/%c,w/%c,%cr/%c,%cw/%c,"
"wait,actv,svc_t,%%%%w,"
"%%%%b,%s",
}
} else {
/* with -n option */
if (do_raw == 0) {
fstr = " r/%c w/%c %cr/%c "
"%cw/%c wait actv wsvc_t asvc_t "
"%%%%w %%%%b %sdevice";
} else {
fstr = "r/%c,w/%c,%cr/%c,%cw/%c,"
"wait,actv,wsvc_t,asvc_t,"
"%%%%w,%%%%b,%sdevice";
}
(void) snprintf(disk_header,
sizeof (disk_header),
}
break;
default:
break;
}
/* do DISK_ERRORS header (already added above for DISK_EXTENDED) */
if ((do_disk & DISK_ERRORS) &&
if (!do_conversions) {
if (do_raw == 0)
(void) snprintf(disk_header,
sizeof (disk_header), "%-*.*s %s",
else
(void) snprintf(disk_header,
} else {
if (do_raw == 0) {
(void) snprintf(disk_header,
sizeof (disk_header),
" %sdevice", header);
} else {
(void) snprintf(disk_header,
sizeof (disk_header),
"%s,device", header);
}
}
} else {
/*
* Need to subtract two characters for the % escape in
* the string.
*/
}
if (do_timestamp)
/*
* -n *and* (-E *or* -e *or* -x)
*/
if (do_tty)
if (do_cpu)
if (do_tty)
if (do_cpu)
if (do_tty)
if (do_cpu)
printxhdr();
} else {
/*
* line in vertical mode.
*/
if (do_disk & PRINT_VERTICAL) {
printxhdr();
if (do_tty)
if (do_cpu)
} else {
if (do_tty)
if (do_cpu)
}
}
if (do_disk & DISK_EXTENDED_ERRORS)
}
/*
* Add a new function to the list of functions
* for this invocation. Once on the stack the
* function is never removed nor does its place
* change.
*/
void
{
if (formatter_end)
else
formatter_end = tmp;
}
/*
* The functions after this comment are devoted to printing
* various parts of the header. They are selected based on the
* options provided when the program was invoked. The functions
* are either directly invoked in printhdr() or are indirectly
* invoked by being placed on the list of functions used when
* extended headers are used.
*/
void
print_tty_hdr1(void)
{
char *fstr;
char *dstr;
if (do_raw == 0) {
fstr = "%10.10s";
dstr = "tty ";
} else {
fstr = "%s";
dstr = "tty";
}
}
void
print_tty_hdr2(void)
{
if (do_raw == 0)
else
push_out("tin,tout");
}
void
print_cpu_hdr1(void)
{
char *dstr;
if (do_raw == 0)
dstr = " cpu";
else
dstr = "cpu";
}
void
print_cpu_hdr2(void)
{
char *dstr;
if (do_raw == 0)
dstr = " us sy wt id";
else
dstr = "us,sy,wt,id";
}
/*
* Assumption is that tty data is always first - no need for raw mode leading
* comma.
*/
void
print_tty_data(void)
{
char *fstr;
double raw;
double outch;
if (oldss)
if (do_raw == 0)
fstr = " %3.0f %4.0f ";
else
fstr = "%.0f,%.0f";
}
/*
* Write out CPU data
*/
void
print_cpu_data(void)
{
char *fstr;
if (oldss)
if (do_raw == 0)
fstr = " %2.0f %2.0f %2.0f %2.0f";
else
fstr = "%.0f,%.0f,%.0f,%.0f";
}
/*
* Emit the appropriate header.
*/
void
hdrout(void)
{
if (do_raw == 0) {
if (--tohdr == 0)
printhdr(0);
} else if (hdr_out == 0) {
printhdr(0);
hdr_out = 1;
}
}
/*
* Write out disk errors when -E is specified.
*/
void
disk_errors(void)
{
}
void
show_first_disk(void)
{
int count = 0;
}
void
show_other_disks(void)
{
int count = 0;
}
void
show_all_disks(void)
{
int count = 0;
}
/*
* Write a newline out and clear the lineout flag.
*/
static void
do_newline(void)
{
if (lineout) {
(void) putchar('\n');
lineout = 0;
}
}
/*
* Generalized printf function that determines what extra
* to print out if we're in raw mode. At this time we
* don't care about errors.
*/
static void
{
(void) putchar(',');
lineout = 1;
}
/*
* Emit the header string when -e is specified.
*/
static void
print_err_hdr(void)
{
char obuf[SMALL_SCRATCH_BUFLEN];
if (do_raw) {
push_out("errors");
return;
}
if (do_conversions == 0) {
if (!(do_disk & DISK_EXTENDED)) {
"%11s", one_blank);
}
} else if (do_disk == DISK_ERRORS)
else
push_out("---- errors --- ");
}
/*
* Emit the header string when -e is specified.
*/
static void
print_disk_header(void)
{
}
/*
* Write out a timestamp. Format is all that goes out on
* the line so no use of push_out.
*
* Write out as decimal reprentation of time_t value
* (-T u was specified) or the string returned from
* ctime() (-T d was specified).
*/
static void
print_timestamp(void)
{
time_t t;
if (time(&t) != -1) {
if (do_timestamp == UDATE) {
(void) printf("%ld\n", t);
} else if (do_timestamp == CDATE) {
char *cpt;
if (cpt) {
}
}
}
}
/*
* No, UINTMAX_MAX isn't the right thing here since
* it is #defined to be either INT32_MAX or INT64_MAX
* depending on the whether _LP64 is defined.
*
* We want to handle the odd future case of having
* ulonglong_t be more than 64 bits but we have
* no nice #define MAX value we can drop in place
* without having to change this code in the future.
*/
{
else
}
/*
* Take the difference of an unsigned 32
* bit int attempting to cater for
* overflow.
*/
{
else
}
/*
* This is exactly what is needed for standard iostat output,
* but make sure to use it only for that
*/
#define EPSILON (0.1)
static int
{
}
static int
{
char *end;
long tmp;
errno = 0;
return ((int)tmp);
}