Last active
October 13, 2022 10:02
-
-
Save rlapz/6f48d7deb086be656d02cda0b44faa5e to your computer and use it in GitHub Desktop.
A simple bandwidth monitor (can monitor multiple network interfaces)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* netsp - A simple bandwidth monitor (can monitor multiple network interfaces) | |
* | |
* Arthur Lapz <[email protected]> | |
* | |
* MIT License | |
*/ | |
#include <errno.h> | |
#include <dirent.h> | |
#include <fcntl.h> | |
#include <stddef.h> | |
#include <stdio.h> | |
#include <stdbool.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <stdlib.h> | |
#define LEN(X) (sizeof(X) / sizeof(*X)) | |
#define unlikely(X) __builtin_expect((bool)(X), 0) | |
#define likely(X) __builtin_expect((bool)(X), 1) | |
#define BUFFER_SIZE 1024u | |
#define MAX_INTERFACES 16u /* unsigned int */ | |
#define DELAY 1000000 /* microsecond */ | |
#define NET_DIR "/sys/class/net/" | |
#define RX_BYTES "/statistics/rx_bytes" | |
#define TX_BYTES "/statistics/tx_bytes" | |
struct stt { | |
FILE *file; | |
size_t bytes; | |
}; | |
struct interface { | |
struct stt rx; | |
struct stt tx; | |
char name[255]; | |
}; | |
struct netsp { | |
int fmt_lpad; | |
unsigned infs_count; | |
struct interface infs[MAX_INTERFACES]; | |
}; | |
static int interface_open(struct interface *inf, const char *name, | |
size_t name_len); | |
static int netsp_interfaces_load(struct netsp *net, const char *pfx[], | |
unsigned pfx_len); | |
static void netsp_show(struct netsp *net); | |
static void netsp_cleanup(struct netsp *net); | |
static int netsp_run(const char *pfx[], unsigned pfx_len); | |
static size_t traf_read(FILE *fp, size_t *traf); | |
static const char *traf_fmt(char *buffer, size_t b_size, size_t bytes); | |
static int | |
interface_open(struct interface *inf, const char *name, size_t name_len) | |
{ | |
int ret; | |
char path[1024]; | |
const char *err_ctx; | |
/* RX */ | |
err_ctx = name; | |
if (snprintf(path, sizeof(path), "%s%s%s", NET_DIR, name, RX_BYTES) < 0) | |
goto err0; | |
err_ctx = path; | |
if ((inf->rx.file = fopen(path, "r")) == NULL) | |
goto err0; | |
/* TX */ | |
err_ctx = name; | |
if (snprintf(path, sizeof(path), "%s%s%s", NET_DIR, name, TX_BYTES) < 0) | |
goto err0; | |
err_ctx = path; | |
if ((inf->tx.file = fopen(path, "r")) == NULL) | |
goto err0; | |
memcpy(inf->name, name, name_len); | |
inf->name[name_len] = '\0'; | |
return 0; | |
err0: | |
ret = -errno; | |
if (inf->rx.file != NULL) | |
fclose(inf->rx.file); | |
fprintf(stderr, "interface_open: %s: %s\n", err_ctx, strerror(-ret)); | |
return ret; | |
} | |
static int | |
netsp_interfaces_load(struct netsp *net, const char *pfx[], unsigned pfx_len) | |
{ | |
int ret = 0; | |
int fmt_lpad = 0; | |
struct interface *infs = net->infs; | |
struct dirent *dirent; | |
unsigned count = 0; | |
DIR *dir; | |
if ((dir = opendir(NET_DIR)) == NULL) { | |
ret = -errno; | |
goto err0; | |
} | |
for (unsigned i = 0; i < MAX_INTERFACES; i++) { | |
readdir_again: | |
errno = 0; | |
if ((dirent = readdir(dir)) == NULL) { | |
if (errno != 0) | |
ret = -errno; | |
break; | |
} | |
const char *d_name = dirent->d_name; | |
if (d_name[0] == '.') | |
goto readdir_again; | |
for (unsigned j = 0; j < pfx_len; j++) { | |
if (strncmp(d_name, pfx[j], strlen(pfx[j])) != 0) | |
continue; | |
const size_t d_name_len = strlen(d_name); | |
if (d_name_len > (size_t)fmt_lpad) | |
fmt_lpad = d_name_len; | |
if (interface_open(&infs[count], d_name, d_name_len) < 0) | |
continue; | |
count++; | |
} | |
} | |
net->infs_count = count; | |
net->fmt_lpad = fmt_lpad; | |
closedir(dir); | |
if (ret < 0) | |
goto err0; | |
return 0; | |
err0: | |
netsp_cleanup(net); | |
fprintf(stderr, "netsp_interfaces_load: %s: %s\n", NET_DIR, | |
strerror(-ret)); | |
return ret; | |
} | |
static void | |
netsp_show(struct netsp *net) | |
{ | |
char buffer[1024]; | |
const unsigned count = net->infs_count; | |
struct interface *infs = net->infs; | |
struct interface *inf; | |
size_t rd_traf; | |
if (count == 0) | |
return; | |
show_again: | |
printf("\x1b[2J\x1b[H"); | |
for (unsigned i = 0; likely(i < count); i++) { | |
inf = &infs[i]; | |
printf("%-*s ", net->fmt_lpad, inf->name); | |
rd_traf = traf_read(inf->tx.file, &inf->tx.bytes); | |
printf("up: %s, ", traf_fmt(buffer, sizeof(buffer), rd_traf)); | |
rd_traf = traf_read(inf->rx.file, &inf->rx.bytes); | |
printf("down: %s\n", traf_fmt(buffer, sizeof(buffer), rd_traf)); | |
} | |
usleep(DELAY); | |
goto show_again; | |
} | |
static void | |
netsp_cleanup(struct netsp *net) | |
{ | |
unsigned count = net->infs_count; | |
while (count--) { | |
fclose(net->infs[count].rx.file); | |
fclose(net->infs[count].tx.file); | |
} | |
} | |
static int | |
netsp_run(const char *pfx[], unsigned pfx_len) | |
{ | |
int ret = 0; | |
struct netsp net; | |
memset(&net, 0, sizeof(net)); | |
if ((ret = netsp_interfaces_load(&net, pfx, pfx_len)) < 0) | |
return ret; | |
netsp_show(&net); | |
/* Maybe not reached */ | |
netsp_cleanup(&net); | |
return ret; | |
} | |
static size_t | |
traf_read(FILE *fp, size_t *traf) | |
{ | |
const size_t old_traf = *traf; | |
while (likely(fscanf(fp, "%zu", traf) != EOF)); | |
rewind(fp); | |
return *traf - old_traf; | |
} | |
/* slstatus: util.c: fmt_human() */ | |
static const char * | |
traf_fmt(char *buffer, size_t b_size, size_t bytes) | |
{ | |
const char prefix[] = { 'b', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' }; | |
size_t scaled = bytes; | |
size_t i; | |
for (i = 0; likely((i < sizeof(prefix)) && (scaled >= 1000)); i++) | |
scaled >>= 10; | |
if (unlikely(snprintf(buffer, b_size, "%zu%c", scaled, prefix[i]) < 0)) | |
return "-"; | |
return buffer; | |
} | |
static void | |
netsp_help(const char *app_name) | |
{ | |
printf("netsp - A simple bandwidth monitor\n\n" | |
"Usage: %s [NET_PREFIX_1] [NET_PREFIX_2] ...\n" | |
"Example:\n" | |
" %s w e\n" | |
" %s wlan eth\n", | |
app_name, app_name, app_name); | |
} | |
int | |
main(int argc, const char *argv[]) | |
{ | |
if (argc < 2) { | |
netsp_help(argv[0]); | |
return EINVAL; | |
} | |
return -netsp_run(argv + 1, (unsigned)argc - 1); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Repo: https://github.com/rlapz/netsp