From 9c8f76f9d233e2dcfd457706e524ba073734655c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Troels=20Bj=C3=B8rnskov?= Date: Mon, 4 Sep 2023 11:53:21 +0200 Subject: [PATCH 1/3] feat: add netspeed as a C module this commit adds the functionality from the bash script as a C module. Currently it supports no options for choice of interfaces, as it just aggregates the network traffic across all physical interfaces, which is what what most users want. --- i3status.c | 23 +++++++++ include/i3status.h | 9 ++++ man/i3status.man | 8 ++++ meson.build | 1 + src/print_netspeed.c | 108 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 149 insertions(+) create mode 100644 src/print_netspeed.c diff --git a/i3status.c b/i3status.c index 2f60b4e5..e2bb3829 100644 --- a/i3status.c +++ b/i3status.c @@ -455,9 +455,20 @@ int main(int argc, char *argv[]) { CFG_CUSTOM_SEP_BLOCK_WIDTH_OPT, CFG_END()}; + cfg_opt_t netspeed_opts[] = { + CFG_STR("format", "Down: %down/s Up: %up/s", CFGF_NONE), + CFG_CUSTOM_ALIGN_OPT, + CFG_CUSTOM_COLOR_OPTS, + CFG_CUSTOM_MIN_WIDTH_OPT, + CFG_CUSTOM_SEPARATOR_OPT, + CFG_CUSTOM_SEP_BLOCK_WIDTH_OPT, + CFG_END()}; + + cfg_opt_t opts[] = { CFG_STR_LIST("order", "{}", CFGF_NONE), CFG_SEC("general", general_opts, CFGF_NONE), + CFG_SEC("netspeed", netspeed_opts, CFGF_NONE), CFG_SEC("run_watch", run_watch_opts, CFGF_TITLE | CFGF_MULTI), CFG_SEC("path_exists", path_exists_opts, CFGF_TITLE | CFGF_MULTI), CFG_SEC("wireless", wireless_opts, CFGF_TITLE | CFGF_MULTI), @@ -729,6 +740,18 @@ int main(int argc, char *argv[]) { SEC_CLOSE_MAP; } + CASE_SEC("netspeed") { + SEC_OPEN_MAP("netspeed"); + netspeed_ctx_t ctx = { + .json_gen = json_gen, + .buf = buffer, + .buflen = sizeof(buffer), + .format = cfg_getstr(sec, "format"), + }; + print_netspeed(&ctx); + SEC_CLOSE_MAP; + } + CASE_SEC_TITLE("battery") { SEC_OPEN_MAP("battery"); battery_info_ctx_t ctx = { diff --git a/include/i3status.h b/include/i3status.h index 1bc2c85f..4caf830d 100644 --- a/include/i3status.h +++ b/include/i3status.h @@ -323,6 +323,15 @@ typedef struct { void print_wireless_info(wireless_info_ctx_t *ctx); +typedef struct { + yajl_gen json_gen; + char *buf; + const size_t buflen; + const char *format; +} netspeed_ctx_t; + +void print_netspeed(netspeed_ctx_t *ctx); + typedef struct { yajl_gen json_gen; char *buf; diff --git a/man/i3status.man b/man/i3status.man index 3cd231cb..328d4496 100644 --- a/man/i3status.man +++ b/man/i3status.man @@ -353,6 +353,14 @@ network interface found on the system (excluding devices starting with "lo"). *Example format_down*: +E: down+ +=== Netspeed + +Shows the traffic per seconnd for all physical network interfaces. + +*Example order*: +netspeed+ + +*Example format*: +%down/s↓ %up/s↑+ + === Battery Gets the status (charging, discharging, unknown, full), percentage, remaining diff --git a/meson.build b/meson.build index 83b1e61a..be5335fb 100644 --- a/meson.build +++ b/meson.build @@ -167,6 +167,7 @@ i3status_srcs = [ 'src/print_mem.c', 'src/print_path_exists.c', 'src/print_run_watch.c', + 'src/print_netspeed.c', 'src/print_time.c', 'src/print_volume.c', 'src/print_wireless_info.c', diff --git a/src/print_netspeed.c b/src/print_netspeed.c new file mode 100644 index 00000000..b4f9d793 --- /dev/null +++ b/src/print_netspeed.c @@ -0,0 +1,108 @@ +#include +#include +#include +#include + +#include "i3status.h" + +#define NET_STAT_BUFFER_SIZE 100 +#define RATE_BUFFER_SIZE 10 + + +typedef struct { + unsigned long last_time; + unsigned long last_rx; + unsigned long last_tx; + char last_rate_down[RATE_BUFFER_SIZE]; + char last_rate_up[RATE_BUFFER_SIZE]; +} NetworkState; + +static NetworkState prev_state = { + 0, + 0, + 0, + {'0', '\0'}, + {'0', '\0'} +}; + +void readable(unsigned long bytes, char *output) { + unsigned long kb = bytes / 1000; // Convert to kilobytes (base-10) + if (kb < 1000) { + sprintf(output, "%lu KB", kb); + } else { + unsigned long mb_int = kb / 1000; + unsigned long mb_dec = (kb % 1000 * 100) / 1000; // Multiplied by 100, then divided by 1000 for 2 decimal places + sprintf(output, "%lu.%02lu MB", mb_int, mb_dec); + } +} + +void print_netspeed(netspeed_ctx_t *ctx) { + char *outwalk = ctx->buf; + const char *selected_format = ctx->format; + + unsigned long current_time = (unsigned long) time(NULL); // Renamed variable + + DIR *d; + d = opendir("/sys/class/net"); + if (d ==NULL) return; + + int ret; + char path[PATH_MAX], line[PATH_MAX]; + char interface[PATH_MAX]; + char resolved_path[PATH_MAX]; + struct dirent *dir; + unsigned long rx = 0, tx = 0; + + while ((dir = readdir(d)) != NULL) { + if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue; + + snprintf(interface, sizeof(interface), "/sys/class/net/%s", dir->d_name); + + if (realpath(interface, resolved_path) == NULL) continue; + if (strstr(resolved_path, "devices/virtual") != NULL) continue; // only get for physical devices + + ret = snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/rx_bytes", dir->d_name); + if (ret >= sizeof(path) || ret < 0) continue; + + FILE *fp = fopen(path, "r"); + fgets(line, NET_STAT_BUFFER_SIZE, fp); + fclose(fp); + rx += strtoul(line, NULL, 10); + + ret = snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/tx_bytes", dir->d_name); + if (ret >= sizeof(path) || ret < 0) continue; + + fp = fopen(path, "r"); + fgets(line, NET_STAT_BUFFER_SIZE, fp); + fclose(fp); + tx += strtoul(line, NULL, 10); + } + closedir(d); + + unsigned long interval = current_time - prev_state.last_time; + if (interval > 0) { + char rate_down[RATE_BUFFER_SIZE], rate_up[RATE_BUFFER_SIZE]; + + readable((rx - prev_state.last_rx) / interval, rate_down); + readable((tx - prev_state.last_tx) / interval, rate_up); + + strncpy(prev_state.last_rate_down, rate_down, RATE_BUFFER_SIZE); + strncpy(prev_state.last_rate_up, rate_up, RATE_BUFFER_SIZE); + + prev_state.last_time = current_time; + prev_state.last_rx = rx; + prev_state.last_tx = tx; + } + + placeholder_t placeholders[] = { + {.name = "%down", .value = prev_state.last_rate_down }, + {.name = "%up", .value = prev_state.last_rate_up } + }; + + const size_t num = sizeof(placeholders) / sizeof(placeholder_t); + char *formatted = format_placeholders(selected_format, &placeholders[0], num); + OUTPUT_FORMATTED; + free(formatted); + OUTPUT_FULL_TEXT(ctx->buf); +} + From 20e74f212fa7d481a196fd67e8eb8a5ac3e5adfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Troels=20Bj=C3=B8rnskov?= Date: Mon, 4 Sep 2023 12:13:38 +0200 Subject: [PATCH 2/3] lint files according to clang-format --- i3status.c | 1 - src/print_netspeed.c | 32 +++++++++++++++++--------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/i3status.c b/i3status.c index e2bb3829..997bbfa8 100644 --- a/i3status.c +++ b/i3status.c @@ -464,7 +464,6 @@ int main(int argc, char *argv[]) { CFG_CUSTOM_SEP_BLOCK_WIDTH_OPT, CFG_END()}; - cfg_opt_t opts[] = { CFG_STR_LIST("order", "{}", CFGF_NONE), CFG_SEC("general", general_opts, CFGF_NONE), diff --git a/src/print_netspeed.c b/src/print_netspeed.c index b4f9d793..0d8bea08 100644 --- a/src/print_netspeed.c +++ b/src/print_netspeed.c @@ -8,7 +8,6 @@ #define NET_STAT_BUFFER_SIZE 100 #define RATE_BUFFER_SIZE 10 - typedef struct { unsigned long last_time; unsigned long last_rx; @@ -22,8 +21,7 @@ static NetworkState prev_state = { 0, 0, {'0', '\0'}, - {'0', '\0'} -}; + {'0', '\0'}}; void readable(unsigned long bytes, char *output) { unsigned long kb = bytes / 1000; // Convert to kilobytes (base-10) @@ -31,7 +29,7 @@ void readable(unsigned long bytes, char *output) { sprintf(output, "%lu KB", kb); } else { unsigned long mb_int = kb / 1000; - unsigned long mb_dec = (kb % 1000 * 100) / 1000; // Multiplied by 100, then divided by 1000 for 2 decimal places + unsigned long mb_dec = (kb % 1000 * 100) / 1000; // Multiplied by 100, then divided by 1000 for 2 decimal places sprintf(output, "%lu.%02lu MB", mb_int, mb_dec); } } @@ -40,11 +38,12 @@ void print_netspeed(netspeed_ctx_t *ctx) { char *outwalk = ctx->buf; const char *selected_format = ctx->format; - unsigned long current_time = (unsigned long) time(NULL); // Renamed variable + unsigned long current_time = (unsigned long)time(NULL); // Renamed variable DIR *d; d = opendir("/sys/class/net"); - if (d ==NULL) return; + if (d == NULL) + return; int ret; char path[PATH_MAX], line[PATH_MAX]; @@ -54,15 +53,19 @@ void print_netspeed(netspeed_ctx_t *ctx) { unsigned long rx = 0, tx = 0; while ((dir = readdir(d)) != NULL) { - if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue; + if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) + continue; snprintf(interface, sizeof(interface), "/sys/class/net/%s", dir->d_name); - if (realpath(interface, resolved_path) == NULL) continue; - if (strstr(resolved_path, "devices/virtual") != NULL) continue; // only get for physical devices + if (realpath(interface, resolved_path) == NULL) + continue; + if (strstr(resolved_path, "devices/virtual") != NULL) + continue; // only get for physical devices ret = snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/rx_bytes", dir->d_name); - if (ret >= sizeof(path) || ret < 0) continue; + if (ret >= sizeof(path) || ret < 0) + continue; FILE *fp = fopen(path, "r"); fgets(line, NET_STAT_BUFFER_SIZE, fp); @@ -70,7 +73,8 @@ void print_netspeed(netspeed_ctx_t *ctx) { rx += strtoul(line, NULL, 10); ret = snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/tx_bytes", dir->d_name); - if (ret >= sizeof(path) || ret < 0) continue; + if (ret >= sizeof(path) || ret < 0) + continue; fp = fopen(path, "r"); fgets(line, NET_STAT_BUFFER_SIZE, fp); @@ -95,9 +99,8 @@ void print_netspeed(netspeed_ctx_t *ctx) { } placeholder_t placeholders[] = { - {.name = "%down", .value = prev_state.last_rate_down }, - {.name = "%up", .value = prev_state.last_rate_up } - }; + {.name = "%down", .value = prev_state.last_rate_down}, + {.name = "%up", .value = prev_state.last_rate_up}}; const size_t num = sizeof(placeholders) / sizeof(placeholder_t); char *formatted = format_placeholders(selected_format, &placeholders[0], num); @@ -105,4 +108,3 @@ void print_netspeed(netspeed_ctx_t *ctx) { free(formatted); OUTPUT_FULL_TEXT(ctx->buf); } - From c39b7f7eb67728da3edefd96dc46b2d1ae48e428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Troels=20Bj=C3=B8rnskov?= Date: Sat, 16 Sep 2023 00:06:47 +0200 Subject: [PATCH 3/3] use full virtual path for interfaces --- src/print_netspeed.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/print_netspeed.c b/src/print_netspeed.c index 0d8bea08..19558d11 100644 --- a/src/print_netspeed.c +++ b/src/print_netspeed.c @@ -38,7 +38,7 @@ void print_netspeed(netspeed_ctx_t *ctx) { char *outwalk = ctx->buf; const char *selected_format = ctx->format; - unsigned long current_time = (unsigned long)time(NULL); // Renamed variable + unsigned long current_time = (unsigned long)time(NULL); DIR *d; d = opendir("/sys/class/net"); @@ -52,6 +52,9 @@ void print_netspeed(netspeed_ctx_t *ctx) { struct dirent *dir; unsigned long rx = 0, tx = 0; + const char virtual_iface_path[] = "/sys/devices/virtual/net/"; + size_t virtual_iface_len = strlen(virtual_iface_path); + while ((dir = readdir(d)) != NULL) { if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue; @@ -60,8 +63,13 @@ void print_netspeed(netspeed_ctx_t *ctx) { if (realpath(interface, resolved_path) == NULL) continue; - if (strstr(resolved_path, "devices/virtual") != NULL) - continue; // only get for physical devices + + size_t full_path_len = virtual_iface_len + strlen(dir->d_name) + 1; + snprintf(path, full_path_len, "%s%s", virtual_iface_path, dir->d_name); + + // check if interface is virtual + if (strcmp(path, resolved_path) == 0) + continue; ret = snprintf(path, sizeof(path), "/sys/class/net/%s/statistics/rx_bytes", dir->d_name); if (ret >= sizeof(path) || ret < 0)