sbm

simple bandwidth monitor
git clone git://git.2f30.org/sbm
Log | Files | Refs | LICENSE

sbm.c (7707B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <sys/types.h>
      3 #include <sys/time.h>
      4 #include <sys/socket.h>
      5 
      6 #include <net/if.h>
      7 #ifndef __linux__
      8 #include <sys/sysctl.h>
      9 #include <net/if_dl.h>
     10 #include <net/route.h>
     11 #endif
     12 
     13 #include <err.h>
     14 #include <errno.h>
     15 #include <ifaddrs.h>
     16 #include <limits.h>
     17 #include <signal.h>
     18 #include <stdint.h>
     19 #include <stdio.h>
     20 #include <stdlib.h>
     21 #include <string.h>
     22 #include <time.h>
     23 #include <unistd.h>
     24 
     25 #ifdef __linux__
     26 #define SIGINFO SIGUSR1
     27 #endif
     28 
     29 #include "arg.h"
     30 
     31 char *argv0;
     32 char *ifname;
     33 uint64_t origrxbytes, origtxbytes, rxbytes, txbytes;
     34 struct timeval begin;
     35 volatile sig_atomic_t interrupted;
     36 int Bflag;
     37 int tflag;
     38 
     39 void
     40 sighdlr(int signo)
     41 {
     42 	(void)signo;
     43 	interrupted = 1;
     44 }
     45 
     46 void
     47 scan(void)
     48 {
     49 	struct ifaddrs *ifas, *ifa;
     50 	int found = 0;
     51 
     52 	if (getifaddrs(&ifas) < 0)
     53 		err(1, "getifaddrs");
     54 	if (*ifname) {
     55 		for (ifa = ifas; ifa; ifa = ifa->ifa_next) {
     56 			if (!strncmp(ifname, ifa->ifa_name, IF_NAMESIZE)) {
     57 				found = 1;
     58 				break;
     59 			}
     60 		}
     61 	} else {
     62 		for (ifa = ifas; ifa; ifa = ifa->ifa_next) {
     63 			if (ifa->ifa_flags & IFF_LOOPBACK ||
     64 			    !(ifa->ifa_flags & IFF_UP) ||
     65 			    !(ifa->ifa_flags & IFF_RUNNING))
     66 				continue;
     67 			strncpy(ifname, ifa->ifa_name, IF_NAMESIZE);
     68 			ifname[IF_NAMESIZE - 1] = '\0';
     69 			found = 1;
     70 			break;
     71 		}
     72 	}
     73 	freeifaddrs(ifas);
     74 	if (!found)
     75 		errx(1, "no usable interface found");
     76 }
     77 
     78 #ifdef __linux__
     79 int
     80 readcounter(char *filename, uint64_t *v)
     81 {
     82 	char path[PATH_MAX];
     83 	char buf[BUFSIZ];
     84 	char *endptr;
     85 	FILE *fp;
     86 
     87 	snprintf(path, sizeof(path),
     88 	         "/sys/class/net/%s/statistics/%s",
     89 	         ifname, filename);
     90 	fp = fopen(path, "r");
     91 	if (fp == NULL) {
     92 		*v = 0;
     93 		return -1;
     94 	}
     95 	if (fgets(buf, sizeof(buf), fp) == NULL) {
     96 		fclose(fp);
     97 		*v = 0;
     98 		return -1;
     99 	}
    100 	fclose(fp);
    101 	buf[strcspn(buf, "\n")] = '\0';
    102 	errno = 0;
    103 	*v = strtoull(buf, &endptr, 10);
    104 	if (*endptr != '\0' || errno != 0) {
    105 		*v = 0;
    106 		return -1;
    107 	}
    108 	return 0;
    109 }
    110 
    111 void
    112 sample(uint64_t *rxbytes, uint64_t *txbytes,
    113        uint64_t *rxpps, uint64_t *txpps)
    114 {
    115 	readcounter("rx_bytes", rxbytes);
    116 	readcounter("tx_bytes", txbytes);
    117 	readcounter("rx_packets", rxpps);
    118 	readcounter("tx_packets", txpps);
    119 }
    120 #else
    121 void
    122 sample(uint64_t *rxbytes, uint64_t *txbytes,
    123        uint64_t *rxpps, uint64_t *txpps)
    124 {
    125 	int mib[6];
    126 	char *buf, *next;
    127 	size_t size;
    128 	struct if_msghdr *ifm;
    129 	struct sockaddr_dl *sdl = NULL;
    130 
    131 	*rxbytes = *txbytes = *rxpps = *txpps = 0;
    132 	mib[0] = CTL_NET;
    133 	mib[1] = PF_ROUTE;
    134 	mib[2] = 0;
    135 	mib[3] = 0;
    136 	mib[4] = NET_RT_IFLIST;
    137 	mib[5] = 0;
    138 	if (sysctl(mib, 6, NULL, &size, NULL, 0) < 0)
    139 		errx(1, "sysctl failed");
    140 	buf = malloc(size);
    141 	if (!buf)
    142 		err(1, "malloc");
    143 	if (sysctl(mib, 6, buf, &size, NULL, 0) < 0)
    144 		errx(1, "sysctl failed");
    145 	for (next = buf; next < buf + size; next += ifm->ifm_msglen) {
    146 		ifm = (struct if_msghdr *)next;
    147 		if (ifm->ifm_type == RTM_NEWADDR ||
    148 		    !(ifm->ifm_flags & IFF_UP))
    149 			continue;
    150 		sdl = (struct sockaddr_dl *)(ifm + 1);
    151 		if (sdl->sdl_family != AF_LINK ||
    152 		    strncmp(sdl->sdl_data, ifname, sdl->sdl_nlen))
    153 			continue;
    154 		*rxbytes = ifm->ifm_data.ifi_ibytes;
    155 		*txbytes = ifm->ifm_data.ifi_obytes;
    156 		*rxpps = ifm->ifm_data.ifi_ipackets;
    157 		*txpps = ifm->ifm_data.ifi_opackets;
    158 		break;
    159 	}
    160 	free(buf);
    161 	if (!sdl)
    162 		errx(1, "interface %s cannot be sampled", ifname);
    163 }
    164 #endif
    165 
    166 int
    167 getmonotime(struct timeval *tv)
    168 {
    169 	struct timespec ts;
    170 	int ret;
    171 
    172 	ret = clock_gettime(CLOCK_MONOTONIC, &ts);
    173 	if (ret < 0)
    174 		return -1;
    175 	tv->tv_sec = ts.tv_sec;
    176 	tv->tv_usec = ts.tv_nsec / 1000;
    177 	return ret;
    178 }
    179 
    180 void
    181 ms2tv(struct timeval *tv, long ms)
    182 {
    183 	tv->tv_sec = ms / 1000;
    184 	tv->tv_usec = (ms % 1000) * 1000;
    185 }
    186 
    187 long
    188 tv2ms(struct timeval *tv)
    189 {
    190 	return (tv->tv_sec * 1000) + ((tv->tv_usec + 500) / 1000);
    191 }
    192 
    193 int
    194 tvsleep(struct timeval *tv)
    195 {
    196 	struct timespec ts;
    197 
    198 	ts.tv_sec = tv->tv_sec;
    199 	ts.tv_nsec = tv->tv_usec * 1000;
    200 	return nanosleep(&ts, NULL);
    201 }
    202 
    203 double
    204 scale(char **suffix, uint64_t bits)
    205 {
    206 	static char *suffixes[][5] = { { "b", "kb", "Mb", "Gb", "Tb" },
    207 	                               { "B", "kB", "MB", "GB", "TB" } };
    208 	double rounded = bits;
    209 	int unit;
    210 
    211 	if (Bflag)
    212 		rounded /= 8;
    213 	for (unit = 0; rounded >= 1000 && unit < 4; ++unit)
    214 		rounded /= 1000;
    215 	*suffix = suffixes[Bflag][unit];
    216 	return rounded;
    217 }
    218 
    219 void
    220 print(uint64_t rxbits, uint64_t txbits, uint64_t rxpps, uint64_t txpps)
    221 {
    222 	char *rxsuffix, *txsuffix;
    223 	double rxround, txround;
    224 
    225 	if (!tflag) {
    226 		rxround = scale(&rxsuffix, rxbits);
    227 		txround = scale(&txsuffix, txbits);
    228 		printf("%s: %6.2f %2sps Rx %6.2f %2sps Tx %5llu pps Rx %5llu pps Tx\n",
    229 		       ifname,
    230 		       rxround,
    231 		       rxsuffix,
    232 		       txround,
    233 		       txsuffix,
    234 		       (unsigned long long)rxpps,
    235 		       (unsigned long long)txpps);
    236 	} else {
    237 		printf("%llu %llu %llu %llu\n",
    238 		       (unsigned long long)rxbits,
    239 		       (unsigned long long)txbits,
    240 		       (unsigned long long)rxpps,
    241 		       (unsigned long long)txpps);
    242 	}
    243 	fflush(stdout);
    244 }
    245 
    246 void
    247 printrxtx(void)
    248 {
    249 	struct timeval now, uptime;
    250 	char *rxsuffix, *txsuffix;
    251 	double rxround, txround;
    252 	uint64_t rxbits, txbits;
    253 	unsigned int days, hours, minutes, seconds;
    254 
    255 	getmonotime(&now);
    256 	timersub(&now, &begin, &uptime);
    257 	seconds = uptime.tv_sec % 60;
    258 	uptime.tv_sec /= 60;
    259 	minutes = uptime.tv_sec % 60;
    260 	uptime.tv_sec /= 60;
    261 	hours = uptime.tv_sec % 24;
    262 	days = uptime.tv_sec / 24;
    263 
    264 	rxbits = (rxbytes - origrxbytes) * 8;
    265 	txbits = (txbytes - origtxbytes) * 8;
    266 	rxround = scale(&rxsuffix, rxbits);
    267 	txround = scale(&txsuffix, txbits);
    268 
    269 	printf("%*s %6.2f %2s   Rx %6.2f %2s   Tx in ",
    270 	       (int)strlen(ifname) + 1, "",
    271 	       rxround, rxsuffix, txround, txsuffix);
    272 	if (days != 0)
    273 		printf("%dd ", days);
    274 	if (hours != 0)
    275 		printf("%dh ", hours);
    276 	if (minutes != 0)
    277 		printf("%dm ", minutes);
    278 	if (seconds != 0)
    279 		printf("%ds", seconds);
    280 	putchar('\n');
    281 }
    282 
    283 void
    284 loop(long count, struct timeval *delay)
    285 {
    286 	struct timeval old, now, diff;
    287 	uint64_t oldrxbytes, diffrxbits;
    288 	uint64_t oldtxbytes, difftxbits;
    289 	uint64_t oldrxpps, rxpps, diffrxpps;
    290 	uint64_t oldtxpps, txpps, difftxpps;
    291 	long n = 0;
    292 
    293 	getmonotime(&old);
    294 	sample(&oldrxbytes, &oldtxbytes, &oldrxpps, &oldtxpps);
    295 	origrxbytes = oldrxbytes, origtxbytes = oldtxbytes;
    296 	for (;;) {
    297 		tvsleep(delay);
    298 		if (interrupted) {
    299 			printrxtx();
    300 			interrupted = 0;
    301 		}
    302 		sample(&rxbytes, &txbytes, &rxpps, &txpps);
    303 		getmonotime(&now);
    304 		timersub(&now, &old, &diff);
    305 		getmonotime(&old);
    306 		diffrxbits = (rxbytes - oldrxbytes) * 8;
    307 		difftxbits = (txbytes - oldtxbytes) * 8;
    308 		diffrxbits = diffrxbits * 1000 / tv2ms(&diff);
    309 		difftxbits = difftxbits * 1000 / tv2ms(&diff);
    310 		diffrxpps = (rxpps - oldrxpps) * 1000 / tv2ms(&diff);
    311 		difftxpps = (txpps - oldtxpps) * 1000 / tv2ms(&diff);
    312 		print(diffrxbits, difftxbits, diffrxpps, difftxpps);
    313 		oldrxbytes = rxbytes, oldtxbytes = txbytes;
    314 		oldrxpps = rxpps, oldtxpps = txpps;
    315 		if (count && ++n >= count)
    316 			break;
    317 	}
    318 }
    319 
    320 void
    321 usage(void)
    322 {
    323 	fprintf(stderr, "usage: %s [-B | -t] [-c count] [-d delay] [-i interface]\n", argv0);
    324 	exit(1);
    325 }
    326 
    327 int
    328 main(int argc, char *argv[])
    329 {
    330 	char ifn[IF_NAMESIZE] = "";
    331 	char *end;
    332 	struct timeval tv;
    333 	long count = 0, delay = 1000;
    334 
    335 	ARGBEGIN {
    336 	case 'B':
    337 		Bflag = 1;
    338 		break;
    339 	case 'c':
    340 		errno = 0;
    341 		count = strtol(EARGF(usage()), &end, 10);
    342 		if (*end != '\0' || errno)
    343 			errx(1, "invalid count");
    344 		break;
    345 	case 'd':
    346 		errno = 0;
    347 		delay = strtol(EARGF(usage()), &end, 10);
    348 		if (*end != '\0' || errno)
    349 			errx(1, "invalid delay");
    350 		break;
    351 	case 'i':
    352 		strncpy(ifn, EARGF(usage()), sizeof(ifn));
    353 		ifn[IF_NAMESIZE - 1] = '\0';
    354 		break;
    355 	case 't':
    356 		tflag = 1;
    357 		break;
    358 	default:
    359 		usage();
    360 	} ARGEND
    361 
    362 	getmonotime(&begin);
    363 	ifname = ifn;
    364 	signal(SIGINFO, sighdlr);
    365 	ms2tv(&tv, delay);
    366 	scan();
    367 	loop(count, &tv);
    368 	return 0;
    369 }