/* play/mem_watch.c — 边播边监控**取证**实现(接口见 mem_watch.h;agent 32bit 大文件 OOM 取证)。 * * 归属演进(2026-09-02 定案,ARCHITECTURE_PRINCIPLES ③混合):**降载决策上收通用层**—— * A(DONTNEED 治理)+ C(/proc/meminfo Cached 监控)分别归 SDK meow_cachegov / meow_svc_perf; * 本模块退化为「RssAnon 取证」:低频读 /proc/self/status(优先 RssAnon,缺失回退 VmRSS), * 记录匿名内存峰值,**仅供日志**。不再驱动下载/预取降载(那由 meow_cachegov_download_paused * / prefetch_paused 查询)。**绝不打断解码/播放**。 * * 阈值滞后(仅用于取证 level 标注): * kb >= CRITICAL → level=2;kb >= HIGH 且当前<2 → level=1;kb <= LOW → level=0;其它保持。 * 本模块自含:只依赖 pthread + meow_log(SDK 日志),不依赖 core/app 其它 TU。 */ #include "mem_watch.h" #include "../sdk/include/meow_ui_log.h" #include #include #include #include #include /* 默认阈值(设备缺省;宿主单测用 env/force 桩覆盖) */ #define MEM_WATCH_PERIOD_MS 500 #define MEM_WATCH_HIGH_KB (30 * 1024) #define MEM_WATCH_LOW_KB (20 * 1024) #define MEM_WATCH_CRITICAL_KB (40 * 1024) struct mw_cfg { int period_ms; long high_kb; long low_kb; long critical_kb; }; static struct mw_cfg g_cfg = { .period_ms = MEM_WATCH_PERIOD_MS, .high_kb = MEM_WATCH_HIGH_KB, .low_kb = MEM_WATCH_LOW_KB, .critical_kb = MEM_WATCH_CRITICAL_KB, }; static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER; static pthread_t g_thr; static int g_thr_on; static volatile int g_quit; static long g_rss_anon_kb; /* 最近一次采样(KB;-1=不可读) */ static long g_peak_song_kb; /* 本曲匿名峰值(song_begin 清零) */ static int g_level; /* NCM_MEM_LVL_*(仅取证日志) */ static long g_forced_kb = -1; /* 宿主桩 ≥0 时替代真读 */ /* env 覆盖阈值:非法值回落默认。 */ static int env_int(const char *k, int def) { const char *e = getenv(k); if (!e || !e[0]) return def; int v = atoi(e); return v > 0 ? v : def; } static long env_long(const char *k, long def) { const char *e = getenv(k); if (!e || !e[0]) return def; long v = atol(e); return v > 0 ? v : def; } /* 读匿名 RSS(KB)。优先 RssAnon;缺则回退 VmRSS;都无 → -1。 */ static long read_rss_kb(void) { pthread_mutex_lock(&g_mu); long forced = g_forced_kb; pthread_mutex_unlock(&g_mu); if (forced >= 0) return forced; FILE *fp = fopen("/proc/self/status", "r"); if (!fp) return -1; char line[256]; long anon = -1, rss = -1; while (fgets(line, sizeof line, fp)) if (!strncmp(line, "RssAnon:", 8)) anon = atol(line + 8); else if (rss < 0 && !strncmp(line, "VmRSS:", 6)) rss = atol(line + 6); fclose(fp); return anon >= 0 ? anon : rss; } static const char *level_str(int lvl) { switch (lvl) { case NCM_MEM_LVL_HIGH: return "high"; case NCM_MEM_LVL_CRITICAL: return "critical"; default: return "normal"; } } static void *watch_thread(void *arg) { (void)arg; while (!g_quit) { long kb = read_rss_kb(); int lvl; pthread_mutex_lock(&g_mu); g_rss_anon_kb = kb; if (kb > g_peak_song_kb) g_peak_song_kb = kb; long hi = g_cfg.high_kb, lo = g_cfg.low_kb, cr = g_cfg.critical_kb; /* 滞后三档(仅取证 level 标注) */ if (kb >= cr) lvl = NCM_MEM_LVL_CRITICAL; else if (kb >= hi) lvl = (g_level == NCM_MEM_LVL_CRITICAL) ? NCM_MEM_LVL_CRITICAL : NCM_MEM_LVL_HIGH; else if (kb <= lo) lvl = NCM_MEM_LVL_NORMAL; else lvl = g_level; /* 滞后带:保持 */ if (lvl != g_level) { meow_log(MEOW_LOG_INFO, "[mem] rss_anon=%ldKB peak=%ldKB level=%s (H=%ldK/L=%ldK/C=%ldK)", kb, g_peak_song_kb, level_str(lvl), hi, lo, cr); } g_level = lvl; pthread_mutex_unlock(&g_mu); usleep((useconds_t)g_cfg.period_ms * 1000u); } return NULL; } int mem_watch_start(void) { pthread_mutex_lock(&g_mu); if (g_thr_on) { pthread_mutex_unlock(&g_mu); return 0; } /* 开关(默认开;NCM_MEM_WATCH_ENABLED=0/off 关闭监控,行为退化为旧版零侵入) */ const char *en = getenv("NCM_MEM_WATCH_ENABLED"); if (en && (!en[0] || !strcmp(en, "0") || !strcmp(en, "off") || !strcmp(en, "false"))) { pthread_mutex_unlock(&g_mu); meow_log(MEOW_LOG_INFO, "[mem] watch disabled (NCM_MEM_WATCH_ENABLED=%s)", en); return 0; } /* 阈值 env 覆盖(宿主单测) */ g_cfg.period_ms = env_int("NCM_MEM_WATCH_PERIOD_MS", MEM_WATCH_PERIOD_MS); g_cfg.high_kb = env_long("NCM_MEM_WATCH_HIGH_KB", MEM_WATCH_HIGH_KB); g_cfg.low_kb = env_long("NCM_MEM_WATCH_LOW_KB", MEM_WATCH_LOW_KB); g_cfg.critical_kb = env_long("NCM_MEM_WATCH_CRITICAL_KB", MEM_WATCH_CRITICAL_KB); g_quit = 0; pthread_attr_t a; pthread_attr_init(&a); pthread_attr_setstacksize(&a, (size_t)(128 * 1024)); /* 同 ncm_thread_create 128KB(内存瘦身,OOM 修复) */ int rc = pthread_create(&g_thr, &a, watch_thread, NULL); pthread_attr_destroy(&a); if (rc != 0) { pthread_mutex_unlock(&g_mu); return -1; } g_thr_on = 1; pthread_mutex_unlock(&g_mu); meow_log(MEOW_LOG_INFO, "[mem] watch started period=%dms H=%ldK L=%ldK C=%ldK (RssAnon 优先)", g_cfg.period_ms, g_cfg.high_kb, g_cfg.low_kb, g_cfg.critical_kb); return 0; } void mem_watch_stop(void) { g_quit = 1; pthread_mutex_lock(&g_mu); if (g_thr_on) { g_thr_on = 0; pthread_detach(g_thr); } pthread_mutex_unlock(&g_mu); } int mem_watch_level(void) { pthread_mutex_lock(&g_mu); int v = g_level; pthread_mutex_unlock(&g_mu); return v; } long mem_watch_rss_anon_kb(void) { pthread_mutex_lock(&g_mu); long v = g_rss_anon_kb; pthread_mutex_unlock(&g_mu); return v; } void mem_watch_song_begin(void) { long kb = read_rss_kb(); /* 起播即采样当前内存,本曲峰值从当前起算 */ pthread_mutex_lock(&g_mu); g_peak_song_kb = kb; pthread_mutex_unlock(&g_mu); meow_log(MEOW_LOG_INFO, "[mem] song begin rss_anon=%ldKB", kb); } /* 播完/切歌释放钩子之后调用:落本曲峰值 + 重置计数。 */ void mem_watch_song_end(void) { pthread_mutex_lock(&g_mu); long peak = g_peak_song_kb; g_peak_song_kb = g_rss_anon_kb; pthread_mutex_unlock(&g_mu); meow_log(MEOW_LOG_INFO, "[mem] song end peak_anon=%ldKB (released)", peak); } void mem_watch_set_forced_kb(long kb) { pthread_mutex_lock(&g_mu); g_forced_kb = kb; pthread_mutex_unlock(&g_mu); }