Files
MeowOS-For-R3P2/patch_v2/ncm_app/bench/cache_evict_sim.c
T
MeowOS f23720f934 feat(ncm): 三期第二批 — 中文搜索接拼音IME+设置页做好(音质全档/缓存上限可设数字+清理缓存/关于)+缓存LRU淘汰接生产路径(写前写后+手动调低上限)
- 搜索页输入框 MEOW_IME_ASCII→MEOW_IME_PINYIN: 候选上屏UTF-8经 on_change 实时回写 g_st.search_query, 键盘「拼/英」「中」切换, 英文零回归; 搜索仍仅在点「搜索」时发 cloudsearch(风控)
- 设置页 5 行: 音质(Hi-Res 默认,Lossless/Exhigh/Higher/Standard 全档,core 既有降级链)/缓存上限(1~100GB 预设+「自定义」NUM IME 数字输入,默认10上限100)/清理缓存(显示当前占用,ncm_cache_clear)/退出登录/关于行
- 缓存淘汰接线: ncm_cache_evict 进生产路径 — player_manager(下载写前/写后)+cover(写前/写后)+appstate_save_settings(手动调低上限立即淘汰); 封面与 mp3 同池; 上限读设置值
- core 新增 ncm_cache_clear; 设置持久化走 meow_config_* → MeowData/dev.player.ncm/data
- bench/cache_evict_sim.c 离线回归(0网络) 6 组 PASS; pagination_sim/core(host+mipsel/qemu)/6页宿主渲染 全过; mipsel -Wall -Wextra 0 warning
(未动 sdk/、meow_launcherd/、vendor/;0 真实 NCM 请求;真机部署留用户)
2026-08-31 20:15:31 +08:00

