r1 用宿主 1.1µs/stat + M28 的**热** stat 150µs 外推「434 次≈65ms」,真机 drop_caches 后 实测是 3.15s(7.26ms/次),与 readdir 的 3.0s 同量级:全目录校验的索引命中冷启动仍要 3.06s,只比全扫快 0.2s。「3.0s→0.15s」在冷启动不成立。按任务 1 自己定的判据 (stat 接近 1s 就退回 level-1)改为只校验 Books 根 + 每个一级目录。 真机冷缓存实测(exFAT /usr/data/mnt/sd_0,434 目录/646 文件/595 本;sync + drop_caches; 探针走 /proc/uptime,UI 走 meow_ui_time_ms): - 索引命中首开:**127 / 138 / 157ms**(复测 147ms),热 dentry 107–120ms; - 全量扫描首开:**3428 / 3477ms**(复测 3711ms),热 dentry 不变(readdir 不吃 page cache); - 探针冷分项:索引载入 20–30ms;载入+rdprog×595 70–120ms;4 个目录 stat 0–30ms; - 从阅读页返回书架 29–31ms(M29 语义未变)。 → 冷启动 22–26 倍提升,方案值得做;索引实测 **63,579B**(v2;r1 全目录 97,490B)。 实现(格式 v2,magic MEOWIDX2 / version 2): - writer 回调过滤 rel 含 '/' 的目录:只落 root + 一级目录的 mtime;书/大小/格式记录仍全量; level-2+ 不落盘、载入不 stat。载入仍然 0 次 opendir/readdir(strace:0 getdents64, 真实库 4 次 stat;宿主模拟库 11 次)。 - 校验记录里出现 level-2+ rel 视为坏档;sum(books_here) 改为「≤ nbooks」合理性上界。 - fail-open/原子写/CRC/安全加固/ts 不缓存全部保留(r1 已过评审的部分未动)。 失效矩阵(报文逐条写清): 自动(根 mtime):Books 根下直接增/删/改名书;一级目录新增/删除/改名。 自动(一级目录 mtime):一级目录里直接增/删/改名书、新建子目录。 必须刷新:level-2 及更深处的任何变化(只改深层目录 mtime)。刷新=删索引+全扫。 真机 exFAT mtime 实测 **1 秒**(tm_nsec=0;同秒连续增删 mtime 不变)。我复测 10 轮 「目录刚改过就扫描、同秒再改」→ 9/10 仍命中(陈旧),与评审 29/30 一致;sleep 跨秒后 全部正常失效。头注释已把「10ms」改为「1 秒」,并写明「不在同一秒刻度的变更靠索引失效, 同秒二次变更需刷新」。 bench 修复(P3-3):MEOW_BENCH_IDX=only —— **不预先 load/scan**,直接进书架量生产首开; miss —— 先删索引再进书架。两者配合 drop_caches 即冷启动口径。旧行为(先 bench_index/ bench_scan 焐热)保留为默认模式。新增 test/shelf_index_probe_mipsel(write/scan/load/ loadts/statpaths/mtime/size)用于真机分项与 mtime 粒度验证,不进发行包。 测试:shelf_index_smoke 170 pass(新增:level-3/level-2 内变更「索引仍命中且先看到旧 列表 → 刷新后可见」、新建 level-2 目录自动失效、一级目录直接删书自动失效、v2 ndirs= 根+一级数);ASan+UBSan 干净;build_reader.sh 0 warning,core 6846 / seg 19 / chapter 71 / shelf_group 63 / index 170 / font 14 / loaders PASS。真机 6 场景逐项验证:一级直接增/ 新建子目录/一级改名/根下删书/一级直接删书 = 自动失效;level-3 深处新增 = 仍命中(需刷新)。 交付:app.json 0.1.5/code6 → **0.1.6/code7**;reader md5 5c848c99… 1,711,988B; mpk md5 10d676f5… 814,574B;未签名不 push。设备验证后已清理 /tmp 与 m32probe, 真实 MeowData 的 shelf.idx 已删(app 自己的章节 .idx 未动),Books 434/646 不变, 已装 0.1.4 md5 4036b889… 未变。
116 lines
4.7 KiB
C
116 lines
4.7 KiB
C
/* test/shelf_index_probe.c — M32 真机测量探针(不进发行包/不参与 mpk)。
|
|
*
|
|
* 只调 shelf_model/shelf_index/progress,不初始化 UI;用 /proc/uptime 计时
|
|
* (该内核 time32 ABI 下 clock_gettime 不可靠,见 sdk/CLOCK_ABI.md;分辨率 10ms)。
|
|
*
|
|
* 用法(设备):
|
|
* MEOW_PROBE_CFG=/usr/data/mnt/sd_0/MeowData/m32probe/data \
|
|
* ./shelf_index_probe <mode> /usr/data/mnt/sd_0/Books
|
|
* mode:
|
|
* write 全量扫描 + 写索引(打印 n/索引字节数)
|
|
* scan 全量扫描(不写索引)
|
|
* load 索引载入(ts_fn=NULL,只量索引+校验)
|
|
* loadts 索引载入 + 逐本 rdprog_load(生产口径)
|
|
* statpaths 冷缓存 stat 指定路径(argv[2..],不做任何枚举,避免预热)
|
|
* mtime 打印路径的 st_mtime/sec+nsec(验证 exFAT 1 秒粒度)
|
|
* size 打印索引文件字节数
|
|
* 冷缓存:每次运行前 `sync; echo 3 > /proc/sys/vm/drop_caches`。
|
|
* 注意:load/level1 本身会按需把 dentry/page cache 焐热,所以冷值必须每次 drop 后单跑一次。
|
|
*/
|
|
#include "shelf_model.h"
|
|
#include "shelf_index.h"
|
|
#include "progress.h"
|
|
|
|
#include <dirent.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
static char g_cfgdir[500];
|
|
const char *meow_config_dir(void) { return g_cfgdir; }
|
|
|
|
static int64_t now_us(void)
|
|
{
|
|
FILE *f = fopen("/proc/uptime", "r");
|
|
if (!f) return 0;
|
|
long sec = 0, cs = 0;
|
|
if (fscanf(f, "%ld.%ld", &sec, &cs) != 2) cs = 0;
|
|
fclose(f);
|
|
return (int64_t)sec * 1000000 + (int64_t)cs * 10000;
|
|
}
|
|
|
|
static uint64_t prog_ts(const char *path)
|
|
{
|
|
rdprog p;
|
|
return (rdprog_load(path, &p) == 1) ? p.ts : 0;
|
|
}
|
|
|
|
static int64_t index_bytes(const char *root)
|
|
{
|
|
char ip[500];
|
|
struct stat st;
|
|
if (shelf_index_path(root, ip, sizeof ip) != 0 || stat(ip, &st) != 0) return -1;
|
|
return (int64_t)st.st_size;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
if (argc < 3) {
|
|
fprintf(stderr, "用法: %s <write|scan|load|loadts|level1|size> <Books目录>\n", argv[0]);
|
|
return 2;
|
|
}
|
|
const char *mode = argv[1];
|
|
const char *root = argv[2];
|
|
const char *cfg = getenv("MEOW_PROBE_CFG");
|
|
snprintf(g_cfgdir, sizeof g_cfgdir, "%s", (cfg && cfg[0]) ? cfg : "/tmp/m32probe_cfg/data");
|
|
|
|
static shelf_book books[SHELF_MAX_BOOKS];
|
|
shelf_scan_report rep;
|
|
int64_t t0, t1;
|
|
|
|
if (strcmp(mode, "scan") == 0) {
|
|
t0 = now_us();
|
|
int n = shelf_scan_books(root, books, SHELF_MAX_BOOKS, NULL, &rep);
|
|
t1 = now_us();
|
|
printf("PROBE scan_ms %lld n=%d trunc=%d skip=%d root_missing=%d\n",
|
|
(long long)((t1 - t0) / 1000), n, rep.truncated, rep.depth_skipped, rep.root_missing);
|
|
} else if (strcmp(mode, "write") == 0) {
|
|
t0 = now_us();
|
|
shelf_idx_writer *w = shelf_index_writer_open(root);
|
|
int n = shelf_scan_books_ex(root, books, SHELF_MAX_BOOKS, NULL, &rep,
|
|
w ? shelf_index_writer_dir : NULL, w);
|
|
int ok = w ? shelf_index_writer_commit(w, books, n, &rep) : 0;
|
|
t1 = now_us();
|
|
printf("PROBE write_ms %lld n=%d commit=%d idx_bytes=%lld\n",
|
|
(long long)((t1 - t0) / 1000), n, ok, (long long)index_bytes(root));
|
|
} else if (strcmp(mode, "load") == 0 || strcmp(mode, "loadts") == 0) {
|
|
int with_ts = (strcmp(mode, "loadts") == 0);
|
|
t0 = now_us();
|
|
int hit = shelf_index_load(root, books, SHELF_MAX_BOOKS, with_ts ? prog_ts : NULL, &rep);
|
|
t1 = now_us();
|
|
printf("PROBE %s_ms %lld hit=%d n=%d trunc=%d skip=%d idx_bytes=%lld\n",
|
|
with_ts ? "loadts" : "load", (long long)((t1 - t0) / 1000),
|
|
hit, rep.nbooks, rep.truncated, rep.depth_skipped, (long long)index_bytes(root));
|
|
} else if (strcmp(mode, "statpaths") == 0) {
|
|
/* 冷缓存 stat 指定路径(不做任何 readdir/枚举,避免预热):argv[2..] 都是路径。
|
|
* 设备用法示例:drop_caches 后 probe statpaths <Books> <Books/kh> <Books/轻小说> ... */
|
|
struct stat st;
|
|
int cnt = argc - 2;
|
|
t0 = now_us();
|
|
for (int i = 2; i < argc; i++) (void)stat(argv[i], &st);
|
|
t1 = now_us();
|
|
printf("PROBE statpaths_ms %lld npaths=%d\n", (long long)((t1 - t0) / 1000), cnt);
|
|
} else if (strcmp(mode, "mtime") == 0) {
|
|
struct stat st;
|
|
int rc = stat(argv[2], &st);
|
|
printf("PROBE mtime rc=%d sec=%lld nsec=%ld\n", rc,
|
|
(long long)st.st_mtime, (long)st.st_mtim.tv_nsec);
|
|
} else if (strcmp(mode, "size") == 0) { printf("PROBE idx_bytes %lld\n", (long long)index_bytes(root));
|
|
} else {
|
|
fprintf(stderr, "未知 mode: %s\n", mode);
|
|
return 2;
|
|
}
|
|
return 0;
|
|
}
|