/* * openuf - sysinfo.c * * Reads system statistics for the inform payload. * * ── CPU: /proc/stat ────────────────────────────────────────────────── * * Format: cpu user nice system idle iowait irq softirq steal * * Usage is calculated with two snapshots taken at different times: * active = user + nice + system + irq + softirq + steal * total = active + idle + iowait * usage % = (Δactive / Δtotal) × 100 * * ── Memory: /proc/meminfo ──────────────────────────────────────────── * * MemTotal, MemFree, Buffers, Cached * used = total - free - buffers - cached * * ── Interfaces: /proc/net/dev + /sys/class/net// ───────────── * * /proc/net/dev → cumulative rx/tx counters * /sys/class/net/speed → negotiated speed (Mbps) * /sys/class/net/duplex → "full" / "half" * /sys/class/net/operstate → "up" / "down" / "unknown" * /sys/class/net/address → MAC * ioctl SIOCGIFADDR → IP * * ── Radio: iw dev info + survey dump ───────────────────────── * * info: current channel, TX power * survey dump: active/busy/tx/rx time → calculate % utilization */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "sysinfo.h" /* ═══════════════════════════════════════════════════════════════════ Memory ═══════════════════════════════════════════════════════════════════ */ int sysinfo_mem(mem_stats_t *out) { memset(out, 0, sizeof(*out)); FILE *f = fopen("/proc/meminfo", "r"); if (!f) return -1; char line[128]; while (fgets(line, sizeof(line), f)) { long val = 0; if (sscanf(line, "MemTotal: %ld kB", &val) == 1) out->total_kb = val; else if (sscanf(line, "MemFree: %ld kB", &val) == 1) out->free_kb = val; else if (sscanf(line, "Buffers: %ld kB", &val) == 1) out->buffer_kb = val; else if (sscanf(line, "Cached: %ld kB", &val) == 1) out->cached_kb = val; } fclose(f); return (out->total_kb > 0) ? 0 : -1; } /* ═══════════════════════════════════════════════════════════════════ CPU ═══════════════════════════════════════════════════════════════════ */ typedef struct { unsigned long long user, nice, sys, idle, iowait, irq, softirq, steal; } cpu_snap_t; static cpu_snap_t g_prev = {0}; static int g_valid = 0; static int read_cpu(cpu_snap_t *s) { FILE *f = fopen("/proc/stat", "r"); if (!f) return -1; int r = fscanf(f, "cpu %llu %llu %llu %llu %llu %llu %llu %llu", &s->user, &s->nice, &s->sys, &s->idle, &s->iowait, &s->irq, &s->softirq, &s->steal); fclose(f); return (r >= 4) ? 0 : -1; } int sysinfo_cpu_percent(void) { cpu_snap_t cur; if (read_cpu(&cur) != 0) return 0; if (!g_valid) { g_prev = cur; g_valid = 1; return 0; } unsigned long long da = (cur.user - g_prev.user) + (cur.nice - g_prev.nice) + (cur.sys - g_prev.sys) + (cur.irq - g_prev.irq) + (cur.softirq - g_prev.softirq) + (cur.steal - g_prev.steal); unsigned long long di = (cur.idle - g_prev.idle) + (cur.iowait - g_prev.iowait); unsigned long long dt = da + di; g_prev = cur; return (dt == 0) ? 0 : (int)((da * 100) / dt); } /* ═══════════════════════════════════════════════════════════════════ Network interface ═══════════════════════════════════════════════════════════════════ */ static int read_sysfs_str(const char *iface, const char *file, char *out, size_t sz) { char path[128]; snprintf(path, sizeof(path), "/sys/class/net/%s/%s", iface, file); FILE *f = fopen(path, "r"); if (!f) return -1; char buf[64] = {0}; fgets(buf, sizeof(buf), f); fclose(f); buf[strcspn(buf, "\r\n")] = '\0'; strncpy(out, buf, sz - 1); return (strlen(out) > 0) ? 0 : -1; } static int read_sysfs_int(const char *iface, const char *file) { char buf[32] = {0}; if (read_sysfs_str(iface, file, buf, sizeof(buf)) != 0) return -1; int v = -1; sscanf(buf, "%d", &v); return v; } static void read_ip_ioctl(const char *iface, char *out, size_t sz) { int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) return; struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, iface, IFNAMSIZ - 1); if (ioctl(fd, SIOCGIFADDR, &ifr) == 0) { struct sockaddr_in *sa = (struct sockaddr_in *)&ifr.ifr_addr; strncpy(out, inet_ntoa(sa->sin_addr), sz - 1); } close(fd); } int sysinfo_iface(const char *ifname, iface_stats_t *out) { memset(out, 0, sizeof(*out)); strncpy(out->name, ifname, sizeof(out->name) - 1); /* MAC, operstate, speed, duplex */ read_sysfs_str(ifname, "address", out->mac, sizeof(out->mac)); char opstate[32] = {0}; read_sysfs_str(ifname, "operstate", opstate, sizeof(opstate)); out->up = (strcmp(opstate, "up") == 0 || strcmp(opstate, "unknown") == 0); int sp = read_sysfs_int(ifname, "speed"); out->speed = (sp > 0) ? sp : 1000; char dup[16] = {0}; read_sysfs_str(ifname, "duplex", dup, sizeof(dup)); out->full_duplex = (strncmp(dup, "full", 4) == 0); /* IP */ read_ip_ioctl(ifname, out->ip, sizeof(out->ip)); /* Counters of /proc/net/dev */ FILE *f = fopen("/proc/net/dev", "r"); if (!f) return 0; char line[512]; fgets(line, sizeof(line), f); /* skip header lines */ fgets(line, sizeof(line), f); while (fgets(line, sizeof(line), f)) { char *colon = strchr(line, ':'); if (!colon) continue; /* Extract interface name (may have leading spaces) */ size_t end = colon - line; while (end > 0 && line[end-1] == ' ') end--; size_t start = 0; while (start < end && line[start] == ' ') start++; char name[32] = {0}; size_t nlen = end - start; if (nlen >= sizeof(name)) continue; strncpy(name, line + start, nlen); if (strcmp(name, ifname) != 0) continue; long long rb,rp,re,rd,rf,rframe,rcomp,rmulti; long long tb,tp,te,td,tf,tcol,tcomp,tcarr; sscanf(colon+1, "%lld %lld %lld %lld %lld %lld %lld %lld" " %lld %lld %lld %lld %lld %lld %lld %lld", &rb,&rp,&re,&rd,&rf,&rframe,&rcomp,&rmulti, &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; break; } fclose(f); return 0; } /* ═══════════════════════════════════════════════════════════════════ 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 channel 6 (2437 MHz), width: 20 MHz txpower 20.00 dBm 2. iw dev wlan0 survey dump → channel utilization We look for the block with "[in use]": frequency: 2437 MHz [in use] channel active time: 12345 ms channel busy time: 987 ms channel transmit time: 456 ms channel receive time: 321 ms We calculate: cu_total = busy/active × 100 cu_self_tx = transmit/active × 100 cu_self_rx = receive/active × 100 */ int sysinfo_radio(const char *iface, radio_stats_t *out) { memset(out, 0, sizeof(*out)); strncpy(out->iface, iface, sizeof(out->iface) - 1); out->noise = -95; char cmd[128]; int current_freq = 0; int wiphy_index = -1; /* iw dev info */ snprintf(cmd, sizeof(cmd), "iw dev %s info 2>/dev/null", iface); FILE *p = popen(cmd, "r"); if (!p) return -1; char line[256]; while (fgets(line, sizeof(line), p)) { int ch; float mhz; 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 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 selected = 0; while (fgets(line, sizeof(line), p)) { 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; if (sscanf(line, " channel busy time: %lld ms", &val) == 1) busy = val; if (sscanf(line, " channel transmit time: %lld ms", &val) == 1) tx_t = val; if (sscanf(line, " channel receive time: %lld ms", &val) == 1) rx_t = val; } pclose(p); 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; } 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) { 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; }