202 lines
8.2 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.
/* bench/cache_evict_sim.c — 缓存池 LRU 淘汰 / 清空 离线验证(0 网络,仅本地文件系统)。
*
* 直接驱动 core 的真实函数 ncm_cache_init / ncm_cache_set_limit_gb / ncm_cache_evict /
* ncm_cache_total_bytes / ncm_cache_clear / ncm_cache_lookup,验证三期第二批接进生产路径的
* 淘汰语义(REPORT §15):
* ① 超上限按 mtime 升序删最旧,直到 <= 上限;
* ② 未超上限无操作;
* ③ ncm_cache_lookup 刷新 mtime(LRU:被访问的最近优先存活);
* ④ ncm_cache_set_limit_gb 越界钳制(1~100GB);
* ⑤ ncm_cache_clear 删除 song_* 全部(mp3/封面/残留 .tmp)。
* 文件用 ftruncate 生成稀疏文件(逻辑大小可控,磁盘零消耗)。
*
* 编译(宿主,链接 core/ncm_cache.c + 本文件):
* $CC_HOST -O2 -DJSMN_HEADER -I. -Icore -c bench/cache_evict_sim.c -o /tmp/ce.o
* $CC_HOST -O2 -DJSMN_HEADER -I. -Icore -c core/ncm_cache.c -o /tmp/ce_cache.o
* $CC_HOST -O2 -o cache_evict_sim /tmp/ce.o /tmp/ce_cache.o -lpthread
* 运行:./cache_evict_sim (0 网络;PASS/FAIL 退出码)
*/
#include "../core/ncm_cache.h"
#include "../core/ncm_common.h"
#include "../core/ncm_store.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/time.h>
/* ---- ncm_cache.c 依赖的符号(核心库其他 TU 提供;此处最小内联/桩,避免链 mbedtls) ---- */
size_t ncm_strlcpy(char *dst, const char *src, size_t sz)
{
size_t n = 0;
if (sz) {
while (src[n] && n < sz - 1) { dst[n] = src[n]; n++; }
dst[n] = 0;
}
while (src[n]) n++;
return n;
}
int ncm_store_mkdir_p(const char *path)
{
if (!path || !*path) return -1;
char tmp[512];
size_t len = strlen(path);
if (len >= sizeof tmp) return -1;
memcpy(tmp, path, len + 1);
if (tmp[len - 1] == '/') tmp[len - 1] = '\0';
for (size_t i = 1; i <= strlen(tmp); i++) {
if (tmp[i] == '/' || tmp[i] == '\0') {
char ch = tmp[i];
tmp[i] = '\0';
if (tmp[0] && mkdir(tmp, 0755) != 0 && errno != EEXIST) return -1;
tmp[i] = ch;
}
}
return 0;
}
int ncm_store_read_cfg_int(const char *dir, const char *key, int def) { (void)dir; (void)key; return def; }
/* ---- 工具 ---- */
static char g_dir[512];
static ncm_cache_t g_c;
static int g_fail;
#define CHECK(cond, fmt, ...) do { \
if (cond) printf(" PASS " fmt "\n", ##__VA_ARGS__); \
else { printf(" FAIL " fmt "\n", ##__VA_ARGS__); g_fail = 1; } \
} while (0)
/* 按真实生产命名建缓存文件:路径经 ncm_cache_path(与 app/play/cover 同约定,ext 带前导点)。
* 结果文件名 = song_<id>.<ext>(ext 带点时即双点,如 song_a..mp3 —— 与 core 现状一致)。 */
static void make_cached(const char *id, const char *ext, unsigned long long size, long mtime)
{
char p[600];
if (ncm_cache_path(&g_c, id, ext, p, sizeof p) != 0) { printf(" path err %s\n", id); g_fail = 1; return; }
int fd = open(p, O_WRONLY | O_CREAT, 0644);
if (fd < 0) { printf(" open %s failed\n", id); g_fail = 1; return; }
if (ftruncate(fd, (off_t)size) != 0) printf(" truncate %s err\n", id);
close(fd);
struct timespec ts[2];
ts[0].tv_sec = mtime; ts[0].tv_nsec = 0;
ts[1].tv_sec = mtime; ts[1].tv_nsec = 0;
utimensat(AT_FDCWD, p, ts, 0);
}
static int cached_exists(const char *id, const char *ext)
{
char p[600];
if (ncm_cache_path(&g_c, id, ext, p, sizeof p) != 0) return 0;
struct stat st;
return stat(p, &st) == 0;
}
static unsigned long long mb(unsigned long long n) { return n * 1024ULL * 1024ULL; }
static void cleanup(void)
{
/* 递归删 g_dir 下 song_*(恢复干净池) */
DIR *d = opendir(g_dir);
if (d) {
struct dirent *e;
while ((e = readdir(d))) {
if (strncmp(e->d_name, "song_", 5) != 0) continue;
char p[800];
snprintf(p, sizeof p, "%s/%s", g_dir, e->d_name);
unlink(p);
}
closedir(d);
}
}
int main(void)
{
snprintf(g_dir, sizeof g_dir, "/tmp/ncm_cache_sim_%d", (int)getpid());
cleanup();
ncm_cache_init(&g_c, g_dir);
ncm_cache_set_limit_gb(&g_c, 1); /* 1GB 最小上限(淘汰只在 total > limit 时触发) */
/* ① 超上限删最旧。A=700MB(最旧), B=300MB, C=400MB(最新) → 删 A 后 700MB<=1GB 停。 */
printf("== case 1: evict oldest over-limit ==\n");
cleanup();
make_cached("a", ".mp3", mb(700), 1000);
make_cached("b", ".mp3", mb(300), 1100);
make_cached("c", ".mp3", mb(400), 1200);
CHECK(ncm_cache_total_bytes(&g_c) == mb(1400), "total=%llu (expect 1400MB)",
(unsigned long long)ncm_cache_total_bytes(&g_c));
ncm_cache_evict(&g_c);
CHECK(!cached_exists("a", ".mp3"), "oldest A evicted");
CHECK(cached_exists("b", ".mp3") && cached_exists("c", ".mp3"), "B/C kept");
CHECK(ncm_cache_total_bytes(&g_c) == mb(700), "total=%llu after evict (expect 700MB)",
(unsigned long long)ncm_cache_total_bytes(&g_c));
/* ② 4×500MB 超 1GB:删 2 个最旧到 <=1GB。 */
printf("== case 2: evict two oldest ==\n");
cleanup();
make_cached("a", ".mp3", mb(500), 1000);
make_cached("b", ".mp3", mb(500), 1100);
make_cached("c", ".mp3", mb(500), 1200);
make_cached("d", ".mp3", mb(500), 1300);
ncm_cache_evict(&g_c);
CHECK(!cached_exists("a", ".mp3") && !cached_exists("b", ".mp3"), "A/B (two oldest) evicted");
CHECK(cached_exists("c", ".mp3") && cached_exists("d", ".mp3"), "C/D kept");
CHECK(ncm_cache_total_bytes(&g_c) == mb(1000), "total=%llu (expect 1000MB)",
(unsigned long long)ncm_cache_total_bytes(&g_c));
/* ③ 未超上限:无删除。 */
printf("== case 3: under limit no-op ==\n");
cleanup();
make_cached("a", ".mp3", mb(300), 1000);
make_cached("b", ".mp3", mb(200), 1100);
ncm_cache_evict(&g_c);
CHECK(cached_exists("a", ".mp3") && cached_exists("b", ".mp3"), "all kept under limit");
/* ④ lookup 刷新 mtime(LRU):A 被访问 → 变最新,淘汰先删 B/C。 */
printf("== case 4: lookup refreshes LRU mtime ==\n");
cleanup();
make_cached("a", ".mp3", mb(600), 1000);
make_cached("b", ".mp3", mb(600), 1100);
make_cached("c", ".mp3", mb(600), 1200);
char path[600];
CHECK(ncm_cache_lookup(&g_c, "a", ".mp3", path, sizeof path) == NCM_OK, "lookup A hit");
ncm_cache_evict(&g_c); /* A mtime 已被刷新为最新 → 删 B、C */
CHECK(cached_exists("a", ".mp3"), "A (recently accessed) survives");
CHECK(!cached_exists("b", ".mp3") && !cached_exists("c", ".mp3"), "B/C evicted");
/* ⑤ set_limit_gb 钳制。 */
printf("== case 5: limit clamping ==\n");
ncm_cache_set_limit_gb(&g_c, 0);
CHECK(ncm_cache_limit_gb(&g_c) == 1, "limit(0)->%d (expect 1)", ncm_cache_limit_gb(&g_c));
ncm_cache_set_limit_gb(&g_c, 200);
CHECK(ncm_cache_limit_gb(&g_c) == 100, "limit(200)->%d (expect 100)", ncm_cache_limit_gb(&g_c));
/* ⑥ clear 删 song_* 全部(mp3 + 封面 + 残留 .tmp)。 */
printf("== case 6: clear removes all (mp3+cover+tmp) ==\n");
cleanup();
make_cached("x", ".mp3", mb(10), 1000);
make_cached("y", ".mp3", mb(10), 1001);
make_cached("cov123", ".jpg", mb(1), 1002);
/* 残留 .tmp:取 mp3 生产路径后追加 .tmp(ncm_cache_open 的临时名)。 */
{
char tp[600];
ncm_cache_path(&g_c, "z", ".mp3", tp, sizeof tp);
strncat(tp, ".tmp", sizeof tp - strlen(tp) - 1);
int fd = open(tp, O_WRONLY | O_CREAT, 0644);
if (ftruncate(fd, (off_t)mb(5)) != 0) printf(" truncate tmp err\n");
close(fd);
}
CHECK(ncm_cache_clear(&g_c) == NCM_OK, "clear rc=0");
CHECK(!cached_exists("x", ".mp3") && !cached_exists("y", ".mp3") &&
!cached_exists("cov123", ".jpg") && ncm_cache_total_bytes(&g_c) == 0,
"all song_* (mp3+cover+tmp) removed; total=0");
CHECK(ncm_cache_total_bytes(&g_c) == 0, "total=%llu after clear (expect 0)",
(unsigned long long)ncm_cache_total_bytes(&g_c));
cleanup();
printf("%s\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS");
return g_fail ? 1 : 0;
}