/*
* nicstat - print network traffic, Kb/s read and written. Solaris 8+.
* "netstat -i" only gives a packet count, this program gives Kbytes.
*
* 25-Jul-2006, ver 0.92 (check for new versions, http://www.brendangregg.com)
*
* COMPILE:
* cc -lgen -lkstat -lrt -o nicstat nicstat.c
*
* USAGE: nicstat [-hsz] [-i int[,int...]] | [interval [count]]
*
* -h # help
* -i interface # track interface only
* -s # summary output
* -z # skip zero value lines
* eg,
* nicstat # print summary since boot only
* nicstat 1 # print every 1 second
* nicstat 1 5 # print 5 times only
* nicstat -z 1 # print every 1 second, skip zero lines
* nicstat -i hme0 1 # print hme0 only every 1 second
*
* This prints out the Kb/s transferred for all the network cards (NICs),
* including packet counts and average sizes. The first line is the historic
* data since boot.
*
* FIELDS:
* Int Interface
* rKb/s read Kbytes/s
* wKb/s write Kbytes/s
* rPk/s read Packets/s
* wPk/s write Packets/s
* rAvs read Average size, bytes
* wAvs write Average size, bytes
* %Util %Utilisation (r+w/ifspeed)
* Sat Saturation (defer, nocanput, norecvbuf, noxmtbuf)
*
* NOTE: Some unusual network cards may not provide all the details to KStat,
* (or provide different symbols). Check for newer versions of this program,
* and the g_network array in the code below.
*
* Utilisation is based on bytes transferred divided by speed of the interface.
* It should be impossible to reach 100% as there are overheads due to bus
* negotiation and timing.
*
* Saturation is determined by counting read and write errors caused by the
* interface running at saturation. This approach is not ideal, and the value
* reported is often lower than it should be (eg, 0.0). Reading the rKb/s and
* wKb/s fields may be more useful.
*
*
* SEE ALSO:
* nicstat # the Perl version
* kstat -n hme0 [interval [count]] # or qfe0, ...
* netstat -iI hme0 [interval [count]]
* se netstat.se [interval] # SE Toolkit
* se nx.se [interval] # SE Toolkit
*
* Standard Disclaimer: This is freeware, use at your own risk.
*
* COPYRIGHT: Copyright (c) 2005 Brendan Gregg.
*
* AUTHOR: Brendan Gregg [Sydney, Australia].
*
* HISTORY:
* 07-Jan-2005 Brendan Gregg Created this, based on perl version
* 07-Jan-2005 " " added summary style (Peter Tribble)
* 03-Jun-2005 [email protected] modified "nocanput" lookup for ce
* 25-Jul-2006 [email protected] use nanosleep(3),gethrtime(3) for
* accurate period; use fflush(stdout)
*/
#include
#include
#include
#include
#include
#include
#include
#include
#define PAGESIZE 20
#define INTERVAL 1
#define LOOP_MAX 1
#define NIC_NAME_MAX 64
#define NIC_COUNT_MAX 256
/* nicdata - useful kstat NIC data */
typedef char nicname[NIC_NAME_MAX];
typedef struct nicdata {
nicname name; /* name of interface */
uint64_t rbytes; /* total read bytes */
uint64_t wbytes; /* total written bytes */
uint64_t rpackets; /* total read packets */
uint64_t wpackets; /* total written packets */
uint64_t speed; /* speed of interface */
uint64_t sat; /* saturation value */
time_t time; /* time of sample */
} nicdata;
/*
* Global variables
*/
static char *g_network[] = { "be", "bge", "ce", "ci", "dmfe", "e1000g", "el",
"eri", "elxl", "fa", "ge", "hme", "ipge", "ipdptp", "iprb", "lane",
"le", "nf", "ppp", "qe", "qfe", "rtls", "sppp", "vge", NULL };
static nicdata g_idnew[NIC_COUNT_MAX]; /* Interface Data, new */
static nicdata g_idold[NIC_COUNT_MAX]; /* Interface Data, old */
static int g_interfacemax; /* number of found NICs */
static int g_style; /* output style */
static int g_skipzero; /* skip zero value lines */
static int g_someints; /* trace some interfaces only */
static int g_forever; /* run forever */
static char *g_tracked[NIC_COUNT_MAX]; /* Tracked interfaces */
/*
* die - print stderr message and exit.
*
* This subroutine prints an error message and exits with a non-zero
* exit status.
*/
static void
die(char *message, int status)
{
(void) fprintf(stderr, "%s\n", message);
exit(status);
}
/*
* usage - print a usage message and exit.
*/
static void
usage(void)
{
(void) fprintf(stderr,
"USAGE: nicstat [-hsz] [-i int[,int...]] | [interval [count]]\n"
"\n"
" -h # help\n"
" -i interface # track interface only\n"
" -s # summary output\n"
" -z # skip zero value lines\n"
" eg,\n");
(void) fprintf(stderr,
" nicstat # print summary since boot only\n"
" nicstat 1 # print every 1 second\n"
" nicstat 1 5 # print 5 times only\n"
" nicstat -z 1 # print every 1 second, skip zero lines\n"
" nicstat -i hme0 1 # print hme0 only every 1 second\n");
exit(1);
}
/*
* fetch64 - return a uint64_t value from kstat.
*
* The arguments are a kstat pointer, the value name,
* and a default value in case the lookup fails.
*/
static uint64_t
fetch64(kstat_t *ksp, char *value64, uint64_t def)
{
kstat_named_t *knp; /* Kstat named pointer */
/* try a lookup and return */
if ((knp = kstat_data_lookup(ksp, value64)) != NULL)
return (knp->value.ui64);
return (def);
}
/*
* fetch32 - return a uint32_t value from kstat.
*
* The arguments are a kstat pointer, the value name,
* and a default value in case the lookup fails.
*/
static uint32_t
fetch32(kstat_t *ksp, char *value, uint32_t def)
{
kstat_named_t *knp; /* Kstat named pointer */
/* try a lookup and return */
if ((knp = kstat_data_lookup(ksp, value)) != NULL)
return (knp->value.ui32);
return (def);
}
/*
* fetch6432 - return a uint64_t or a uint32_t value from kstat.
*
* The arguments are a kstat pointer, a potential ui64 value name,
* a potential ui32 value name, and a default value in case both
* lookup fails. The ui64 value is attempted first.
*/
static uint64_t
fetch6432(kstat_t *ksp, char *value64, char *value, uint64_t def)
{
kstat_named_t *knp; /* Kstat named pointer */
/* try lookups and return */
if ((knp = kstat_data_lookup(ksp, value64)) != NULL)
return (knp->value.ui64);
if ((knp = kstat_data_lookup(ksp, value)) != NULL)
return (knp->value.ui32);
return (def);
}
/*
* fetch_nocanput - return nocanput value, whose name(s) are driver-dependent.
*
* Most drivers have a kstat "nocanput", but the ce driver
* at least has "rx_nocanput" and "tx_nocanput"
*/
static uint32_t
fetch_nocanput(kstat_t *ksp, uint32_t def)
{
kstat_named_t *knp; /* Kstat named pointer */
uint32_t sum;
/* Check "nocanput" first */
if ((knp = kstat_data_lookup(ksp, "nocanput")) != NULL) {
return (knp->value.ui32);
} else {
if ((knp = kstat_data_lookup(ksp, "rx_nocanput")) != NULL) {
sum = knp->value.ui32;
if ((knp = kstat_data_lookup(ksp, "tx_nocanput"))
!= NULL) {
sum += knp->value.ui32;
return (sum);
}
}
}
return (def);
}
/*
* fetch_boot_time - return the boot time in secs.
*
* This takes a kstat control pointer and looks up the boot time
* from unix:0:system_misc:boot:time. If found, this is returned,
* else 0.
*/
static time_t
fetch_boot_time(kstat_ctl_t *kc)
{
kstat_t *ksp; /* Kstat struct pointer */
kstat_named_t *knp; /* Kstat named pointer */
if ((ksp = kstat_lookup(kc, "unix", 0, "system_misc")) == NULL)
die("ERROR2: Can't read boot_time.\n", 2);
if ((kstat_read(kc, ksp, NULL) != -1) &&
((knp = kstat_data_lookup(ksp, "boot_time")) != NULL)) {
/* summary since boot */
return (knp->value.ui32);
} else {
/* summary since, erm, epoch */
return (0);
}
}
/*
* populate_g_idnew - the master kstat function.
*
* This fetches all the network data from kstat and populates the
* global variables g_idnew and g_interfacemax. It uses a kstat control
* pointer as an argument, and the global array g_network.
*
* This function works by climbing down the kstat chains looking
* for modules that look like network interfaces. The first step is
* to check the module name against the global array g_network (the code
* for this will need maintenance as new network cards are developed);
* then a kstat variable is checked "obytes" or "obytes64" to ensure
* that this really is a network module. This approach is not ideal,
* I'd rather base the test on the kstat class == "net", however this
* data does not yet appear reliable across all interfaces.
*/
static void
populate_g_idnew(kstat_ctl_t *kc)
{
kstat_t *ksp; /* Kstat struct pointer */
int ok, i;
int num = 0;
for (ksp = kc->kc_chain; ksp != NULL; ksp = ksp->ks_next) {
/* Search all modules */
for (ok = 0, i = 0; g_network[i] != NULL; i++) {
if (strcmp(ksp->ks_module, g_network[i]) == 0)
ok = 1;
}
/* Skip if this isn't a network module */
if (ok == 0) continue;
if (kstat_read(kc, ksp, NULL) == -1) continue;
if ((kstat_data_lookup(ksp, "obytes") == NULL) &&
(kstat_data_lookup(ksp, "obytes64") == NULL)) continue;
/* Check for tracked interfaces */
if (g_someints) {
for (ok = 0, i = 0; *g_tracked[i] != NULL; i++) {
if (strcmp(ksp->ks_name, g_tracked[i]) == 0)
ok = 1;
}
if (ok == 0) continue;
}
/* Save network values */
g_idnew[num].rbytes = fetch6432(ksp, "rbytes64", "rbytes", 0);
g_idnew[num].wbytes = fetch6432(ksp, "obytes64", "obytes", 0);
g_idnew[num].rpackets =
fetch6432(ksp, "ipackets64", "ipackets", 0);
g_idnew[num].wpackets =
fetch6432(ksp, "opackets64", "opackets", 0);
g_idnew[num].sat = fetch32(ksp, "defer", 0);
g_idnew[num].sat += fetch_nocanput(ksp, 0);
g_idnew[num].sat += fetch32(ksp, "norcvbuf", 0);
g_idnew[num].sat += fetch32(ksp, "noxmtbuf", 0);
g_idnew[num].time = time(0);
/* if the speed can't be fetched, this makes %util 0.0 */
g_idnew[num].speed = fetch64(ksp, "ifspeed", 1LL << 48);
(void) strcpy(g_idnew[num].name, ksp->ks_name);
num++;
}
g_interfacemax = num - 1;
}
/*
* print_header - print the header line.
*/
static void
print_header(void)
{
if (g_style)
(void) printf("%8s %5s %14s %14s\n",
"Time", "Int", "rKb/s", "wKb/s");
else
(void) printf(
"%8s %5s %7s %7s %7s %7s %7s %7s %7s %7s\n",
"Time", "Int", "rKb/s", "wKb/s", "rPk/s",
"wPk/s", "rAvs", "wAvs", "%Util", "Sat");
}
/*
* Main Program
*/
int main(int argc, char *argv[]) {
/*
* Variable Declaration
*/
kstat_ctl_t *kc; /* Kstat controller */
double rbps; /* read bytes per sec */
double wbps; /* write bytes per sec */
double rkps; /* read Kb per sec */
double wkps; /* write Kb per sec */
double rpps; /* read packets per sec */
double wpps; /* write packets per sec */
double ravs; /* read average packet size */
double wavs; /* write average packet size */
double sats; /* saturation value per sec */
double tdiff; /* time difference between samples */
double util; /* utilisation */
struct tm *times; /* time struct */
char timestr[16]; /* time string */
time_t boot_time; /* boot time */
int num; /* NIC counter */
int interval; /* interval, secs */
int loop_max; /* max output lines */
int loop; /* current loop number */
int line; /* output line counter */
int option; /* command line switch */
hrtime_t period_n; /* period of each iteration in nanoseconds */
hrtime_t start_n; /* start point of an iteration, nsec */
hrtime_t end_n; /* end time of work in an iteration, nsec */
hrtime_t pause_n; /* time until start of next iteration, nsec */
struct timespec pause_tv;
/* time until start of next iteration */
/* defaults */
interval = INTERVAL;
loop_max = LOOP_MAX;
line = PAGESIZE;
loop = 0;
g_style = 0;
g_skipzero = 0;
g_someints = 0;
g_forever = 0;
for (num = 0; num <= g_interfacemax; num++) {
g_idold[num].rbytes = 0;
g_idold[num].wbytes = 0;
g_idold[num].rpackets = 0;
g_idold[num].wpackets = 0;
g_idold[num].sat = 0;
}
/*
* Process arguments
*/
while ((option = getopt(argc, argv, "hi:sz")) != -1) {
switch (option) {
case 'h':
usage();
break;
case 'i':
g_someints = 1;
(void) bufsplit(",", 0, (char **)0);
(void) bufsplit(optarg, NIC_COUNT_MAX,
g_tracked);
break;
case 's':
g_style = 1;
break;
case 'z':
g_skipzero = 1;
break;
default:
usage();
}
}
argv += optind;
if ((argc - optind) >= 1) {
interval = atoi(*argv);
if (interval == 0)
usage();
argv++;
if ((argc - optind) >= 2)
loop_max = atoi(*argv);
else
g_forever = 1;
}
/* Open Kstat */
if ((kc = kstat_open()) == NULL)
die("ERROR3: Can't open /dev/kstat.\n", 3);
/* Fetch boot time */
boot_time = fetch_boot_time(kc);
for (num = 0; num <= g_interfacemax; num++)
g_idold[num].time = boot_time;
/* Calculate the period of each iteration */
period_n = (hrtime_t) interval * NANOSEC;
/* Get time when we started */
start_n = gethrtime();
/*
* Main Loop
*/
for (;;)
{
/* Print header line */
if (line >= PAGESIZE) {
line = 0;
print_header();
}
/*
* Fetch Data
*/
populate_g_idnew(kc);
/* Check we matched some NICs */
if (g_interfacemax == -1)
die("ERROR4: No such interface found.", 4);
/*
* Calculate and Print Data
*/
for (num = 0; num <= g_interfacemax; num++) {
/* Calculate time difference */
tdiff = g_idnew[num].time - g_idold[num].time;
if (tdiff == 0) tdiff = 1;
/* Calculate per second values */
rbps = (g_idnew[num].rbytes - g_idold[num].rbytes)
/ tdiff;
wbps = (g_idnew[num].wbytes - g_idold[num].wbytes)
/ tdiff;
rpps = (g_idnew[num].rpackets - g_idold[num].rpackets)
/ tdiff;
wpps = (g_idnew[num].wpackets - g_idold[num].wpackets)
/ tdiff;
sats = (g_idnew[num].sat - g_idold[num].sat) / tdiff;
rkps = rbps / 1024;
wkps = wbps / 1024;
if (rpps > 0) ravs = rbps / rpps; else ravs = 0;
if (wpps > 0) wavs = wbps / wpps; else wavs = 0;
/* Calculate utilisation */
if (g_idnew[num].speed > 0) {
/*
* the following has a mysterious "800", it is
* 100 for the % conversion, and 8 for
* bytes2bits.
*/
util = (rbps + wbps) * 800 / g_idnew[num].speed;
if (util > 100) util = 100;
} else
util = 0;
/* always print header if there are multiple NICs */
if (g_interfacemax > 0)
line += PAGESIZE;
else
line++;
/* Skip zero lines */
if (g_skipzero && (util == 0)) continue;
/* Fetch time */
times = localtime(&g_idnew[num].time);
(void) strftime(timestr, sizeof (timestr),
"%H:%M:%S", times);
/* Print output line */
(void) printf("%s %5s ", timestr, g_idnew[num].name);
if (g_style)
(void) printf("%14.3f %14.3f\n", rkps, wkps);
else
(void) printf(
"%7.2f %7.2f %7.2f %7.2f "
"%7.2f %7.2f %7.2f %7.2f\n",
rkps, wkps, rpps, wpps, ravs, wavs,
util, sats);
g_idold[num] = g_idnew[num];
}
/* end point */
if (!g_forever)
if (++loop == loop_max) break;
/* flush output */
if (fflush(stdout) != 0)
die("ERROR5: fflush(stdout) failed\n", 5);
/* have a kip */
end_n = gethrtime();
pause_n = start_n + period_n - end_n;
if (pause_n > 0) {
pause_tv.tv_sec = pause_n / NANOSEC;
pause_tv.tv_nsec = pause_n % NANOSEC;
(void) nanosleep(&pause_tv, (struct timespec *) NULL);
}
start_n += period_n;
}
/*
* Close Kstat
*/
(void) kstat_close(kc);
return (0);
}