Updates
CI / test (push) Successful in 1m15s
Release rolling / release (push) Successful in 7m6s

This commit is contained in:
Raven Scott
2026-07-18 19:49:57 -04:00
parent 2e1a3e9b06
commit a639b3c953
19 changed files with 1705 additions and 293 deletions
+268
View File
@@ -0,0 +1,268 @@
/**
* peardata-ebpf — embedded host helper for eBPF-family charts.
*
* Emits NDJSON lines on stdout every interval (default 1000ms):
* {"chart":"ebpf.cachestat","ts":...,"values":{...}}
*
* Uses /proc + /sys (works without CAP_BPF). When compiled with
* -DPEARDATA_HAVE_LIBBPF and libbpf is linked, additional BPF-backed
* series can be enabled later without changing the agent protocol.
*
* Build:
* cc -O2 -o peardata-ebpf main.c
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <stdint.h>
#include <errno.h>
#include <dirent.h>
#include <sys/stat.h>
static long long now_ms(void) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return (long long)ts.tv_sec * 1000LL + ts.tv_nsec / 1000000LL;
}
static int read_file(const char *path, char *buf, size_t n) {
FILE *f = fopen(path, "r");
if (!f) return -1;
size_t r = fread(buf, 1, n - 1, f);
fclose(f);
buf[r] = 0;
return (int)r;
}
static long long read_ll(const char *path) {
char buf[64];
if (read_file(path, buf, sizeof(buf)) < 0) return -1;
return atoll(buf);
}
static long long vmstat(const char *key) {
FILE *f = fopen("/proc/vmstat", "r");
if (!f) return 0;
char line[256];
char k[64];
long long v = 0;
while (fgets(line, sizeof(line), f)) {
if (sscanf(line, "%63s %lld", k, &v) == 2 && strcmp(k, key) == 0) {
fclose(f);
return v;
}
}
fclose(f);
return 0;
}
static void emit(const char *chart, const char *values_json) {
printf("{\"chart\":\"%s\",\"ts\":%lld,\"values\":%s}\n", chart, now_ms(), values_json);
fflush(stdout);
}
static void collect_cachestat(
long long *prev_hit,
long long *prev_miss,
long long *prev_in,
long long *prev_out,
double dt
) {
/* Page-cache efficiency proxy from vmstat (no CAP_BPF required). */
long long pgfault = vmstat("pgfault");
long long pgmajfault = vmstat("pgmajfault");
long long pgpgin = vmstat("pgpgin");
long long pgpgout = vmstat("pgpgout");
long long hit = pgfault > pgmajfault ? pgfault - pgmajfault : 0;
long long miss = pgmajfault;
double hits = 0, misses = 0, ratio = 0, rin = 0, rout = 0;
if (*prev_hit >= 0 && dt > 0) {
hits = (hit - *prev_hit) / dt;
misses = (miss - *prev_miss) / dt;
rin = (pgpgin - *prev_in) / dt;
rout = (pgpgout - *prev_out) / dt;
double den = hits + misses;
ratio = den > 0 ? (hits / den) * 100.0 : 0;
}
*prev_hit = hit;
*prev_miss = miss;
*prev_in = pgpgin;
*prev_out = pgpgout;
char buf[256];
snprintf(
buf,
sizeof(buf),
"{\"hits\":%.3f,\"misses\":%.3f,\"ratio\":%.3f,\"pgpgin\":%.3f,\"pgpgout\":%.3f}",
hits,
misses,
ratio,
rin,
rout
);
emit("ebpf.cachestat", buf);
}
static void collect_fd(void) {
char buf[128];
long long alloc = 0, max = 0;
if (read_file("/proc/sys/fs/file-nr", buf, sizeof(buf)) > 0) {
sscanf(buf, "%lld %*s %lld", &alloc, &max);
}
char out[128];
snprintf(out, sizeof(out), "{\"open\":%lld,\"max\":%lld}", alloc, max);
emit("ebpf.fd", out);
}
static void collect_oom(long long *prev, double dt) {
long long kills = vmstat("oom_kill");
double rate = 0;
if (*prev >= 0 && dt > 0) rate = (kills - *prev) / dt;
*prev = kills;
char out[64];
snprintf(out, sizeof(out), "{\"kills\":%.3f,\"total\":%lld}", rate, kills);
emit("ebpf.oom", out);
}
static void collect_process(long long *prev_forks, long long *prev_ctxt, double dt) {
FILE *f = fopen("/proc/stat", "r");
long long forks = 0, ctxt = 0;
if (f) {
char line[256];
while (fgets(line, sizeof(line), f)) {
if (strncmp(line, "processes ", 10) == 0) forks = atoll(line + 10);
else if (strncmp(line, "ctxt ", 5) == 0) ctxt = atoll(line + 5);
}
fclose(f);
}
double fr = 0, cr = 0;
if (*prev_forks >= 0 && dt > 0) {
fr = (forks - *prev_forks) / dt;
cr = (ctxt - *prev_ctxt) / dt;
}
*prev_forks = forks;
*prev_ctxt = ctxt;
char out[128];
snprintf(out, sizeof(out), "{\"forks\":%.3f,\"ctxt\":%.3f}", fr, cr);
emit("ebpf.process", out);
}
static void collect_shm(void) {
FILE *f = fopen("/proc/sysvipc/shm", "r");
long long segs = 0, bytes = 0;
if (f) {
char line[512];
int first = 1;
while (fgets(line, sizeof(line), f)) {
if (first) {
first = 0;
continue;
}
long long size = 0;
/* key shmid perms size ... */
if (sscanf(line, "%*s %*s %*s %lld", &size) >= 1) {
segs++;
bytes += size;
}
}
fclose(f);
}
char out[128];
snprintf(out, sizeof(out), "{\"segments\":%lld,\"bytes\":%lld}", segs, bytes);
emit("ebpf.shm", out);
}
static void collect_swap(long long *prev_in, long long *prev_out, double dt) {
long long in = vmstat("pswpin");
long long out = vmstat("pswpout");
double ri = 0, ro = 0;
if (*prev_in >= 0 && dt > 0) {
ri = (in - *prev_in) / dt;
ro = (out - *prev_out) / dt;
}
*prev_in = in;
*prev_out = out;
char buf[128];
snprintf(buf, sizeof(buf), "{\"in\":%.3f,\"out\":%.3f}", ri, ro);
emit("ebpf.swap", buf);
}
static void collect_vfs(long long *prev_read, long long *prev_write, double dt) {
long long r = vmstat("pgpgout"); /* proxy */
long long w = vmstat("pgpgin");
/* Prefer ext4-ish: use /proc/vmstat nr_dirty_threshold unrelated — keep simple */
double rr = 0, ww = 0;
if (*prev_read >= 0 && dt > 0) {
rr = (r - *prev_read) / dt;
ww = (w - *prev_write) / dt;
}
*prev_read = r;
*prev_write = w;
char buf[128];
snprintf(buf, sizeof(buf), "{\"read_pages\":%.3f,\"write_pages\":%.3f}", rr, ww);
emit("ebpf.vfs", buf);
}
static void collect_net(long long *prev_tcp, double dt) {
FILE *f = fopen("/proc/net/sockstat", "r");
long long tcp = 0;
if (f) {
char line[256];
while (fgets(line, sizeof(line), f)) {
if (strncmp(line, "TCP:", 4) == 0) {
sscanf(line, "TCP: inuse %lld", &tcp);
}
}
fclose(f);
}
double rate = 0;
if (*prev_tcp >= 0 && dt > 0) rate = (tcp - *prev_tcp) / dt;
*prev_tcp = tcp;
char buf[128];
snprintf(buf, sizeof(buf), "{\"tcp_inuse\":%lld,\"tcp_delta\":%.3f}", tcp, rate);
emit("ebpf.socket", buf);
}
int main(int argc, char **argv) {
int interval_ms = 1000;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--interval") == 0 && i + 1 < argc) {
interval_ms = atoi(argv[++i]);
if (interval_ms < 200) interval_ms = 200;
}
}
long long prev_hit = -1, prev_miss = -1, prev_cin = -1, prev_cout = -1;
long long prev_oom = -1;
long long prev_forks = -1, prev_ctxt = -1;
long long prev_swap_in = -1, prev_swap_out = -1;
long long prev_vfs_r = -1, prev_vfs_w = -1;
long long prev_tcp = -1;
long long last = now_ms();
fprintf(stderr, "peardata-ebpf ready interval=%dms\n", interval_ms);
for (;;) {
long long t0 = now_ms();
double dt = (t0 - last) / 1000.0;
if (dt <= 0) dt = interval_ms / 1000.0;
last = t0;
collect_cachestat(&prev_hit, &prev_miss, &prev_cin, &prev_cout, dt);
collect_fd();
collect_oom(&prev_oom, dt);
collect_process(&prev_forks, &prev_ctxt, dt);
collect_shm();
collect_swap(&prev_swap_in, &prev_swap_out, dt);
collect_vfs(&prev_vfs_r, &prev_vfs_w, dt);
collect_net(&prev_tcp, dt);
long long elapsed = now_ms() - t0;
long long sleep_ms = interval_ms - elapsed;
if (sleep_ms < 50) sleep_ms = 50;
usleep((useconds_t)(sleep_ms * 1000));
}
return 0;
}