adding more metric reporting (#27)

Reviewed-on: #27
This commit was merged in pull request #27.
This commit is contained in:
2026-07-14 23:09:21 +01:00
parent b014e67753
commit b52a0a165d
5 changed files with 573 additions and 42 deletions
+266 -18
View File
@@ -38,6 +38,7 @@
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <time.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
@@ -200,6 +201,7 @@ int sysinfo_iface(const char *ifname, iface_stats_t *out)
&tb,&tp,&te,&td,&tf,&tcol,&tcomp,&tcarr);
out->rx_bytes = rb; out->rx_packets = rp;
out->rx_errors = re; out->rx_dropped = rd;
out->rx_frame = rframe;
out->rx_multicast= rmulti;
out->tx_bytes = tb; out->tx_packets = tp;
out->tx_errors = te; out->tx_dropped = td;
@@ -213,6 +215,46 @@ int sysinfo_iface(const char *ifname, iface_stats_t *out)
WiFi Radio
═══════════════════════════════════════════════════════════════════
Survey counters are cumulative. Keep a small per-interface snapshot so
each inform reports utilization during the latest interval rather than an
average since the radio was started.
*/
typedef struct {
char iface[32];
long long active;
long long busy;
long long tx;
long long rx;
long long sta_tx_duration;
long long sta_rx_duration;
struct timespec duration_time;
int duration_valid;
int valid;
} survey_snapshot_t;
#define MAX_SURVEY_SNAPSHOTS 8
static survey_snapshot_t survey_snapshots[MAX_SURVEY_SNAPSHOTS];
static int utilization_percent(long long part, long long total)
{
if (part <= 0 || total <= 0) return 0;
long long value = (part * 100 + total / 2) / total;
if (value < 0) return 0;
if (value > 100) return 100;
return (int)value;
}
static int count_antenna_chains(unsigned int mask)
{
int count = 0;
while (mask) {
count += mask & 1U;
mask >>= 1;
}
return count;
}
/*
1. iw dev wlan0 info → channel and power
Example:
Interface wlan0
@@ -239,6 +281,8 @@ int sysinfo_radio(const char *iface, radio_stats_t *out)
out->noise = -95;
char cmd[128];
int current_freq = 0;
int wiphy_index = -1;
/* iw dev <iface> info */
snprintf(cmd, sizeof(cmd), "iw dev %s info 2>/dev/null", iface);
@@ -248,30 +292,83 @@ int sysinfo_radio(const char *iface, radio_stats_t *out)
char line[256];
while (fgets(line, sizeof(line), p)) {
int ch; float mhz;
if (sscanf(line, " channel %d (%f MHz)", &ch, &mhz) == 2)
if (sscanf(line, " channel %d (%f MHz)", &ch, &mhz) == 2) {
out->channel = ch;
current_freq = (int)(mhz + 0.5f);
}
int index;
if (sscanf(line, " wiphy %d", &index) == 1)
wiphy_index = index;
float tp;
if (sscanf(line, " txpower %f dBm", &tp) == 1)
out->tx_power = (int)tp;
}
pclose(p);
/* Read the physical radio rather than trusting the emulated model. */
if (wiphy_index >= 0) {
snprintf(cmd, sizeof(cmd), "iw phy phy%d info 2>/dev/null", wiphy_index);
p = popen(cmd, "r");
if (p) {
unsigned int available_tx = 0, available_rx = 0;
unsigned int configured_tx = 0, configured_rx = 0;
int max_mcs_nss = 0;
while (fgets(line, sizeof(line), p)) {
unsigned int tx_mask, rx_mask;
if (sscanf(line, " Configured Antennas: TX 0x%x RX 0x%x",
&tx_mask, &rx_mask) == 2) {
configured_tx = tx_mask;
configured_rx = rx_mask;
} else if (sscanf(line, " Available Antennas: TX 0x%x RX 0x%x",
&tx_mask, &rx_mask) == 2) {
available_tx = tx_mask;
available_rx = rx_mask;
}
int streams, top_mcs;
if (sscanf(line, " %d streams: MCS 0-%d", &streams, &top_mcs) == 2 &&
streams > max_mcs_nss)
max_mcs_nss = streams;
if (sscanf(line,
" HT TX/RX MCS rate indexes supported: 0-%d",
&top_mcs) == 1) {
int ht_nss = top_mcs / 8 + 1;
if (ht_nss > max_mcs_nss) max_mcs_nss = ht_nss;
}
}
pclose(p);
out->tx_antennas = count_antenna_chains(
configured_tx ? configured_tx : available_tx);
out->rx_antennas = count_antenna_chains(
configured_rx ? configured_rx : available_rx);
if (out->tx_antennas && out->rx_antennas)
out->nss = out->tx_antennas < out->rx_antennas
? out->tx_antennas : out->rx_antennas;
else
out->nss = max_mcs_nss;
}
}
/* iw dev <iface> survey dump */
snprintf(cmd, sizeof(cmd), "iw dev %s survey dump 2>/dev/null", iface);
p = popen(cmd, "r");
if (!p) return 0;
long long active=0, busy=0, tx_t=0, rx_t=0;
int in_use = 0;
int selected = 0;
while (fgets(line, sizeof(line), p)) {
if (strstr(line, "[in use]")) {
in_use = 1; active=busy=tx_t=rx_t=0; continue;
}
if (!in_use) continue;
/* New frequency without [in use] resets the block */
if (strstr(line, "frequency:") && !strstr(line, "[in use]")) {
in_use = 0; continue;
int survey_freq;
if (sscanf(line, " frequency: %d MHz", &survey_freq) == 1) {
/* Some drivers omit the optional [in use] marker, especially on
* the secondary radio. Match the frequency reported by iw info. */
selected = strstr(line, "[in use]") != NULL ||
(current_freq > 0 && survey_freq == current_freq);
if (selected)
active=busy=tx_t=rx_t=0;
continue;
}
if (!selected) continue;
float noise; long long val;
if (sscanf(line, " noise: %f dBm", &noise) == 1) out->noise = (int)noise;
if (sscanf(line, " channel active time: %lld ms", &val) == 1) active = val;
@@ -281,18 +378,169 @@ int sysinfo_radio(const char *iface, radio_stats_t *out)
}
pclose(p);
if (active > 0) {
out->cu_total = (int)(busy * 100 / active);
out->cu_self_tx = (int)(tx_t * 100 / active);
out->cu_self_rx = (int)(rx_t * 100 / active);
long long sample_active = active;
long long sample_busy = busy;
long long sample_tx = tx_t;
long long sample_rx = rx_t;
survey_snapshot_t *snapshot = NULL;
survey_snapshot_t *free_slot = NULL;
for (int i = 0; i < MAX_SURVEY_SNAPSHOTS; i++) {
if (survey_snapshots[i].valid &&
!strcmp(survey_snapshots[i].iface, iface)) {
snapshot = &survey_snapshots[i];
break;
}
if (!survey_snapshots[i].valid && !free_slot)
free_slot = &survey_snapshots[i];
}
if (!snapshot) snapshot = free_slot;
if (snapshot && snapshot->valid && active > snapshot->active &&
busy >= snapshot->busy && tx_t >= snapshot->tx && rx_t >= snapshot->rx) {
sample_active = active - snapshot->active;
sample_busy = busy - snapshot->busy;
sample_tx = tx_t - snapshot->tx;
sample_rx = rx_t - snapshot->rx;
}
/* Number of associated clients */
snprintf(cmd, sizeof(cmd),
"iw dev %s station dump 2>/dev/null | grep -c '^Station'",
iface);
if (snapshot && active > 0) {
snprintf(snapshot->iface, sizeof(snapshot->iface), "%s", iface);
snapshot->active = active;
snapshot->busy = busy;
snapshot->tx = tx_t;
snapshot->rx = rx_t;
snapshot->valid = 1;
}
if (sample_active > 0) {
out->cu_total = utilization_percent(sample_busy, sample_active);
out->cu_self_tx = utilization_percent(sample_tx, sample_active);
out->cu_self_rx = utilization_percent(sample_rx, sample_active);
}
/* Associated clients and counters exposed by nl80211. */
snprintf(cmd, sizeof(cmd), "iw dev %s station dump 2>/dev/null", iface);
p = popen(cmd, "r");
if (p) { fscanf(p, "%d", &out->num_sta); pclose(p); }
if (p) {
while (fgets(line, sizeof(line), p)) {
long long val;
if (!strncmp(line, "Station ", 8)) {
out->num_sta++;
} else if (sscanf(line, " tx packets: %lld", &val) == 1) {
out->tx_packets += val;
} else if (sscanf(line, " tx retries: %lld", &val) == 1) {
out->tx_retries += val;
} else if (sscanf(line, " tx failed: %lld", &val) == 1) {
out->tx_failed += val;
} else if (sscanf(line, " tx duration: %lld us", &val) == 1) {
out->tx_duration += val;
} else if (sscanf(line, " rx duration: %lld us", &val) == 1) {
out->rx_duration += val;
}
}
pclose(p);
}
/* Some drivers expose no survey busy time on their secondary radio but
* do expose per-station airtime durations. Use those interval counters as
* a conservative "This AP" fallback; interference remains zero because
* station data cannot measure neighboring transmitters. */
struct timespec now;
if (snapshot && clock_gettime(CLOCK_MONOTONIC, &now) == 0) {
long long elapsed_us = 0;
if (snapshot->duration_valid) {
elapsed_us = (now.tv_sec - snapshot->duration_time.tv_sec) * 1000000LL +
(now.tv_nsec - snapshot->duration_time.tv_nsec) / 1000LL;
}
if (snapshot->duration_valid && elapsed_us >= 1000000LL &&
out->tx_duration >= snapshot->sta_tx_duration &&
out->rx_duration >= snapshot->sta_rx_duration &&
out->cu_total == 0) {
long long tx_delta = out->tx_duration - snapshot->sta_tx_duration;
long long rx_delta = out->rx_duration - snapshot->sta_rx_duration;
out->cu_self_tx = utilization_percent(tx_delta, elapsed_us);
out->cu_self_rx = utilization_percent(rx_delta, elapsed_us);
out->cu_total = out->cu_self_tx + out->cu_self_rx;
if (out->cu_total > 100) out->cu_total = 100;
}
/* sysinfo_radio() is called more than once while building an inform.
* Ignore sub-second calls so they do not replace the interval base. */
if (!snapshot->duration_valid || elapsed_us >= 1000000LL) {
if (!snapshot->valid) {
snprintf(snapshot->iface, sizeof(snapshot->iface), "%s", iface);
snapshot->valid = 1;
}
snapshot->sta_tx_duration = out->tx_duration;
snapshot->sta_rx_duration = out->rx_duration;
snapshot->duration_time = now;
snapshot->duration_valid = 1;
}
}
return 0;
}
static int frequency_to_channel(int frequency)
{
if (frequency == 2484) return 14;
if (frequency >= 2412 && frequency <= 2472)
return (frequency - 2407) / 5;
if (frequency >= 5000 && frequency <= 5895)
return (frequency - 5000) / 5;
if (frequency >= 5955 && frequency <= 7115)
return (frequency - 5950) / 5;
return 0;
}
int sysinfo_wifi_scan_cache(const char *iface, wifi_scan_t *out, int max_out)
{
if (!iface || !out || max_out <= 0) return 0;
char cmd[128];
snprintf(cmd, sizeof(cmd), "iw dev %s scan dump 2>/dev/null", iface);
FILE *p = popen(cmd, "r");
if (!p) return 0;
int count = 0;
wifi_scan_t *cur = NULL;
char line[512];
while (fgets(line, sizeof(line), p)) {
char bssid[32];
if (sscanf(line, "BSS %31[^ (]", bssid) == 1) {
if (count >= max_out) {
cur = NULL;
continue;
}
cur = &out[count++];
memset(cur, 0, sizeof(*cur));
snprintf(cur->bssid, sizeof(cur->bssid), "%s", bssid);
continue;
}
if (!cur) continue;
int value;
float signal;
char essid[64];
if (sscanf(line, " freq: %d", &value) == 1) {
cur->frequency = value;
cur->channel = frequency_to_channel(value);
} else if (sscanf(line, " signal: %f dBm", &signal) == 1) {
cur->signal = (int)signal;
value = (cur->signal + 100) * 2;
cur->rssi = value < 0 ? 0 : value > 100 ? 100 : value;
} else if (sscanf(line, " last seen: %d ms ago", &value) == 1) {
cur->age = value / 1000;
} else if (sscanf(line, " SSID: %63[^\n]", essid) == 1) {
snprintf(cur->essid, sizeof(cur->essid), "%s", essid);
} else if (strstr(line, "capability:") && strstr(line, "Privacy")) {
cur->secured = true;
} else if (strstr(line, "RSN:") || strstr(line, "WPA:")) {
cur->secured = true;
}
}
pclose(p);
return count;
}