#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 <mach/mach_host.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, Uflag = 0, Kflag = 0;
static volatile sig_atomic_t phdr_flag = 0;
static IONotificationPortRef notifyPort;
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 void loadstats(void);
static int readvar(const char *name, void *ptr, size_t len);
static int record_all_devices(void);
static void record_drivelist(void* context, io_iterator_t drivelist);
static void remove_drivelist(void* context, io_iterator_t drivelist);
static int record_one_device(char *name);
static int record_device(io_registry_entry_t drive);
static int compare_drivestats(const void* pa, const void* pb);
static long double compute_etime(struct timeval cur_time,
struct timeval prev_time);
static void
usage(void)
{
fprintf(stderr, "usage: iostat [-CUdIKoT?] [-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;
CFRunLoopSourceRef rls;
maxshowdevs = 3;
while ((c = getopt(argc, argv, "c:CdIKM:n:oTUw:?")) != -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 'U':
Uflag++;
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);
notifyPort = IONotificationPortCreate(masterPort);
rls = IONotificationPortGetRunLoopSource(notifyPort);
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
if (dflag == 0) {
Cflag = 1;
Tflag = 1;
Uflag = 1;
}
Tflag = 0;
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 (!--headercount || phdr_flag) {
phdr_flag = 0;
headercount = 20;
do_phdr();
}
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();
if (Uflag > 0)
loadstats();
printf("\n");
fflush(stdout);
if (count >= 0 && --count <= 0)
break;
CFRunLoopRunInMode(kCFRunLoopDefaultMode, (CFTimeInterval)waittime, 1);
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 < maxshowdevs; i++){
if (oflag > 0)
(void)printf("%12.6s ", drivestat[i].name);
else
printf("%15.6s ", drivestat[i].name);
}
if (Cflag > 0)
(void)printf(" cpu");
if (Uflag > 0)
(void)printf(" load average\n");
else
(void)printf("\n");
if (Tflag > 0)
(void)printf(" tin tout");
for (i=0; i < num_devices && i < maxshowdevs; 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");
if (Uflag > 0)
(void)printf(" 1m 5m 15m\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 < maxshowdevs; i++) {
total_bytes = 0;
total_transfers = 0;
total_time = 0;
status = IORegistryEntryCreateCFProperties(drivestat[i].driver,
(CFMutableDictionaryRef *)&properties,
kCFAllocatorDefault,
kNilOptions);
if (status != KERN_SUCCESS)
continue;
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(" %7.2Lf %3.0Lf %5.2Lf ",
kb_per_transfer,
transfers_per_second,
mb_per_second);
else {
interval_mb = interval_bytes;
interval_mb /= 1024 * 1024;
printf(" %7.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];
#define PTIME(kind) { \
double cpu = rint(100. * cur.load.cpu_ticks[kind] / (time ? time : 1));\
printf("%*.0f", (100 == cpu) ? 4 : 3, cpu); \
}
PTIME(CPU_STATE_USER);
PTIME(CPU_STATE_SYSTEM);
PTIME(CPU_STATE_IDLE);
}
static void
loadstats(void)
{
double loadavg[3];
if(getloadavg(loadavg,3)!=3)
errx(1, "couldn't fetch load average");
printf(" %4.2f %4.2f %4.2f",loadavg[0],loadavg[1],loadavg[2]);
}
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;
CFMutableDictionaryRef match;
kern_return_t status;
match = IOServiceMatching("IOMedia");
CFDictionaryAddValue(match, CFSTR(kIOMediaWholeKey), kCFBooleanTrue);
CFRetain(match);
status = IOServiceAddMatchingNotification(notifyPort, kIOFirstMatchNotification, match, &record_drivelist, NULL, &drivelist);
if (status != KERN_SUCCESS)
errx(1, "couldn't match whole IOMedia devices");
record_drivelist(NULL, drivelist);
status = IOServiceAddMatchingNotification(notifyPort, kIOTerminatedNotification, match, &remove_drivelist, NULL, &drivelist);
if (status != KERN_SUCCESS)
errx(1, "couldn't match whole IOMedia device removal");
remove_drivelist(NULL, drivelist);
return(0);
}
static void record_drivelist(void* context, io_iterator_t drivelist)
{
io_registry_entry_t drive;
while ((drive = IOIteratorNext(drivelist))) {
if (num_devices < MAXDRIVES) {
record_device(drive);
phdr_flag = 1;
}
IOObjectRelease(drive);
}
qsort(drivestat, num_devices, sizeof(struct drivestats), &compare_drivestats);
}
static void remove_drivelist(void* context, io_iterator_t drivelist)
{
io_registry_entry_t drive;
while ((drive = IOIteratorNext(drivelist))) {
kern_return_t status;
char bsdname[MAXDRIVENAME];
CFDictionaryRef properties;
CFStringRef name;
status = IORegistryEntryCreateCFProperties(drive,
(CFMutableDictionaryRef *)&properties,
kCFAllocatorDefault,
kNilOptions);
if (status != KERN_SUCCESS) continue;
name = (CFStringRef)CFDictionaryGetValue(properties,
CFSTR(kIOBSDNameKey));
CFRelease(properties);
if (!name) continue;
if (CFStringGetCString(name, bsdname, MAXDRIVENAME, CFStringGetSystemEncoding())) {
int i;
for (i = 0; i < num_devices; ++i) {
if (strcmp(bsdname,drivestat[i].name) == 0) {
if (i < MAXDRIVES-1) {
memmove(&drivestat[i], &drivestat[i+1], sizeof(struct drivestats)*(MAXDRIVES-i));
}
--num_devices;
phdr_flag = 1;
qsort(drivestat, num_devices, sizeof(struct drivestats), &compare_drivestats);
break;
}
}
}
IOObjectRelease(drive);
}
}
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)))
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));
if (name)
CFStringGetCString(name, drivestat[num_devices].name,
MAXDRIVENAME, CFStringGetSystemEncoding());
else {
errx(1, "device does not have a BSD name");
}
number = (CFNumberRef)CFDictionaryGetValue(properties,
CFSTR(kIOMediaPreferredBlockSizeKey));
if (number)
CFNumberGetValue(number, kCFNumberSInt64Type,
&drivestat[num_devices].blocksize);
else
errx(1, "device does not have a preferred block size");
CFRelease(properties);
num_devices++;
return(0);
}
IOObjectRelease(parent);
return(1);
}
static int
compare_drivestats(const void* pa, const void* pb)
{
struct drivestats* a = (struct drivestats*)pa;
struct drivestats* b = (struct drivestats*)pb;
return strcmp(a->name, b->name);
}