#define IOKIT 1
#include <sys/param.h>
#include <sys/sysctl.h>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/storage/IOBlockStorageDriver.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/IOBSD.h>
#include <err.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAXDRIVES 16
#define MAXDRIVENAME 31
struct drivestats {
io_registry_entry_t driver;
char name[MAXDRIVENAME + 1];
u_int64_t blocksize;
u_int64_t total_bytes;
u_int64_t total_transfers;
u_int64_t total_time;
};
static struct drivestats drivestat[MAXDRIVES];
static struct timeval cur_time, last_time;
struct statinfo {
long tk_nin;
long tk_nout;
host_cpu_load_info_data_t load;
};
static struct statinfo cur, last;
static mach_port_t host_priv_port;
static mach_port_t masterPort;
static int num_devices;
static int maxshowdevs;
static int dflag = 0, Iflag = 0, Cflag = 0, Tflag = 0, oflag = 0, Kflag = 0;
static volatile sig_atomic_t phdr_flag = 0;
static void usage(void);
static void phdr(int signo);
static void do_phdr();
static void devstats(int perf_select, long double etime, int havelast);
static void cpustats(void);
static int readvar(const char *name, void *ptr, size_t len);
static int record_all_devices(void);
static int record_one_device(char *name);
static int record_device(io_registry_entry_t drive);
static long double compute_etime(struct timeval cur_time,
struct timeval prev_time);
static void
usage(void)
{
fprintf(stderr, "usage: iostat [-CdIKoT?] [-c count] [-n devs]\n"
"\t [-w wait] [drives]\n");
}
int
main(int argc, char **argv)
{
int c;
int hflag = 0, cflag = 0, wflag = 0, nflag = 0;
int count = 0, waittime = 0;
int headercount;
int num_devices_specified;
int havelast = 0;
maxshowdevs = 3;
while ((c = getopt(argc, argv, "c:CdIKM:n:oTw:?")) != -1) {
switch(c) {
case 'c':
cflag++;
count = atoi(optarg);
if (count < 1)
errx(1, "count %d is < 1", count);
break;
case 'C':
Cflag++;
break;
case 'd':
dflag++;
break;
case 'I':
Iflag++;
break;
case 'K':
Kflag++;
break;
case 'n':
nflag++;
maxshowdevs = atoi(optarg);
if (maxshowdevs < 0)
errx(1, "number of devices %d is < 0",
maxshowdevs);
break;
case 'o':
oflag++;
break;
case 'T':
Tflag++;
break;
case 'w':
wflag++;
waittime = atoi(optarg);
if (waittime < 1)
errx(1, "wait time is < 1");
break;
default:
usage();
exit(1);
break;
}
}
argc -= optind;
argv += optind;
host_priv_port = mach_host_self();
IOMasterPort(bootstrap_port, &masterPort);
if (dflag == 0) {
Cflag = 1;
Tflag = 1;
}
if (nflag == 0) {
if (oflag > 0) {
if ((dflag > 0) && (Cflag == 0) && (Tflag == 0))
maxshowdevs = 5;
else if ((dflag > 0) && (Tflag > 0) && (Cflag == 0))
maxshowdevs = 5;
else
maxshowdevs = 4;
} else {
if ((dflag > 0) && (Cflag == 0))
maxshowdevs = 4;
else
maxshowdevs = 3;
}
}
for (num_devices_specified = 0; *argv; ++argv) {
if (isdigit(**argv))
break;
if (record_one_device(*argv))
errx(1, "can't record '%s' for monitoring");
num_devices_specified++;
}
if (nflag == 0 && maxshowdevs < num_devices_specified)
maxshowdevs = num_devices_specified;
if ((num_devices_specified == 0) && record_all_devices())
err(1, "can't find any devices to display");
if (*argv) {
waittime = atoi(*argv);
if (wflag != 0)
warnx("discarding previous wait interval, using"
" %d instead", waittime);
wflag++;
if (*++argv) {
count = atoi(*argv);
if (cflag != 0)
warnx("discarding previous count, using %d"
" instead", count);
cflag++;
} else
count = -1;
}
if ((wflag == 0) && (cflag > 0))
waittime = 1;
if ((wflag > 0) && (cflag == 0))
count = -1;
cur.tk_nout = 0;
cur.tk_nin = 0;
if (readvar("kern.boottime", &cur_time, sizeof(cur_time)) != 0)
exit(1);
(void)signal(SIGCONT, phdr);
for (headercount = 1;;) {
long tmp;
long double etime;
if (Tflag > 0) {
if ((readvar("kern.tty_nin", &cur.tk_nin,
sizeof(cur.tk_nin)) != 0)
|| (readvar("kern.tty_nout",
&cur.tk_nout, sizeof(cur.tk_nout))!= 0)) {
Tflag = 0;
warnx("disabling TTY statistics");
}
}
if (phdr_flag) {
phdr_flag = 0;
do_phdr();
}
if (!--headercount) {
do_phdr();
headercount = 20;
}
last_time = cur_time;
gettimeofday(&cur_time, NULL);
if (Tflag > 0) {
tmp = cur.tk_nin;
cur.tk_nin -= last.tk_nin;
last.tk_nin = tmp;
tmp = cur.tk_nout;
cur.tk_nout -= last.tk_nout;
last.tk_nout = tmp;
}
etime = compute_etime(cur_time, last_time);
if (etime == 0.0)
etime = 1.0;
if (Tflag > 0)
printf("%4.0Lf%5.0Lf", cur.tk_nin / etime,
cur.tk_nout / etime);
devstats(hflag, etime, havelast);
if (Cflag > 0)
cpustats();
printf("\n");
fflush(stdout);
if (count >= 0 && --count <= 0)
break;
sleep(waittime);
havelast = 1;
}
exit(0);
}
static void
phdr(int signo)
{
phdr_flag = 1;
}
static void
do_phdr()
{
register int i;
if (Tflag > 0)
(void)printf(" tty");
for (i = 0; (i < num_devices); i++){
if (oflag > 0)
(void)printf("%12.6s ", drivestat[i].name);
else
printf("%15.6s ", drivestat[i].name);
}
if (Cflag > 0)
(void)printf(" cpu\n");
else
(void)printf("\n");
if (Tflag > 0)
(void)printf(" tin tout");
for (i=0; i < num_devices; i++){
if (oflag > 0) {
if (Iflag == 0)
(void)printf(" sps tps msps ");
else
(void)printf(" blk xfr msps ");
} else {
if (Iflag == 0)
printf(" KB/t tps MB/s ");
else
printf(" KB/t xfrs MB ");
}
}
if (Cflag > 0)
(void)printf(" us sy id\n");
else
printf("\n");
}
static void
devstats(int perf_select, long double etime, int havelast)
{
CFNumberRef number;
CFDictionaryRef properties;
CFDictionaryRef statistics;
long double transfers_per_second;
long double kb_per_transfer, mb_per_second;
u_int64_t value;
u_int64_t total_bytes, total_transfers, total_blocks, total_time;
u_int64_t interval_bytes, interval_transfers, interval_blocks;
u_int64_t interval_time;
long double interval_mb;
long double blocks_per_second, ms_per_transaction;
kern_return_t status;
int i;
for (i = 0; i < num_devices; i++) {
total_bytes = 0;
total_transfers = 0;
total_time = 0;
status = IORegistryEntryCreateCFProperties(drivestat[i].driver,
(CFMutableDictionaryRef *)&properties,
kCFAllocatorDefault,
kNilOptions);
if (status != KERN_SUCCESS)
err(1, "device has no properties");
statistics = (CFDictionaryRef)CFDictionaryGetValue(properties,
CFSTR(kIOBlockStorageDriverStatisticsKey));
if (statistics) {
if ((number = (CFNumberRef)CFDictionaryGetValue(statistics,
CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey)))) {
CFNumberGetValue(number, kCFNumberSInt64Type, &value);
total_bytes += value;
}
if ((number = (CFNumberRef)CFDictionaryGetValue(statistics,
CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey)))) {
CFNumberGetValue(number, kCFNumberSInt64Type, &value);
total_bytes += value;
}
if ((number = (CFNumberRef)CFDictionaryGetValue(statistics,
CFSTR(kIOBlockStorageDriverStatisticsReadsKey)))) {
CFNumberGetValue(number, kCFNumberSInt64Type, &value);
total_transfers += value;
}
if ((number = (CFNumberRef)CFDictionaryGetValue(statistics,
CFSTR(kIOBlockStorageDriverStatisticsWritesKey)))) {
CFNumberGetValue(number, kCFNumberSInt64Type, &value);
total_transfers += value;
}
if ((number = (CFNumberRef)CFDictionaryGetValue(statistics,
CFSTR(kIOBlockStorageDriverStatisticsLatentReadTimeKey)))) {
CFNumberGetValue(number, kCFNumberSInt64Type, &value);
total_time += value;
}
if ((number = (CFNumberRef)CFDictionaryGetValue(statistics,
CFSTR(kIOBlockStorageDriverStatisticsLatentWriteTimeKey)))) {
CFNumberGetValue(number, kCFNumberSInt64Type, &value);
total_time += value;
}
}
CFRelease(properties);
interval_bytes = total_bytes - drivestat[i].total_bytes;
interval_transfers = total_transfers
- drivestat[i].total_transfers;
interval_time = total_time - drivestat[i].total_time;
if ((Iflag == 0) || (drivestat[i].total_bytes == 0)) {
drivestat[i].total_bytes = total_bytes;
drivestat[i].total_transfers = total_transfers;
drivestat[i].total_time = total_time;
}
interval_blocks = interval_bytes / drivestat[i].blocksize;
total_blocks = total_bytes / drivestat[i].blocksize;
blocks_per_second = interval_blocks / etime;
transfers_per_second = interval_transfers / etime;
mb_per_second = (interval_bytes / etime) / (1024 * 1024);
kb_per_transfer = (interval_transfers > 0) ?
((long double)interval_bytes / interval_transfers)
/ 1024 : 0;
ms_per_transaction = (interval_transfers > 0) ?
((long double)interval_time / interval_transfers)
/ 1000 : 0;
if (Kflag)
total_blocks = total_blocks * drivestat[i].blocksize
/ 1024;
if (oflag > 0) {
int msdig = (ms_per_transaction < 100.0) ? 1 : 0;
if (Iflag == 0)
printf("%4.0Lf%4.0Lf%5.*Lf ",
blocks_per_second,
transfers_per_second,
msdig,
ms_per_transaction);
else
printf("%4.1qu%4.1qu%5.*Lf ",
interval_blocks,
interval_transfers,
msdig,
ms_per_transaction);
} else {
if (Iflag == 0)
printf(" %5.2Lf %3.0Lf %5.2Lf ",
kb_per_transfer,
transfers_per_second,
mb_per_second);
else {
interval_mb = interval_bytes;
interval_mb /= 1024 * 1024;
printf(" %5.2Lf %3.1qu %5.2Lf ",
kb_per_transfer,
interval_transfers,
interval_mb);
}
}
}
}
static void
cpustats(void)
{
mach_msg_type_number_t count;
kern_return_t status;
double time;
count = HOST_CPU_LOAD_INFO_COUNT;
status = host_statistics(host_priv_port, HOST_CPU_LOAD_INFO,
(host_info_t)&cur.load, &count);
if (status != KERN_SUCCESS)
errx(1, "couldn't fetch CPU stats");
time = 0.0;
cur.load.cpu_ticks[CPU_STATE_USER]
-= last.load.cpu_ticks[CPU_STATE_USER];
last.load.cpu_ticks[CPU_STATE_USER]
+= cur.load.cpu_ticks[CPU_STATE_USER];
time += cur.load.cpu_ticks[CPU_STATE_USER];
cur.load.cpu_ticks[CPU_STATE_SYSTEM]
-= last.load.cpu_ticks[CPU_STATE_SYSTEM];
last.load.cpu_ticks[CPU_STATE_SYSTEM]
+= cur.load.cpu_ticks[CPU_STATE_SYSTEM];
time += cur.load.cpu_ticks[CPU_STATE_SYSTEM];
cur.load.cpu_ticks[CPU_STATE_IDLE]
-= last.load.cpu_ticks[CPU_STATE_IDLE];
last.load.cpu_ticks[CPU_STATE_IDLE]
+= cur.load.cpu_ticks[CPU_STATE_IDLE];
time += cur.load.cpu_ticks[CPU_STATE_IDLE];
printf("%3.0f",
rint(100. * cur.load.cpu_ticks[CPU_STATE_USER]
/ (time ? time : 1)));
printf("%3.0f",
rint(100. * cur.load.cpu_ticks[CPU_STATE_SYSTEM]
/ (time ? time : 1)));
printf("%3.0f",
rint(100. * cur.load.cpu_ticks[CPU_STATE_IDLE]
/ (time ? time : 1)));
}
static int
readvar(const char *name, void *ptr, size_t len)
{
int oid[4];
int oidlen;
size_t nlen = len;
if (sysctlbyname(name, ptr, &nlen, NULL, 0) == -1) {
if (errno != ENOENT) {
warn("sysctl(%s) failed", name);
return (1);
}
if (!strcmp(name, "kern.boottime")) {
oid[0] = CTL_KERN;
oid[1] = KERN_BOOTTIME;
oidlen = 2;
} else {
warn("sysctl(%s) failed", name);
return (1);
}
nlen = len;
if (sysctl(oid, oidlen, ptr, &nlen, NULL, 0) == -1) {
warn("sysctl(%s) failed", name);
return (1);
}
}
if (nlen != len) {
warnx("sysctl(%s): expected %lu, got %lu", name,
(unsigned long)len, (unsigned long)nlen);
return (1);
}
return (0);
}
static long double
compute_etime(struct timeval cur_time, struct timeval prev_time)
{
struct timeval busy_time;
u_int64_t busy_usec;
long double etime;
timersub(&cur_time, &prev_time, &busy_time);
busy_usec = busy_time.tv_sec;
busy_usec *= 1000000;
busy_usec += busy_time.tv_usec;
etime = busy_usec;
etime /= 1000000;
return(etime);
}
static int
record_all_devices(void)
{
io_iterator_t drivelist;
io_registry_entry_t drive;
CFMutableDictionaryRef match;
int error, ndrives;
kern_return_t status;
match = IOServiceMatching("IOMedia");
CFDictionaryAddValue(match, CFSTR(kIOMediaWholeKey), kCFBooleanTrue);
status = IOServiceGetMatchingServices(masterPort, match, &drivelist);
if (status != KERN_SUCCESS)
errx(1, "couldn't match whole IOMedia devices");
error = 1;
ndrives = 0;
while ((drive = IOIteratorNext(drivelist))
&& (ndrives < maxshowdevs)) {
if (!record_device(drive)) {
error = 0;
ndrives++;
}
IOObjectRelease(drive);
}
IOObjectRelease(drivelist);
return(error);
}
static int
record_one_device(char *name)
{
io_iterator_t drivelist;
io_registry_entry_t drive;
kern_return_t status;
status = IOServiceGetMatchingServices(masterPort,
IOBSDNameMatching(masterPort, kNilOptions, name),
&drivelist);
if (status != KERN_SUCCESS)
errx(1, "couldn't match '%s'", name);
if ((drive = IOIteratorNext(drivelist)) == NULL)
errx(1, "'%s' not found", name);
if (!IOObjectConformsTo(drive, "IOMedia"))
errx(1, "'%s' is not a storage device", name);
if (record_device(drive))
errx(1, "could not record '%s' for monitoring", name);
IOObjectRelease(drive);
IOObjectRelease(drivelist);
return(0);
}
static int
record_device(io_registry_entry_t drive)
{
io_registry_entry_t parent;
CFDictionaryRef properties;
CFStringRef name;
CFNumberRef number;
kern_return_t status;
status = IORegistryEntryGetParentEntry(drive,
kIOServicePlane, &parent);
if (status != KERN_SUCCESS)
errx(1, "device has no parent");
if (IOObjectConformsTo(parent, "IOBlockStorageDriver")) {
drivestat[num_devices].driver = parent;
status = IORegistryEntryCreateCFProperties(drive,
(CFMutableDictionaryRef *)&properties,
kCFAllocatorDefault,
kNilOptions);
if (status != KERN_SUCCESS)
errx(1, "device has no properties");
name = (CFStringRef)CFDictionaryGetValue(properties,
CFSTR(kIOBSDNameKey));
CFStringGetCString(name, drivestat[num_devices].name,
MAXDRIVENAME, CFStringGetSystemEncoding());
number = (CFNumberRef)CFDictionaryGetValue(properties,
CFSTR(kIOMediaPreferredBlockSizeKey));
CFNumberGetValue(number, kCFNumberSInt64Type,
&drivestat[num_devices].blocksize);
CFRelease(properties);
num_devices++;
return(0);
}
IOObjectRelease(parent);
return(1);
}