feat(watchdog): M52 资源压力可观测 + multi_font 日志去刷屏 + audiod bind 失败即退
- watchdog(新 wd_resguard.h + meow_watchdog.c):每 10s 采 /proc/meminfo (MemFree/MemAvailable)、进程总数、单父进程子进程数前 5 名;过阈值时向 MeowOS/logs 打 RESGUARD 告警,带等级/命中规则/进程名+PID 名单。零 fork、零堆分配、 不发任何信号(只报警)。阈值依据 = 真机实测 56872kB / 空闲 100 进程 / maxch=11, 推导写在 wd_resguard.h 文件头;日志只在等级跃迁或同态每 5 分钟复述。 - launcherd:multi_font 那行「每 2s 无条件打一条」改为只在配置签名跃迁时打 (新 mf_logsig.h 纯函数 + test_mf_logsig 宿主单测,接进 build_launcher.sh test)。 真机 6603 行日志里该行占 5297 行(80%)。 - audiod:io bind 失败不再只记一行就 return(旧行为让主线程 watchdog 空转 720s、 客户端全 ECONNREFUSED),改为明确报错 + g_stop 干净退出;仅动 io_thread 一个 hunk。
This commit is contained in:
1 parent
f3d7b386b2
commit
166b32d0e5
8 files changed
+755
-4
No files matched your search
@@ -65,6 +65,7 @@
|
||||
#include "meow_ipc.h" /* MSG_AUDIO_VOLUME/MSG_ACK/帧格式(音量收编 v2:首选 audiod 权威) */
|
||||
#include "../sdk/meow_time32.h" /* o32 time32 取时/睡眠(watchdog 不链 meow_ui.a) */
|
||||
#include "wd_power.h" /* 电源阈值/边沿纯判定(宿主单测同源) */
|
||||
#include "wd_resguard.h" /* M52 资源压力阈值/判定(纯函数,宿主单测同源) */
|
||||
|
||||
/* device-verified: VSCREENINFO=0x4600, FSCREENINFO=0x4602 (do not swap) */
|
||||
#define FBIOGET_VSCREENINFO 0x4600
|
||||
@@ -171,12 +172,14 @@ static void wd_log_init(void)
|
||||
|
||||
static void wd_log(const char *fmt, ...)
|
||||
{
|
||||
char buf[256];
|
||||
/* M52:缓冲从 256/288 提到 512/544 —— 资源告警行要带 top5 父进程与子进程名单
|
||||
* (名字+PID),一条 200~280 字符;旧 256 会把名单截断,而截断的告警等于没告警。 */
|
||||
char buf[512];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, sizeof buf, fmt, ap);
|
||||
va_end(ap);
|
||||
char line[288];
|
||||
char line[544];
|
||||
int l = snprintf(line, sizeof line, "%llu [watchdog] %s\n", now_ms(), buf);
|
||||
if (wd_log_fd >= 0) {
|
||||
if (write(wd_log_fd, line, l) < 0) wd_log_fd = -1;
|
||||
@@ -847,6 +850,157 @@ static int wd_framework_from_pid(int pid)
|
||||
return wd_app_is_framework(exe);
|
||||
}
|
||||
|
||||
/* ================================================================== */
|
||||
/* M52 设备资源压力可观测与自保(只告警,绝不 kill) */
|
||||
/* 事故:2026-11-11 18:44 用户态崩塌。launcherd 最后一行日志 18:44:13, */
|
||||
/* 之后 11 分钟连 sshd 都写不出 "Child connection from"(fork 都做不到),*/
|
||||
/* 而 ICMP 仍应答;当晚 M50 清掉 55 个挂在「耳机不在」上的 bluetoothctl */
|
||||
/* 残留进程。无 pstore、dmesg 重启即清、crash_*.log 全 0 字节 → OOM 无法 */
|
||||
/* 事后证实。本段:每 10s 采 /proc/meminfo + 进程总数 + 单父进程子进程 */
|
||||
/* 数前 5 名;过阈值(依据见 wd_resguard.h 文件头)时把「带进程名与 PID */
|
||||
/* 的现场」写进 MeowOS/logs(即 wd_log 的同一文件)。 */
|
||||
/* 硬约束:不发任何信号(App 治理属既有逻辑,dropbear/launcherd 绝不可 */
|
||||
/* 碰);零 fork 零 popen(内存见底时 fork 正是第一个失败的动作);零堆 */
|
||||
/* 分配(malloc 在 MemAvailable 见底时可能失败或直接喂 OOM killer)。 */
|
||||
/* ================================================================== */
|
||||
|
||||
/* getdents64 的最小结构 —— 不用 <dirent.h> 的 readdir:那要 malloc 一个 DIR 缓冲。 */
|
||||
struct wd_dent64 {
|
||||
uint64_t d_ino;
|
||||
int64_t d_off;
|
||||
unsigned short d_reclen;
|
||||
unsigned char d_type;
|
||||
char d_name[];
|
||||
};
|
||||
|
||||
static int wd_rg_pid[WD_RG_MAX_PIDS];
|
||||
static int wd_rg_ppid[WD_RG_MAX_PIDS];
|
||||
static char wd_rg_comm[WD_RG_MAX_PIDS][WD_RG_COMM_LEN];
|
||||
|
||||
/* 读一个 /proc 伪文件(一次 read 足够)。返回字节数;-1=打开/读失败(errno 保留)。 */
|
||||
static int wd_rg_read(const char *path, char *buf, int len)
|
||||
{
|
||||
int fd = open(path, O_RDONLY | O_NONBLOCK);
|
||||
if (fd < 0) return -1;
|
||||
int n = (int)read(fd, buf, (size_t)len);
|
||||
close(fd);
|
||||
return n;
|
||||
}
|
||||
|
||||
/* 扫 /proc 顶层,填 pid/ppid/comm。返回进程数(≤max);-1=打不开 /proc。 */
|
||||
static int wd_rg_scan(int max)
|
||||
{
|
||||
int fd = open("/proc", O_RDONLY | O_DIRECTORY);
|
||||
if (fd < 0) return -1;
|
||||
/* 8 字节对齐的静态缓冲:dirent64 含 uint64/int64 字段,char 数组直接强转在严格
|
||||
* 对齐的 ABI 上是 UB。 */
|
||||
static uint64_t dbuf[4096 / sizeof(uint64_t)];
|
||||
char *base = (char *)(void *)dbuf;
|
||||
int n = 0;
|
||||
for (;;) {
|
||||
long r = (long)syscall(SYS_getdents64, fd, base, sizeof dbuf);
|
||||
if (r <= 0) break;
|
||||
for (long off = 0; off < r; ) {
|
||||
struct wd_dent64 *d = (struct wd_dent64 *)(void *)(base + off);
|
||||
if (d->d_reclen < sizeof(struct wd_dent64)) break;
|
||||
const char *nm = d->d_name;
|
||||
if (n < max && nm[0] >= '0' && nm[0] <= '9') {
|
||||
char path[48], sbuf[256];
|
||||
snprintf(path, sizeof path, "/proc/%s/stat", nm);
|
||||
int k = wd_rg_read(path, sbuf, (int)sizeof sbuf - 1);
|
||||
if (k > 0) {
|
||||
int pid = 0, ppid = 0;
|
||||
sbuf[k] = 0;
|
||||
if (wd_rg_parse_stat(sbuf, &pid, wd_rg_comm[n], WD_RG_COMM_LEN, &ppid) == 0) {
|
||||
wd_rg_pid[n] = pid;
|
||||
wd_rg_ppid[n] = ppid;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
}
|
||||
off += d->d_reclen;
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
return n;
|
||||
}
|
||||
|
||||
/* 已扫描表里 pid 的 comm(找不到 → "?")。 */
|
||||
static const char *wd_rg_name_of(int npids, int pid)
|
||||
{
|
||||
for (int i = 0; i < npids; i++) if (wd_rg_pid[i] == pid) return wd_rg_comm[i];
|
||||
return "?";
|
||||
}
|
||||
|
||||
/* 一次采样 + 判定 +(必要时)告警。日志只在等级跃迁(含首次)/同等级每 5 分钟。 */
|
||||
static void wd_rg_tick(void)
|
||||
{
|
||||
static int last_level = WD_RG_OK;
|
||||
static int first = 1;
|
||||
static unsigned long long last_emit_ms = 0;
|
||||
static char mbuf[768];
|
||||
|
||||
wd_rg_snap s;
|
||||
memset(&s, 0, sizeof s);
|
||||
int mn = wd_rg_read("/proc/meminfo", mbuf, (int)sizeof mbuf - 1);
|
||||
if (mn <= 0) {
|
||||
wd_log("RESGUARD: /proc/meminfo unreadable (%s), skip this round", strerror(errno));
|
||||
return;
|
||||
}
|
||||
mbuf[mn] = 0;
|
||||
if (wd_rg_parse_meminfo(mbuf, &s.total_kb, &s.free_kb, &s.avail_kb) != 0) {
|
||||
wd_log("RESGUARD: /proc/meminfo parse failed (no MemTotal/MemFree), skip this round");
|
||||
return;
|
||||
}
|
||||
int np = wd_rg_scan(WD_RG_MAX_PIDS);
|
||||
if (np < 0) {
|
||||
wd_log("RESGUARD: /proc unreadable (%s), skip this round", strerror(errno));
|
||||
return;
|
||||
}
|
||||
s.procs = np;
|
||||
int tp[WD_RG_TOP_PARENTS], tc[WD_RG_TOP_PARENTS];
|
||||
int ntop = wd_rg_top_parents(wd_rg_ppid, np, WD_RG_TOP_PARENTS, tp, tc);
|
||||
if (ntop > 0) { s.maxch = tc[0]; s.maxch_ppid = tp[0]; }
|
||||
|
||||
int lvl = wd_rg_level(&s);
|
||||
unsigned long long now = now_ms();
|
||||
if (!wd_rg_should_emit(last_level, lvl, now - last_emit_ms, first)) return;
|
||||
|
||||
char why[128];
|
||||
wd_rg_reason(&s, why, sizeof why);
|
||||
wd_log("RESGUARD %s mem avail=%llukB free=%llukB total=%llukB procs=%d maxch=%d(ppid=%d/%s)%s%s",
|
||||
wd_rg_level_name(lvl), s.avail_kb, s.free_kb, s.total_kb, s.procs,
|
||||
s.maxch, s.maxch_ppid, wd_rg_name_of(np, s.maxch_ppid),
|
||||
why[0] ? " why=" : "", why);
|
||||
/* top5:谁在批量拉子进程(事故形态 = 55 个 bluetoothctl 孤儿全挂 ppid=1) */
|
||||
if (ntop > 0) {
|
||||
char tl[384];
|
||||
int off = 0;
|
||||
tl[0] = 0;
|
||||
for (int i = 0; i < ntop && off + 1 < (int)sizeof tl; i++)
|
||||
off += snprintf(tl + off, sizeof tl - (size_t)off, " %d/%s=%d",
|
||||
tp[i], wd_rg_name_of(np, tp[i]), tc[i]);
|
||||
wd_log("RESGUARD top%d parents (ppid/comm=children):%s", ntop, tl);
|
||||
}
|
||||
/* 名单:仅过子进程阈值时列,避免每次跃迁都多一行 */
|
||||
if (s.maxch >= WD_RG_CHILD_WARN) {
|
||||
char cl[512];
|
||||
int off = 0, shown = 0;
|
||||
cl[0] = 0;
|
||||
for (int i = 0; i < np && shown < WD_RG_CHILD_LIST && off + 1 < (int)sizeof cl; i++) {
|
||||
if (wd_rg_ppid[i] != s.maxch_ppid) continue;
|
||||
off += snprintf(cl + off, sizeof cl - (size_t)off, " %s/%d", wd_rg_comm[i], wd_rg_pid[i]);
|
||||
shown++;
|
||||
}
|
||||
wd_log("RESGUARD children of %d/%s (n=%d):%s%s", s.maxch_ppid,
|
||||
wd_rg_name_of(np, s.maxch_ppid), s.maxch, cl,
|
||||
shown < s.maxch ? " (+more)" : "");
|
||||
}
|
||||
last_level = lvl;
|
||||
last_emit_ms = now;
|
||||
first = 0;
|
||||
}
|
||||
|
||||
#ifndef WD_NO_MAIN
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
@@ -914,6 +1068,9 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
long elapsed = 0;
|
||||
/* M52:资源采样用真单调时钟排程,不用 elapsed —— elapsed 是「每轮 +100ms」的假时钟,
|
||||
* 触摸事件密集时 poll 早返回会让它跑快,采样周期就跟着缩水。 */
|
||||
unsigned long long rg_next_ms = now_ms() + WD_RG_PERIOD_MS;
|
||||
for (;;) {
|
||||
/* app gone? */
|
||||
if (kill(app_pid, 0) < 0 && errno == ESRCH) {
|
||||
@@ -979,6 +1136,11 @@ int main(int argc, char **argv)
|
||||
}
|
||||
wd_power_tick(); /* M2.24:电源长按检测(3s 菜单 / 8s 强关 / 菜单期让位) */
|
||||
wd_jack_tick(); /* 插孔状态节流轮询(消抖音量键幽灵按下) */
|
||||
/* M52:资源压力采样(10s 一次;只在跃迁/5 分钟复述时写日志,空转零日志增长) */
|
||||
if (now_ms() >= rg_next_ms) {
|
||||
wd_rg_tick();
|
||||
rg_next_ms = now_ms() + WD_RG_PERIOD_MS;
|
||||
}
|
||||
elapsed += 100;
|
||||
}
|
||||
return 0;
|
||||
|
||||
Reference in new issue
Block a user