adding more metric reporting (#27)
Reviewed-on: #27
This commit was merged in pull request #27.
This commit is contained in:
+266
-18
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user