Files
MeowOS-For-R3P2/patch_v2/ncm_app/bench/meta_ipc_sim.c
T
AskaEth 294ed6ffc7 test(ncm): M10 宿主回归三套(44 断言)+ 构建脚本 + REPORT §42
- bench/retry_storm_sim.c(25 断言):真实 retry_ctl.c/meta_gate.c,100ms tick 断网
  120s→4 次/间隔 ≥30s;状态每 tick 抖动→2 次;时钟回退→不立即重试且间隔仍 ≥30s;
  worker 45s>间隔→不并发;META 空闲 3 帧/60s(旧 600)、播放 13 帧(≤1/5s)、
  状态抖动 ≤4 帧/秒、元数据变化/seek 立即报、超长标题截断不重报、回退不连发。
- bench/home_retry_sim.c(13 断言):真实 app/ui/home.c 的 home_tick 编进宿主
  (--gc-sections + 桩),断网 120s→4 次重试、单飞、全帧重绘 0 次;
  把真机形态的垃圾 64 位 now 喂给 home_tick 仍为 4 次/≥30s;回在线再断网不清基准。
- bench/meta_ipc_sim.c(6 断言):真实 ipc_media 线程 + 假 launcher AF_UNIX socket,
  空闲 2s 只收到 1 帧 META(旧 ~10 帧),状态翻转恰好多 1 帧,帧头合法。
- build_ncm.sh / build_ncm_host.sh:源列表加 retry_ctl.c、meta_gate.c(host 补
  mem_watch.c,否则 player_manager 链接失败);build_ncm_sims.sh 加三个 sim。
- REPORT §42:真机日志证据(时间戳低/高 32 位拆解、21016 帧 META)、根因三链、
  实现、验证(session_gate 88 / http_truncate 13 / core 139 全过)、真机验证建议。
2026-11-05 13:00:32 +08:00

