Files
MeowOS-SDK/sdk/meow_cachegov.c
T

303 lines
12 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.
/* sdk/meow_cachegov.c — 磁盘缓存 page-cache 治理 SDK 库(A:DONTNEED 主导侧)。
*
* 权威方案:devdocs/plan/OOM_PAGECACHE_GOVERNANCE.md。归属(ARCHITECTURE_PRINCIPLES ③混合):
* A 热路径(register/tick/drop_finished/refault)随 SDK 库;C(/proc/meminfo Cached 监控)
* 归 meow_svc_perf,本库只读其共享状态文件 MEOW_CACHEGOV_C_FILE 作为降载信号。
*
* 红线(务必保留本注释):**L2 禁止**——绝不丢 `[read_pos, ...)` 任何未解码区(含 read_pos 前方
* 刚写入尚未解码的页)。本库所有 drop 的上界只能是 read_pos-keep_hot(L1)或整文件曲终(L0)。
*
* 分层:只依赖 pthread + meow_log + POSIX(posix_fadvise),不依赖 core/App 其它 TU。
* 自含:refault 滚窗 + 三级回退(放大 keep_hot → 降 L0 → 关 A)。
*/
#include "include/meow_cachegov.h"
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
/* meow_ui.h:SDK 公共伞头(含 meow_log/meow_ui_log.h + 公共类型)。 */
#include "meow_ui.h"
/* 共享协议头:C 侧状态文件路径(meow_svc_perf 写入 / 各 App 读取,同源防漂移)。 */
#include "meow_ipc.h"
#define MEOW_CACHEGOV_MAXREG 8 /* 同时治理的缓存文件上限(ncm 单点播放,远小于此) */
#define MEOW_CACHEGOV_REFAULTRING 64 /* refault 滚窗环形缓冲 */
#define MEOW_CACHEGOV_WINDOW_MS 10000 /* refault 滚窗 10s */
#define MEOW_CACHEGOV_HOTESCALATE 1 /* 回退档>=1 时 keep_hot 放大倍数(翻倍) */
#define MEOW_CACHEGOV_DROP_THROTTLE (64u * 1024u) /* L1 每次至少推进 64KB 才再 drop(省 open/fadvise) */
typedef struct cg_entry {
char path[512];
uint64_t size; /* 标称大小(流式=nominal;命中=实际) */
uint64_t (*read_pos_fn)(void *ctx);
void *ctx;
uint64_t dropped_to; /* 已 DONTNEED 的已读前缀上界(只推进,不回退) */
int used;
} cg_entry_t;
static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER;
static struct meow_cachegov_cfg g_cfg = {
.enabled = 1,
.cached_threshold_kb = 0, /* C 归属 svc_perf,本库只读 */
.dirty_warn_kb = 0, /* 同上 */
.keep_hot_bytes = (256u * 1024u), /* 保 256KB 解码窗口供倒带 */
.min_hot_ahead = (0), /* L2 红线防御值;绝不参与 drop */
.drop_mode = 0, /* 默认仅曲终(L0);L1 需显式开(带 seek 回退) */
.refault_backoff = 3, /* 10s 内 3 次 refault → 回退 */
};
static cg_entry_t g_reg[MEOW_CACHEGOV_MAXREG];
static int g_backoff = 0; /* 0=正常 1=keep_hot放大 2=降L0 3=A关 */
static int64_t g_last_step_ms = -1000000000000LL; /* 上次升/降档时刻(初始很远 → 首次可动作) */
static int64_t g_refault_ring[MEOW_CACHEGOV_REFAULTRING];
static int g_refault_head = 0;
static int g_refault_n = 0;
/* ---- 时钟(宿主测试桩可注入) ---- */
static int64_t g_test_now_ms = -1;
static int64_t now_ms(void)
{
if (g_test_now_ms >= 0) return g_test_now_ms;
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
void meow_cachegov_test_set_time_ms(int64_t t) { g_test_now_ms = t; }
/* ---- C 侧状态文件(svc_perf 写 / 本库读) ---- */
static const char *c_file(void)
{
const char *e = getenv("MEOW_CACHEGOV_C_FILE");
return (e && e[0]) ? e : MEOW_CACHEGOV_C_FILE;
}
/* C 状态(svc_perf 写)最近一次解析缓存:下载 cb 每 4KB 块会查一次,避免每块都读文件(200ms TTL)。 */
static int g_cached_c_dl = 0, g_cached_c_pf = 0;
static int64_t g_cached_c_at = 0;
/* 读 C 状态文件(缺省 0=不降载)。解析失败/文件不存在 → 全部 0。 */
static void read_c_state(int *dl_pause, int *pf_pause, long *cached_kb, long *dirty_kb)
{
if (dl_pause) *dl_pause = 0;
if (pf_pause) *pf_pause = 0;
if (cached_kb) *cached_kb = 0;
if (dirty_kb) *dirty_kb = 0;
FILE *f = fopen(c_file(), "r");
if (!f) return;
char line[160];
while (fgets(line, sizeof line, f)) {
if (!strncmp(line, "cached_kb=", 10)) { if (cached_kb) *cached_kb = atol(line + 10); }
else if (!strncmp(line, "dirty_kb=", 9)) { if (dirty_kb) *dirty_kb = atol(line + 9); }
else if (!strncmp(line, "dl_pause=", 9)) { if (dl_pause) *dl_pause = atol(line + 9) ? 1 : 0; }
else if (!strncmp(line, "prefetch_pause=", 15)) { if (pf_pause) *pf_pause = atol(line + 15) ? 1 : 0; }
}
fclose(f);
}
/* ---- refault 滚窗 ---- */
void meow_cachegov_note_refault(void)
{
int64_t t = now_ms();
pthread_mutex_lock(&g_mu);
g_refault_ring[g_refault_head] = t;
g_refault_head = (g_refault_head + 1) % MEOW_CACHEGOV_REFAULTRING;
if (g_refault_n < MEOW_CACHEGOV_REFAULTRING) g_refault_n++;
pthread_mutex_unlock(&g_mu);
}
static int refaults_last_window(void)
{
int64_t now = now_ms();
int cnt = 0, start = (g_refault_head - g_refault_n + MEOW_CACHEGOV_REFAULTRING) %
MEOW_CACHEGOV_REFAULTRING;
for (int i = 0; i < g_refault_n; i++) {
int64_t t = g_refault_ring[(start + i) % MEOW_CACHEGOV_REFAULTRING];
if (t >= now - MEOW_CACHEGOV_WINDOW_MS) cnt++;
}
return cnt;
}
/* 曲终/cur_finished 后:整文件 L0 DONTNEED。 */
int meow_cachegov_drop_finished(const char *path)
{
if (!path) return -1;
pthread_mutex_lock(&g_mu);
for (int i = 0; i < MEOW_CACHEGOV_MAXREG; i++) {
if (g_reg[i].used && strcmp(g_reg[i].path, path) == 0) {
g_reg[i].used = 0;
break;
}
}
int lbf = g_backoff, en = g_cfg.enabled;
pthread_mutex_unlock(&g_mu);
if (!en || lbf >= 3) { /* A 已整体关闭:连曲终 L0 也不丢(§2.3 步骤 3) */
meow_log(MEOW_LOG_INFO, "[cachegov] drop_finished skip %s (A off, backoff=%d)", path, lbf);
return 0;
}
int fd = open(path, O_RDONLY);
if (fd < 0) return -1;
/* len=0 → 到 EOF(整文件)。曲终播放器已不再读该文件,重播=冷缓存首读(等价未命中)。 */
posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
close(fd);
meow_log(MEOW_LOG_INFO, "[cachegov] drop_finished L0 %s", path);
return 0;
}
/* L1:丢已读前缀 [dropped_to, read_pos-keep_hot)。返回 1=有丢。 */
static int entry_drop_l1(cg_entry_t *e, size_t keep)
{
if (!e->read_pos_fn) return 0;
uint64_t pos = e->read_pos_fn(e->ctx);
if (pos <= keep) return 0; /* keep_hot 覆盖整个已读前缀,无需丢 */
uint64_t drop_end = pos - (uint64_t)keep; /* L1 上界:read_pos 前方绝不丢(L2 红线) */
if (drop_end <= e->dropped_to) return 0; /* 无推进(seek 回退 / 前进不足) */
if (drop_end - e->dropped_to < MEOW_CACHEGOV_DROP_THROTTLE && e->dropped_to != 0)
return 0; /* 节流:不足 64KB 不重复 open */
int fd = open(e->path, O_RDONLY);
if (fd < 0) return 0;
off_t off = (off_t)e->dropped_to;
off_t len = (off_t)(drop_end - e->dropped_to);
ssize_t r = posix_fadvise(fd, off, len, POSIX_FADV_DONTNEED);
close(fd);
if (r != 0) return 0;
e->dropped_to = drop_end;
meow_log(MEOW_LOG_INFO, "[cachegov] L1 drop %s off=%lld len=%lld (pos=%lld keep=%zu)",
e->path, (long long)off, (long long)len, (long long)pos, keep);
return 1;
}
int meow_cachegov_tick(void)
{
pthread_mutex_lock(&g_mu);
int64_t now = now_ms();
/* ① 回退评估(滚窗 10s):refault 超门限 → 升一档;滚窗干净 ≥10s → 降一档。
* 每升降一档需间隔 ≥10s(逐级回退,不因窗内持续 refault 一次跳过多档,防误回退)。 */
int cnt = refaults_last_window();
if (cnt >= g_cfg.refault_backoff && g_cfg.refault_backoff > 0 && g_backoff < 3 &&
(now - g_last_step_ms) >= MEOW_CACHEGOV_WINDOW_MS) {
g_backoff = g_backoff + 1;
g_last_step_ms = now;
meow_log(MEOW_LOG_INFO, "[cachegov] backoff -> %d (refault=%d/%lds)",
g_backoff, cnt, MEOW_CACHEGOV_WINDOW_MS / 1000);
} else if (cnt == 0 && g_backoff > 0 &&
(now - g_last_step_ms) >= MEOW_CACHEGOV_WINDOW_MS) {
g_backoff = g_backoff - 1;
g_last_step_ms = now;
meow_log(MEOW_LOG_INFO, "[cachegov] backoff -> %d (clean window)", g_backoff);
}
size_t keep = g_cfg.keep_hot_bytes;
if (g_backoff >= MEOW_CACHEGOV_HOTESCALATE) keep = g_cfg.keep_hot_bytes * 2; /* 档1:放大 keep_hot */
int l1_on = g_cfg.enabled && g_cfg.drop_mode >= 1 && g_backoff < 2; /* 档2:L1 关 */
int en = g_cfg.enabled;
/* ② L1 丢弃 */
int dropped = 0;
if (en && l1_on) {
for (int i = 0; i < MEOW_CACHEGOV_MAXREG; i++)
if (g_reg[i].used) dropped += entry_drop_l1(&g_reg[i], keep);
}
pthread_mutex_unlock(&g_mu);
return dropped;
}
/* ---- 注册 / 注销 ---- */
int meow_cachegov_register(const char *path, uint64_t size,
uint64_t (*read_pos_fn)(void *ctx), void *ctx)
{
if (!path) return -1;
pthread_mutex_lock(&g_mu);
int slot = -1;
for (int i = 0; i < MEOW_CACHEGOV_MAXREG; i++) {
if (g_reg[i].used && strcmp(g_reg[i].path, path) == 0) { slot = i; break; }
if (!g_reg[i].used && slot < 0) slot = i;
}
if (slot < 0) { pthread_mutex_unlock(&g_mu); return -1; }
snprintf(g_reg[slot].path, sizeof g_reg[slot].path, "%s", path);
g_reg[slot].size = size;
g_reg[slot].read_pos_fn = read_pos_fn;
g_reg[slot].ctx = ctx;
g_reg[slot].dropped_to = 0;
g_reg[slot].used = 1;
pthread_mutex_unlock(&g_mu);
meow_log(MEOW_LOG_INFO, "[cachegov] register %s size=%llu", path, (unsigned long long)size);
return 0;
}
int meow_cachegov_unregister(const char *path)
{
if (!path) return -1;
pthread_mutex_lock(&g_mu);
int rc = -1;
for (int i = 0; i < MEOW_CACHEGOV_MAXREG; i++)
if (g_reg[i].used && strcmp(g_reg[i].path, path) == 0) { g_reg[i].used = 0; rc = 0; break; }
pthread_mutex_unlock(&g_mu);
return rc;
}
/* ---- 降载查询(A 回退 OR svc_perf C) ---- */
static void c_state(int *dl_pause, int *pf_pause)
{
int64_t now = now_ms();
pthread_mutex_lock(&g_mu);
if (g_cached_c_at == 0 || (now - g_cached_c_at) > 200) {
read_c_state(&g_cached_c_dl, &g_cached_c_pf, NULL, NULL);
g_cached_c_at = now;
}
if (dl_pause) *dl_pause = g_cached_c_dl;
if (pf_pause) *pf_pause = g_cached_c_pf;
pthread_mutex_unlock(&g_mu);
}
int meow_cachegov_download_paused(void)
{
pthread_mutex_lock(&g_mu);
int a = (g_backoff >= 2);
pthread_mutex_unlock(&g_mu);
int c = 0;
c_state(&c, NULL);
return (a || c) ? 1 : 0;
}
int meow_cachegov_prefetch_paused(void)
{
pthread_mutex_lock(&g_mu);
int a = (g_backoff >= 3);
pthread_mutex_unlock(&g_mu);
int c = 0;
c_state(NULL, &c);
return (a || c) ? 1 : 0;
}
/* ---- 配置 ---- */
int meow_cachegov_set_cfg(const struct meow_cachegov_cfg *cfg)
{
if (!cfg) return -1;
pthread_mutex_lock(&g_mu);
g_cfg = *cfg;
pthread_mutex_unlock(&g_mu);
return 0;
}
int meow_cachegov_get_cfg(struct meow_cachegov_cfg *cfg)
{
if (!cfg) return -1;
pthread_mutex_lock(&g_mu);
*cfg = g_cfg;
pthread_mutex_unlock(&g_mu);
return 0;
}
int meow_cachegov_backoff_level(void)
{
pthread_mutex_lock(&g_mu);
int v = g_backoff;
pthread_mutex_unlock(&g_mu);
return v;
}