Files
MeowOS-For-R3P2/patch_v2/meow_svc_perf/meow_svc_perf.c
T
AskaEth 3d70a3dd2c feat: M6.10–M6.19 批量收编 + M6.19 推卡验收
- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘)
- OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复
- ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640
- 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫)
- 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
2026-09-03 12:16:01 +08:00

1433 lines
56 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <poll.h>
#include <time.h>
#include <ctype.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/utsname.h>
#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 <path> 第 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_<bootts>.log:每 MEOW_PERF_BASELINE_MS(默认 60s)追一行
* load/cpu/mem/topN 进程(单文件 1MB 上限,超则新开一轮)。
* 2) hang 快照 hang_<bootts>_<epoch>.snap:loadavg[1] 持续超限
* MEOW_PERF_HANG_CONSEC 次(默认 2)落一份完整现场(loadavg/cpu/mem/关键进程
* VmRSS+线程/连接数),带冷却(默认 300s)与每 boot 上限(默认 4),绝不连写刷爆卡。
* 降载/自死规避:采集全部【单文件读】(/proc/loadavg、/proc/meminfo、/proc/stat、
* /proc/<pid>/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;
}
/* 以 <name> 在 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/<file> 行(跳过头行)。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_<bootts>.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_<bootts>_<epoch>.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);
}