236 lines
9.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/meta_ipc_sim.c — M10:真实 app/play/ipc_media.c 的 META 上报频率回归(假 launcher socket)。
*
* 真机证据:launcher meow_20261105_122541.log 单次启动 21016 条
* `ipc: META app=dev.player.ncm state=0 pos=0/0 rc=0 -> ACK`,间隔 ~100ms。
* 旧 ipc_media 用 `now(meow_ui_time_ms) - last_meta >= 1000` 做 1s 心跳;真机该值高 32 位是垃圾
* (REPORT §42)→ 条件恒真 → 每个 IPC 轮次(200ms poll)都发一帧。
*
* 本测试把真实 ipc_media.c 编进宿主(--gc-sections 只保留 ipc_media_start 调用链),
* 在临时目录起一个 AF_UNIX “假 launcher”,统计 App 真正发出的 MSG_META 帧数:
* A. 空闲(无播放、无元数据)2s → 只应有 1 帧(注册媒体源),旧实现为 ~10 帧;
* B. 播放态翻转一次 → 只多 1 帧(状态变化立即上报),之后持续播放 2s 不再刷屏;
* C. 帧格式合法(magic/version/app_id),且没有 MSG_META 以外的上行帧。
*
* 编译(ncm_app 目录下):
* gcc -O2 -Wall -Wextra -ffunction-sections -fdata-sections -DMEOW_UI_HOST_TEST -DJSMN_HEADER \
* -I. -I../sdk -I../sdk/include -I../meow_launcherd -Icore -Ivendor/jsmn \
* -c app/play/ipc_media.c -o out/chk/ipc_m10.o
* gcc -O2 -Wall -Wextra -ffunction-sections -fdata-sections -DMEOW_UI_HOST_TEST -DJSMN_HEADER \
* -I. -I../sdk -I../sdk/include -I../meow_launcherd -Icore -Ivendor/jsmn \
* -c app/play/meta_gate.c -o out/chk/meta_gate_m10.o
* gcc -O2 -Wall -Wextra -ffunction-sections -fdata-sections -DMEOW_UI_HOST_TEST -DJSMN_HEADER \
* -I. -I../sdk -I../sdk/include -I../meow_launcherd -Icore -Ivendor/jsmn \
* -c bench/meta_ipc_sim.c -o out/chk/meta_ipc_sim.o
* gcc -Wl,--gc-sections -o meta_ipc_sim out/chk/ipc_m10.o out/chk/meta_gate_m10.o \
* out/chk/meta_ipc_sim.o -lpthread
* ./meta_ipc_sim
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <time.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include "../app/ncm_app_internal.h"
#include "meow_ipc.h"
/* ---- app.c 全局 ---- */
ncm_app_state_t g_st;
ncm_play_run_t g_play = { .mu = PTHREAD_MUTEX_INITIALIZER,
.state = NCM_PC_STOPPED, .cur_song = -1 };
volatile int g_app_quit = 0;
volatile int g_net_ready = 0;
int g_host_mock = 0;
static int g_pass, g_fail;
#define CHECK(cond, ...) do { \
int _ok = (cond) ? 1 : 0; \
printf(" %s ", _ok ? "PASS" : "FAIL"); printf(__VA_ARGS__); printf("\n"); \
if (_ok) g_pass++; else g_fail++; \
} while (0)
/* ---- 播放器桩(本测试只关心「有没有播放/元数据变化」) ---- */
static volatile int g_pm_state = NCM_PC_STOPPED;
int player_manager_state(void) { return g_pm_state; }
int player_manager_playing_song(void) { return -1; }
int player_manager_current_song(ncm_song_t *out) { (void)out; return -1; }
void player_manager_pause(int on) { g_pm_state = on ? NCM_PC_PAUSED : NCM_PC_PLAYING; }
void player_manager_next(int dir) { (void)dir; }
void player_manager_seek(int64_t ms) { (void)ms; }
const char *cover_current_path(void) { return ""; }
int lyric_count(void) { return 0; }
/* ---- 日志/时间桩 ---- */
static volatile int g_meta_logs;
void meow_log(meow_log_level lvl, const char *fmt, ...)
{
(void)fmt;
if (lvl == MEOW_LOG_DEBUG) g_meta_logs++;
}
int64_t ncm_monotonic_ms(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
size_t ncm_strlcpy(char *dst, const char *src, size_t cap)
{
size_t n = 0;
if (!dst || cap == 0) return src ? strlen(src) : 0;
if (src) { for (; src[n] && n + 1 < cap; n++) dst[n] = src[n]; }
dst[n] = 0;
return n;
}
/* ============================================================
* 假 launcher:AF_UNIX 服务端 + 帧计数
* ============================================================ */
static int g_srv = -1, g_cli = -1;
static char g_sock_path[256];
static int g_bad_frames;
static char g_last_app_id[64];
static void set_play_state(int st)
{
pthread_mutex_lock(&g_play.mu);
g_play.state = st; /* META 的 state 取自 g_play(与真实播放线程同源) */
g_play.err = 0;
pthread_mutex_unlock(&g_play.mu);
}
static void put_hdr(uint8_t *h, uint16_t type, uint32_t req, uint32_t len)
{
uint32_t magic = MEOW_IPC_MAGIC;
uint16_t ver = MEOW_IPC_VERSION;
memcpy(h + 0, &magic, 4); memcpy(h + 4, &ver, 2);
memcpy(h + 6, &type, 2); memcpy(h + 8, &req, 4);
memcpy(h + 12, &len, 4);
}
static int srv_open(void)
{
snprintf(g_sock_path, sizeof g_sock_path, "%s/m10_ipc.sock",
getenv("TMPDIR") && getenv("TMPDIR")[0] ? getenv("TMPDIR") : "/tmp");
unlink(g_sock_path);
g_srv = socket(AF_UNIX, SOCK_STREAM, 0);
if (g_srv < 0) return -1;
struct sockaddr_un a;
memset(&a, 0, sizeof a);
a.sun_family = AF_UNIX;
snprintf(a.sun_path, sizeof a.sun_path, "%s", g_sock_path);
if (bind(g_srv, (struct sockaddr *)&a, sizeof a) != 0) return -1;
if (listen(g_srv, 1) != 0) return -1;
setenv("MEOW_IPC_SOCK", g_sock_path, 1);
return 0;
}
/* 收一帧(带超时)。返回帧长(负载),*type 出帧类型;-1=超时/断开。 */
static int srv_read(int timeout_ms, uint16_t *type, uint8_t *buf, int cap)
{
struct pollfd p = { .fd = g_cli, .events = POLLIN };
if (poll(&p, 1, timeout_ms) <= 0) return -1;
uint8_t hdr[MEOW_IPC_HDR_LEN];
size_t off = 0;
while (off < sizeof hdr) {
ssize_t r = read(g_cli, hdr + off, sizeof hdr - off);
if (r <= 0) return -1;
off += (size_t)r;
}
uint32_t magic, len; uint16_t ver;
memcpy(&magic, hdr, 4); memcpy(&ver, hdr + 4, 2);
memcpy(type, hdr + 6, 2); memcpy(&len, hdr + 12, 4);
if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION || len > (uint32_t)cap) {
g_bad_frames++;
return -1;
}
off = 0;
while (off < len) {
ssize_t r = read(g_cli, buf + off, len - off);
if (r <= 0) return -1;
off += (size_t)r;
}
return (int)len;
}
/* 收 HELLO 并回一帧 ACK(ipc_thread 等这一帧才进入主循环)。 */
static int srv_handshake(void)
{
struct pollfd p = { .fd = g_srv, .events = POLLIN };
if (poll(&p, 1, 2000) <= 0) return -1;
g_cli = accept(g_srv, NULL, NULL);
if (g_cli < 0) return -1;
uint8_t buf[MEOW_IPC_MAX]; uint16_t type = 0;
if (srv_read(2000, &type, buf, sizeof buf) < 0) return -1;
if (type != MSG_HELLO) return -1;
m_hello_t hh;
memset(&hh, 0, sizeof hh);
memcpy(&hh, buf, sizeof hh < sizeof buf ? sizeof hh : sizeof buf);
snprintf(g_last_app_id, sizeof g_last_app_id, "%s", hh.app_id);
uint8_t out[MEOW_IPC_HDR_LEN + sizeof(m_ack_t)];
m_ack_t ack; memset(&ack, 0, sizeof ack); ack.code = MEOW_OK;
put_hdr(out, MSG_ACK, 1, (uint32_t)sizeof ack);
memcpy(out + MEOW_IPC_HDR_LEN, &ack, sizeof ack);
return (write(g_cli, out, sizeof out) == (ssize_t)sizeof out) ? 0 : -1;
}
/* 收 run_ms 内的全部上行帧,按类型计数。 */
static void srv_collect(int run_ms, int *meta, int *other)
{
int64_t end = ncm_monotonic_ms() + run_ms;
uint8_t buf[MEOW_IPC_MAX];
*meta = *other = 0;
while (ncm_monotonic_ms() < end) {
uint16_t type = 0;
int n = srv_read(50, &type, buf, sizeof buf);
if (n < 0) continue;
if (type == MSG_META) (*meta)++;
else (*other)++;
}
}
int main(void)
{
printf("meta_ipc_sim — M10 真实 ipc_media 线程 META 帧频回归(REPORT §42)\n\n");
if (srv_open() != 0) { printf(" FAIL 假 launcher socket 建立失败 (%s)\n", g_sock_path); return 1; }
if (ipc_media_start() != 0) { printf(" FAIL ipc_media_start\n"); return 1; }
if (srv_handshake() != 0) { printf(" FAIL HELLO 握手失败\n"); return 1; }
printf("A. 空闲(state=0,无播放/无元数据)2s\n");
int meta = 0, other = 0;
srv_collect(2000, &meta, &other);
CHECK(meta == 1, "上行 META 帧 = %d(期望 1:注册媒体源;旧实现 ~10 帧/2s)", meta);
CHECK(other == 0, "无其它上行帧(%d)", other);
CHECK(strcmp(g_last_app_id, "dev.player.ncm") == 0, "HELLO app_id = %s", g_last_app_id);
printf("B. 播放态变化(0 → PLAYING)后继续空闲 2s\n");
set_play_state(NCM_PC_PLAYING);
int meta2 = 0, other2 = 0;
srv_collect(2000, &meta2, &other2);
CHECK(meta2 == 1, "状态变化只上报 1 帧(%d)", meta2);
/* 停止播放:又是一次状态变化 → 再 1 帧 */
set_play_state(NCM_PC_STOPPED);
int meta3 = 0, other3 = 0;
srv_collect(2000, &meta3, &other3);
CHECK(meta3 == 1, "停止播放只上报 1 帧(%d)", meta3);
CHECK(g_bad_frames == 0, "帧头 magic/version/长度全部合法(坏帧 %d)", g_bad_frames);
ipc_media_stop();
g_app_quit = 1;
usleep(400 * 1000); /* 让 ipc 线程在 poll 超时内退出 */
if (g_cli >= 0) close(g_cli);
if (g_srv >= 0) close(g_srv);
unlink(g_sock_path);
printf("\nmeta_ipc_sim: %d-%d\n", g_pass, g_fail);
return g_fail ? 1 : 0;
}