/* bench/mem_release_sim.c — 宿主「彻底释放」路径验证(agent 32bit OOM 专项)。 * * 目的:验证「32bit(>16bit)→S32 解码 播完/切歌 是否真正释放」——ncm_dec_open(整文件)→ * ncm_dec_read 全量(dr_flac S32 满幅路径)→ ncm_dec_close(dr_flac_close + stream_src_close) * 循环 N 次,断言进程匿名 RSS(RssAnon,OOM 高危口径)**不单调累积**(播完回落基线), * 且解码中峰值有界(32bit 解码缓冲存在但有限,非 40MB 级)。 * * 依赖:仅 app/play 解码层(decoder.c + stream_src.c + dr_flac_impl + minimp3_impl), * 无 core/SDK/app.c——本 sim 是解码链路的最小区块,直接量「解码器 buff 播完 free」。 * 编译(ncm_app 目录下): * gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd \ * -Ivendor/jsmn -Ivendor/minimp3 -Ivendor/dr_flac -Ivendor/mbedtls/include \ * -c bench/mem_release_sim.c -o /tmp/mr.o * gcc -O2 -Wall -Wextra ... -c app/play/stream_src.c -o /tmp/mr_ss.o * gcc -O2 -Wall -Wextra ... -c app/play/decoder.c -o /tmp/mr_dec.o * gcc -O2 -c -Ivendor/minimp3 app/play/minimp3_impl.c -o /tmp/mr_mp3.o * gcc -O2 -c -Ivendor/dr_flac app/play/dr_flac_impl.c -o /tmp/mr_flac.o * gcc -O2 -o mem_release_sim /tmp/mr.o /tmp/mr_ss.o /tmp/mr_dec.o /tmp/mr_mp3.o /tmp/mr_flac.o -lpthread -lm * 运行:./mem_release_sim [iters] (iters 默认 5) * 断言(PASS 才 exit 0): * - RssAnon[最后] - RssAnon[基准] < 256KB(播完回落,无累积); * - 解码中峰值有界(peak - 基准 < 8MB,32bit 解码缓冲不是 40MB 级); * - 总帧数 > 0 且 bits==32(确实走了 S32 满幅路径——本 sim 只受 24bit 输入触发)。 */ #include "../app/play/decoder.h" #include #include #include #include #define READ_CHUNK_FRAMES 4096 #define ITER_DEFAULT 5 #define SETTLE_TOLERANCE_KB (256) /* 播完回落允许抖动(<256KB 视为无累积) */ #define PEAK_BOUND_KB (8 * 1024) /* 解码中峰值必须 <8MB(区别于 40MB 级异常) */ /* 静默 meow_log(本 sim 不链 core;decoder/stream_src 不调它,仅占位防漏链) */ void meow_log(int lvl, const char *fmt, ...) { (void)lvl; (void)fmt; } static long rss_anon_kb(void) { FILE *f = fopen("/proc/self/status", "r"); if (!f) return -1; char line[256]; long v = -1; while (fgets(line, sizeof line, f)) if (!strncmp(line, "RssAnon:", 8)) { v = atol(line + 8); break; } fclose(f); return v; } int main(int argc, char **argv) { if (argc < 2) { printf("usage: mem_release_sim [iters]\n"); return 2; } const char *path = argv[1]; int iters = (argc > 2) ? atoi(argv[2]) : ITER_DEFAULT; if (iters < 2) iters = 2; long base = rss_anon_kb(); if (base < 0) { printf("FAIL cannot read RssAnon\n"); return 1; } printf("mem_release[0] baseline RssAnon=%ldKB\n", base); int first_dec_ok = 0; long prev_after = 0, last_after = 0; /* 最近两次播完 close 后的 RssAnon */ for (int i = 0; i < iters; i++) { ncm_decoder_t *dec = NULL; int rate = 0, ch = 0, bits = 0; int64_t tf = 0; if (ncm_dec_open(&dec, path, &rate, &ch, &bits, &tf) != 0) { printf("FAIL iter%d ncm_dec_open (%s)\n", i, path); return 1; } if (i == 0) { printf("mem_release: fmt=%s %dHz/%dch/%dbit total_frames=%lld (S32 path=%d)\n", ncm_dec_format(dec), rate, ch, bits, (long long)tf, bits > 16); first_dec_ok = 1; } /* 全量读(S32 满幅路径),测解码中匿名峰值 */ static int32_t buf[READ_CHUNK_FRAMES * 2]; long peak = 0; int64_t frames = 0, got = 0; do { got = ncm_dec_read(dec, buf, READ_CHUNK_FRAMES); frames += got; long r = rss_anon_kb(); if (r > peak) peak = r; } while (got > 0); printf("mem_release[%d] after decode: frames=%lld peak_RssAnon=%ldKB\n", i + 1, (long long)frames, peak); ncm_dec_close(dec); /* drflac_close + stream_src_close(整文件静态源) */ long r = rss_anon_kb(); prev_after = last_after; last_after = r; printf("mem_release[%d] after close RssAnon=%ldKB (delta=%ldKB)\n", i + 1, r, r - base); /* 解码中峰值有界:32bit 解码缓冲不是 40MB 级 */ if (peak - base > PEAK_BOUND_KB) { printf("FAIL iter%d decode peak %ldKB - base %ldKB = %ldKB > %dKB (异常高)\n", i, peak, base, peak - base, PEAK_BOUND_KB); return 1; } } /* 累积断言:最后两次播完后的 RssAnon 相对基线的增量都 < 容忍(无单调上升) */ long d1 = last_after - base; long d2 = prev_after - base; int no_accum = (d1 < SETTLE_TOLERANCE_KB) && (d2 < SETTLE_TOLERANCE_KB); printf("mem_release: settle delta last=%ldKB prev=%ldKB -> %s\n", d1, d2, no_accum ? "PASS(无累积)" : "FAIL(有残留)"); int ok = first_dec_ok && no_accum; printf("mem_release: %s\n", ok ? "PASS" : "FAIL"); return ok ? 0 : 1; }