649 lines
24 KiB
C
649 lines
24 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;
|
||
}
|
||
|
||
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"); 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);
|
||
}
|
||
|
||
/* ---------------- 聚合 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);
|
||
SEP(); sA(&s, "\"memory\":{\"total\":%lu,\"used\":%lu,\"free\":%lu,\"available\":%lu,\"swap_total\":%lu,\"swap_used\":%lu}",
|
||
mtot, mused, mfree, mavail, swapT, swapU);
|
||
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;
|
||
}
|
||
}
|
||
|
||
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);
|
||
for (;;) {
|
||
int c = accept(lfd, NULL, NULL);
|
||
if (c < 0) { if (errno == EINTR) continue; continue; }
|
||
handle_conn(c);
|
||
close(c);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* ---------------- CLI ---------------- */
|
||
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] (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], "-h") || !strcmp(argv[i], "--help")) { puts(cli_usage()); return 0; }
|
||
else break;
|
||
}
|
||
if (i >= argc) return run_daemon(g_sock); /* svcd 空参 → IPC 常驻 */
|
||
return cli_run(argc - i, argv + i);
|
||
}
|