- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘) - OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复 - ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640 - 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫) - 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
1433 lines
56 KiB
C
1433 lines
56 KiB
C
#define _GNU_SOURCE /* gethostname 等 GNU/musl 扩展 */
|
||
|
||
/* meow_svc_perf.c — MeowOS 性能分析专项子服务(SYSTEM_FRAMEWORK_SERVICES §1.2/§2.2)。
|
||
*
|
||
* 单一二进制双形态:
|
||
* - svcd 以**空参**拉起(§1.3 conf `args=` 空)→ IPC 服务常驻:bind `/usr/data/meow_perf.sock`
|
||
* (`--sock`/`MEOW_IPC_PERF_SOCK` 可覆盖),收 MSG_HELLO→MSG_ACK、MSG_PERF_QUERY→MSG_PERF_RESULT。
|
||
* - root shell `meow_svc_perf [query]` → 一次性 CLI(免鉴权)打印聚合 JSON(不常驻)。
|
||
*
|
||
* 聚合的数据源与 launcher `about.c`/`util.c`/bootmenu**同源**(/proc/stat、/proc/meminfo、
|
||
* /proc/uptime、/proc/loadavg、/sys/class/thermal 各 zone、df/mount、/proc/cpuinfo、/etc/meowos_version、
|
||
* gethostname/uname),聚合成与 MEOWPANEL_PROTOCOL §3.1 `/api/status`/.data 对齐的 JSON——
|
||
* 单一权威源,MeowPanel 后端/launcher 不再各自爬 /proc。
|
||
*
|
||
* MSG_PERF_QUERY.subset 位掩码(m_perf_query_t):0=全部;PERF_SUBSET_PERF / PERF_SUBSET_SYSTEM。
|
||
* 鉴权(一期):性能查询**只读安全**,本批不强制 os.meow.*;完整读级鉴权见 §5.3(二期)。
|
||
*
|
||
* 编译(mipsel 静态):build_svc_perf.sh;宿主编译 build_svc_perf.sh host。
|
||
*/
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdarg.h>
|
||
#include <stdint.h>
|
||
#include <unistd.h>
|
||
#include <fcntl.h>
|
||
#include <errno.h>
|
||
#include <signal.h>
|
||
#include <poll.h>
|
||
#include <time.h>
|
||
#include <ctype.h>
|
||
#include <dirent.h>
|
||
#include <sys/types.h>
|
||
#include <sys/socket.h>
|
||
#include <sys/un.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/wait.h>
|
||
#include <sys/utsname.h>
|
||
|
||
#include "meow_ipc.h" /* 帧格式/消息号 + m_perf_query_t/m_perf_result_t(与 launcher 同源) */
|
||
|
||
#define VERSION_FILE "/etc/meowos_version"
|
||
#define TF_MOUNT "/usr/data/mnt/sd_0"
|
||
#define MEOW_PIPE_MAX 4096
|
||
#define MEOW_TIMEOUT_MS 1500
|
||
|
||
static const char *g_sock = "/usr/data/meow_perf.sock";
|
||
|
||
static void svc_log(const char *fmt, ...)
|
||
{
|
||
va_list ap;
|
||
va_start(ap, fmt);
|
||
fprintf(stderr, "[meow_svc_perf] ");
|
||
vfprintf(stderr, fmt, ap);
|
||
fprintf(stderr, "\n");
|
||
va_end(ap);
|
||
}
|
||
|
||
/* ---------------- 有界 JSON 追加 ---------------- */
|
||
typedef struct { char *b; size_t n; size_t cap; } sbuf_t;
|
||
|
||
static void sA(sbuf_t *s, const char *fmt, ...)
|
||
{
|
||
if (s->n >= s->cap - 1) return;
|
||
va_list ap;
|
||
va_start(ap, fmt);
|
||
int w = vsnprintf(s->b + s->n, s->cap - s->n, fmt, ap);
|
||
va_end(ap);
|
||
if (w < 0) return;
|
||
if ((size_t)w >= s->cap - s->n) s->n = s->cap - 1;
|
||
else s->n += (size_t)w;
|
||
}
|
||
|
||
/* JSON 字符串转义(version/hostname 等;ASCII 常见字符直通,引号/反斜杠转义) */
|
||
static void sA_str(sbuf_t *s, const char *v)
|
||
{
|
||
if (!v) v = "";
|
||
sA(s, "\"");
|
||
for (const char *p = v; *p; p++) {
|
||
unsigned char c = (unsigned char)*p;
|
||
if (c == '"' || c == '\\') { sA(s, "\\%c", (int)c); }
|
||
else if (c < 0x20) { sA(s, "\\u%04x", c); }
|
||
else sA(s, "%c", (int)c); /* buf 0x80.. 按字节透传(utf-8) */
|
||
}
|
||
sA(s, "\"");
|
||
}
|
||
|
||
/* ---------------- 限时读整文件 ---------------- */
|
||
static int read_file_txt(const char *path, char *buf, int cap)
|
||
{
|
||
int fd = open(path, O_RDONLY);
|
||
if (fd < 0) return -1;
|
||
int n = 0;
|
||
while (n < cap - 1) {
|
||
ssize_t r = read(fd, buf + n, (size_t)(cap - 1 - n));
|
||
if (r <= 0) break;
|
||
n += (int)r;
|
||
}
|
||
close(fd);
|
||
buf[n] = 0;
|
||
while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r')) buf[--n] = 0;
|
||
return n;
|
||
}
|
||
|
||
/* ---------------- popen 限时读(kill 子进程防挂) ---------------- */
|
||
static char *popen_read_tmo(int tmo_ms, const char *cmd)
|
||
{
|
||
int p[2];
|
||
if (pipe(p) < 0) return NULL;
|
||
pid_t pid = fork();
|
||
if (pid < 0) { close(p[0]); close(p[1]); return NULL; }
|
||
if (pid == 0) { dup2(p[1], 1); close(p[0]); close(p[1]); execl("/bin/sh", "sh", "-c", cmd, (char *)0); _exit(127); }
|
||
close(p[1]);
|
||
fcntl(p[0], F_SETFL, fcntl(p[0], F_GETFL, 0) | O_NONBLOCK);
|
||
char *buf = malloc(MEOW_PIPE_MAX);
|
||
if (!buf) { close(p[0]); waitpid(pid, NULL, 0); return NULL; }
|
||
size_t off = 0;
|
||
int remain = tmo_ms;
|
||
while (remain > 0 && off < MEOW_PIPE_MAX - 1) {
|
||
struct pollfd pf = { p[0], POLLIN, 0 };
|
||
int r = poll(&pf, 1, remain);
|
||
if (r < 0) { if (errno == EINTR) continue; break; }
|
||
if (r == 0) break;
|
||
ssize_t rd = read(p[0], buf + off, MEOW_PIPE_MAX - 1 - off);
|
||
if (rd == 0) break;
|
||
if (rd < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) continue; break; }
|
||
off += (size_t)rd;
|
||
}
|
||
close(p[0]);
|
||
waitpid(pid, NULL, 0);
|
||
buf[off] = 0;
|
||
return buf;
|
||
}
|
||
|
||
/* ---------------- CPU ---------------- */
|
||
/* /proc/stat 首行 "cpu user nice system idle iowait irq softirq steal ..."。 */
|
||
static int g_cpu_prev[10];
|
||
static int g_cpu_prev_init = 0;
|
||
|
||
static double cpu_usage_pct(void)
|
||
{
|
||
FILE *f = fopen("/proc/stat", "r");
|
||
char line[256];
|
||
if (!f || !fgets(line, sizeof line, f)) { if (f) fclose(f); return -1; }
|
||
fclose(f);
|
||
unsigned long long vals[10];
|
||
char *p = line + 3; /* skip "cpu" */
|
||
int n = 0;
|
||
while (n < 10) {
|
||
while (*p == ' ') p++;
|
||
if (!*p || !isdigit((unsigned char)*p)) break;
|
||
vals[n++] = strtoull(p, &p, 10);
|
||
}
|
||
if (n < 4) return -1;
|
||
unsigned long long total = 0, idle = 0;
|
||
for (int i = 0; i < n; i++) total += vals[i];
|
||
idle = vals[3] + (n > 4 ? vals[4] : 0);
|
||
if (!g_cpu_prev_init) {
|
||
for (int i = 0; i < 10; i++) g_cpu_prev[i] = (int)vals[i];
|
||
g_cpu_prev_init = 1;
|
||
return 0;
|
||
}
|
||
unsigned long long dtotal = total, didle = idle;
|
||
for (int i = 0; i < n; i++) if (i < 10) dtotal -= (unsigned long long)g_cpu_prev[i];
|
||
if (n > 4) didle -= (unsigned long long)g_cpu_prev[3] + (unsigned long long)g_cpu_prev[4];
|
||
else didle -= (unsigned long long)g_cpu_prev[3];
|
||
for (int i = 0; i < 10; i++) g_cpu_prev[i] = (int)vals[i];
|
||
if (!dtotal) return 0;
|
||
double u = 100.0 * (double)(dtotal - didle) / (double)dtotal;
|
||
if (u < 0) u = 0;
|
||
return u;
|
||
}
|
||
|
||
static int core_count(void)
|
||
{
|
||
FILE *f = fopen("/proc/stat", "r");
|
||
char line[256];
|
||
int n = 0;
|
||
if (!f) return 1;
|
||
while (fgets(line, sizeof line, f)) {
|
||
if (line[0] == 'c' && line[1] == 'p' && line[2] == 'u' && isdigit((unsigned char)line[3]))
|
||
n++;
|
||
if (line[0] != 'c') break;
|
||
}
|
||
fclose(f);
|
||
return n > 0 ? n : 1;
|
||
}
|
||
|
||
static void loadavg(double *l1, double *l5, double *l15)
|
||
{
|
||
FILE *f = fopen("/proc/loadavg", "r");
|
||
*l1 = *l5 = *l15 = 0;
|
||
if (f) { if (fscanf(f, "%lf %lf %lf", l1, l5, l15) != 3) { *l1 = *l5 = *l15 = 0; } fclose(f); }
|
||
}
|
||
|
||
/* ---------------- 内存 ---------------- */
|
||
static void meminfo(unsigned long *total, unsigned long *used, unsigned long *free_kb,
|
||
unsigned long *avail, unsigned long *stotal, unsigned long *sused)
|
||
{
|
||
*total = *used = *free_kb = *avail = *stotal = *sused = 0;
|
||
FILE *f = fopen("/proc/meminfo", "r");
|
||
if (!f) return;
|
||
char k[64], line[256];
|
||
unsigned long mtot = 0, mfree = 0, mavail = 0, swapT = 0, swapF = 0;
|
||
while (fgets(line, sizeof line, f)) {
|
||
unsigned long v;
|
||
if (sscanf(line, "%63s %lu", k, &v) != 2) continue;
|
||
if (!strcmp(k, "MemTotal:")) mtot = v;
|
||
else if (!strcmp(k, "MemFree:")) mfree = v;
|
||
else if (!strcmp(k, "MemAvailable:")) mavail = v;
|
||
else if (!strcmp(k, "SwapTotal:")) swapT = v;
|
||
else if (!strcmp(k, "SwapFree:")) swapF = v;
|
||
}
|
||
fclose(f);
|
||
*total = mtot;
|
||
*free_kb = mfree;
|
||
if (mavail > 0) { *avail = mavail; *used = (mtot >= mavail) ? mtot - mavail : 0; }
|
||
else { *avail = mfree; *used = (mtot >= mfree) ? mtot - mfree : 0; }
|
||
*stotal = swapT;
|
||
*sused = (swapT >= swapF) ? swapT - swapF : 0;
|
||
}
|
||
|
||
/* ---------------- page-cache 治理 C 侧(OOM_PAGECACHE_GOVERNANCE §3;③混合:C 归本服务,A 归 SDK) ----------------
|
||
* 设备级 /proc/meminfo Cached 监控降载:Cached 超阈值 → 置「暂停下载/预取」,落共享状态文件
|
||
* MEOW_CACHEGOV_C_FILE(App/SDK meow_cachegov 只读,系统级单点,App 不重复起线程)。
|
||
* Dirty 为早信号(仅告警日志,不直接触发降载)。滞回:≥halt 触发 / ≤resume 恢复(防抖动)。 */
|
||
static long g_cg_halt_kb = 32 * 1024; /* Cached 超此暂停下载+预取(初值 56MB 设备) */
|
||
static long g_cg_resume_kb = 24 * 1024; /* Cached 落此恢复 */
|
||
static long g_cg_dirty_warn_kb = 8 * 1024; /* Dirty 早信号告警 */
|
||
static int g_cg_dl_pause = 0;
|
||
static int g_cg_pf_pause = 0;
|
||
static long g_cg_last_cached_kb = 0;
|
||
static long g_cg_last_dirty_kb = 0;
|
||
static long g_cg_last_update_ms = 0;
|
||
static const char *g_cg_file = MEOW_CACHEGOV_C_FILE;
|
||
static long g_cg_test_cached_kb = -1; /* 宿主测试注入(>=0 不读真 /proc/meminfo) */
|
||
static long g_cg_test_dirty_kb = -1;
|
||
|
||
static long now_ms(void); /* 前向声明(定义在卡死快照节) */
|
||
|
||
static void cachegov_config_load(void)
|
||
{
|
||
const char *e;
|
||
if ((e = getenv("MEOW_CACHEGOV_HALT_KB")) && e[0]) g_cg_halt_kb = atol(e);
|
||
if ((e = getenv("MEOW_CACHEGOV_RESUME_KB")) && e[0]) g_cg_resume_kb = atol(e);
|
||
if ((e = getenv("MEOW_CACHEGOV_DIRTY_WARN_KB")) && e[0]) g_cg_dirty_warn_kb = atol(e);
|
||
if ((e = getenv("MEOW_CACHEGOV_C_FILE")) && e[0]) g_cg_file = e;
|
||
if ((e = getenv("MEOW_CACHEGOV_TEST_CACHED_KB")) && e[0]) g_cg_test_cached_kb = atol(e);
|
||
if ((e = getenv("MEOW_CACHEGOV_TEST_DIRTY_KB")) && e[0]) g_cg_test_dirty_kb = atol(e);
|
||
}
|
||
|
||
/* 读 /proc/meminfo 的 Cached/Dirty(kB)。测试注入 >=0 时替代真读。 */
|
||
static void cachegov_read_cached(long *cached_kb, long *dirty_kb)
|
||
{
|
||
*cached_kb = *dirty_kb = 0;
|
||
if (g_cg_test_cached_kb >= 0) {
|
||
*cached_kb = g_cg_test_cached_kb;
|
||
*dirty_kb = (g_cg_test_dirty_kb >= 0) ? g_cg_test_dirty_kb : 0;
|
||
return;
|
||
}
|
||
FILE *f = fopen("/proc/meminfo", "r");
|
||
if (!f) return;
|
||
char line[256], k[64];
|
||
unsigned long v;
|
||
while (fgets(line, sizeof line, f)) {
|
||
if (sscanf(line, "%63s %lu", k, &v) != 2) continue;
|
||
if (!strcmp(k, "Cached:")) *cached_kb = (long)v;
|
||
else if (!strcmp(k, "Dirty:")) *dirty_kb = (long)v;
|
||
}
|
||
fclose(f);
|
||
}
|
||
|
||
/* 落共享状态文件(tmp+rename 原子;单文件读,App 廉价查询不重复起线程)。 */
|
||
static void cachegov_write_state(long cached, long dirty, int dl, int pf)
|
||
{
|
||
char tmp[384];
|
||
snprintf(tmp, sizeof tmp, "%s.tmp", g_cg_file);
|
||
int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||
if (fd < 0) return;
|
||
char buf[160];
|
||
int n = snprintf(buf, sizeof buf,
|
||
"cached_kb=%ld\ndirty_kb=%ld\ndl_pause=%d\nprefetch_pause=%d\n",
|
||
cached, dirty, dl, pf);
|
||
if (n > 0) { ssize_t r = write(fd, buf, (size_t)n); (void)r; }
|
||
close(fd);
|
||
rename(tmp, g_cg_file);
|
||
}
|
||
|
||
/* 单步降载评估(可被 daemon 节拍实值或 CLI/测试注入值调用)。 */
|
||
static void cachegov_monitor_step(long cached_kb, long dirty_kb)
|
||
{
|
||
if (cached_kb >= g_cg_halt_kb) { /* 触发:Cached 超阈值 → 暂停下载+预取 */
|
||
g_cg_dl_pause = 1;
|
||
g_cg_pf_pause = 1;
|
||
} else if (cached_kb <= g_cg_resume_kb) { /* 恢复:跌破恢复阈值 → 放行 */
|
||
g_cg_dl_pause = 0;
|
||
g_cg_pf_pause = 0;
|
||
} /* 滞回带(resume < x < halt):保持当前档 */
|
||
if (dirty_kb > g_cg_dirty_warn_kb)
|
||
svc_log("cachegov: dirty warn %ldkB (>%ldkB 早信号)", dirty_kb, g_cg_dirty_warn_kb);
|
||
g_cg_last_cached_kb = cached_kb;
|
||
g_cg_last_dirty_kb = dirty_kb;
|
||
cachegov_write_state(cached_kb, dirty_kb, g_cg_dl_pause, g_cg_pf_pause);
|
||
}
|
||
|
||
/* 节拍(daemon 主循环调用;500ms 轻量单文件读 /proc/meminfo)。 */
|
||
static void cachegov_monitor_tick(void)
|
||
{
|
||
long now = now_ms();
|
||
if (now - g_cg_last_update_ms < 500) return;
|
||
long cached, dirty;
|
||
cachegov_read_cached(&cached, &dirty);
|
||
cachegov_monitor_step(cached, dirty);
|
||
g_cg_last_update_ms = now;
|
||
}
|
||
|
||
static double uptime_sec(void)
|
||
{
|
||
FILE *f = fopen("/proc/uptime", "r");
|
||
double up = 0;
|
||
if (f) { if (fscanf(f, "%lf", &up) != 1) up = 0; fclose(f); }
|
||
return up;
|
||
}
|
||
|
||
/* ---------------- 温度(/sys/class/thermal/thermal_zone*;type 匹配 cpu→cpu,其余→board) ---------------- */
|
||
static void temp_json(sbuf_t *s, int *any)
|
||
{
|
||
DIR *d = opendir("/sys/class/thermal");
|
||
*any = 0;
|
||
if (!d) { sA(s, "{}"); return; }
|
||
double cpu = -1, board = -1;
|
||
struct dirent *e;
|
||
while ((e = readdir(d))) {
|
||
if (strncmp(e->d_name, "thermal_zone", 12) != 0) continue;
|
||
char path[300], typ[64] = "", tmp[32] = "";
|
||
snprintf(path, sizeof path, "/sys/class/thermal/%s/type", e->d_name);
|
||
read_file_txt(path, typ, sizeof typ);
|
||
snprintf(path, sizeof path, "/sys/class/thermal/%s/temp", e->d_name);
|
||
read_file_txt(path, tmp, sizeof tmp);
|
||
long millideg = atol(tmp);
|
||
if (millideg <= 0) continue;
|
||
double c = (double)millideg / 1000.0;
|
||
if (strstr(typ, "cpu") || strstr(typ, "CPU")) { if (cpu < 0) cpu = c; }
|
||
else if (board < 0) board = c;
|
||
}
|
||
closedir(d);
|
||
if (cpu < 0 && board < 0) { sA(s, "{}"); return; }
|
||
sA(s, "{");
|
||
int first = 1;
|
||
if (cpu >= 0) { sA(s, "\"cpu\":%.1f", cpu); first = 0; }
|
||
if (board >= 0) { if (!first) sA(s, ","); sA(s, "\"board\":%.1f", board); }
|
||
sA(s, "}");
|
||
*any = 1;
|
||
}
|
||
|
||
/* ---------------- 存储(df -k <path> 第 2 行:右 5 字段 = fs,total,used,avail,use%,mount) ---------------- */
|
||
static int df_parse(const char *path, unsigned long *total, unsigned long *used, unsigned long *avail)
|
||
{
|
||
*total = *used = *avail = 0;
|
||
char cmd[160];
|
||
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin df -k %s 2>/dev/null", path);
|
||
char *o = popen_read_tmo(MEOW_TIMEOUT_MS, cmd);
|
||
if (!o) return -1;
|
||
char *ln = strchr(o, '\n');
|
||
if (ln) ln++;
|
||
char *save = NULL;
|
||
char *toks[8]; int nt = 0;
|
||
for (char *tk = strtok_r(ln ? ln : o, " \t", &save); tk && nt < 8; tk = strtok_r(NULL, " \t", &save))
|
||
toks[nt++] = tk;
|
||
if (nt >= 5) { *total = strtoul(toks[nt - 5], NULL, 10); *used = strtoul(toks[nt - 4], NULL, 10); *avail = strtoul(toks[nt - 3], NULL, 10); }
|
||
free(o);
|
||
return (*total || *avail) ? 0 : -1;
|
||
}
|
||
|
||
static int tf_present(void)
|
||
{
|
||
FILE *f = fopen("/proc/mounts", "r");
|
||
if (!f) return 0;
|
||
char line[512];
|
||
int present = 0;
|
||
while (fgets(line, sizeof line, f)) {
|
||
if (strstr(line, TF_MOUNT)) { present = 1; break; }
|
||
}
|
||
fclose(f);
|
||
return present;
|
||
}
|
||
|
||
/* ---------------- 系统静态(version/hostname/kernel/arch) ---------------- */
|
||
static void sys_arch(char *out, int cap)
|
||
{
|
||
struct utsname u;
|
||
const char *m = "mipsel";
|
||
if (uname(&u) == 0) m = u.machine;
|
||
if (strstr(m, "mips")) m = "mipsel"; /* mipsel-linux-muslsf 目标 */
|
||
snprintf(out, cap, "%.31s", m);
|
||
}
|
||
|
||
static void sys_kernel(char *out, int cap)
|
||
{
|
||
struct utsname u;
|
||
size_t rl = 0;
|
||
if (uname(&u) != 0) { snprintf(out, cap, "-"); return; }
|
||
rl = strnlen(u.release, 64);
|
||
if (rl > (size_t)(cap - 1)) rl = (size_t)(cap - 1);
|
||
memcpy(out, u.release, rl);
|
||
out[rl] = 0;
|
||
}
|
||
|
||
/* ---------------- 网络(wifi/bluetooth/hostname_local,SYSTEM 子集) ----------------
|
||
* 单一权威源:/api/system、/api/status 的 network 段由 meow_svc_perf 聚合(SYSTEM_FRAMEWORK_SERVICES §4.1)。
|
||
* 采集只走 popen(wpa_cli/ip/hciconfig) + /sys 只读,带超时防挂;拿不到的真实值回 null/占位,绝不编造。
|
||
*/
|
||
/* wifi:解析 wpa_cli status;返回 connected(1/0)。ssid/ip 失败置空串(→ null)。 */
|
||
static int net_wifi(char *ssid, size_t sscap, char *ip, size_t ipcap)
|
||
{
|
||
ssid[0] = 0; ip[0] = 0;
|
||
int connected = 0;
|
||
char *o = popen_read_tmo(MEOW_TIMEOUT_MS,
|
||
"PATH=/bin:/sbin:/usr/bin:/usr/sbin wpa_cli status 2>/dev/null");
|
||
if (o) {
|
||
char *ln = o;
|
||
while (ln && *ln) {
|
||
char *nl = strchr(ln, '\n');
|
||
if (nl) *nl = 0;
|
||
if (!strncmp(ln, "wpa_state=", 10)) { if (!strcmp(ln + 10, "COMPLETED")) connected = 1; }
|
||
else if (!strncmp(ln, "ssid=", 5)) { snprintf(ssid, sscap, "%s", ln + 5); }
|
||
if (!nl) break;
|
||
ln = nl + 1;
|
||
}
|
||
free(o);
|
||
}
|
||
/* IP:ip -o -4 addr show wlan0 → "2: wlan0 inet 10.78.193.50/24 ...",取含 '.'+'/' 的 token 的 '/' 前段 */
|
||
char *o2 = popen_read_tmo(MEOW_TIMEOUT_MS,
|
||
"PATH=/bin:/sbin:/usr/bin:/usr/sbin ip -o -4 addr show wlan0 2>/dev/null");
|
||
if (o2) {
|
||
for (char *tk = strtok(o2, " \t"); tk; tk = strtok(NULL, " \t")) {
|
||
char *dot = strchr(tk, '.'); if (!dot) continue;
|
||
char *sl = strchr(tk, '/'); if (!sl || sl < dot) continue;
|
||
size_t len = (size_t)(sl - tk);
|
||
if (len >= ipcap) len = ipcap - 1;
|
||
memcpy(ip, tk, len); ip[len] = 0;
|
||
break;
|
||
}
|
||
free(o2);
|
||
}
|
||
return connected;
|
||
}
|
||
|
||
/* bluetooth 段字段(不含外层 {}):available/powered/address/connected/devices。 */
|
||
static void net_bluetooth_json(sbuf_t *s)
|
||
{
|
||
int available = 0;
|
||
struct stat st;
|
||
if (stat("/sys/class/bluetooth/hci0", &st) == 0) available = 1;
|
||
sA(s, "\"available\":%s", available ? "true" : "false");
|
||
/* address:/sys/class/bluetooth/hci0/address 优先,回退 hciconfig 的 BD Address */
|
||
char addr[32] = "";
|
||
if (available) {
|
||
read_file_txt("/sys/class/bluetooth/hci0/address", addr, sizeof addr);
|
||
if (!addr[0]) {
|
||
char *o = popen_read_tmo(MEOW_TIMEOUT_MS,
|
||
"PATH=/bin:/sbin:/usr/bin:/usr/sbin hciconfig hci0 2>/dev/null");
|
||
if (o) {
|
||
char *p = strstr(o, "BD Address:");
|
||
if (p) {
|
||
p += 11; /* "BD Address:" 长 11 */
|
||
while (*p == ' ') p++;
|
||
int i = 0;
|
||
while (*p && *p != ' ' && *p != '\n' && i < (int)sizeof addr - 1) addr[i++] = *p++;
|
||
addr[i] = 0;
|
||
}
|
||
free(o);
|
||
}
|
||
}
|
||
}
|
||
sA(s, ",\"address\":");
|
||
if (addr[0]) sA_str(s, addr); else sA(s, "null");
|
||
/* powered:/sys/.../power(on/off)优先,文件不可读回退 hciconfig 行首 UP/DOWN */
|
||
int powered = 0;
|
||
char pw[8] = "";
|
||
int pwok = 0;
|
||
if (available) {
|
||
pwok = read_file_txt("/sys/class/bluetooth/hci0/power", pw, sizeof pw);
|
||
if (pwok > 0 && pw[0]) {
|
||
powered = (!strcmp(pw, "on"));
|
||
} else {
|
||
char *o = popen_read_tmo(MEOW_TIMEOUT_MS,
|
||
"PATH=/bin:/sbin:/usr/bin:/usr/sbin hciconfig hci0 2>/dev/null");
|
||
if (o) {
|
||
char *ln = o;
|
||
while (ln && *ln) {
|
||
char *nl = strchr(ln, '\n');
|
||
if (nl) *nl = 0;
|
||
char *q = ln;
|
||
while (*q == ' ' || *q == '\t') q++;
|
||
if (!strncmp(q, "UP", 2)) { powered = 1; break; }
|
||
if (!strncmp(q, "DOWN", 4)) { powered = 0; break; }
|
||
if (!nl) break;
|
||
ln = nl + 1;
|
||
}
|
||
free(o);
|
||
}
|
||
}
|
||
}
|
||
sA(s, ",\"powered\":%s", powered ? "true" : "false");
|
||
/* connected/devices:无配对记录可查(/var/lib/bluetooth 空/未知)→ false/[],不编造 */
|
||
sA(s, ",\"connected\":false,\"devices\":[]");
|
||
}
|
||
|
||
/* network 完整对象(含外层 {})。hostname 无效(空/"-")时 hostname_local=null。 */
|
||
static void net_json(sbuf_t *s, const char *hostname)
|
||
{
|
||
char ssid[64] = "", ip[64] = "";
|
||
int wc = net_wifi(ssid, sizeof ssid, ip, sizeof ip);
|
||
sA(s, "{\"wifi\":{\"connected\":%s", wc ? "true" : "false");
|
||
sA(s, ",\"ssid\":");
|
||
if (ssid[0]) sA_str(s, ssid); else sA(s, "null");
|
||
sA(s, ",\"ip\":");
|
||
if (ip[0]) sA_str(s, ip); else sA(s, "null");
|
||
sA(s, ",\"rssi\":null,\"bitrate_mbps\":null},\"bluetooth\":{");
|
||
net_bluetooth_json(s);
|
||
sA(s, "},\"hostname_local\":");
|
||
const char *hn = (hostname && hostname[0] && strcmp(hostname, "-")) ? hostname : NULL;
|
||
if (!hn) { sA(s, "null"); sA(s, "}"); return; }
|
||
char hl[96];
|
||
size_t l = strlen(hn);
|
||
if (l >= 6 && !strcmp(hn + l - 6, ".local")) snprintf(hl, sizeof hl, "%s", hn);
|
||
else snprintf(hl, sizeof hl, "%s.local", hn);
|
||
sA_str(s, hl);
|
||
sA(s, "}"); /* 闭合 network 对象(缺它则 agg_json 收尾的 "}" 只闭合 network,根对象不闭合 → JSON 非法) */
|
||
}
|
||
|
||
/* ---------------- 聚合 JSON(subset 位掩码;与 MEOWPANEL_PROTOCOL §3.1 .data 对齐) ---------------- */
|
||
static int agg_json(int subset, char *out, int cap)
|
||
{
|
||
int ip = (subset == 0) || (subset & PERF_SUBSET_PERF);
|
||
int isys = (subset == 0) || (subset & PERF_SUBSET_SYSTEM);
|
||
sbuf_t s = { out, 0, (size_t)cap };
|
||
sA(&s, "{");
|
||
int first = 1;
|
||
#define SEP() do { if (!first) sA(&s, ","); first = 0; } while (0)
|
||
|
||
if (ip) {
|
||
SEP(); sA(&s, "\"ts\":%ld", (long)time(NULL));
|
||
SEP(); sA(&s, "\"uptime_sec\":%.0f", uptime_sec());
|
||
double u = cpu_usage_pct(); int cores = core_count();
|
||
double l1, l5, l15; loadavg(&l1, &l5, &l15);
|
||
SEP(); sA(&s, "\"cpu\":{\"usage\":%.1f,\"cores\":%d,\"load\":[%.2f,%.2f,%.2f]}", u, cores, l1, l5, l15);
|
||
unsigned long mtot, mused, mfree, mavail, swapT, swapU;
|
||
meminfo(&mtot, &mused, &mfree, &mavail, &swapT, &swapU);
|
||
long cc = 0, cd = 0;
|
||
cachegov_read_cached(&cc, &cd);
|
||
SEP(); sA(&s, "\"memory\":{\"total\":%lu,\"used\":%lu,\"free\":%lu,\"available\":%lu,\"swap_total\":%lu,\"swap_used\":%lu,\"cached\":%ld,\"dirty\":%ld,\"cachegov_dl_pause\":%d,\"cachegov_prefetch_pause\":%d}",
|
||
mtot, mused, mfree, mavail, swapT, swapU, cc, cd, g_cg_dl_pause, g_cg_pf_pause);
|
||
unsigned long st, su, sa;
|
||
if (df_parse("/", &st, &su, &sa) == 0) {
|
||
SEP();
|
||
sA(&s, "\"storage\":{\"path\":\"/\",\"total\":%lu,\"used\":%lu,\"avail\":%lu}", st, su, sa);
|
||
} else {
|
||
SEP();
|
||
sA(&s, "\"storage\":{\"path\":\"/\",\"total\":0,\"used\":0,\"avail\":0}");
|
||
}
|
||
if (tf_present()) {
|
||
unsigned long tt, tu, ta;
|
||
if (df_parse(TF_MOUNT, &tt, &tu, &ta) == 0) {
|
||
SEP();
|
||
sA(&s, "\"tfcard\":{\"present\":true,\"path\":\"" TF_MOUNT "\",\"total\":%lu,\"used\":%lu,\"avail\":%lu,\"filesystem\":\"exfat\"}", tt, tu, ta);
|
||
} else {
|
||
SEP();
|
||
sA(&s, "\"tfcard\":{\"present\":true,\"path\":\"" TF_MOUNT "\",\"total\":0,\"used\":0,\"avail\":0,\"filesystem\":\"exfat\"}");
|
||
}
|
||
} else {
|
||
SEP();
|
||
sA(&s, "\"tfcard\":{\"present\":false,\"path\":\"" TF_MOUNT "\",\"total\":0,\"used\":0,\"avail\":0,\"filesystem\":null}");
|
||
}
|
||
int any;
|
||
SEP(); sA(&s, "\"temp\":"); temp_json(&s, &any);
|
||
}
|
||
if (isys) {
|
||
char ver[64] = "-", host[64] = "-", ker[64] = "-", arch[32] = "-";
|
||
if (read_file_txt(VERSION_FILE, ver, sizeof ver) <= 0 || !ver[0]) snprintf(ver, sizeof ver, "-");
|
||
if (gethostname(host, sizeof host) != 0 || !host[0]) snprintf(host, sizeof host, "-");
|
||
sys_kernel(ker, sizeof ker);
|
||
sys_arch(arch, sizeof arch);
|
||
SEP(); sA(&s, "\"system\":{\"version\":");
|
||
sA_str(&s, ver);
|
||
sA(&s, ",\"hostname\":");
|
||
sA_str(&s, host);
|
||
sA(&s, ",\"kernel\":"); sA_str(&s, ker);
|
||
sA(&s, ",\"arch\":"); sA_str(&s, arch);
|
||
sA(&s, "}");
|
||
/* network(wifi/bluetooth/hostname_local):SYSTEM 子集,/api/system 与 /api/status 透传 */
|
||
SEP(); sA(&s, "\"network\":"); net_json(&s, host);
|
||
}
|
||
sA(&s, "}"); /* 外层闭合 */
|
||
#undef SEP
|
||
if (s.n >= (size_t)cap) s.n = (size_t)cap - 1;
|
||
s.b[s.n] = 0;
|
||
return 0;
|
||
}
|
||
|
||
/* ---------------- IPC 帧读写 ---------------- */
|
||
static int x_read_full(int fd, uint8_t *buf, size_t n)
|
||
{
|
||
size_t off = 0;
|
||
while (off < n) { ssize_t r = read(fd, buf + off, n - off); if (r <= 0) return -1; off += (size_t)r; }
|
||
return 0;
|
||
}
|
||
static int x_write_full(int fd, const uint8_t *buf, size_t n)
|
||
{
|
||
size_t off = 0;
|
||
while (off < n) { ssize_t w = write(fd, buf + off, n - off); if (w <= 0) return -1; off += (size_t)w; }
|
||
return 0;
|
||
}
|
||
static int send_frame(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len)
|
||
{
|
||
uint8_t hdr[MEOW_IPC_HDR_LEN];
|
||
uint32_t magic = MEOW_IPC_MAGIC, rlen = len;
|
||
uint16_t ver = MEOW_IPC_VERSION, t = type;
|
||
memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &t, 2);
|
||
memcpy(hdr + 8, &req_id, 4); memcpy(hdr + 12, &rlen, 4);
|
||
if (x_write_full(fd, hdr, MEOW_IPC_HDR_LEN) < 0) return -1;
|
||
if (len && payload && x_write_full(fd, payload, len) < 0) return -1;
|
||
return 0;
|
||
}
|
||
static int send_ack(int fd, uint32_t req_id, uint32_t code, uint32_t status)
|
||
{
|
||
m_ack_t a; memset(&a, 0, sizeof a); a.code = code; a.status = status;
|
||
return send_frame(fd, MSG_ACK, req_id, &a, sizeof a);
|
||
}
|
||
static int send_err(int fd, uint32_t req_id, uint32_t code, const char *msg)
|
||
{
|
||
m_err_t e; memset(&e, 0, sizeof e);
|
||
uint16_t ml = msg ? (uint16_t)strlen(msg) : 0;
|
||
if (ml > 63) ml = 63;
|
||
e.code = code; e.msg_len = ml;
|
||
if (ml) memcpy(e.msg, msg, ml);
|
||
return send_frame(fd, MSG_ERR, req_id, &e, sizeof e);
|
||
}
|
||
static int send_perf_result(int fd, uint32_t req_id, int subset, const char *json)
|
||
{
|
||
m_perf_result_t r;
|
||
memset(&r, 0, sizeof r);
|
||
r.code = MEOW_OK;
|
||
r.subset = (uint32_t)subset;
|
||
uint16_t jl = (uint16_t)strlen(json);
|
||
if (jl > sizeof r.json - 1) jl = (uint16_t)(sizeof r.json - 1);
|
||
memcpy(r.json, json, jl);
|
||
r.json[jl] = 0;
|
||
r.json_len = jl;
|
||
return send_frame(fd, MSG_PERF_RESULT, req_id, &r, sizeof r);
|
||
}
|
||
|
||
/* MSG_PERF_QUERY:读取 subset 后聚合。0=全部。 */
|
||
static void handle_query(int c, uint32_t req_id, const uint8_t *payload, uint32_t rlen)
|
||
{
|
||
m_perf_query_t q;
|
||
memset(&q, 0, sizeof q);
|
||
if (rlen > sizeof q) rlen = sizeof q;
|
||
memcpy(&q, payload, rlen);
|
||
int subset = (q.subset == 0) ? 0 : (int)q.subset;
|
||
char json[MEOW_IPC_MAX - 12];
|
||
agg_json(subset, json, sizeof json);
|
||
send_perf_result(c, req_id, subset, json);
|
||
}
|
||
|
||
static void handle_conn(int c)
|
||
{
|
||
for (;;) {
|
||
uint8_t hdr[MEOW_IPC_HDR_LEN];
|
||
if (x_read_full(c, hdr, sizeof hdr) < 0) break;
|
||
uint32_t magic, rlen, req_id; uint16_t ver, type;
|
||
memcpy(&magic, hdr, 4); memcpy(&ver, hdr + 4, 2); memcpy(&type, hdr + 6, 2);
|
||
memcpy(&req_id, hdr + 8, 4); memcpy(&rlen, hdr + 12, 4);
|
||
if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) { send_err(c, req_id, MEOW_ERR_INVAL, "bad magic/ver"); break; }
|
||
if (rlen > MEOW_IPC_MAX) { send_err(c, req_id, MEOW_ERR_INVAL, "payload too big"); break; }
|
||
uint8_t payload[MEOW_IPC_MAX];
|
||
if (rlen && x_read_full(c, payload, rlen) < 0) break;
|
||
if (type == MSG_HELLO) { send_ack(c, req_id, MEOW_OK, 0); continue; }
|
||
if (type == MSG_PERF_QUERY) { handle_query(c, req_id, payload, rlen); continue; }
|
||
send_err(c, req_id, MEOW_ERR_INVAL, "unknown type");
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* =====================================================================
|
||
* 卡死诊断快照 + RMM 基线(系统 log 模块升级:高负载/停滞/内存紧张自动留档)
|
||
* ---------------------------------------------------------------------
|
||
* 为什么放 meow_svc_perf:
|
||
* - svcd `trigger=boot` 常驻服务(/usr/data/meow/services.d/meow_svc_perf.conf),
|
||
* 独立于 launcher UI 主循环——launcher 是 CPU 风暴受害者时本进程仍可被调度到;
|
||
* - 本文件已聚合 /proc/loadavg、/proc/meminfo、/proc/stat、/proc/uptime、core_count,
|
||
* 正是快照/基线所需数据源,直接复用,不重写、不重造。
|
||
* 输出两类(均走 TF 卡 MeowOS/logs/,无卡降级 /tmp):
|
||
* 1) RMM 基线 perf_<bootts>.log:每 MEOW_PERF_BASELINE_MS(默认 60s)追一行
|
||
* load/cpu/mem/topN 进程(单文件 1MB 上限,超则新开一轮)。
|
||
* 2) hang 快照 hang_<bootts>_<epoch>.snap:loadavg[1] 持续超限
|
||
* MEOW_PERF_HANG_CONSEC 次(默认 2)落一份完整现场(loadavg/cpu/mem/关键进程
|
||
* VmRSS+线程/连接数),带冷却(默认 300s)与每 boot 上限(默认 4),绝不连写刷爆卡。
|
||
* 降载/自死规避:采集全部【单文件读】(/proc/loadavg、/proc/meminfo、/proc/stat、
|
||
* /proc/<pid>/stat、/proc/net/{unix,tcp,tcp6,udp}),零 popen/零 fork——卡死时本进程
|
||
* 若被调度到仍能轻量完成,且不放大负载;快照写入限时(每文件一次 open+write+close)。
|
||
* 配置(env,默认开且轻量):
|
||
* MEOW_PERF_SNAP 默认 1;0=整体关闭
|
||
* MEOW_PERF_BASELINE_MS默认 60000;0=只做 hang 检测不写基线
|
||
* MEOW_PERF_HANG_LOAD 默认 3.0(loadavg[1] 阈值)
|
||
* MEOW_PERF_HANG_CONSEC默认 2(连续异常次数)
|
||
* MEOW_PERF_HANG_COOLDOWN_MS 默认 300000(两次快照最小间隔)
|
||
* MEOW_PERF_HANG_MAX 默认 4(每 boot 快照上限)
|
||
* MEOW_PERF_SNAP_DIR 默认 <卡>/MeowOS/logs;无卡降级 /tmp
|
||
* 测试钩子:MEOW_PERF_SNAP_TEST_LOAD(伪造 loadavg[1],宿主触发高负载用)
|
||
* ===================================================================== */
|
||
|
||
#define SNAP_LOG_DIR_REL "MeowOS/logs"
|
||
#define SNAP_CARD_DEFAULT "/usr/data/mnt/sd_0"
|
||
#define SNAP_TMP_DIR "/tmp"
|
||
#define SNAP_BOOTTS_LEN 64
|
||
#define SNAP_PATH_MAX 512
|
||
#define SNAP_NAME_MAX 32
|
||
#define SNAP_PROC_MAX 128 /* /proc 遍历上限 + cpu 增量元表上限 */
|
||
#define SNAP_TOP_N 12 /* 基线/snapshot 展示前 N */
|
||
#define SNAP_LINE_MAX 1024
|
||
#define SNAP_BASELINE_MAX (1u << 20) /* perf_ 单文件上限 1MB,超则新开一轮 */
|
||
#define SNAP_TICK_MS 60000 /* 无基线时 hang 检测的兜底节拍 */
|
||
|
||
static int snap_enabled = 1;
|
||
static long snap_baseline_ms = 60000;
|
||
static double snap_hang_load = 3.0;
|
||
static int snap_hang_consec = 2;
|
||
static long snap_hang_cooldown = 300000;
|
||
static int snap_hang_max = 4;
|
||
static int snap_test_load_on = 0;
|
||
static double snap_test_load = 0.0;
|
||
static char snap_snap_dir[320] = ""; /* 显式覆盖(--snap-dir/env);空=卡默认 */
|
||
|
||
static char snap_card[256] = SNAP_CARD_DEFAULT;
|
||
static char snap_dir[320] = ""; /* 实际 logs 目录(卡可用时) */
|
||
static int snap_on_card = 0;
|
||
static char snap_bootts[SNAP_BOOTTS_LEN] = "";
|
||
|
||
static long snap_step_last_ms = 0;
|
||
static int snap_load_hot = 0;
|
||
static long snap_last_hang_ms = 0;
|
||
static int snap_hang_count = 0;
|
||
static long snap_page_kb = 4;
|
||
|
||
/* 每进程 cpu 增量元表(pcpu=Δ(utime+stime)/tick) */
|
||
typedef struct { unsigned pid; unsigned long long utime_stime; } snap_cpu_prev_t;
|
||
static snap_cpu_prev_t snap_prev[SNAP_PROC_MAX];
|
||
static int snap_prev_n = 0;
|
||
|
||
static long now_ms(void)
|
||
{
|
||
struct timespec ts;
|
||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
return (long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||
}
|
||
|
||
/* 设备侧配置回退(/usr/data/meow/perf_snap.conf,key=value)——svcd 只传 args= 无法注入 env,
|
||
* 故另立配置文件让「可配置开关」在真机生效。优先级:文件 < env < CLI 旗标。 */
|
||
#define SNAP_CFG_FILE "/usr/data/meow/perf_snap.conf"
|
||
|
||
static void snap_config_load_file(void)
|
||
{
|
||
const char *cfg = getenv("MEOW_PERF_SNAP_CFG");
|
||
if (!cfg || !cfg[0]) cfg = SNAP_CFG_FILE;
|
||
FILE *f = fopen(cfg, "r");
|
||
if (!f) return;
|
||
char line[128];
|
||
while (fgets(line, sizeof line, f)) {
|
||
char *eq = strchr(line, '=');
|
||
if (!eq) continue;
|
||
char *k = line; int kl = (int)strlen(line);
|
||
while (kl > 0 && (k[kl-1] == '\n' || k[kl-1] == '\r' || k[kl-1] == ' ' || k[kl-1] == '\t')) k[--kl] = 0;
|
||
*eq = 0;
|
||
char *v = eq + 1; while (*v == ' ' || *v == '\t') v++;
|
||
if (!strcmp(k, "snap_enabled")) snap_enabled = atoi(v) ? 1 : 0;
|
||
else if (!strcmp(k, "baseline_ms")) snap_baseline_ms = atol(v);
|
||
else if (!strcmp(k, "hang_load")) snap_hang_load = atof(v);
|
||
else if (!strcmp(k, "hang_consec")) snap_hang_consec = atoi(v);
|
||
else if (!strcmp(k, "cooldown_ms")) snap_hang_cooldown = atol(v);
|
||
else if (!strcmp(k, "hang_max")) snap_hang_max = atoi(v);
|
||
else if (!strcmp(k, "snap_dir")) snprintf(snap_snap_dir, sizeof snap_snap_dir, "%s", v);
|
||
}
|
||
fclose(f);
|
||
}
|
||
|
||
static void snap_config_load(void)
|
||
{
|
||
const char *e;
|
||
snap_config_load_file(); /* 设备侧配置(低了 env 一级) */
|
||
if ((e = getenv("MEOW_PERF_SNAP")) && e[0]) snap_enabled = atoi(e) ? 1 : 0;
|
||
if ((e = getenv("MEOW_PERF_BASELINE_MS")) && e[0]) snap_baseline_ms = atol(e);
|
||
if ((e = getenv("MEOW_PERF_HANG_LOAD")) && e[0]) snap_hang_load = atof(e);
|
||
if ((e = getenv("MEOW_PERF_HANG_CONSEC")) && e[0]) snap_hang_consec = atoi(e);
|
||
if ((e = getenv("MEOW_PERF_HANG_COOLDOWN_MS")) && e[0]) snap_hang_cooldown = atol(e);
|
||
if ((e = getenv("MEOW_PERF_HANG_MAX")) && e[0]) snap_hang_max = atoi(e);
|
||
if ((e = getenv("MEOW_PERF_SNAP_DIR")) && e[0]) {
|
||
snprintf(snap_snap_dir, sizeof snap_snap_dir, "%s", e);
|
||
}
|
||
if ((e = getenv("MEOW_PERF_SNAP_TEST_LOAD")) && e[0]) {
|
||
snap_test_load_on = 1;
|
||
snap_test_load = atof(e);
|
||
}
|
||
long sz = sysconf(_SC_PAGESIZE);
|
||
if (sz > 0) snap_page_kb = sz / 1024;
|
||
if (snap_page_kb <= 0) snap_page_kb = 4;
|
||
}
|
||
|
||
static int snap_card_available(void)
|
||
{
|
||
if (snap_snap_dir[0]) {
|
||
/* 显式覆盖(宿主测试/生产改向):视为普通目录 */
|
||
(void)snap_snap_dir; return 0; /* 由 snap_setup 用覆盖目录 */
|
||
}
|
||
FILE *f = fopen("/proc/mounts", "r");
|
||
if (!f) return 0;
|
||
char line[512];
|
||
char needle[320];
|
||
snprintf(needle, sizeof needle, " %s ", snap_card);
|
||
int found = 0;
|
||
while (fgets(line, sizeof line, f))
|
||
if (strstr(line, needle)) { found = 1; break; }
|
||
fclose(f);
|
||
return found;
|
||
}
|
||
|
||
static void snap_boot_timestamp(char *out, size_t n)
|
||
{
|
||
out[0] = 0;
|
||
int fd = open("/tmp/meow_bootts", O_RDONLY);
|
||
if (fd >= 0) {
|
||
ssize_t r = read(fd, out, (ssize_t)n - 1);
|
||
close(fd);
|
||
if (r > 0) {
|
||
out[r] = 0;
|
||
if (strncmp(out, "meow_", 5) == 0) memmove(out, out + 5, r - 4); /* 剥 launcher 前缀 */
|
||
if (out[0]) return;
|
||
}
|
||
}
|
||
time_t now = time(NULL);
|
||
FILE *f = fopen("/proc/uptime", "r");
|
||
double up = 0.0;
|
||
if (f) { if (fscanf(f, "%lf", &up) != 1) up = 0.0; fclose(f); }
|
||
struct tm tm;
|
||
time_t boot = (time_t)((double)now - up + 0.5);
|
||
localtime_r(&boot, &tm);
|
||
strftime(out, n, "%Y%m%d_%H%M%S", &tm);
|
||
char cached[96];
|
||
snprintf(cached, sizeof cached, "meow_%s", out);
|
||
fd = open("/tmp/meow_bootts", O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||
if (fd >= 0) { ssize_t w = write(fd, cached, strlen(cached)); (void)w; close(fd); }
|
||
}
|
||
|
||
/* 定位/打开 logs 目录 + bootts。返回 0=就绪。 */
|
||
static int snap_setup(void)
|
||
{
|
||
if (snap_snap_dir[0]) {
|
||
snprintf(snap_dir, sizeof snap_dir, "%s", snap_snap_dir);
|
||
snap_on_card = 1;
|
||
(void)mkdir(snap_dir, 0755);
|
||
} else if (snap_card_available()) {
|
||
snprintf(snap_dir, sizeof snap_dir, "%s/%s", snap_card, SNAP_LOG_DIR_REL);
|
||
snap_on_card = 1;
|
||
(void)mkdir(snap_dir, 0755);
|
||
} else {
|
||
snprintf(snap_dir, sizeof snap_dir, "%s", SNAP_TMP_DIR);
|
||
snap_on_card = 0;
|
||
}
|
||
snap_boot_timestamp(snap_bootts, sizeof snap_bootts);
|
||
return 0;
|
||
}
|
||
|
||
/* 以 <name> 在 logs 目录开一个追加文件(卡可用写卡;否则 /tmp)。返回 fd(<0=失败)。 */
|
||
static int snap_open_append(const char *name)
|
||
{
|
||
char path[SNAP_PATH_MAX];
|
||
snprintf(path, sizeof path, "%s/%s", snap_dir, name);
|
||
return open(path, O_WRONLY | O_APPEND | O_CREAT, 0644);
|
||
}
|
||
|
||
static void snap_wr(int fd, const char *s)
|
||
{
|
||
if (fd < 0) return;
|
||
ssize_t w = write(fd, s, strlen(s));
|
||
(void)w;
|
||
}
|
||
|
||
static void snap_close(int fd)
|
||
{
|
||
if (fd >= 0) close(fd);
|
||
}
|
||
|
||
/* ---- 每进程采集(单文件读,无 popen) ---- */
|
||
typedef struct {
|
||
unsigned pid;
|
||
double pcpu;
|
||
long rss_kb;
|
||
int threads;
|
||
char comm[SNAP_NAME_MAX];
|
||
} snap_proc_t;
|
||
static snap_proc_t g_proc[SNAP_PROC_MAX];
|
||
static int g_proc_n = 0;
|
||
|
||
static int snap_read_stat(const char *fmt, char *out, int cap)
|
||
{
|
||
int fd = open(fmt, O_RDONLY);
|
||
if (fd < 0) return -1;
|
||
ssize_t n = read(fd, out, (size_t)cap - 1);
|
||
close(fd);
|
||
if (n <= 0) return -1;
|
||
out[n] = 0;
|
||
return (int)n;
|
||
}
|
||
|
||
static void snap_load(double *l1, double *l5, double *l15)
|
||
{
|
||
loadavg(l1, l5, l15);
|
||
if (snap_test_load_on) *l1 = snap_test_load;
|
||
}
|
||
|
||
/* 收集 /proc 进程快照:pcpu(相对上次)、rss_kB、线程数、comm。返回进程数。 */
|
||
static int snap_proc_collect(long tick_ms)
|
||
{
|
||
DIR *d = opendir("/proc");
|
||
if (!d) return -1;
|
||
g_proc_n = 0;
|
||
double hz = sysconf(_SC_CLK_TCK);
|
||
if (hz <= 0) hz = 100;
|
||
double dsec = (double)tick_ms / 1000.0;
|
||
if (dsec <= 0) dsec = 1.0;
|
||
|
||
struct dirent *e;
|
||
while ((e = readdir(d)) != NULL && g_proc_n < SNAP_PROC_MAX) {
|
||
if (e->d_name[0] < '0' || e->d_name[0] > '9') continue;
|
||
unsigned pid = (unsigned)atoi(e->d_name);
|
||
if (pid <= 1) continue;
|
||
char path[SNAP_PATH_MAX];
|
||
char buf[512];
|
||
snprintf(path, sizeof path, "/proc/%u/stat", pid);
|
||
if (snap_read_stat(path, buf, sizeof buf) <= 0) continue;
|
||
|
||
const char *lp = strchr(buf, '(');
|
||
const char *rp = strrchr(buf, ')');
|
||
if (!lp || !rp || rp <= lp) continue;
|
||
const char *p = rp + 1;
|
||
while (*p == ' ') p++;
|
||
|
||
/* 字段(stat 已按 1 基编号,此处自 ') ' 后 0 基):0=state 11=utime 12=stime
|
||
* 17=num_threads 21=rss(页) */
|
||
char toks[40][16];
|
||
int nt = 0;
|
||
while (*p && nt < 40) {
|
||
int i = 0;
|
||
while (*p == ' ') p++;
|
||
if (!*p) break;
|
||
while (*p && *p != ' ' && i < 15) toks[nt][i++] = *p++;
|
||
toks[nt][i] = 0;
|
||
nt++;
|
||
}
|
||
if (nt <= 21) continue;
|
||
|
||
unsigned long long utime = strtoull(toks[11], NULL, 10);
|
||
unsigned long long stime = strtoull(toks[12], NULL, 10);
|
||
int threads = atoi(toks[17]);
|
||
long rss_pages = strtol(toks[21], NULL, 10);
|
||
|
||
snap_proc_t *sp = &g_proc[g_proc_n];
|
||
sp->pid = pid;
|
||
sp->threads = threads > 0 ? threads : 1;
|
||
sp->rss_kb = rss_pages * snap_page_kb;
|
||
size_t cl = (size_t)(rp - lp - 1);
|
||
if (cl >= SNAP_NAME_MAX) cl = SNAP_NAME_MAX - 1;
|
||
memcpy(sp->comm, lp + 1, cl);
|
||
sp->comm[cl] = 0;
|
||
|
||
unsigned long long cur = utime + stime;
|
||
unsigned long long prev = 0;
|
||
int had = 0;
|
||
for (int i = 0; i < snap_prev_n; i++)
|
||
if (snap_prev[i].pid == pid) { prev = snap_prev[i].utime_stime; had = 1; break; }
|
||
sp->pcpu = had ? (100.0 * (double)(cur - prev) / (hz * dsec)) : 0.0;
|
||
if (sp->pcpu < 0) sp->pcpu = 0;
|
||
|
||
int found = 0;
|
||
for (int i = 0; i < snap_prev_n; i++)
|
||
if (snap_prev[i].pid == pid) { snap_prev[i].utime_stime = cur; found = 1; break; }
|
||
if (!found && snap_prev_n < SNAP_PROC_MAX) {
|
||
snap_prev[snap_prev_n].pid = pid;
|
||
snap_prev[snap_prev_n].utime_stime = cur;
|
||
snap_prev_n++;
|
||
}
|
||
g_proc_n++;
|
||
}
|
||
closedir(d);
|
||
return g_proc_n;
|
||
}
|
||
|
||
/* 按 pcpu 降序取前 topn,写成 "pid:pct:rsskb:comm" 以 ';' 分隔(comm 空格/控制转 '_')。 */
|
||
static void snap_proc_line(char *out, int cap, int topn)
|
||
{
|
||
int i, j;
|
||
for (i = 0; i < g_proc_n; i++) {
|
||
int im = i;
|
||
for (j = i + 1; j < g_proc_n; j++)
|
||
if (g_proc[j].pcpu > g_proc[im].pcpu) im = j;
|
||
if (im != i) { snap_proc_t t = g_proc[i]; g_proc[i] = g_proc[im]; g_proc[im] = t; }
|
||
}
|
||
int o = 0;
|
||
out[0] = 0;
|
||
for (i = 0; i < g_proc_n && i < topn && o < cap - 2; i++) {
|
||
int r = snprintf(out + o, (size_t)(cap - o), "%s%u:%.1f:%ld:%s",
|
||
i ? ";" : "", g_proc[i].pid, g_proc[i].pcpu, g_proc[i].rss_kb, g_proc[i].comm);
|
||
if (r < 0 || o + r >= cap) break;
|
||
for (int k = o; k < o + r; k++) if (out[k] == ' ' || out[k] == '\n' || out[k] == '\t') out[k] = '_';
|
||
o += r;
|
||
}
|
||
if (o == 0 && cap > 0) out[0] = 0;
|
||
}
|
||
|
||
/* ---- socket 汇总(/proc/net,单文件读) ---- */
|
||
typedef struct { int unix_total, unix_conn, unix_audio; int tcp_est, tcp_total, udp; } snap_sock_t;
|
||
|
||
/* 数 /proc/net/<file> 行(跳过头行)。mincols>0 时要求字段数>=mincols 才计。 */
|
||
static int snap_count_net(const char *file, int mincols)
|
||
{
|
||
char path[192];
|
||
snprintf(path, sizeof path, "/proc/net/%s", file);
|
||
int fd = open(path, O_RDONLY);
|
||
if (fd < 0) return 0;
|
||
char b[8192];
|
||
ssize_t n = read(fd, b, sizeof b - 1);
|
||
close(fd);
|
||
if (n <= 0) return 0;
|
||
b[n] = 0;
|
||
int cnt = 0, first = 1;
|
||
char *p = b;
|
||
while (*p) {
|
||
char *nl = strchr(p, '\n');
|
||
if (nl) *nl = 0;
|
||
if (!first) {
|
||
/* 计字段:非空 token 数 */
|
||
int tok = 0; char *q = p;
|
||
while (*q) { while (*q == ' ' || *q == '\t') q++; if (!*q) break; tok++; while (*q && *q != ' ' && *q != '\t') q++; }
|
||
if (tok >= mincols) cnt++;
|
||
}
|
||
first = 0;
|
||
if (!nl) break;
|
||
p = nl + 1;
|
||
}
|
||
return cnt;
|
||
}
|
||
|
||
static void snap_sock_collect(snap_sock_t *s)
|
||
{
|
||
memset(s, 0, sizeof *s);
|
||
s->unix_total = snap_count_net("unix", 8);
|
||
s->tcp_total = snap_count_net("tcp", 4);
|
||
s->tcp_est = 0;
|
||
s->udp = snap_count_net("udp", 4);
|
||
s->unix_conn = 0;
|
||
s->unix_audio = 0;
|
||
|
||
/* 细读 unix:连通(St=03) 计数 + 路径含 meow_audio(audiod IPC 客户端数) */
|
||
int fd = open("/proc/net/unix", O_RDONLY);
|
||
if (fd >= 0) {
|
||
char b[16384];
|
||
ssize_t n = read(fd, b, sizeof b - 1);
|
||
close(fd);
|
||
if (n > 0) {
|
||
b[n] = 0;
|
||
char *p = b;
|
||
int first = 1;
|
||
while (*p && first < 0x7fffffff) {
|
||
char *nl = strchr(p, '\n');
|
||
if (nl) *nl = 0;
|
||
if (!first) {
|
||
char *tok[8]; int nt = 0; char *q = p;
|
||
while (*q && nt < 8) {
|
||
while (*q == ' ' || *q == '\t') q++;
|
||
if (!*q) break;
|
||
tok[nt++] = q;
|
||
while (*q && *q != ' ' && *q != '\t') q++;
|
||
}
|
||
if (nt >= 7) {
|
||
if (strcmp(tok[5], "03") == 0) s->unix_conn++;
|
||
if (nt >= 8 && strstr(tok[7], "meow_audio") != NULL) s->unix_audio++;
|
||
}
|
||
}
|
||
first = 0;
|
||
if (!nl) break;
|
||
p = nl + 1;
|
||
}
|
||
}
|
||
}
|
||
/* tcp 状态:每行第 4 列(st)== "01" 为 ESTABLISHED */
|
||
fd = open("/proc/net/tcp", O_RDONLY);
|
||
if (fd >= 0) {
|
||
char b[16384];
|
||
ssize_t n = read(fd, b, sizeof b - 1);
|
||
close(fd);
|
||
if (n > 0) {
|
||
b[n] = 0; char *p = b; int first = 1;
|
||
while (*p) {
|
||
char *nl = strchr(p, '\n'); if (nl) *nl = 0;
|
||
if (!first) {
|
||
char *tok[5]; int nt = 0; char *q = p;
|
||
while (*q && nt < 5) {
|
||
while (*q == ' ' || *q == '\t') q++;
|
||
if (!*q) break;
|
||
tok[nt++] = q;
|
||
while (*q && *q != ' ' && *q != '\t') q++;
|
||
}
|
||
if (nt >= 4 && strcmp(tok[3], "01") == 0) s->tcp_est++;
|
||
}
|
||
first = 0;
|
||
if (!nl) break;
|
||
p = nl + 1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ---- 写基线行 perf_<bootts>.log ---- */
|
||
static void snap_write_baseline(double l1, double l5, double l15, int cores, double cpu,
|
||
unsigned long mtot, unsigned long mused, unsigned long mfree,
|
||
unsigned long mavail, unsigned long swapT, unsigned long swapU)
|
||
{
|
||
char top[512];
|
||
snap_proc_line(top, sizeof top, SNAP_TOP_N);
|
||
char line[SNAP_LINE_MAX];
|
||
snprintf(line, sizeof line,
|
||
"%ld %ld load=%.2f/%.2f/%.2f cores=%d cpu=%.1f mem=%lu/%lu/%lu/%lu swap=%lu/%lu top=%s\n",
|
||
(long)time(NULL), (long)uptime_sec(), l1, l5, l15, cores, cpu,
|
||
mtot, mused, mfree, mavail, swapT, swapU, top);
|
||
|
||
char name[SNAP_BOOTTS_LEN + 16];
|
||
snprintf(name, sizeof name, "perf_%s.log", snap_bootts);
|
||
/* 单文件 1MB 上限:超则新开一轮(截断重来,防无限涨;基线是周期追加,重写可接受) */
|
||
int fd = snap_open_append(name);
|
||
if (fd < 0) return;
|
||
struct stat st;
|
||
if (fstat(fd, &st) == 0 && st.st_size > (long long)SNAP_BASELINE_MAX) {
|
||
/* 已超上限:关闭,截断重开(只留本次) */
|
||
close(fd);
|
||
fd = -1;
|
||
} else {
|
||
snap_wr(fd, line);
|
||
snap_close(fd);
|
||
return;
|
||
}
|
||
char path[SNAP_PATH_MAX];
|
||
snprintf(path, sizeof path, "%s/%s", snap_dir, name);
|
||
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||
if (fd >= 0) { snap_wr(fd, line); snap_close(fd); }
|
||
}
|
||
|
||
/* ---- 写完整 hang 快照 hang_<bootts>_<epoch>.snap ---- */
|
||
static void snap_write_hang(double l1, double l5, double l15, int cores, double cpu,
|
||
unsigned long mtot, unsigned long mused, unsigned long mfree,
|
||
unsigned long mavail, unsigned long swapT, unsigned long swapU)
|
||
{
|
||
snap_sock_t sk;
|
||
snap_sock_collect(&sk);
|
||
|
||
char name[SNAP_BOOTTS_LEN + 48];
|
||
snprintf(name, sizeof name, "hang_%s_%ld.snap", snap_bootts, (long)time(NULL));
|
||
int fd = snap_open_append(name);
|
||
if (fd < 0) return;
|
||
|
||
char b[SNAP_LINE_MAX];
|
||
snprintf(b, sizeof b, "=== hang snapshot bootts=%s epoch=%ld uptime=%ld cores=%d ===\n",
|
||
snap_bootts, (long)time(NULL), (long)uptime_sec(), cores);
|
||
snap_wr(fd, b);
|
||
snprintf(b, sizeof b, "loadavg: %.2f %.2f %.2f cpu: %.1f%%\n", l1, l5, l15, cpu);
|
||
snap_wr(fd, b);
|
||
snprintf(b, sizeof b, "mem: MemTotal=%luKB MemFree=%luKB MemAvailable=%luKB used=%luKB"
|
||
" SwapTotal=%luKB SwapUsed=%luKB\n", mtot, mfree, mavail, mused, swapT, swapU);
|
||
snap_wr(fd, b);
|
||
snap_wr(fd, "--- top processes (by cpu) ---\n");
|
||
if (g_proc_n > 0) {
|
||
int i, j;
|
||
for (i = 0; i < g_proc_n; i++) {
|
||
int im = i;
|
||
for (j = i + 1; j < g_proc_n; j++)
|
||
if (g_proc[j].pcpu > g_proc[im].pcpu) im = j;
|
||
if (im != i) { snap_proc_t t = g_proc[i]; g_proc[i] = g_proc[im]; g_proc[im] = t; }
|
||
}
|
||
for (i = 0; i < g_proc_n && i < SNAP_TOP_N; i++) {
|
||
snprintf(b, sizeof b, " %u %.1f%% %ldKB nthreads=%d %s\n",
|
||
g_proc[i].pid, g_proc[i].pcpu, g_proc[i].rss_kb, g_proc[i].threads, g_proc[i].comm);
|
||
snap_wr(fd, b);
|
||
}
|
||
} else {
|
||
snap_wr(fd, " (none)\n");
|
||
}
|
||
/* 关键进程(launcher/audiod/ncm/netmgrd/svcd/panel)VmRSS+线程 */
|
||
snap_wr(fd, "--- key processes ---\n");
|
||
static const char *keys[] = {"meow_launcherd", "meow_audiod", "meow_netmgrd",
|
||
"meow_swvc", "meow_svc", "ncm", "player", "meowpanel", NULL};
|
||
int shown = 0;
|
||
for (int k = 0; keys[k]; k++) {
|
||
for (int i = 0; i < g_proc_n; i++) {
|
||
if (strcmp(g_proc[i].comm, keys[k]) == 0) {
|
||
snprintf(b, sizeof b, " %s pid=%u VmRSS=%ldKB threads=%d pcpu=%.1f%%\n",
|
||
keys[k], g_proc[i].pid, g_proc[i].rss_kb, g_proc[i].threads, g_proc[i].pcpu);
|
||
snap_wr(fd, b);
|
||
shown++;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (!shown) snap_wr(fd, " (none matched)\n");
|
||
snprintf(b, sizeof b,
|
||
"--- sockets --- unix=%d conn=%d audiod_clients=%d tcp_est=%d tcp_total=%d udp=%d\n",
|
||
sk.unix_total, sk.unix_conn, sk.unix_audio, sk.tcp_est, sk.tcp_total, sk.udp);
|
||
snap_wr(fd, b);
|
||
snap_close(fd);
|
||
fprintf(stderr, "[meow_svc_perf] hang snapshot written: %s/%s\n", snap_dir, name);
|
||
}
|
||
|
||
/* ---- 单步:采集(含 load 覆写钩子)+ 写基线 + 评估 hang 触发。返回 1=本次写了快照 ---- */
|
||
static int snap_step(long tick_ms, int *p_cores)
|
||
{
|
||
double l1, l5, l15;
|
||
snap_load(&l1, &l5, &l15);
|
||
int cores = core_count();
|
||
if (p_cores) *p_cores = cores;
|
||
double cpu = cpu_usage_pct();
|
||
if (cpu < 0) cpu = 0;
|
||
unsigned long mtot, mused, mfree, mavail, swapT, swapU;
|
||
meminfo(&mtot, &mused, &mfree, &mavail, &swapT, &swapU);
|
||
snap_proc_collect(tick_ms);
|
||
|
||
if (snap_baseline_ms > 0)
|
||
snap_write_baseline(l1, l5, l15, cores, cpu, mtot, mused, mfree, mavail, swapT, swapU);
|
||
|
||
int wrote = 0;
|
||
if (l1 >= snap_hang_load) snap_load_hot++;
|
||
else snap_load_hot = 0;
|
||
|
||
long now_ms_v = now_ms();
|
||
if (snap_load_hot >= snap_hang_consec &&
|
||
(now_ms_v - snap_last_hang_ms) >= snap_hang_cooldown &&
|
||
snap_hang_count < snap_hang_max) {
|
||
snap_write_hang(l1, l5, l15, cores, cpu, mtot, mused, mfree, mavail, swapT, swapU);
|
||
snap_load_hot = 0;
|
||
snap_last_hang_ms = now_ms_v;
|
||
snap_hang_count++;
|
||
wrote = 1;
|
||
}
|
||
return wrote;
|
||
}
|
||
|
||
/* 守护进程内的节拍入口:按 snap_step 节拍做采集/评估(时间门控)。 */
|
||
static void snap_monitor_tick(void)
|
||
{
|
||
if (!snap_enabled) return;
|
||
long tick = snap_baseline_ms > 0 ? snap_baseline_ms : SNAP_TICK_MS;
|
||
if (tick < 1000) tick = 1000;
|
||
long now = now_ms();
|
||
if (now - snap_step_last_ms < tick) return;
|
||
snap_step_last_ms = now;
|
||
snap_step(tick, NULL);
|
||
}
|
||
|
||
static int run_daemon(const char *sock)
|
||
{
|
||
signal(SIGPIPE, SIG_IGN);
|
||
cpu_usage_pct(); /* 预热:设 /proc/stat 基线,首次查询即有值 */
|
||
int lfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
if (lfd < 0) { svc_log("socket fail: %s", strerror(errno)); return 1; }
|
||
struct sockaddr_un a;
|
||
memset(&a, 0, sizeof a);
|
||
a.sun_family = AF_UNIX;
|
||
strncpy(a.sun_path, sock, sizeof a.sun_path - 1);
|
||
unlink(sock);
|
||
if (bind(lfd, (struct sockaddr *)&a, sizeof a) < 0) { svc_log("bind %s fail: %s", sock, strerror(errno)); close(lfd); return 1; }
|
||
chmod(sock, 0666);
|
||
if (listen(lfd, 8) < 0) { svc_log("listen fail: %s", strerror(errno)); close(lfd); return 1; }
|
||
svc_log("meow_svc_perf listening on %s", sock);
|
||
|
||
/* 卡死快照/RMM 基线:复用本进程,不新起 daemon。失败(无卡)降级 /tmp 仍可用。 */
|
||
snap_config_load();
|
||
snap_setup();
|
||
svc_log("snap: enabled=%d baseline_ms=%ld hang_load=%.1f consec=%d cooldown_ms=%ld max=%d dir=%s%s",
|
||
snap_enabled, snap_baseline_ms, snap_hang_load, snap_hang_consec,
|
||
snap_hang_cooldown, snap_hang_max, snap_dir, snap_on_card ? "" : " (no card -> /tmp)");
|
||
|
||
/* page-cache C 侧降载监控(③混合:C 归本服务);写共享状态文件 MEOW_CACHEGOV_C_FILE。 */
|
||
cachegov_config_load();
|
||
cachegov_monitor_tick();
|
||
svc_log("cachegov: halt=%ldkB resume=%ldkB dirty_warn=%ldkB state_file=%s",
|
||
g_cg_halt_kb, g_cg_resume_kb, g_cg_dirty_warn_kb, g_cg_file);
|
||
|
||
/* poll 化主循环:IPC 服务语义不变,仅以超时让位给监控节拍(低频、限时、可关)。 */
|
||
for (;;) {
|
||
struct pollfd pfd = { lfd, POLLIN, 0 };
|
||
int r = poll(&pfd, 1, 500);
|
||
if (r < 0) { if (errno == EINTR) continue; svc_log("poll fail: %s", strerror(errno)); break; }
|
||
if (pfd.revents & (POLLIN | POLLERR | POLLHUP)) {
|
||
int c = accept(lfd, NULL, NULL);
|
||
if (c < 0) { if (errno == EINTR) continue; continue; }
|
||
handle_conn(c);
|
||
close(c);
|
||
}
|
||
snap_monitor_tick(); /* 自带宽限期(默认 60s),不阻塞 IPC */
|
||
cachegov_monitor_tick(); /* page-cache C 降载(500ms 节拍,轻量单文件读) */
|
||
}
|
||
close(lfd);
|
||
return 0;
|
||
}
|
||
|
||
/* ---------------- 快照 CLI(snap / snap-check) ---------------- */
|
||
|
||
/* 手动快照:立即采集并写一份完整 hang 快照(不受触发门槛),诊断/测试用。 */
|
||
static int snap_cli_snap(void)
|
||
{
|
||
snap_config_load();
|
||
snap_setup();
|
||
double l1, l5, l15;
|
||
snap_load(&l1, &l5, &l15);
|
||
int cores = core_count();
|
||
double cpu = cpu_usage_pct(); if (cpu < 0) cpu = 0;
|
||
unsigned long mtot, mused, mfree, mavail, swapT, swapU;
|
||
meminfo(&mtot, &mused, &mfree, &mavail, &swapT, &swapU);
|
||
snap_proc_collect(1000);
|
||
snap_write_hang(l1, l5, l15, cores, cpu, mtot, mused, mfree, mavail, swapT, swapU);
|
||
printf("snap dir=%s bootts=%s\n", snap_dir, snap_bootts);
|
||
return 0;
|
||
}
|
||
|
||
/* 确定性宿主测试:伪造 load(MEOW_PERF_SNAP_TEST_LOAD)跑 consec+1 步,必落基线 + 1 张快照。 */
|
||
static int snap_cli_check(void)
|
||
{
|
||
snap_config_load();
|
||
if (!snap_test_load_on) { snap_test_load = snap_hang_load; snap_test_load_on = 1; } /* 未注入则用阈值直接触发 */
|
||
snap_setup();
|
||
int steps = snap_hang_consec + 1;
|
||
if (steps < 2) steps = 2;
|
||
int wrote = 0;
|
||
for (int i = 0; i < steps; i++) {
|
||
if (snap_step(1000, NULL)) wrote = 1;
|
||
}
|
||
printf("snap-check: dir=%s bootts=%s steps=%d fake_load=%.2f wrote_snapshot=%d\n",
|
||
snap_dir, snap_bootts, steps, snap_test_load, wrote);
|
||
return wrote ? 0 : 1;
|
||
}
|
||
|
||
/* ---------------- CLI ---------------- */
|
||
/* 手动 C 降载评估:读真 /proc/meminfo Cached,落共享状态文件。 */
|
||
static int cachegov_cli(void)
|
||
{
|
||
cachegov_config_load();
|
||
long cached, dirty;
|
||
cachegov_read_cached(&cached, &dirty);
|
||
cachegov_monitor_step(cached, dirty);
|
||
printf("cachegov: cached=%ld dirty=%ld dl_pause=%d prefetch_pause=%d file=%s\n",
|
||
cached, dirty, g_cg_dl_pause, g_cg_pf_pause, g_cg_file);
|
||
return 0;
|
||
}
|
||
|
||
/* 确定性宿主测试:注入 MEOW_CACHEGOV_TEST_CACHED_KB(/proc/meminfo 被注入值替代),
|
||
* 单步评估并落共享状态文件,供 test_cachegov_monitor 断言阈值/滞回/降载。
|
||
* MEOW_CACHEGOV_TEST_SEQ="35000,20000,28000" 时按序多步评估(同一进程,测滞回保持)。 */
|
||
static int cachegov_cli_check(void)
|
||
{
|
||
cachegov_config_load();
|
||
const char *seq = getenv("MEOW_CACHEGOV_TEST_SEQ");
|
||
if (seq && seq[0]) {
|
||
char buf[256];
|
||
snprintf(buf, sizeof buf, "%s", seq);
|
||
char *save = NULL, *tok = strtok_r(buf, ",", &save);
|
||
int idx = 0;
|
||
while (tok) {
|
||
long v = atol(tok), dirty = 0;
|
||
cachegov_monitor_step(v, dirty);
|
||
printf("cachegov-check[%d]: cached=%ld dl_pause=%d prefetch_pause=%d file=%s\n",
|
||
idx++, v, g_cg_dl_pause, g_cg_pf_pause, g_cg_file);
|
||
tok = strtok_r(NULL, ",", &save);
|
||
}
|
||
return 0;
|
||
}
|
||
long cached = g_cg_test_cached_kb >= 0 ? g_cg_test_cached_kb : 0;
|
||
long dirty = g_cg_test_dirty_kb >= 0 ? g_cg_test_dirty_kb : 0;
|
||
cachegov_monitor_step(cached, dirty);
|
||
printf("cachegov-check: cached=%ld dirty=%ld dl_pause=%d prefetch_pause=%d file=%s\n",
|
||
cached, dirty, g_cg_dl_pause, g_cg_pf_pause, g_cg_file);
|
||
return 0;
|
||
}
|
||
|
||
static int cli_run(int n, char **v)
|
||
{
|
||
int subset = 0;
|
||
if (n >= 1) {
|
||
if (!strcmp(v[0], "perf")) subset = PERF_SUBSET_PERF;
|
||
else if (!strcmp(v[0], "system")) subset = PERF_SUBSET_SYSTEM;
|
||
else if (!strcmp(v[0], "all") || !strcmp(v[0], "-")) subset = 0;
|
||
else { fprintf(stderr, "usage: meow_svc_perf [all|perf|system]\n"); return 1; }
|
||
}
|
||
cpu_usage_pct(); /* 预热基线 */
|
||
usleep(100000); /* 采样间隔,取 delta */
|
||
char json[4096];
|
||
agg_json(subset, json, sizeof json);
|
||
puts(json);
|
||
return 0;
|
||
}
|
||
|
||
static const char *cli_usage(void)
|
||
{
|
||
return "meow_svc_perf [all|perf|system|snap|snap-check|cachegov|cachegov-check] (CLI 免鉴权;无参=守护)";
|
||
}
|
||
|
||
int main(int argc, char **argv)
|
||
{
|
||
const char *env = getenv("MEOW_IPC_PERF_SOCK");
|
||
if (env && env[0]) g_sock = env;
|
||
int i = 1;
|
||
for (; i < argc; i++) {
|
||
if (!strcmp(argv[i], "--sock") && i + 1 < argc) g_sock = argv[++i];
|
||
else if (!strcmp(argv[i], "--snap-dir") && i + 1 < argc) snprintf(snap_snap_dir, sizeof snap_snap_dir, "%s", argv[++i]);
|
||
else if (!strcmp(argv[i], "--card") && i + 1 < argc) snprintf(snap_card, sizeof snap_card, "%s", argv[++i]);
|
||
else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { puts(cli_usage()); return 0; }
|
||
else break;
|
||
}
|
||
if (i >= argc) return run_daemon(g_sock); /* svcd 空参 → IPC 常驻 */
|
||
if (!strcmp(argv[i], "snap")) return snap_cli_snap();
|
||
if (!strcmp(argv[i], "snap-check")) return snap_cli_check();
|
||
if (!strcmp(argv[i], "cachegov")) return cachegov_cli();
|
||
if (!strcmp(argv[i], "cachegov-check")) return cachegov_cli_check();
|
||
return cli_run(argc - i, argv + i);
|
||
}
|