#define _GNU_SOURCE /* gethostname 等 GNU/musl 扩展 */ /* meow_svc_perf.c — MeowOS 性能分析专项子服务(SYSTEM_FRAMEWORK_SERVICES §1.2/§2.2)。 * * 单一二进制双形态: * - svcd 以**空参**拉起(§1.3 conf `args=` 空)→ IPC 服务常驻:bind `/usr/data/meow_perf.sock` * (`--sock`/`MEOW_IPC_PERF_SOCK` 可覆盖),收 MSG_HELLO→MSG_ACK、MSG_PERF_QUERY→MSG_PERF_RESULT。 * - root shell `meow_svc_perf [query]` → 一次性 CLI(免鉴权)打印聚合 JSON(不常驻)。 * * 聚合的数据源与 launcher `about.c`/`util.c`/bootmenu**同源**(/proc/stat、/proc/meminfo、 * /proc/uptime、/proc/loadavg、/sys/class/thermal 各 zone、df/mount、/proc/cpuinfo、/etc/meowos_version、 * gethostname/uname),聚合成与 MEOWPANEL_PROTOCOL §3.1 `/api/status`/.data 对齐的 JSON—— * 单一权威源,MeowPanel 后端/launcher 不再各自爬 /proc。 * * MSG_PERF_QUERY.subset 位掩码(m_perf_query_t):0=全部;PERF_SUBSET_PERF / PERF_SUBSET_SYSTEM。 * 鉴权(一期):性能查询**只读安全**,本批不强制 os.meow.*;完整读级鉴权见 §5.3(二期)。 * * 编译(mipsel 静态):build_svc_perf.sh;宿主编译 build_svc_perf.sh host。 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "meow_ipc.h" /* 帧格式/消息号 + m_perf_query_t/m_perf_result_t(与 launcher 同源) */ #define VERSION_FILE "/etc/meowos_version" #define TF_MOUNT "/usr/data/mnt/sd_0" #define MEOW_PIPE_MAX 4096 #define MEOW_TIMEOUT_MS 1500 static const char *g_sock = "/usr/data/meow_perf.sock"; static void svc_log(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "[meow_svc_perf] "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); } /* ---------------- 有界 JSON 追加 ---------------- */ typedef struct { char *b; size_t n; size_t cap; } sbuf_t; static void sA(sbuf_t *s, const char *fmt, ...) { if (s->n >= s->cap - 1) return; va_list ap; va_start(ap, fmt); int w = vsnprintf(s->b + s->n, s->cap - s->n, fmt, ap); va_end(ap); if (w < 0) return; if ((size_t)w >= s->cap - s->n) s->n = s->cap - 1; else s->n += (size_t)w; } /* JSON 字符串转义(version/hostname 等;ASCII 常见字符直通,引号/反斜杠转义) */ static void sA_str(sbuf_t *s, const char *v) { if (!v) v = ""; sA(s, "\""); for (const char *p = v; *p; p++) { unsigned char c = (unsigned char)*p; if (c == '"' || c == '\\') { sA(s, "\\%c", (int)c); } else if (c < 0x20) { sA(s, "\\u%04x", c); } else sA(s, "%c", (int)c); /* buf 0x80.. 按字节透传(utf-8) */ } sA(s, "\""); } /* ---------------- 限时读整文件 ---------------- */ static int read_file_txt(const char *path, char *buf, int cap) { int fd = open(path, O_RDONLY); if (fd < 0) return -1; int n = 0; while (n < cap - 1) { ssize_t r = read(fd, buf + n, (size_t)(cap - 1 - n)); if (r <= 0) break; n += (int)r; } close(fd); buf[n] = 0; while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r')) buf[--n] = 0; return n; } /* ---------------- popen 限时读(kill 子进程防挂) ---------------- */ static char *popen_read_tmo(int tmo_ms, const char *cmd) { int p[2]; if (pipe(p) < 0) return NULL; pid_t pid = fork(); if (pid < 0) { close(p[0]); close(p[1]); return NULL; } if (pid == 0) { dup2(p[1], 1); close(p[0]); close(p[1]); execl("/bin/sh", "sh", "-c", cmd, (char *)0); _exit(127); } close(p[1]); fcntl(p[0], F_SETFL, fcntl(p[0], F_GETFL, 0) | O_NONBLOCK); char *buf = malloc(MEOW_PIPE_MAX); if (!buf) { close(p[0]); waitpid(pid, NULL, 0); return NULL; } size_t off = 0; int remain = tmo_ms; while (remain > 0 && off < MEOW_PIPE_MAX - 1) { struct pollfd pf = { p[0], POLLIN, 0 }; int r = poll(&pf, 1, remain); if (r < 0) { if (errno == EINTR) continue; break; } if (r == 0) break; ssize_t rd = read(p[0], buf + off, MEOW_PIPE_MAX - 1 - off); if (rd == 0) break; if (rd < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) continue; break; } off += (size_t)rd; } close(p[0]); waitpid(pid, NULL, 0); buf[off] = 0; return buf; } /* ---------------- CPU ---------------- */ /* /proc/stat 首行 "cpu user nice system idle iowait irq softirq steal ..."。 */ static int g_cpu_prev[10]; static int g_cpu_prev_init = 0; static double cpu_usage_pct(void) { FILE *f = fopen("/proc/stat", "r"); char line[256]; if (!f || !fgets(line, sizeof line, f)) { if (f) fclose(f); return -1; } fclose(f); unsigned long long vals[10]; char *p = line + 3; /* skip "cpu" */ int n = 0; while (n < 10) { while (*p == ' ') p++; if (!*p || !isdigit((unsigned char)*p)) break; vals[n++] = strtoull(p, &p, 10); } if (n < 4) return -1; unsigned long long total = 0, idle = 0; for (int i = 0; i < n; i++) total += vals[i]; idle = vals[3] + (n > 4 ? vals[4] : 0); if (!g_cpu_prev_init) { for (int i = 0; i < 10; i++) g_cpu_prev[i] = (int)vals[i]; g_cpu_prev_init = 1; return 0; } unsigned long long dtotal = total, didle = idle; for (int i = 0; i < n; i++) if (i < 10) dtotal -= (unsigned long long)g_cpu_prev[i]; if (n > 4) didle -= (unsigned long long)g_cpu_prev[3] + (unsigned long long)g_cpu_prev[4]; else didle -= (unsigned long long)g_cpu_prev[3]; for (int i = 0; i < 10; i++) g_cpu_prev[i] = (int)vals[i]; if (!dtotal) return 0; double u = 100.0 * (double)(dtotal - didle) / (double)dtotal; if (u < 0) u = 0; return u; } static int core_count(void) { FILE *f = fopen("/proc/stat", "r"); char line[256]; int n = 0; if (!f) return 1; while (fgets(line, sizeof line, f)) { if (line[0] == 'c' && line[1] == 'p' && line[2] == 'u' && isdigit((unsigned char)line[3])) n++; if (line[0] != 'c') break; } fclose(f); return n > 0 ? n : 1; } static void loadavg(double *l1, double *l5, double *l15) { FILE *f = fopen("/proc/loadavg", "r"); *l1 = *l5 = *l15 = 0; if (f) { if (fscanf(f, "%lf %lf %lf", l1, l5, l15) != 3) { *l1 = *l5 = *l15 = 0; } fclose(f); } } /* ---------------- 内存 ---------------- */ static void meminfo(unsigned long *total, unsigned long *used, unsigned long *free_kb, unsigned long *avail, unsigned long *stotal, unsigned long *sused) { *total = *used = *free_kb = *avail = *stotal = *sused = 0; FILE *f = fopen("/proc/meminfo", "r"); if (!f) return; char k[64], line[256]; unsigned long mtot = 0, mfree = 0, mavail = 0, swapT = 0, swapF = 0; while (fgets(line, sizeof line, f)) { unsigned long v; if (sscanf(line, "%63s %lu", k, &v) != 2) continue; if (!strcmp(k, "MemTotal:")) mtot = v; else if (!strcmp(k, "MemFree:")) mfree = v; else if (!strcmp(k, "MemAvailable:")) mavail = v; else if (!strcmp(k, "SwapTotal:")) swapT = v; else if (!strcmp(k, "SwapFree:")) swapF = v; } fclose(f); *total = mtot; *free_kb = mfree; if (mavail > 0) { *avail = mavail; *used = (mtot >= mavail) ? mtot - mavail : 0; } else { *avail = mfree; *used = (mtot >= mfree) ? mtot - mfree : 0; } *stotal = swapT; *sused = (swapT >= swapF) ? swapT - swapF : 0; } static double uptime_sec(void) { FILE *f = fopen("/proc/uptime", "r"); double up = 0; if (f) { if (fscanf(f, "%lf", &up) != 1) up = 0; fclose(f); } return up; } /* ---------------- 温度(/sys/class/thermal/thermal_zone*;type 匹配 cpu→cpu,其余→board) ---------------- */ static void temp_json(sbuf_t *s, int *any) { DIR *d = opendir("/sys/class/thermal"); *any = 0; if (!d) { sA(s, "{}"); return; } double cpu = -1, board = -1; struct dirent *e; while ((e = readdir(d))) { if (strncmp(e->d_name, "thermal_zone", 12) != 0) continue; char path[300], typ[64] = "", tmp[32] = ""; snprintf(path, sizeof path, "/sys/class/thermal/%s/type", e->d_name); read_file_txt(path, typ, sizeof typ); snprintf(path, sizeof path, "/sys/class/thermal/%s/temp", e->d_name); read_file_txt(path, tmp, sizeof tmp); long millideg = atol(tmp); if (millideg <= 0) continue; double c = (double)millideg / 1000.0; if (strstr(typ, "cpu") || strstr(typ, "CPU")) { if (cpu < 0) cpu = c; } else if (board < 0) board = c; } closedir(d); if (cpu < 0 && board < 0) { sA(s, "{}"); return; } sA(s, "{"); int first = 1; if (cpu >= 0) { sA(s, "\"cpu\":%.1f", cpu); first = 0; } if (board >= 0) { if (!first) sA(s, ","); sA(s, "\"board\":%.1f", board); } sA(s, "}"); *any = 1; } /* ---------------- 存储(df -k 第 2 行:右 5 字段 = fs,total,used,avail,use%,mount) ---------------- */ static int df_parse(const char *path, unsigned long *total, unsigned long *used, unsigned long *avail) { *total = *used = *avail = 0; char cmd[160]; snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin df -k %s 2>/dev/null", path); char *o = popen_read_tmo(MEOW_TIMEOUT_MS, cmd); if (!o) return -1; char *ln = strchr(o, '\n'); if (ln) ln++; char *save = NULL; char *toks[8]; int nt = 0; for (char *tk = strtok_r(ln ? ln : o, " \t", &save); tk && nt < 8; tk = strtok_r(NULL, " \t", &save)) toks[nt++] = tk; if (nt >= 5) { *total = strtoul(toks[nt - 5], NULL, 10); *used = strtoul(toks[nt - 4], NULL, 10); *avail = strtoul(toks[nt - 3], NULL, 10); } free(o); return (*total || *avail) ? 0 : -1; } static int tf_present(void) { FILE *f = fopen("/proc/mounts", "r"); if (!f) return 0; char line[512]; int present = 0; while (fgets(line, sizeof line, f)) { if (strstr(line, TF_MOUNT)) { present = 1; break; } } fclose(f); return present; } /* ---------------- 系统静态(version/hostname/kernel/arch) ---------------- */ static void sys_arch(char *out, int cap) { struct utsname u; const char *m = "mipsel"; if (uname(&u) == 0) m = u.machine; if (strstr(m, "mips")) m = "mipsel"; /* mipsel-linux-muslsf 目标 */ snprintf(out, cap, "%.31s", m); } static void sys_kernel(char *out, int cap) { struct utsname u; size_t rl = 0; if (uname(&u) != 0) { snprintf(out, cap, "-"); return; } rl = strnlen(u.release, 64); if (rl > (size_t)(cap - 1)) rl = (size_t)(cap - 1); memcpy(out, u.release, rl); out[rl] = 0; } /* ---------------- 网络(wifi/bluetooth/hostname_local,SYSTEM 子集) ---------------- * 单一权威源:/api/system、/api/status 的 network 段由 meow_svc_perf 聚合(SYSTEM_FRAMEWORK_SERVICES §4.1)。 * 采集只走 popen(wpa_cli/ip/hciconfig) + /sys 只读,带超时防挂;拿不到的真实值回 null/占位,绝不编造。 */ /* wifi:解析 wpa_cli status;返回 connected(1/0)。ssid/ip 失败置空串(→ null)。 */ static int net_wifi(char *ssid, size_t sscap, char *ip, size_t ipcap) { ssid[0] = 0; ip[0] = 0; int connected = 0; char *o = popen_read_tmo(MEOW_TIMEOUT_MS, "PATH=/bin:/sbin:/usr/bin:/usr/sbin wpa_cli status 2>/dev/null"); if (o) { char *ln = o; while (ln && *ln) { char *nl = strchr(ln, '\n'); if (nl) *nl = 0; if (!strncmp(ln, "wpa_state=", 10)) { if (!strcmp(ln + 10, "COMPLETED")) connected = 1; } else if (!strncmp(ln, "ssid=", 5)) { snprintf(ssid, sscap, "%s", ln + 5); } if (!nl) break; ln = nl + 1; } free(o); } /* IP:ip -o -4 addr show wlan0 → "2: wlan0 inet 10.78.193.50/24 ...",取含 '.'+'/' 的 token 的 '/' 前段 */ char *o2 = popen_read_tmo(MEOW_TIMEOUT_MS, "PATH=/bin:/sbin:/usr/bin:/usr/sbin ip -o -4 addr show wlan0 2>/dev/null"); if (o2) { for (char *tk = strtok(o2, " \t"); tk; tk = strtok(NULL, " \t")) { char *dot = strchr(tk, '.'); if (!dot) continue; char *sl = strchr(tk, '/'); if (!sl || sl < dot) continue; size_t len = (size_t)(sl - tk); if (len >= ipcap) len = ipcap - 1; memcpy(ip, tk, len); ip[len] = 0; break; } free(o2); } return connected; } /* bluetooth 段字段(不含外层 {}):available/powered/address/connected/devices。 */ static void net_bluetooth_json(sbuf_t *s) { int available = 0; struct stat st; if (stat("/sys/class/bluetooth/hci0", &st) == 0) available = 1; sA(s, "\"available\":%s", available ? "true" : "false"); /* address:/sys/class/bluetooth/hci0/address 优先,回退 hciconfig 的 BD Address */ char addr[32] = ""; if (available) { read_file_txt("/sys/class/bluetooth/hci0/address", addr, sizeof addr); if (!addr[0]) { char *o = popen_read_tmo(MEOW_TIMEOUT_MS, "PATH=/bin:/sbin:/usr/bin:/usr/sbin hciconfig hci0 2>/dev/null"); if (o) { char *p = strstr(o, "BD Address:"); if (p) { p += 11; /* "BD Address:" 长 11 */ while (*p == ' ') p++; int i = 0; while (*p && *p != ' ' && *p != '\n' && i < (int)sizeof addr - 1) addr[i++] = *p++; addr[i] = 0; } free(o); } } } sA(s, ",\"address\":"); if (addr[0]) sA_str(s, addr); else sA(s, "null"); /* powered:/sys/.../power(on/off)优先,文件不可读回退 hciconfig 行首 UP/DOWN */ int powered = 0; char pw[8] = ""; int pwok = 0; if (available) { pwok = read_file_txt("/sys/class/bluetooth/hci0/power", pw, sizeof pw); if (pwok > 0 && pw[0]) { powered = (!strcmp(pw, "on")); } else { char *o = popen_read_tmo(MEOW_TIMEOUT_MS, "PATH=/bin:/sbin:/usr/bin:/usr/sbin hciconfig hci0 2>/dev/null"); if (o) { char *ln = o; while (ln && *ln) { char *nl = strchr(ln, '\n'); if (nl) *nl = 0; char *q = ln; while (*q == ' ' || *q == '\t') q++; if (!strncmp(q, "UP", 2)) { powered = 1; break; } if (!strncmp(q, "DOWN", 4)) { powered = 0; break; } if (!nl) break; ln = nl + 1; } free(o); } } } sA(s, ",\"powered\":%s", powered ? "true" : "false"); /* connected/devices:无配对记录可查(/var/lib/bluetooth 空/未知)→ false/[],不编造 */ sA(s, ",\"connected\":false,\"devices\":[]"); } /* network 完整对象(含外层 {})。hostname 无效(空/"-")时 hostname_local=null。 */ static void net_json(sbuf_t *s, const char *hostname) { char ssid[64] = "", ip[64] = ""; int wc = net_wifi(ssid, sizeof ssid, ip, sizeof ip); sA(s, "{\"wifi\":{\"connected\":%s", wc ? "true" : "false"); sA(s, ",\"ssid\":"); if (ssid[0]) sA_str(s, ssid); else sA(s, "null"); sA(s, ",\"ip\":"); if (ip[0]) sA_str(s, ip); else sA(s, "null"); sA(s, ",\"rssi\":null,\"bitrate_mbps\":null},\"bluetooth\":{"); net_bluetooth_json(s); sA(s, "},\"hostname_local\":"); const char *hn = (hostname && hostname[0] && strcmp(hostname, "-")) ? hostname : NULL; if (!hn) { sA(s, "null"); sA(s, "}"); return; } char hl[96]; size_t l = strlen(hn); if (l >= 6 && !strcmp(hn + l - 6, ".local")) snprintf(hl, sizeof hl, "%s", hn); else snprintf(hl, sizeof hl, "%s.local", hn); sA_str(s, hl); sA(s, "}"); /* 闭合 network 对象(缺它则 agg_json 收尾的 "}" 只闭合 network,根对象不闭合 → JSON 非法) */ } /* ---------------- 聚合 JSON(subset 位掩码;与 MEOWPANEL_PROTOCOL §3.1 .data 对齐) ---------------- */ static int agg_json(int subset, char *out, int cap) { int ip = (subset == 0) || (subset & PERF_SUBSET_PERF); int isys = (subset == 0) || (subset & PERF_SUBSET_SYSTEM); sbuf_t s = { out, 0, (size_t)cap }; sA(&s, "{"); int first = 1; #define SEP() do { if (!first) sA(&s, ","); first = 0; } while (0) if (ip) { SEP(); sA(&s, "\"ts\":%ld", (long)time(NULL)); SEP(); sA(&s, "\"uptime_sec\":%.0f", uptime_sec()); double u = cpu_usage_pct(); int cores = core_count(); double l1, l5, l15; loadavg(&l1, &l5, &l15); SEP(); sA(&s, "\"cpu\":{\"usage\":%.1f,\"cores\":%d,\"load\":[%.2f,%.2f,%.2f]}", u, cores, l1, l5, l15); unsigned long mtot, mused, mfree, mavail, swapT, swapU; meminfo(&mtot, &mused, &mfree, &mavail, &swapT, &swapU); SEP(); sA(&s, "\"memory\":{\"total\":%lu,\"used\":%lu,\"free\":%lu,\"available\":%lu,\"swap_total\":%lu,\"swap_used\":%lu}", mtot, mused, mfree, mavail, swapT, swapU); unsigned long st, su, sa; if (df_parse("/", &st, &su, &sa) == 0) { SEP(); sA(&s, "\"storage\":{\"path\":\"/\",\"total\":%lu,\"used\":%lu,\"avail\":%lu}", st, su, sa); } else { SEP(); sA(&s, "\"storage\":{\"path\":\"/\",\"total\":0,\"used\":0,\"avail\":0}"); } if (tf_present()) { unsigned long tt, tu, ta; if (df_parse(TF_MOUNT, &tt, &tu, &ta) == 0) { SEP(); sA(&s, "\"tfcard\":{\"present\":true,\"path\":\"" TF_MOUNT "\",\"total\":%lu,\"used\":%lu,\"avail\":%lu,\"filesystem\":\"exfat\"}", tt, tu, ta); } else { SEP(); sA(&s, "\"tfcard\":{\"present\":true,\"path\":\"" TF_MOUNT "\",\"total\":0,\"used\":0,\"avail\":0,\"filesystem\":\"exfat\"}"); } } else { SEP(); sA(&s, "\"tfcard\":{\"present\":false,\"path\":\"" TF_MOUNT "\",\"total\":0,\"used\":0,\"avail\":0,\"filesystem\":null}"); } int any; SEP(); sA(&s, "\"temp\":"); temp_json(&s, &any); } if (isys) { char ver[64] = "-", host[64] = "-", ker[64] = "-", arch[32] = "-"; if (read_file_txt(VERSION_FILE, ver, sizeof ver) <= 0 || !ver[0]) snprintf(ver, sizeof ver, "-"); if (gethostname(host, sizeof host) != 0 || !host[0]) snprintf(host, sizeof host, "-"); sys_kernel(ker, sizeof ker); sys_arch(arch, sizeof arch); SEP(); sA(&s, "\"system\":{\"version\":"); sA_str(&s, ver); sA(&s, ",\"hostname\":"); sA_str(&s, host); sA(&s, ",\"kernel\":"); sA_str(&s, ker); sA(&s, ",\"arch\":"); sA_str(&s, arch); sA(&s, "}"); /* network(wifi/bluetooth/hostname_local):SYSTEM 子集,/api/system 与 /api/status 透传 */ SEP(); sA(&s, "\"network\":"); net_json(&s, host); } sA(&s, "}"); /* 外层闭合 */ #undef SEP if (s.n >= (size_t)cap) s.n = (size_t)cap - 1; s.b[s.n] = 0; return 0; } /* ---------------- IPC 帧读写 ---------------- */ static int x_read_full(int fd, uint8_t *buf, size_t n) { size_t off = 0; while (off < n) { ssize_t r = read(fd, buf + off, n - off); if (r <= 0) return -1; off += (size_t)r; } return 0; } static int x_write_full(int fd, const uint8_t *buf, size_t n) { size_t off = 0; while (off < n) { ssize_t w = write(fd, buf + off, n - off); if (w <= 0) return -1; off += (size_t)w; } return 0; } static int send_frame(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len) { uint8_t hdr[MEOW_IPC_HDR_LEN]; uint32_t magic = MEOW_IPC_MAGIC, rlen = len; uint16_t ver = MEOW_IPC_VERSION, t = type; memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &t, 2); memcpy(hdr + 8, &req_id, 4); memcpy(hdr + 12, &rlen, 4); if (x_write_full(fd, hdr, MEOW_IPC_HDR_LEN) < 0) return -1; if (len && payload && x_write_full(fd, payload, len) < 0) return -1; return 0; } static int send_ack(int fd, uint32_t req_id, uint32_t code, uint32_t status) { m_ack_t a; memset(&a, 0, sizeof a); a.code = code; a.status = status; return send_frame(fd, MSG_ACK, req_id, &a, sizeof a); } static int send_err(int fd, uint32_t req_id, uint32_t code, const char *msg) { m_err_t e; memset(&e, 0, sizeof e); uint16_t ml = msg ? (uint16_t)strlen(msg) : 0; if (ml > 63) ml = 63; e.code = code; e.msg_len = ml; if (ml) memcpy(e.msg, msg, ml); return send_frame(fd, MSG_ERR, req_id, &e, sizeof e); } static int send_perf_result(int fd, uint32_t req_id, int subset, const char *json) { m_perf_result_t r; memset(&r, 0, sizeof r); r.code = MEOW_OK; r.subset = (uint32_t)subset; uint16_t jl = (uint16_t)strlen(json); if (jl > sizeof r.json - 1) jl = (uint16_t)(sizeof r.json - 1); memcpy(r.json, json, jl); r.json[jl] = 0; r.json_len = jl; return send_frame(fd, MSG_PERF_RESULT, req_id, &r, sizeof r); } /* MSG_PERF_QUERY:读取 subset 后聚合。0=全部。 */ static void handle_query(int c, uint32_t req_id, const uint8_t *payload, uint32_t rlen) { m_perf_query_t q; memset(&q, 0, sizeof q); if (rlen > sizeof q) rlen = sizeof q; memcpy(&q, payload, rlen); int subset = (q.subset == 0) ? 0 : (int)q.subset; char json[MEOW_IPC_MAX - 12]; agg_json(subset, json, sizeof json); send_perf_result(c, req_id, subset, json); } static void handle_conn(int c) { for (;;) { uint8_t hdr[MEOW_IPC_HDR_LEN]; if (x_read_full(c, hdr, sizeof hdr) < 0) break; uint32_t magic, rlen, req_id; uint16_t ver, type; memcpy(&magic, hdr, 4); memcpy(&ver, hdr + 4, 2); memcpy(&type, hdr + 6, 2); memcpy(&req_id, hdr + 8, 4); memcpy(&rlen, hdr + 12, 4); if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) { send_err(c, req_id, MEOW_ERR_INVAL, "bad magic/ver"); break; } if (rlen > MEOW_IPC_MAX) { send_err(c, req_id, MEOW_ERR_INVAL, "payload too big"); break; } uint8_t payload[MEOW_IPC_MAX]; if (rlen && x_read_full(c, payload, rlen) < 0) break; if (type == MSG_HELLO) { send_ack(c, req_id, MEOW_OK, 0); continue; } if (type == MSG_PERF_QUERY) { handle_query(c, req_id, payload, rlen); continue; } send_err(c, req_id, MEOW_ERR_INVAL, "unknown type"); break; } } static int run_daemon(const char *sock) { signal(SIGPIPE, SIG_IGN); cpu_usage_pct(); /* 预热:设 /proc/stat 基线,首次查询即有值 */ int lfd = socket(AF_UNIX, SOCK_STREAM, 0); if (lfd < 0) { svc_log("socket fail: %s", strerror(errno)); return 1; } struct sockaddr_un a; memset(&a, 0, sizeof a); a.sun_family = AF_UNIX; strncpy(a.sun_path, sock, sizeof a.sun_path - 1); unlink(sock); if (bind(lfd, (struct sockaddr *)&a, sizeof a) < 0) { svc_log("bind %s fail: %s", sock, strerror(errno)); close(lfd); return 1; } chmod(sock, 0666); if (listen(lfd, 8) < 0) { svc_log("listen fail: %s", strerror(errno)); close(lfd); return 1; } svc_log("meow_svc_perf listening on %s", sock); for (;;) { int c = accept(lfd, NULL, NULL); if (c < 0) { if (errno == EINTR) continue; continue; } handle_conn(c); close(c); } return 0; } /* ---------------- CLI ---------------- */ static int cli_run(int n, char **v) { int subset = 0; if (n >= 1) { if (!strcmp(v[0], "perf")) subset = PERF_SUBSET_PERF; else if (!strcmp(v[0], "system")) subset = PERF_SUBSET_SYSTEM; else if (!strcmp(v[0], "all") || !strcmp(v[0], "-")) subset = 0; else { fprintf(stderr, "usage: meow_svc_perf [all|perf|system]\n"); return 1; } } cpu_usage_pct(); /* 预热基线 */ usleep(100000); /* 采样间隔,取 delta */ char json[4096]; agg_json(subset, json, sizeof json); puts(json); return 0; } static const char *cli_usage(void) { return "meow_svc_perf [all|perf|system] (CLI 免鉴权;无参=全部)"; } int main(int argc, char **argv) { const char *env = getenv("MEOW_IPC_PERF_SOCK"); if (env && env[0]) g_sock = env; int i = 1; for (; i < argc; i++) { if (!strcmp(argv[i], "--sock") && i + 1 < argc) g_sock = argv[++i]; else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { puts(cli_usage()); return 0; } else break; } if (i >= argc) return run_daemon(g_sock); /* svcd 空参 → IPC 常驻 */ return cli_run(argc - i, argv + i); }