#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/errno.h>
#include <ctype.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "devstat.h"
char devstat_errbuf[DEVSTAT_ERRBUF_SIZE];
struct devstat_match_table match_table[] = {
{"da", DEVSTAT_TYPE_DIRECT, DEVSTAT_MATCH_TYPE},
{"nvme", DEVSTAT_TYPE_DIRECT, DEVSTAT_MATCH_TYPE},
{"xa", DEVSTAT_TYPE_DIRECT, DEVSTAT_MATCH_TYPE},
{"cd", DEVSTAT_TYPE_CDROM, DEVSTAT_MATCH_TYPE},
{"scsi", DEVSTAT_TYPE_IF_SCSI, DEVSTAT_MATCH_IF},
{"ide", DEVSTAT_TYPE_IF_IDE, DEVSTAT_MATCH_IF},
{"other", DEVSTAT_TYPE_IF_OTHER, DEVSTAT_MATCH_IF},
{"worm", DEVSTAT_TYPE_WORM, DEVSTAT_MATCH_TYPE},
{"sa", DEVSTAT_TYPE_SEQUENTIAL,DEVSTAT_MATCH_TYPE},
{"pass", DEVSTAT_TYPE_PASS, DEVSTAT_MATCH_PASS},
{"optical", DEVSTAT_TYPE_OPTICAL, DEVSTAT_MATCH_TYPE},
{"array", DEVSTAT_TYPE_STORARRAY, DEVSTAT_MATCH_TYPE},
{"changer", DEVSTAT_TYPE_CHANGER, DEVSTAT_MATCH_TYPE},
{"scanner", DEVSTAT_TYPE_SCANNER, DEVSTAT_MATCH_TYPE},
{"printer", DEVSTAT_TYPE_PRINTER, DEVSTAT_MATCH_TYPE},
{"floppy", DEVSTAT_TYPE_FLOPPY, DEVSTAT_MATCH_TYPE},
{"proc", DEVSTAT_TYPE_PROCESSOR, DEVSTAT_MATCH_TYPE},
{"comm", DEVSTAT_TYPE_COMM, DEVSTAT_MATCH_TYPE},
{"enclosure", DEVSTAT_TYPE_ENCLOSURE, DEVSTAT_MATCH_TYPE},
{NULL, 0, 0}
};
static int compare_select(const void *arg1, const void *arg2);
int
getnumdevs(void)
{
size_t numdevsize;
int numdevs;
const char *func_name = "getnumdevs";
numdevsize = sizeof(int);
if (sysctlbyname("kern.devstat.numdevs", &numdevs,
&numdevsize, NULL, 0) == -1) {
sprintf(devstat_errbuf, "%s: error getting number of devices\n"
"%s: %s", func_name, func_name, strerror(errno));
return(-1);
} else
return(numdevs);
}
long
getgeneration(void)
{
size_t gensize;
long generation;
const char *func_name = "getgeneration";
gensize = sizeof(long);
if (sysctlbyname("kern.devstat.generation", &generation,
&gensize, NULL, 0) == -1) {
sprintf(devstat_errbuf,"%s: error getting devstat generation\n"
"%s: %s", func_name, func_name, strerror(errno));
return(-1);
} else
return(generation);
}
int
getversion(void)
{
size_t versize;
int version;
const char *func_name = "getversion";
versize = sizeof(int);
if (sysctlbyname("kern.devstat.version", &version, &versize,
NULL, 0) == -1) {
sprintf(devstat_errbuf, "%s: error getting devstat version\n"
"%s: %s", func_name, func_name, strerror(errno));
return(-1);
} else
return(version);
}
int
checkversion(void)
{
int retval = 0;
int errlen = 0;
const char *func_name = "checkversion";
int version;
version = getversion();
if (version != DEVSTAT_VERSION) {
int buflen = 0;
char tmpstr[256];
if (version == -1) {
buflen = strlen(devstat_errbuf);
errlen = snprintf(tmpstr, sizeof(tmpstr), "\n");
strncat(devstat_errbuf, tmpstr,
DEVSTAT_ERRBUF_SIZE - buflen - 1);
buflen += errlen;
}
errlen = snprintf(tmpstr, sizeof(tmpstr),
"%s: userland devstat version %d is not "
"the same as the kernel\n%s: devstat "
"version %d\n", func_name, DEVSTAT_VERSION,
func_name, version);
if (version == -1) {
strncat(devstat_errbuf, tmpstr,
DEVSTAT_ERRBUF_SIZE - buflen - 1);
buflen += errlen;
} else {
strncpy(devstat_errbuf, tmpstr, DEVSTAT_ERRBUF_SIZE);
devstat_errbuf[DEVSTAT_ERRBUF_SIZE - 1] = '\0';
}
if (version < DEVSTAT_VERSION)
snprintf(tmpstr, sizeof(tmpstr),
"%s: libdevstat newer than kernel\n",
func_name);
else
snprintf(tmpstr, sizeof(tmpstr),
"%s: kernel newer than libdevstat\n",
func_name);
strncat(devstat_errbuf, tmpstr,
DEVSTAT_ERRBUF_SIZE - buflen - 1);
retval = -1;
}
return(retval);
}
int
getdevs(struct statinfo *stats)
{
int error;
size_t dssize;
long oldgeneration;
int retval = 0;
struct devinfo *dinfo;
const char *func_name = "getdevs";
dinfo = stats->dinfo;
if (dinfo == NULL) {
sprintf(devstat_errbuf, "%s: stats->dinfo was NULL", func_name);
return(-1);
}
oldgeneration = dinfo->generation;
if (dinfo->mem_ptr == NULL) {
if ((dinfo->numdevs = getnumdevs()) < 0)
return(-1);
dssize =(dinfo->numdevs * sizeof(struct devstat)) +sizeof(long);
dinfo->mem_ptr = (u_int8_t *)malloc(dssize);
} else
dssize =(dinfo->numdevs * sizeof(struct devstat)) +sizeof(long);
gettimeofday(&stats->busy_time, NULL);
if ((error = sysctlbyname("kern.devstat.all", dinfo->mem_ptr,
&dssize, NULL, 0)) == -1) {
if (errno == ENOMEM) {
if ((dinfo->numdevs = getnumdevs()) < 0)
return(-1);
dssize = (dinfo->numdevs * sizeof(struct devstat)) +
sizeof(long);
dinfo->mem_ptr = (u_int8_t *)realloc(dinfo->mem_ptr,
dssize);
if ((error = sysctlbyname("kern.devstat.all",
dinfo->mem_ptr, &dssize, NULL, 0)) == -1) {
sprintf(devstat_errbuf,
"%s: error getting device stats\n"
"%s: %s", func_name, func_name,
strerror(errno));
return(-1);
}
} else {
sprintf(devstat_errbuf,
"%s: error getting device stats\n"
"%s: %s", func_name, func_name,
strerror(errno));
return(-1);
}
}
dinfo->generation = *(long *)dinfo->mem_ptr;
if (oldgeneration != dinfo->generation) {
if (getnumdevs() != dinfo->numdevs) {
if ((dinfo->numdevs = getnumdevs()) < 0)
return(-1);
dssize = (dinfo->numdevs * sizeof(struct devstat)) +
sizeof(long);
dinfo->mem_ptr = (u_int8_t *)realloc(dinfo->mem_ptr,
dssize);
}
retval = 1;
}
dinfo->devices = (struct devstat *)(dinfo->mem_ptr + sizeof(long));
return(retval);
}
int
selectdevs(struct device_selection **dev_select, int *num_selected,
int *num_selections, long *select_generation,
long current_generation, struct devstat *devices, int numdevs,
struct devstat_match *matches, int num_matches,
char **dev_selections, int num_dev_selections,
devstat_select_mode select_mode, int maxshowdevs, int perf_select)
{
int i, j, k;
int init_selections = 0, init_selected_var = 0;
struct device_selection *old_dev_select = NULL;
int old_num_selections = 0, old_num_selected;
int selection_number = 0;
int changed = 0, found = 0;
if ((dev_select == NULL) || (devices == NULL) || (numdevs <= 0))
return(-1);
if (*dev_select == NULL) {
*dev_select = (struct device_selection *)malloc(numdevs *
sizeof(struct device_selection));
*select_generation = current_generation;
init_selections = 1;
changed = 1;
} else if (*num_selections != numdevs) {
*dev_select = (struct device_selection *)realloc(*dev_select,
numdevs * sizeof(struct device_selection));
*select_generation = current_generation;
init_selections = 1;
} else if (*select_generation < current_generation) {
*select_generation = current_generation;
init_selections = 1;
}
if (select_mode == DS_SELECT_ONLY)
init_selected_var = 1;
old_num_selected = *num_selected;
if (((init_selected_var != 0) || (init_selections != 0)
|| (perf_select != 0)) && (changed == 0)){
old_dev_select = (struct device_selection *)malloc(
*num_selections * sizeof(struct device_selection));
old_num_selections = *num_selections;
bcopy(*dev_select, old_dev_select,
sizeof(struct device_selection) * *num_selections);
}
if (init_selections != 0) {
bzero(*dev_select, sizeof(struct device_selection) * numdevs);
for (i = 0; i < numdevs; i++) {
(*dev_select)[i].device_number =
devices[i].device_number;
strncpy((*dev_select)[i].device_name,
devices[i].device_name,
DEVSTAT_NAME_LEN);
(*dev_select)[i].device_name[DEVSTAT_NAME_LEN - 1]='\0';
(*dev_select)[i].unit_number = devices[i].unit_number;
(*dev_select)[i].position = i;
}
*num_selections = numdevs;
} else if (init_selected_var != 0) {
for (i = 0; i < numdevs; i++)
(*dev_select)[i].selected = 0;
}
if ((select_mode == DS_SELECT_ONLY) || (init_selections != 0)
|| (init_selected_var != 0))
*num_selected = 0;
for (i = 0; (i < *num_selections) && (num_dev_selections > 0); i++) {
char tmpstr[80];
snprintf(tmpstr, sizeof(tmpstr), "%s%d",
(*dev_select)[i].device_name,
(*dev_select)[i].unit_number);
for (j = 0; j < num_dev_selections; j++) {
if (strcmp(tmpstr, dev_selections[j]) == 0) {
switch(select_mode) {
case DS_SELECT_ADD:
case DS_SELECT_ADDONLY:
if ((*dev_select)[i].selected)
break;
case DS_SELECT_ONLY:
(*dev_select)[i].selected =
++selection_number;
(*num_selected)++;
break;
case DS_SELECT_REMOVE:
(*dev_select)[i].selected = 0;
(*num_selected)--;
break;
}
break;
}
}
}
for (i = 0; (i < num_matches) && (*num_selected < maxshowdevs); i++) {
if ((matches[i].match_fields == DEVSTAT_MATCH_NONE)
|| (matches[i].num_match_categories <= 0))
continue;
for (j = 0; j < numdevs; j++) {
int num_match_categories;
num_match_categories = matches[i].num_match_categories;
if ((((matches[i].match_fields & DEVSTAT_MATCH_TYPE)!=0)
&& ((devices[j].device_type & DEVSTAT_TYPE_MASK) ==
(matches[i].device_type & DEVSTAT_TYPE_MASK))
&&(((matches[i].match_fields & DEVSTAT_MATCH_PASS)!=0)
|| (((devices[j].device_type &
DEVSTAT_TYPE_PASS) == 0)))
&& (--num_match_categories == 0))
|| (((matches[i].match_fields & DEVSTAT_MATCH_IF) != 0)
&& ((devices[j].device_type & DEVSTAT_TYPE_IF_MASK) ==
(matches[i].device_type & DEVSTAT_TYPE_IF_MASK))
&&(((matches[i].match_fields & DEVSTAT_MATCH_PASS)!=0)
|| (((devices[j].device_type &
DEVSTAT_TYPE_PASS) == 0)))
&& (--num_match_categories == 0))
|| (((matches[i].match_fields & DEVSTAT_MATCH_PASS)!=0)
&& ((devices[j].device_type & DEVSTAT_TYPE_PASS) != 0)
&& (--num_match_categories == 0))) {
for (k = 0; k < numdevs; k++) {
if ((*dev_select)[k].position == j) {
found = 1;
break;
}
}
if (found != 1) {
fprintf(stderr, "selectdevs: couldn't"
" find %s%d in selection "
"list\n",
devices[j].device_name,
devices[j].unit_number);
break;
}
switch(select_mode) {
case DS_SELECT_ADD:
case DS_SELECT_ADDONLY:
case DS_SELECT_ONLY:
if ((*dev_select)[k].selected != 0)
break;
(*dev_select)[k].selected =
++selection_number;
(*num_selected)++;
break;
case DS_SELECT_REMOVE:
(*dev_select)[k].selected = 0;
(*num_selected)--;
break;
}
}
}
}
if (perf_select != 0) {
qsort(*dev_select, *num_selections,
sizeof(struct device_selection),
compare_select);
if (*num_selected == 0) {
for (i = 0; i < *num_selections; i++) {
if ((*dev_select)[i].selected == 0) {
(*dev_select)[i].selected =
++selection_number;
(*num_selected)++;
}
}
} else {
selection_number = 0;
for (i = 0; i < *num_selections; i++) {
if ((*dev_select)[i].selected != 0) {
(*dev_select)[i].selected =
++selection_number;
}
}
}
}
if ((select_mode == DS_SELECT_ADD) && (*num_selected < maxshowdevs)) {
for (i = 0; i < *num_selections; i++)
if ((*dev_select)[i].selected == 0) {
(*dev_select)[i].selected = ++selection_number;
(*num_selected)++;
}
}
if ((changed == 0) && (old_num_selected != *num_selected))
changed = 1;
else if ((changed == 0) && (old_dev_select != NULL)) {
for (i = 0; (i < *num_selections) && (changed == 0) &&
(i < old_num_selections); i++) {
if (((*dev_select)[i].device_number ==
old_dev_select[i].device_number)
&& ((*dev_select)[i].selected ==
old_dev_select[i].selected))
continue;
else if ((*dev_select)[i].device_number ==
old_dev_select[i].device_number) {
changed = 1;
break;
}
else {
found = 0;
for (j = 0; j < old_num_selections; j++) {
if (((*dev_select)[i].device_number ==
old_dev_select[j].device_number)
&& ((*dev_select)[i].selected ==
old_dev_select[j].selected)){
found = 1;
break;
}
else if ((*dev_select)[i].device_number
== old_dev_select[j].device_number)
break;
}
if (found == 0)
changed = 1;
}
}
}
if (old_dev_select != NULL)
free(old_dev_select);
return(changed);
}
static int
compare_select(const void *arg1, const void *arg2)
{
if ((((const struct device_selection *)arg1)->selected)
&& (((const struct device_selection *)arg2)->selected == 0))
return(-1);
else if ((((const struct device_selection *)arg1)->selected == 0)
&& (((const struct device_selection *)arg2)->selected))
return(1);
else if (((const struct device_selection *)arg2)->bytes <
((const struct device_selection *)arg1)->bytes)
return(-1);
else if (((const struct device_selection *)arg2)->bytes >
((const struct device_selection *)arg1)->bytes)
return(1);
else
return(0);
}
int
buildmatch(const char *match_str, struct devstat_match **matches,
int *num_matches)
{
char *tstr[5];
char **tempstr;
char *matchbuf_orig;
char *matchbuf;
int num_args;
int i, j;
int retval = -1;
if (match_str == NULL) {
sprintf(devstat_errbuf, "%s: no match expression", __func__);
return(-1);
}
matchbuf = matchbuf_orig = strdup(match_str);
if (matchbuf == NULL) {
sprintf(devstat_errbuf, "%s: out of memory", __func__);
return(-1);
}
for (tempstr = tstr, num_args = 0;
(*tempstr = strsep(&matchbuf, ",")) != NULL && (num_args < 5);
num_args++)
if (**tempstr != '\0')
if (++tempstr >= &tstr[5])
break;
if (num_args > 3) {
sprintf(devstat_errbuf, "%s: too many type arguments",
__func__);
goto cleanup;
}
if (*num_matches == 0)
*matches = (struct devstat_match *)malloc(
sizeof(struct devstat_match));
else
*matches = (struct devstat_match *)realloc(*matches,
sizeof(struct devstat_match) * (*num_matches + 1));
bzero(&matches[0][*num_matches], sizeof(struct devstat_match));
for (i = 0; i < num_args; i++) {
char *tempstr2, *tempstr3;
tempstr2 = tstr[i];
while (isspace(*tempstr2) && (*tempstr2 != '\0'))
tempstr2++;
tempstr3 = &tempstr2[strlen(tempstr2) - 1];
while ((*tempstr3 != '\0') && (tempstr3 > tempstr2)
&& (isspace(*tempstr3))) {
*tempstr3 = '\0';
tempstr3--;
}
for (j = 0; match_table[j].match_str != NULL; j++) {
if (strncasecmp(tempstr2, match_table[j].match_str,
strlen(match_table[j].match_str)) == 0) {
if (((*matches)[*num_matches].match_fields &
match_table[j].match_field) != 0) {
sprintf(devstat_errbuf,
"%s: cannot have more than "
"one match item in a single "
"category", __func__);
goto cleanup;
}
(*matches)[*num_matches].match_fields |=
match_table[j].match_field;
(*matches)[*num_matches].device_type |=
match_table[j].type;
(*matches)[*num_matches].num_match_categories++;
break;
}
}
if ((*matches)[*num_matches].num_match_categories != (i + 1)) {
snprintf(devstat_errbuf, sizeof(devstat_errbuf),
"%s: unknown match item \"%s\"", __func__,
tstr[i]);
goto cleanup;
}
}
(*num_matches)++;
retval = 0;
cleanup:
free(matchbuf_orig);
return(retval);
}
int
compute_stats(struct devstat *current, struct devstat *previous,
long double etime, u_int64_t *total_bytes,
u_int64_t *total_transfers, u_int64_t *total_blocks,
long double *kb_per_transfer, long double *transfers_per_second,
long double *mb_per_second, long double *blocks_per_second,
long double *ms_per_transaction)
{
u_int64_t totalbytes, totaltransfers, totalblocks;
if (current == NULL) {
sprintf(devstat_errbuf, "%s: current stats structure was NULL",
__func__);
return(-1);
}
totalbytes = (current->bytes_written + current->bytes_read) -
((previous) ? (previous->bytes_written +
previous->bytes_read) : 0);
if (total_bytes)
*total_bytes = totalbytes;
totaltransfers = (current->num_reads +
current->num_writes +
current->num_other) -
((previous) ?
(previous->num_reads +
previous->num_writes +
previous->num_other) : 0);
if (total_transfers)
*total_transfers = totaltransfers;
if (transfers_per_second) {
if (etime > 0.0) {
*transfers_per_second = totaltransfers;
*transfers_per_second /= etime;
} else
*transfers_per_second = 0.0;
}
if (kb_per_transfer) {
*kb_per_transfer = totalbytes;
*kb_per_transfer /= 1024;
if (totaltransfers > 0)
*kb_per_transfer /= totaltransfers;
else
*kb_per_transfer = 0.0;
}
if (mb_per_second) {
*mb_per_second = totalbytes;
*mb_per_second /= 1024 * 1024;
if (etime > 0.0)
*mb_per_second /= etime;
else
*mb_per_second = 0.0;
}
totalblocks = totalbytes;
if (current->block_size > 0)
totalblocks /= current->block_size;
else
totalblocks /= 512;
if (total_blocks)
*total_blocks = totalblocks;
if (blocks_per_second) {
*blocks_per_second = totalblocks;
if (etime > 0.0)
*blocks_per_second /= etime;
else
*blocks_per_second = 0.0;
}
if (ms_per_transaction) {
if (totaltransfers > 0) {
*ms_per_transaction = etime;
*ms_per_transaction /= totaltransfers;
*ms_per_transaction *= 1000;
} else
*ms_per_transaction = 0.0;
}
return(0);
}
int
compute_stats_read(struct devstat *current, struct devstat *previous,
long double etime, u_int64_t *total_bytes,
u_int64_t *total_transfers, u_int64_t *total_blocks,
long double *kb_per_transfer, long double *transfers_per_second,
long double *mb_per_second, long double *blocks_per_second,
long double *ms_per_transaction)
{
u_int64_t totalbytes, totaltransfers, totalblocks;
if (current == NULL) {
sprintf(devstat_errbuf, "%s: current stats structure was NULL",
__func__);
return(-1);
}
totalbytes = current->bytes_read -
(previous ? previous->bytes_read : 0);
if (total_bytes)
*total_bytes = totalbytes;
totaltransfers = current->num_reads -
(previous ? previous->num_reads : 0);
if (total_transfers)
*total_transfers = totaltransfers;
if (transfers_per_second) {
if (etime > 0.0) {
*transfers_per_second = totaltransfers;
*transfers_per_second /= etime;
} else
*transfers_per_second = 0.0;
}
if (kb_per_transfer) {
*kb_per_transfer = totalbytes;
*kb_per_transfer /= 1024;
if (totaltransfers > 0)
*kb_per_transfer /= totaltransfers;
else
*kb_per_transfer = 0.0;
}
if (mb_per_second) {
*mb_per_second = totalbytes;
*mb_per_second /= 1024 * 1024;
if (etime > 0.0)
*mb_per_second /= etime;
else
*mb_per_second = 0.0;
}
totalblocks = totalbytes;
if (current->block_size > 0)
totalblocks /= current->block_size;
else
totalblocks /= 512;
if (total_blocks)
*total_blocks = totalblocks;
if (blocks_per_second) {
*blocks_per_second = totalblocks;
if (etime > 0.0)
*blocks_per_second /= etime;
else
*blocks_per_second = 0.0;
}
if (ms_per_transaction) {
if (totaltransfers > 0) {
*ms_per_transaction = etime;
*ms_per_transaction /= totaltransfers;
*ms_per_transaction *= 1000;
} else
*ms_per_transaction = 0.0;
}
return(0);
}
int
compute_stats_write(struct devstat *current, struct devstat *previous,
long double etime, u_int64_t *total_bytes,
u_int64_t *total_transfers, u_int64_t *total_blocks,
long double *kb_per_transfer, long double *transfers_per_second,
long double *mb_per_second, long double *blocks_per_second,
long double *ms_per_transaction)
{
u_int64_t totalbytes, totaltransfers, totalblocks;
if (current == NULL) {
sprintf(devstat_errbuf, "%s: current stats structure was NULL",
__func__);
return(-1);
}
totalbytes = current->bytes_written -
(previous ? previous->bytes_written : 0);
if (total_bytes)
*total_bytes = totalbytes;
totaltransfers = current->num_writes -
(previous ? previous->num_writes : 0);
if (total_transfers)
*total_transfers = totaltransfers;
if (transfers_per_second) {
if (etime > 0.0) {
*transfers_per_second = totaltransfers;
*transfers_per_second /= etime;
} else
*transfers_per_second = 0.0;
}
if (kb_per_transfer) {
*kb_per_transfer = totalbytes;
*kb_per_transfer /= 1024;
if (totaltransfers > 0)
*kb_per_transfer /= totaltransfers;
else
*kb_per_transfer = 0.0;
}
if (mb_per_second) {
*mb_per_second = totalbytes;
*mb_per_second /= 1024 * 1024;
if (etime > 0.0)
*mb_per_second /= etime;
else
*mb_per_second = 0.0;
}
totalblocks = totalbytes;
if (current->block_size > 0)
totalblocks /= current->block_size;
else
totalblocks /= 512;
if (total_blocks)
*total_blocks = totalblocks;
if (blocks_per_second) {
*blocks_per_second = totalblocks;
if (etime > 0.0)
*blocks_per_second /= etime;
else
*blocks_per_second = 0.0;
}
if (ms_per_transaction) {
if (totaltransfers > 0) {
*ms_per_transaction = etime;
*ms_per_transaction /= totaltransfers;
*ms_per_transaction *= 1000;
} else
*ms_per_transaction = 0.0;
}
return(0);
}
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);
}