#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; } /* ---------------- page-cache 治理 C 侧(OOM_PAGECACHE_GOVERNANCE §3;③混合:C 归本服务,A 归 SDK) ---------------- * 设备级 /proc/meminfo Cached 监控降载:Cached 超阈值 → 置「暂停下载/预取」,落共享状态文件 * MEOW_CACHEGOV_C_FILE(App/SDK meow_cachegov 只读,系统级单点,App 不重复起线程)。 * Dirty 为早信号(仅告警日志,不直接触发降载)。滞回:≥halt 触发 / ≤resume 恢复(防抖动)。 */ static long g_cg_halt_kb = 32 * 1024; /* Cached 超此暂停下载+预取(初值 56MB 设备) */ static long g_cg_resume_kb = 24 * 1024; /* Cached 落此恢复 */ static long g_cg_dirty_warn_kb = 8 * 1024; /* Dirty 早信号告警 */ static int g_cg_dl_pause = 0; static int g_cg_pf_pause = 0; static long g_cg_last_cached_kb = 0; static long g_cg_last_dirty_kb = 0; static long g_cg_last_update_ms = 0; static const char *g_cg_file = MEOW_CACHEGOV_C_FILE; static long g_cg_test_cached_kb = -1; /* 宿主测试注入(>=0 不读真 /proc/meminfo) */ static long g_cg_test_dirty_kb = -1; static long now_ms(void); /* 前向声明(定义在卡死快照节) */ static void cachegov_config_load(void) { const char *e; if ((e = getenv("MEOW_CACHEGOV_HALT_KB")) && e[0]) g_cg_halt_kb = atol(e); if ((e = getenv("MEOW_CACHEGOV_RESUME_KB")) && e[0]) g_cg_resume_kb = atol(e); if ((e = getenv("MEOW_CACHEGOV_DIRTY_WARN_KB")) && e[0]) g_cg_dirty_warn_kb = atol(e); if ((e = getenv("MEOW_CACHEGOV_C_FILE")) && e[0]) g_cg_file = e; if ((e = getenv("MEOW_CACHEGOV_TEST_CACHED_KB")) && e[0]) g_cg_test_cached_kb = atol(e); if ((e = getenv("MEOW_CACHEGOV_TEST_DIRTY_KB")) && e[0]) g_cg_test_dirty_kb = atol(e); } /* 读 /proc/meminfo 的 Cached/Dirty(kB)。测试注入 >=0 时替代真读。 */ static void cachegov_read_cached(long *cached_kb, long *dirty_kb) { *cached_kb = *dirty_kb = 0; if (g_cg_test_cached_kb >= 0) { *cached_kb = g_cg_test_cached_kb; *dirty_kb = (g_cg_test_dirty_kb >= 0) ? g_cg_test_dirty_kb : 0; return; } FILE *f = fopen("/proc/meminfo", "r"); if (!f) return; char line[256], k[64]; unsigned long v; while (fgets(line, sizeof line, f)) { if (sscanf(line, "%63s %lu", k, &v) != 2) continue; if (!strcmp(k, "Cached:")) *cached_kb = (long)v; else if (!strcmp(k, "Dirty:")) *dirty_kb = (long)v; } fclose(f); } /* 落共享状态文件(tmp+rename 原子;单文件读,App 廉价查询不重复起线程)。 */ static void cachegov_write_state(long cached, long dirty, int dl, int pf) { char tmp[384]; snprintf(tmp, sizeof tmp, "%s.tmp", g_cg_file); int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) return; char buf[160]; int n = snprintf(buf, sizeof buf, "cached_kb=%ld\ndirty_kb=%ld\ndl_pause=%d\nprefetch_pause=%d\n", cached, dirty, dl, pf); if (n > 0) { ssize_t r = write(fd, buf, (size_t)n); (void)r; } close(fd); rename(tmp, g_cg_file); } /* 单步降载评估(可被 daemon 节拍实值或 CLI/测试注入值调用)。 */ static void cachegov_monitor_step(long cached_kb, long dirty_kb) { if (cached_kb >= g_cg_halt_kb) { /* 触发:Cached 超阈值 → 暂停下载+预取 */ g_cg_dl_pause = 1; g_cg_pf_pause = 1; } else if (cached_kb <= g_cg_resume_kb) { /* 恢复:跌破恢复阈值 → 放行 */ g_cg_dl_pause = 0; g_cg_pf_pause = 0; } /* 滞回带(resume < x < halt):保持当前档 */ if (dirty_kb > g_cg_dirty_warn_kb) svc_log("cachegov: dirty warn %ldkB (>%ldkB 早信号)", dirty_kb, g_cg_dirty_warn_kb); g_cg_last_cached_kb = cached_kb; g_cg_last_dirty_kb = dirty_kb; cachegov_write_state(cached_kb, dirty_kb, g_cg_dl_pause, g_cg_pf_pause); } /* 节拍(daemon 主循环调用;500ms 轻量单文件读 /proc/meminfo)。 */ static void cachegov_monitor_tick(void) { long now = now_ms(); if (now - g_cg_last_update_ms < 500) return; long cached, dirty; cachegov_read_cached(&cached, &dirty); cachegov_monitor_step(cached, dirty); g_cg_last_update_ms = now; } 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); long cc = 0, cd = 0; cachegov_read_cached(&cc, &cd); SEP(); sA(&s, "\"memory\":{\"total\":%lu,\"used\":%lu,\"free\":%lu,\"available\":%lu,\"swap_total\":%lu,\"swap_used\":%lu,\"cached\":%ld,\"dirty\":%ld,\"cachegov_dl_pause\":%d,\"cachegov_prefetch_pause\":%d}", mtot, mused, mfree, mavail, swapT, swapU, cc, cd, g_cg_dl_pause, g_cg_pf_pause); 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; } } /* ===================================================================== * 卡死诊断快照 + RMM 基线(系统 log 模块升级:高负载/停滞/内存紧张自动留档) * --------------------------------------------------------------------- * 为什么放 meow_svc_perf: * - svcd `trigger=boot` 常驻服务(/usr/data/meow/services.d/meow_svc_perf.conf), * 独立于 launcher UI 主循环——launcher 是 CPU 风暴受害者时本进程仍可被调度到; * - 本文件已聚合 /proc/loadavg、/proc/meminfo、/proc/stat、/proc/uptime、core_count, * 正是快照/基线所需数据源,直接复用,不重写、不重造。 * 输出两类(均走 TF 卡 MeowOS/logs/,无卡降级 /tmp): * 1) RMM 基线 perf_.log:每 MEOW_PERF_BASELINE_MS(默认 60s)追一行 * load/cpu/mem/topN 进程(单文件 1MB 上限,超则新开一轮)。 * 2) hang 快照 hang__.snap:loadavg[1] 持续超限 * MEOW_PERF_HANG_CONSEC 次(默认 2)落一份完整现场(loadavg/cpu/mem/关键进程 * VmRSS+线程/连接数),带冷却(默认 300s)与每 boot 上限(默认 4),绝不连写刷爆卡。 * 降载/自死规避:采集全部【单文件读】(/proc/loadavg、/proc/meminfo、/proc/stat、 * /proc//stat、/proc/net/{unix,tcp,tcp6,udp}),零 popen/零 fork——卡死时本进程 * 若被调度到仍能轻量完成,且不放大负载;快照写入限时(每文件一次 open+write+close)。 * 配置(env,默认开且轻量): * MEOW_PERF_SNAP 默认 1;0=整体关闭 * MEOW_PERF_BASELINE_MS默认 60000;0=只做 hang 检测不写基线 * MEOW_PERF_HANG_LOAD 默认 3.0(loadavg[1] 阈值) * MEOW_PERF_HANG_CONSEC默认 2(连续异常次数) * MEOW_PERF_HANG_COOLDOWN_MS 默认 300000(两次快照最小间隔) * MEOW_PERF_HANG_MAX 默认 4(每 boot 快照上限) * MEOW_PERF_SNAP_DIR 默认 <卡>/MeowOS/logs;无卡降级 /tmp * 测试钩子:MEOW_PERF_SNAP_TEST_LOAD(伪造 loadavg[1],宿主触发高负载用) * ===================================================================== */ #define SNAP_LOG_DIR_REL "MeowOS/logs" #define SNAP_CARD_DEFAULT "/usr/data/mnt/sd_0" #define SNAP_TMP_DIR "/tmp" #define SNAP_BOOTTS_LEN 64 #define SNAP_PATH_MAX 512 #define SNAP_NAME_MAX 32 #define SNAP_PROC_MAX 128 /* /proc 遍历上限 + cpu 增量元表上限 */ #define SNAP_TOP_N 12 /* 基线/snapshot 展示前 N */ #define SNAP_LINE_MAX 1024 #define SNAP_BASELINE_MAX (1u << 20) /* perf_ 单文件上限 1MB,超则新开一轮 */ #define SNAP_TICK_MS 60000 /* 无基线时 hang 检测的兜底节拍 */ static int snap_enabled = 1; static long snap_baseline_ms = 60000; static double snap_hang_load = 3.0; static int snap_hang_consec = 2; static long snap_hang_cooldown = 300000; static int snap_hang_max = 4; static int snap_test_load_on = 0; static double snap_test_load = 0.0; static char snap_snap_dir[320] = ""; /* 显式覆盖(--snap-dir/env);空=卡默认 */ static char snap_card[256] = SNAP_CARD_DEFAULT; static char snap_dir[320] = ""; /* 实际 logs 目录(卡可用时) */ static int snap_on_card = 0; static char snap_bootts[SNAP_BOOTTS_LEN] = ""; static long snap_step_last_ms = 0; static int snap_load_hot = 0; static long snap_last_hang_ms = 0; static int snap_hang_count = 0; static long snap_page_kb = 4; /* 每进程 cpu 增量元表(pcpu=Δ(utime+stime)/tick) */ typedef struct { unsigned pid; unsigned long long utime_stime; } snap_cpu_prev_t; static snap_cpu_prev_t snap_prev[SNAP_PROC_MAX]; static int snap_prev_n = 0; static long now_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } /* 设备侧配置回退(/usr/data/meow/perf_snap.conf,key=value)——svcd 只传 args= 无法注入 env, * 故另立配置文件让「可配置开关」在真机生效。优先级:文件 < env < CLI 旗标。 */ #define SNAP_CFG_FILE "/usr/data/meow/perf_snap.conf" static void snap_config_load_file(void) { const char *cfg = getenv("MEOW_PERF_SNAP_CFG"); if (!cfg || !cfg[0]) cfg = SNAP_CFG_FILE; FILE *f = fopen(cfg, "r"); if (!f) return; char line[128]; while (fgets(line, sizeof line, f)) { char *eq = strchr(line, '='); if (!eq) continue; char *k = line; int kl = (int)strlen(line); while (kl > 0 && (k[kl-1] == '\n' || k[kl-1] == '\r' || k[kl-1] == ' ' || k[kl-1] == '\t')) k[--kl] = 0; *eq = 0; char *v = eq + 1; while (*v == ' ' || *v == '\t') v++; if (!strcmp(k, "snap_enabled")) snap_enabled = atoi(v) ? 1 : 0; else if (!strcmp(k, "baseline_ms")) snap_baseline_ms = atol(v); else if (!strcmp(k, "hang_load")) snap_hang_load = atof(v); else if (!strcmp(k, "hang_consec")) snap_hang_consec = atoi(v); else if (!strcmp(k, "cooldown_ms")) snap_hang_cooldown = atol(v); else if (!strcmp(k, "hang_max")) snap_hang_max = atoi(v); else if (!strcmp(k, "snap_dir")) snprintf(snap_snap_dir, sizeof snap_snap_dir, "%s", v); } fclose(f); } static void snap_config_load(void) { const char *e; snap_config_load_file(); /* 设备侧配置(低了 env 一级) */ if ((e = getenv("MEOW_PERF_SNAP")) && e[0]) snap_enabled = atoi(e) ? 1 : 0; if ((e = getenv("MEOW_PERF_BASELINE_MS")) && e[0]) snap_baseline_ms = atol(e); if ((e = getenv("MEOW_PERF_HANG_LOAD")) && e[0]) snap_hang_load = atof(e); if ((e = getenv("MEOW_PERF_HANG_CONSEC")) && e[0]) snap_hang_consec = atoi(e); if ((e = getenv("MEOW_PERF_HANG_COOLDOWN_MS")) && e[0]) snap_hang_cooldown = atol(e); if ((e = getenv("MEOW_PERF_HANG_MAX")) && e[0]) snap_hang_max = atoi(e); if ((e = getenv("MEOW_PERF_SNAP_DIR")) && e[0]) { snprintf(snap_snap_dir, sizeof snap_snap_dir, "%s", e); } if ((e = getenv("MEOW_PERF_SNAP_TEST_LOAD")) && e[0]) { snap_test_load_on = 1; snap_test_load = atof(e); } long sz = sysconf(_SC_PAGESIZE); if (sz > 0) snap_page_kb = sz / 1024; if (snap_page_kb <= 0) snap_page_kb = 4; } static int snap_card_available(void) { if (snap_snap_dir[0]) { /* 显式覆盖(宿主测试/生产改向):视为普通目录 */ (void)snap_snap_dir; return 0; /* 由 snap_setup 用覆盖目录 */ } FILE *f = fopen("/proc/mounts", "r"); if (!f) return 0; char line[512]; char needle[320]; snprintf(needle, sizeof needle, " %s ", snap_card); int found = 0; while (fgets(line, sizeof line, f)) if (strstr(line, needle)) { found = 1; break; } fclose(f); return found; } static void snap_boot_timestamp(char *out, size_t n) { out[0] = 0; int fd = open("/tmp/meow_bootts", O_RDONLY); if (fd >= 0) { ssize_t r = read(fd, out, (ssize_t)n - 1); close(fd); if (r > 0) { out[r] = 0; if (strncmp(out, "meow_", 5) == 0) memmove(out, out + 5, r - 4); /* 剥 launcher 前缀 */ if (out[0]) return; } } time_t now = time(NULL); FILE *f = fopen("/proc/uptime", "r"); double up = 0.0; if (f) { if (fscanf(f, "%lf", &up) != 1) up = 0.0; fclose(f); } struct tm tm; time_t boot = (time_t)((double)now - up + 0.5); localtime_r(&boot, &tm); strftime(out, n, "%Y%m%d_%H%M%S", &tm); char cached[96]; snprintf(cached, sizeof cached, "meow_%s", out); fd = open("/tmp/meow_bootts", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd >= 0) { ssize_t w = write(fd, cached, strlen(cached)); (void)w; close(fd); } } /* 定位/打开 logs 目录 + bootts。返回 0=就绪。 */ static int snap_setup(void) { if (snap_snap_dir[0]) { snprintf(snap_dir, sizeof snap_dir, "%s", snap_snap_dir); snap_on_card = 1; (void)mkdir(snap_dir, 0755); } else if (snap_card_available()) { snprintf(snap_dir, sizeof snap_dir, "%s/%s", snap_card, SNAP_LOG_DIR_REL); snap_on_card = 1; (void)mkdir(snap_dir, 0755); } else { snprintf(snap_dir, sizeof snap_dir, "%s", SNAP_TMP_DIR); snap_on_card = 0; } snap_boot_timestamp(snap_bootts, sizeof snap_bootts); return 0; } /* 以 在 logs 目录开一个追加文件(卡可用写卡;否则 /tmp)。返回 fd(<0=失败)。 */ static int snap_open_append(const char *name) { char path[SNAP_PATH_MAX]; snprintf(path, sizeof path, "%s/%s", snap_dir, name); return open(path, O_WRONLY | O_APPEND | O_CREAT, 0644); } static void snap_wr(int fd, const char *s) { if (fd < 0) return; ssize_t w = write(fd, s, strlen(s)); (void)w; } static void snap_close(int fd) { if (fd >= 0) close(fd); } /* ---- 每进程采集(单文件读,无 popen) ---- */ typedef struct { unsigned pid; double pcpu; long rss_kb; int threads; char comm[SNAP_NAME_MAX]; } snap_proc_t; static snap_proc_t g_proc[SNAP_PROC_MAX]; static int g_proc_n = 0; static int snap_read_stat(const char *fmt, char *out, int cap) { int fd = open(fmt, O_RDONLY); if (fd < 0) return -1; ssize_t n = read(fd, out, (size_t)cap - 1); close(fd); if (n <= 0) return -1; out[n] = 0; return (int)n; } static void snap_load(double *l1, double *l5, double *l15) { loadavg(l1, l5, l15); if (snap_test_load_on) *l1 = snap_test_load; } /* 收集 /proc 进程快照:pcpu(相对上次)、rss_kB、线程数、comm。返回进程数。 */ static int snap_proc_collect(long tick_ms) { DIR *d = opendir("/proc"); if (!d) return -1; g_proc_n = 0; double hz = sysconf(_SC_CLK_TCK); if (hz <= 0) hz = 100; double dsec = (double)tick_ms / 1000.0; if (dsec <= 0) dsec = 1.0; struct dirent *e; while ((e = readdir(d)) != NULL && g_proc_n < SNAP_PROC_MAX) { if (e->d_name[0] < '0' || e->d_name[0] > '9') continue; unsigned pid = (unsigned)atoi(e->d_name); if (pid <= 1) continue; char path[SNAP_PATH_MAX]; char buf[512]; snprintf(path, sizeof path, "/proc/%u/stat", pid); if (snap_read_stat(path, buf, sizeof buf) <= 0) continue; const char *lp = strchr(buf, '('); const char *rp = strrchr(buf, ')'); if (!lp || !rp || rp <= lp) continue; const char *p = rp + 1; while (*p == ' ') p++; /* 字段(stat 已按 1 基编号,此处自 ') ' 后 0 基):0=state 11=utime 12=stime * 17=num_threads 21=rss(页) */ char toks[40][16]; int nt = 0; while (*p && nt < 40) { int i = 0; while (*p == ' ') p++; if (!*p) break; while (*p && *p != ' ' && i < 15) toks[nt][i++] = *p++; toks[nt][i] = 0; nt++; } if (nt <= 21) continue; unsigned long long utime = strtoull(toks[11], NULL, 10); unsigned long long stime = strtoull(toks[12], NULL, 10); int threads = atoi(toks[17]); long rss_pages = strtol(toks[21], NULL, 10); snap_proc_t *sp = &g_proc[g_proc_n]; sp->pid = pid; sp->threads = threads > 0 ? threads : 1; sp->rss_kb = rss_pages * snap_page_kb; size_t cl = (size_t)(rp - lp - 1); if (cl >= SNAP_NAME_MAX) cl = SNAP_NAME_MAX - 1; memcpy(sp->comm, lp + 1, cl); sp->comm[cl] = 0; unsigned long long cur = utime + stime; unsigned long long prev = 0; int had = 0; for (int i = 0; i < snap_prev_n; i++) if (snap_prev[i].pid == pid) { prev = snap_prev[i].utime_stime; had = 1; break; } sp->pcpu = had ? (100.0 * (double)(cur - prev) / (hz * dsec)) : 0.0; if (sp->pcpu < 0) sp->pcpu = 0; int found = 0; for (int i = 0; i < snap_prev_n; i++) if (snap_prev[i].pid == pid) { snap_prev[i].utime_stime = cur; found = 1; break; } if (!found && snap_prev_n < SNAP_PROC_MAX) { snap_prev[snap_prev_n].pid = pid; snap_prev[snap_prev_n].utime_stime = cur; snap_prev_n++; } g_proc_n++; } closedir(d); return g_proc_n; } /* 按 pcpu 降序取前 topn,写成 "pid:pct:rsskb:comm" 以 ';' 分隔(comm 空格/控制转 '_')。 */ static void snap_proc_line(char *out, int cap, int topn) { int i, j; for (i = 0; i < g_proc_n; i++) { int im = i; for (j = i + 1; j < g_proc_n; j++) if (g_proc[j].pcpu > g_proc[im].pcpu) im = j; if (im != i) { snap_proc_t t = g_proc[i]; g_proc[i] = g_proc[im]; g_proc[im] = t; } } int o = 0; out[0] = 0; for (i = 0; i < g_proc_n && i < topn && o < cap - 2; i++) { int r = snprintf(out + o, (size_t)(cap - o), "%s%u:%.1f:%ld:%s", i ? ";" : "", g_proc[i].pid, g_proc[i].pcpu, g_proc[i].rss_kb, g_proc[i].comm); if (r < 0 || o + r >= cap) break; for (int k = o; k < o + r; k++) if (out[k] == ' ' || out[k] == '\n' || out[k] == '\t') out[k] = '_'; o += r; } if (o == 0 && cap > 0) out[0] = 0; } /* ---- socket 汇总(/proc/net,单文件读) ---- */ typedef struct { int unix_total, unix_conn, unix_audio; int tcp_est, tcp_total, udp; } snap_sock_t; /* 数 /proc/net/ 行(跳过头行)。mincols>0 时要求字段数>=mincols 才计。 */ static int snap_count_net(const char *file, int mincols) { char path[192]; snprintf(path, sizeof path, "/proc/net/%s", file); int fd = open(path, O_RDONLY); if (fd < 0) return 0; char b[8192]; ssize_t n = read(fd, b, sizeof b - 1); close(fd); if (n <= 0) return 0; b[n] = 0; int cnt = 0, first = 1; char *p = b; while (*p) { char *nl = strchr(p, '\n'); if (nl) *nl = 0; if (!first) { /* 计字段:非空 token 数 */ int tok = 0; char *q = p; while (*q) { while (*q == ' ' || *q == '\t') q++; if (!*q) break; tok++; while (*q && *q != ' ' && *q != '\t') q++; } if (tok >= mincols) cnt++; } first = 0; if (!nl) break; p = nl + 1; } return cnt; } static void snap_sock_collect(snap_sock_t *s) { memset(s, 0, sizeof *s); s->unix_total = snap_count_net("unix", 8); s->tcp_total = snap_count_net("tcp", 4); s->tcp_est = 0; s->udp = snap_count_net("udp", 4); s->unix_conn = 0; s->unix_audio = 0; /* 细读 unix:连通(St=03) 计数 + 路径含 meow_audio(audiod IPC 客户端数) */ int fd = open("/proc/net/unix", O_RDONLY); if (fd >= 0) { char b[16384]; ssize_t n = read(fd, b, sizeof b - 1); close(fd); if (n > 0) { b[n] = 0; char *p = b; int first = 1; while (*p && first < 0x7fffffff) { char *nl = strchr(p, '\n'); if (nl) *nl = 0; if (!first) { char *tok[8]; int nt = 0; char *q = p; while (*q && nt < 8) { while (*q == ' ' || *q == '\t') q++; if (!*q) break; tok[nt++] = q; while (*q && *q != ' ' && *q != '\t') q++; } if (nt >= 7) { if (strcmp(tok[5], "03") == 0) s->unix_conn++; if (nt >= 8 && strstr(tok[7], "meow_audio") != NULL) s->unix_audio++; } } first = 0; if (!nl) break; p = nl + 1; } } } /* tcp 状态:每行第 4 列(st)== "01" 为 ESTABLISHED */ fd = open("/proc/net/tcp", O_RDONLY); if (fd >= 0) { char b[16384]; ssize_t n = read(fd, b, sizeof b - 1); close(fd); if (n > 0) { b[n] = 0; char *p = b; int first = 1; while (*p) { char *nl = strchr(p, '\n'); if (nl) *nl = 0; if (!first) { char *tok[5]; int nt = 0; char *q = p; while (*q && nt < 5) { while (*q == ' ' || *q == '\t') q++; if (!*q) break; tok[nt++] = q; while (*q && *q != ' ' && *q != '\t') q++; } if (nt >= 4 && strcmp(tok[3], "01") == 0) s->tcp_est++; } first = 0; if (!nl) break; p = nl + 1; } } } } /* ---- 写基线行 perf_.log ---- */ static void snap_write_baseline(double l1, double l5, double l15, int cores, double cpu, unsigned long mtot, unsigned long mused, unsigned long mfree, unsigned long mavail, unsigned long swapT, unsigned long swapU) { char top[512]; snap_proc_line(top, sizeof top, SNAP_TOP_N); char line[SNAP_LINE_MAX]; snprintf(line, sizeof line, "%ld %ld load=%.2f/%.2f/%.2f cores=%d cpu=%.1f mem=%lu/%lu/%lu/%lu swap=%lu/%lu top=%s\n", (long)time(NULL), (long)uptime_sec(), l1, l5, l15, cores, cpu, mtot, mused, mfree, mavail, swapT, swapU, top); char name[SNAP_BOOTTS_LEN + 16]; snprintf(name, sizeof name, "perf_%s.log", snap_bootts); /* 单文件 1MB 上限:超则新开一轮(截断重来,防无限涨;基线是周期追加,重写可接受) */ int fd = snap_open_append(name); if (fd < 0) return; struct stat st; if (fstat(fd, &st) == 0 && st.st_size > (long long)SNAP_BASELINE_MAX) { /* 已超上限:关闭,截断重开(只留本次) */ close(fd); fd = -1; } else { snap_wr(fd, line); snap_close(fd); return; } char path[SNAP_PATH_MAX]; snprintf(path, sizeof path, "%s/%s", snap_dir, name); fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd >= 0) { snap_wr(fd, line); snap_close(fd); } } /* ---- 写完整 hang 快照 hang__.snap ---- */ static void snap_write_hang(double l1, double l5, double l15, int cores, double cpu, unsigned long mtot, unsigned long mused, unsigned long mfree, unsigned long mavail, unsigned long swapT, unsigned long swapU) { snap_sock_t sk; snap_sock_collect(&sk); char name[SNAP_BOOTTS_LEN + 48]; snprintf(name, sizeof name, "hang_%s_%ld.snap", snap_bootts, (long)time(NULL)); int fd = snap_open_append(name); if (fd < 0) return; char b[SNAP_LINE_MAX]; snprintf(b, sizeof b, "=== hang snapshot bootts=%s epoch=%ld uptime=%ld cores=%d ===\n", snap_bootts, (long)time(NULL), (long)uptime_sec(), cores); snap_wr(fd, b); snprintf(b, sizeof b, "loadavg: %.2f %.2f %.2f cpu: %.1f%%\n", l1, l5, l15, cpu); snap_wr(fd, b); snprintf(b, sizeof b, "mem: MemTotal=%luKB MemFree=%luKB MemAvailable=%luKB used=%luKB" " SwapTotal=%luKB SwapUsed=%luKB\n", mtot, mfree, mavail, mused, swapT, swapU); snap_wr(fd, b); snap_wr(fd, "--- top processes (by cpu) ---\n"); if (g_proc_n > 0) { int i, j; for (i = 0; i < g_proc_n; i++) { int im = i; for (j = i + 1; j < g_proc_n; j++) if (g_proc[j].pcpu > g_proc[im].pcpu) im = j; if (im != i) { snap_proc_t t = g_proc[i]; g_proc[i] = g_proc[im]; g_proc[im] = t; } } for (i = 0; i < g_proc_n && i < SNAP_TOP_N; i++) { snprintf(b, sizeof b, " %u %.1f%% %ldKB nthreads=%d %s\n", g_proc[i].pid, g_proc[i].pcpu, g_proc[i].rss_kb, g_proc[i].threads, g_proc[i].comm); snap_wr(fd, b); } } else { snap_wr(fd, " (none)\n"); } /* 关键进程(launcher/audiod/ncm/netmgrd/svcd/panel)VmRSS+线程 */ snap_wr(fd, "--- key processes ---\n"); static const char *keys[] = {"meow_launcherd", "meow_audiod", "meow_netmgrd", "meow_swvc", "meow_svc", "ncm", "player", "meowpanel", NULL}; int shown = 0; for (int k = 0; keys[k]; k++) { for (int i = 0; i < g_proc_n; i++) { if (strcmp(g_proc[i].comm, keys[k]) == 0) { snprintf(b, sizeof b, " %s pid=%u VmRSS=%ldKB threads=%d pcpu=%.1f%%\n", keys[k], g_proc[i].pid, g_proc[i].rss_kb, g_proc[i].threads, g_proc[i].pcpu); snap_wr(fd, b); shown++; break; } } } if (!shown) snap_wr(fd, " (none matched)\n"); snprintf(b, sizeof b, "--- sockets --- unix=%d conn=%d audiod_clients=%d tcp_est=%d tcp_total=%d udp=%d\n", sk.unix_total, sk.unix_conn, sk.unix_audio, sk.tcp_est, sk.tcp_total, sk.udp); snap_wr(fd, b); snap_close(fd); fprintf(stderr, "[meow_svc_perf] hang snapshot written: %s/%s\n", snap_dir, name); } /* ---- 单步:采集(含 load 覆写钩子)+ 写基线 + 评估 hang 触发。返回 1=本次写了快照 ---- */ static int snap_step(long tick_ms, int *p_cores) { double l1, l5, l15; snap_load(&l1, &l5, &l15); int cores = core_count(); if (p_cores) *p_cores = cores; double cpu = cpu_usage_pct(); if (cpu < 0) cpu = 0; unsigned long mtot, mused, mfree, mavail, swapT, swapU; meminfo(&mtot, &mused, &mfree, &mavail, &swapT, &swapU); snap_proc_collect(tick_ms); if (snap_baseline_ms > 0) snap_write_baseline(l1, l5, l15, cores, cpu, mtot, mused, mfree, mavail, swapT, swapU); int wrote = 0; if (l1 >= snap_hang_load) snap_load_hot++; else snap_load_hot = 0; long now_ms_v = now_ms(); if (snap_load_hot >= snap_hang_consec && (now_ms_v - snap_last_hang_ms) >= snap_hang_cooldown && snap_hang_count < snap_hang_max) { snap_write_hang(l1, l5, l15, cores, cpu, mtot, mused, mfree, mavail, swapT, swapU); snap_load_hot = 0; snap_last_hang_ms = now_ms_v; snap_hang_count++; wrote = 1; } return wrote; } /* 守护进程内的节拍入口:按 snap_step 节拍做采集/评估(时间门控)。 */ static void snap_monitor_tick(void) { if (!snap_enabled) return; long tick = snap_baseline_ms > 0 ? snap_baseline_ms : SNAP_TICK_MS; if (tick < 1000) tick = 1000; long now = now_ms(); if (now - snap_step_last_ms < tick) return; snap_step_last_ms = now; snap_step(tick, NULL); } 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); /* 卡死快照/RMM 基线:复用本进程,不新起 daemon。失败(无卡)降级 /tmp 仍可用。 */ snap_config_load(); snap_setup(); svc_log("snap: enabled=%d baseline_ms=%ld hang_load=%.1f consec=%d cooldown_ms=%ld max=%d dir=%s%s", snap_enabled, snap_baseline_ms, snap_hang_load, snap_hang_consec, snap_hang_cooldown, snap_hang_max, snap_dir, snap_on_card ? "" : " (no card -> /tmp)"); /* page-cache C 侧降载监控(③混合:C 归本服务);写共享状态文件 MEOW_CACHEGOV_C_FILE。 */ cachegov_config_load(); cachegov_monitor_tick(); svc_log("cachegov: halt=%ldkB resume=%ldkB dirty_warn=%ldkB state_file=%s", g_cg_halt_kb, g_cg_resume_kb, g_cg_dirty_warn_kb, g_cg_file); /* poll 化主循环:IPC 服务语义不变,仅以超时让位给监控节拍(低频、限时、可关)。 */ for (;;) { struct pollfd pfd = { lfd, POLLIN, 0 }; int r = poll(&pfd, 1, 500); if (r < 0) { if (errno == EINTR) continue; svc_log("poll fail: %s", strerror(errno)); break; } if (pfd.revents & (POLLIN | POLLERR | POLLHUP)) { int c = accept(lfd, NULL, NULL); if (c < 0) { if (errno == EINTR) continue; continue; } handle_conn(c); close(c); } snap_monitor_tick(); /* 自带宽限期(默认 60s),不阻塞 IPC */ cachegov_monitor_tick(); /* page-cache C 降载(500ms 节拍,轻量单文件读) */ } close(lfd); return 0; } /* ---------------- 快照 CLI(snap / snap-check) ---------------- */ /* 手动快照:立即采集并写一份完整 hang 快照(不受触发门槛),诊断/测试用。 */ static int snap_cli_snap(void) { snap_config_load(); snap_setup(); double l1, l5, l15; snap_load(&l1, &l5, &l15); int cores = core_count(); double cpu = cpu_usage_pct(); if (cpu < 0) cpu = 0; unsigned long mtot, mused, mfree, mavail, swapT, swapU; meminfo(&mtot, &mused, &mfree, &mavail, &swapT, &swapU); snap_proc_collect(1000); snap_write_hang(l1, l5, l15, cores, cpu, mtot, mused, mfree, mavail, swapT, swapU); printf("snap dir=%s bootts=%s\n", snap_dir, snap_bootts); return 0; } /* 确定性宿主测试:伪造 load(MEOW_PERF_SNAP_TEST_LOAD)跑 consec+1 步,必落基线 + 1 张快照。 */ static int snap_cli_check(void) { snap_config_load(); if (!snap_test_load_on) { snap_test_load = snap_hang_load; snap_test_load_on = 1; } /* 未注入则用阈值直接触发 */ snap_setup(); int steps = snap_hang_consec + 1; if (steps < 2) steps = 2; int wrote = 0; for (int i = 0; i < steps; i++) { if (snap_step(1000, NULL)) wrote = 1; } printf("snap-check: dir=%s bootts=%s steps=%d fake_load=%.2f wrote_snapshot=%d\n", snap_dir, snap_bootts, steps, snap_test_load, wrote); return wrote ? 0 : 1; } /* ---------------- CLI ---------------- */ /* 手动 C 降载评估:读真 /proc/meminfo Cached,落共享状态文件。 */ static int cachegov_cli(void) { cachegov_config_load(); long cached, dirty; cachegov_read_cached(&cached, &dirty); cachegov_monitor_step(cached, dirty); printf("cachegov: cached=%ld dirty=%ld dl_pause=%d prefetch_pause=%d file=%s\n", cached, dirty, g_cg_dl_pause, g_cg_pf_pause, g_cg_file); return 0; } /* 确定性宿主测试:注入 MEOW_CACHEGOV_TEST_CACHED_KB(/proc/meminfo 被注入值替代), * 单步评估并落共享状态文件,供 test_cachegov_monitor 断言阈值/滞回/降载。 * MEOW_CACHEGOV_TEST_SEQ="35000,20000,28000" 时按序多步评估(同一进程,测滞回保持)。 */ static int cachegov_cli_check(void) { cachegov_config_load(); const char *seq = getenv("MEOW_CACHEGOV_TEST_SEQ"); if (seq && seq[0]) { char buf[256]; snprintf(buf, sizeof buf, "%s", seq); char *save = NULL, *tok = strtok_r(buf, ",", &save); int idx = 0; while (tok) { long v = atol(tok), dirty = 0; cachegov_monitor_step(v, dirty); printf("cachegov-check[%d]: cached=%ld dl_pause=%d prefetch_pause=%d file=%s\n", idx++, v, g_cg_dl_pause, g_cg_pf_pause, g_cg_file); tok = strtok_r(NULL, ",", &save); } return 0; } long cached = g_cg_test_cached_kb >= 0 ? g_cg_test_cached_kb : 0; long dirty = g_cg_test_dirty_kb >= 0 ? g_cg_test_dirty_kb : 0; cachegov_monitor_step(cached, dirty); printf("cachegov-check: cached=%ld dirty=%ld dl_pause=%d prefetch_pause=%d file=%s\n", cached, dirty, g_cg_dl_pause, g_cg_pf_pause, g_cg_file); return 0; } 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|snap|snap-check|cachegov|cachegov-check] (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], "--snap-dir") && i + 1 < argc) snprintf(snap_snap_dir, sizeof snap_snap_dir, "%s", argv[++i]); else if (!strcmp(argv[i], "--card") && i + 1 < argc) snprintf(snap_card, sizeof snap_card, "%s", 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 常驻 */ if (!strcmp(argv[i], "snap")) return snap_cli_snap(); if (!strcmp(argv[i], "snap-check")) return snap_cli_check(); if (!strcmp(argv[i], "cachegov")) return cachegov_cli(); if (!strcmp(argv[i], "cachegov-check")) return cachegov_cli_check(); return cli_run(argc - i, argv + i); }