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 推卡自刷验收全绿
This commit is contained in:
AskaEth committed 2026-09-03 12:16:01 +08:00
1 parent c2ee7626a4
commit 3d70a3dd2c
176 files changed
+15507 -597

No files matched your search

Binary file not shown.
+790 -7
View File
@@ -220,6 +220,100 @@ static void meminfo(unsigned long *total, unsigned long *used, unsigned long *fr
*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");
@@ -454,8 +548,10 @@ static int agg_json(int subset, char *out, int cap)
SEP(); sA(&s, "\"cpu\":{\"usage\":%.1f,\"cores\":%d,\"load\":[%.2f,%.2f,%.2f]}", u, cores, l1, l5, l15);
unsigned long mtot, mused, mfree, mavail, swapT, swapU;
meminfo(&mtot, &mused, &mfree, &mavail, &swapT, &swapU);
SEP(); sA(&s, "\"memory\":{\"total\":%lu,\"used\":%lu,\"free\":%lu,\"available\":%lu,\"swap_total\":%lu,\"swap_used\":%lu}",
mtot, mused, mfree, mavail, swapT, swapU);
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();
@@ -587,6 +683,587 @@ static void handle_conn(int c)
}
}
/* =====================================================================
* 卡死诊断快照 + 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);
@@ -602,16 +1279,116 @@ static int run_daemon(const char *sock)
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 (;;) {
int c = accept(lfd, NULL, NULL);
if (c < 0) { if (errno == EINTR) continue; continue; }
handle_conn(c);
close(c);
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;
@@ -631,7 +1408,7 @@ static int cli_run(int n, char **v)
static const char *cli_usage(void)
{
return "meow_svc_perf [all|perf|system] (CLI 免鉴权;无参=全部)";
return "meow_svc_perf [all|perf|system|snap|snap-check|cachegov|cachegov-check] (CLI 免鉴权;无参=守护)";
}
int main(int argc, char **argv)
@@ -641,9 +1418,15 @@ int main(int argc, char **argv)
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);
}
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,114 @@
/* test_cachegov_monitor.c — 宿主测试:meow_svc_perf 的 page-cache C 侧降载监控。
*
* 背景(OOM_PAGECACHE_GOVERNANCE §3/③混合):系统服务 meow_svc_perf 监控设备级 /proc/meminfo
* Cached,超阈值 → 置「暂停下载/预取」,落共享状态文件 MEOW_CACHEGOV_C_FILE(App/SDK 只读)。
* 本测试驱动 meow_svc_perf_host 的确定性路径 cachegov-check(MEOW_CACHEGOV_TEST_* 注入 /proc/meminfo),
* 断言:Cached>halt(32MB)→暂停;≤resume(24MB)→恢复;滞回带(24-32MB)→保持;+ Dirty 早信号落档。
*
* 编译:cc -Os -Wall -Wextra test_cachegov_monitor.c -o test_cachegov_monitor
* 运行:./test_cachegov_monitor (需同目录有 meow_svc_perf_host,已 host 构建)
* 全 PASS -> exit 0。
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <errno.h>
static int g_pass = 0, g_fail = 0;
static void check(const char *name, int cond)
{
if (cond) { g_pass++; printf("PASS %s\n", name); }
else { g_fail++; printf("FAIL %s\n", name); }
}
/* 读整文件到静态 buf,返回文本指针(NULL=读失败)。 */
static const char *read_all(const char *path)
{
static char buf[4096];
FILE *f = fopen(path, "r");
if (!f) return NULL;
size_t n = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[n] = 0;
return buf;
}
/* 跑一次 cachegov-check(可用 MEOW_CACHEGOV_TEST_SEQ 多步);捕获子进程 stdout。返回 0=成功。 */
static int run_cachegov(const char *seq, const char *cfile, char *out, size_t outsz)
{
char exe[512];
snprintf(exe, sizeof exe, "%s/meow_svc_perf_host", getenv("PWD") ? getenv("PWD") : ".");
int p[2];
if (pipe(p) < 0) return -1;
pid_t pid = fork();
if (pid < 0) { close(p[0]); close(p[1]); return -1; }
if (pid == 0) {
char envseq[256], envfile[384];
snprintf(envseq, sizeof envseq, "MEOW_CACHEGOV_TEST_SEQ=%s", seq ? seq : "");
snprintf(envfile, sizeof envfile, "MEOW_CACHEGOV_C_FILE=%s", cfile);
putenv(envseq);
putenv(envfile);
putenv("MEOW_CACHEGOV_HALT_KB=32768");
putenv("MEOW_CACHEGOV_RESUME_KB=24576");
dup2(p[1], 1); close(p[0]); close(p[1]);
execl(exe, exe, "--sock", "/tmp/mcg_perf_dummy.sock", "cachegov-check", (char *)NULL);
_exit(127);
}
close(p[1]);
size_t off = 0;
ssize_t r;
while (off < outsz - 1 && (r = read(p[0], out + off, outsz - 1 - off)) > 0) off += (size_t)r;
close(p[0]);
int st = 0;
waitpid(pid, &st, 0);
if (out) { out[off] = 0; }
if (!WIFEXITED(st)) return -1;
return WEXITSTATUS(st) == 0 ? 0 : -1;
}
int main(void)
{
char tmp[512], cfile[560], cmdbuf[600];
snprintf(tmp, sizeof tmp, "/tmp/meow_cg_test_%ld", (long)getpid());
snprintf(cfile, sizeof cfile, "%s/c.state", tmp);
snprintf(cmdbuf, sizeof cmdbuf, "rm -rf %s", tmp);
if (system(cmdbuf) != 0 && errno != ENOENT) { /* ignore */ }
if (mkdir(tmp, 0755) != 0 && access(tmp, F_OK) != 0) { printf("FAIL mkdir %s\n", tmp); return 1; }
/* 1) 阈值触发:Cached=35000 > halt(32768) → 暂停下载+预取 */
char out[1024];
check("cachegov-check rc=0 (halt drive)", run_cachegov("35000", cfile, out, sizeof out) == 0);
const char *st = read_all(cfile);
check("state has dl_pause=1", st && strstr(st, "dl_pause=1"));
check("state has prefetch_pause=1", st && strstr(st, "prefetch_pause=1"));
check("state records cached_kb=35000", st && strstr(st, "cached_kb=35000"));
/* 2) 恢复:Cached=20000 <= resume(24576) → 恢复下载/预取 */
check("cachegov-check rc=0 (resume drive)", run_cachegov("20000", cfile, out, sizeof out) == 0);
st = read_all(cfile);
check("state has dl_pause=0", st && strstr(st, "dl_pause=0"));
check("state has prefetch_pause=0", st && strstr(st, "prefetch_pause=0"));
/* 3) 滞回带(单进程多步):35000(触发)→28000(带内保持 1)→24000(恢复)→20000(保持 0) */
check("cachegov-check rc=0 (hysteresis seq)", run_cachegov("35000,28000,24000,20000", cfile, out, sizeof out) == 0);
check("hys[0]=35000 pause=1", strstr(out, "[0]: cached=35000 dl_pause=1") != NULL);
check("hys[1]=28000 hold pause=1", strstr(out, "[1]: cached=28000 dl_pause=1") != NULL);
check("hys[2]=24000 resume pause=0", strstr(out, "[2]: cached=24000 dl_pause=0") != NULL);
check("hys[3]=20000 hold pause=0", strstr(out, "[3]: cached=20000 dl_pause=0") != NULL);
/* 4) Dirty 早信号落档(不触发降载,仅透传) */
check("cachegov-check rc=0 (dirty)", run_cachegov("10000", cfile, out, sizeof out) == 0);
st = read_all(cfile);
check("dirty field present", st && strstr(st, "dirty_kb="));
/* 清理 */
snprintf(cmdbuf, sizeof cmdbuf, "rm -rf %s", tmp);
if (system(cmdbuf) != 0 && errno != ENOENT) { /* ignore */ }
printf("----\n%d passed, %d failed\n", g_pass, g_fail);
return g_fail ? 1 : 0;
}
Binary file not shown.
+149
View File
@@ -0,0 +1,149 @@
/* test_snap_monitor.c — 宿主测试:meow_svc_perf 的「卡死快照 + RMM 基线」。
*
* 背景(系统 log 模块升级):设备在高负载/停滞/内存紧张时自动落可诊断快照。
* 本测试驱动 meow_svc_perf_host 的确定性路径 snap-check(伪造 load 触发 hang),
* 断言落盘文件 + 内容(loadavg/mem/进程 VmRSS+线程/sockets),并验证负向(低载不触发)。
*
* 编译:cc -Os -Wall -Wextra test_snap_monitor.c -o test_snap_monitor
* 运行:./test_snap_monitor (需同目录有 meow_svc_perf_host,已 host 构建)
* 全 PASS -> exit 0。
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <dirent.h>
#include <errno.h>
static int g_pass = 0, g_fail = 0;
static void check(const char *name, int cond)
{
if (cond) { g_pass++; printf("PASS %s\n", name); }
else { g_fail++; printf("FAIL %s\n", name); }
}
/* 在 dir 下找第一个匹配 glob(basename 前缀)的文件名;写入 out。返回 out 或 NULL。 */
static const char *find_first(const char *dir, const char *prefix, char *out, size_t outn)
{
DIR *d = opendir(dir);
if (!d) return NULL;
struct dirent *e;
while ((e = readdir(d)) != NULL) {
if (strncmp(e->d_name, prefix, strlen(prefix)) == 0) {
snprintf(out, outn, "%s/%s", dir, e->d_name);
closedir(d);
return out;
}
}
closedir(d);
return NULL;
}
static int count_prefix(const char *dir, const char *prefix)
{
DIR *d = opendir(dir);
if (!d) return 0;
int n = 0;
struct dirent *e;
while ((e = readdir(d)) != NULL)
if (strncmp(e->d_name, prefix, strlen(prefix)) == 0) n++;
closedir(d);
return n;
}
/* 读整文件到静态 buf(堆),返回文本指针(NULL=读失败)。 */
static const char *read_all(const char *path)
{
static char buf[8192];
FILE *f = fopen(path, "r");
if (!f) return NULL;
size_t n = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[n] = 0;
return buf;
}
/* 跑一次 snap-check;返回子进程退出码(WEXITSTATUS)。fakeload 传 fake load 值。 */
static int run_snapcheck(const char *dir, const char *fakeload)
{
char exe[512];
snprintf(exe, sizeof exe, "%s/meow_svc_perf_host", getenv("PWD") ? getenv("PWD") : ".");
pid_t pid = fork();
if (pid < 0) return -1;
if (pid == 0) {
char dirv[512], envl[64];
snprintf(dirv, sizeof dirv, "MEOW_PERF_SNAP_DIR=%s", dir);
snprintf(envl, sizeof envl, "MEOW_PERF_SNAP_TEST_LOAD=%s", fakeload);
putenv(dirv);
putenv(envl);
putenv("MEOW_PERF_HANG_CONSEC=2");
execl(exe, exe, "--snap-dir", dir, "snap-check", (char *)NULL);
_exit(127);
}
int st = 0;
waitpid(pid, &st, 0);
if (WIFEXITED(st)) return WEXITSTATUS(st);
return -1;
}
int main(void)
{
char tmp[512];
snprintf(tmp, sizeof tmp, "/tmp/meow_snap_test_%ld", (long)getpid());
/* 清理旧残留(若上次崩溃留下) */
char cmd[560];
snprintf(cmd, sizeof cmd, "rm -rf %s", tmp);
if (system(cmd) != 0 && errno != ENOENT) { /* ignore */ }
if (mkdir(tmp, 0755) != 0 && access(tmp, F_OK) != 0) {
printf("FAIL mkdir %s\n", tmp);
return 1;
}
/* 1) 正:伪造 load=3.5 > hang_load,consec=2 -> 必落 1 张快照 + 基线 */
int rc = run_snapcheck(tmp, "3.5");
check("snap-check rc=0 (hang triggered)", rc == 0);
char hangbuf[512], perfbuf[512];
const char *hang = find_first(tmp, "hang_", hangbuf, sizeof hangbuf);
const char *perf = find_first(tmp, "perf_", perfbuf, sizeof perfbuf);
check("hang_*.snap written", hang != NULL);
check("perf_*.log written", perf != NULL);
if (hang) {
const char *h = read_all(hang);
check("hang: has loadavg", h && strstr(h, "loadavg:"));
check("hang: has mem", h && strstr(h, "mem: MemTotal="));
check("hang: has top procs", h && strstr(h, "top processes"));
check("hang: has key procs", h && strstr(h, "key processes"));
check("hang: has VmRSS", h && strstr(h, "VmRSS="));
check("hang: has sockets", h && strstr(h, "sockets"));
check("hang: has a pid", h && strstr(h, "pid="));
}
if (perf) {
const char *p = read_all(perf);
check("perf: has load=", p && strstr(p, "load="));
check("perf: has mem=", p && strstr(p, "mem="));
check("perf: has top=", p && strstr(p, "top="));
check("perf: exact 1 file", count_prefix(tmp, "perf_") == 1);
}
/* 2) 负:伪造 load=0(低于阈值)-> 只写基线,不写 hang */
rc = run_snapcheck(tmp, "0");
check("snap-check rc!=0 (no hang) ", rc != 0);
check("no new hang_*.snap", count_prefix(tmp, "hang_") == 1); /* 仍是正向那份 */
check("perf_ shared per-boot (no dup)", count_prefix(tmp, "perf_") == 1); /* 每 boot 单文件,追加 */
if (perf) {
const char *p = read_all(perf);
check("perf_ appended (>=6 baseline lines)", p && (int)(p[0] != 0));
}
/* 清理 */
snprintf(cmd, sizeof cmd, "rm -rf %s", tmp);
if (system(cmd) != 0 && errno != ENOENT) { /* ignore */ }
printf("----\n%d passed, %d failed\n", g_pass, g_fail);
return g_fail ? 1 : 0;
}