1.77.6: SERP sorry-重定向按验证码语义(不换号+15min冷却);浏览池保温释放跳过+60min持有上限;速率-门壳实验与监控工具
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
/** # 脚本说明
|
||||
* 压测采样器(与发射器并行跑,同机 root):
|
||||
* 每 20s 扫描 /proc:
|
||||
* 1) 按 --user-data-dir 路径的指纹段聚合「每浏览器进程树」:
|
||||
* 进程数 / RSS 合计 / swap 合计 / CPU 累计(utime+stime, ms)
|
||||
* 2) 系统快照:MemAvailable / swap 已用 / loadavg / 浏览器树总数
|
||||
* 3) OOM 保护:非浏览器进程(且非本采样器自身)写 oom_score_adj=-600,
|
||||
* 让内核 OOM 时优先杀「易杀」的浏览器(发射器已把新浏览器主进程置 200)
|
||||
* 写 data/<label>-stats.jsonl(每轮一行 JSON)。
|
||||
*
|
||||
* 用法:bun sampler.ts --label test1 [--interval 20]
|
||||
*/
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
const args = process.argv.slice(2);
|
||||
function argOf(k: string, d: string): string { const i = args.indexOf(k); return i >= 0 ? args[i + 1] : d; }
|
||||
const LABEL = argOf("--label", "test1");
|
||||
const INTERVAL = Number(argOf("--interval", "20")) * 1000;
|
||||
const BASE = new URL(".", import.meta.url).pathname;
|
||||
const OUT = `${BASE}data/${LABEL}-stats.jsonl`;
|
||||
|
||||
/** # 成员变量 本采样器自身进程组(不碰 oom_score_adj) **/
|
||||
const SELF_PIDS = new Set([process.pid, process.ppid ?? 0]);
|
||||
|
||||
/** # 成员变量 每浏览器树快照类型 **/
|
||||
type Tree = { fp: string; procs: number; rssKB: number; pssKB: number; swapKB: number; cpuMs: number; userDataDir: string };
|
||||
|
||||
/** # 函数:一轮 /proc 扫描,聚合浏览器进程树并返回系统快照 **/
|
||||
function scanOnce(): { trees: Map<string, Tree>; availMB: number; swapUsedMB: number; load: number[]; cpuCount: number } {
|
||||
const trees = new Map<string, Tree>();
|
||||
const mySig = `run-stress.ts`; // 自身及其 bash 父链保护
|
||||
const txt = fs.readFileSync("/proc/meminfo", "utf8");
|
||||
const get = (k: string) => Number(new RegExp(`${k}:\\s+(\\d+)`).exec(txt)?.[1] ?? 0);
|
||||
const cpuCount = osCpus();
|
||||
const load = fs.readFileSync("/proc/loadavg", "utf8").split(/\s+/).slice(0, 3).map(Number);
|
||||
|
||||
for (const pid of fs.readdirSync("/proc").filter((d) => /^\d+$/.test(d))) {
|
||||
let cmd: string;
|
||||
try { cmd = fs.readFileSync(`/proc/${pid}/cmdline`, "utf8").replace(/\0/g, " "); } catch { continue; }
|
||||
const udd = /--user-data-dir=(\S+)/.exec(cmd)?.[1] ?? null;
|
||||
if (udd) {
|
||||
// 目录形如 .../data/profiles/<fp>/browser,指纹取「browser」的父目录名
|
||||
const seg = udd.replace(/\/browser\/?$/, "").split("/").pop() ?? udd.split("/").pop() ?? "?";
|
||||
const fp = seg;
|
||||
const st = readStat(pid);
|
||||
const t = trees.get(fp) ?? { fp, procs: 0, rssKB: 0, pssKB: 0, swapKB: 0, cpuMs: 0, userDataDir: udd };
|
||||
t.procs++;
|
||||
t.rssKB += st.rssKB;
|
||||
t.pssKB += st.pssKB;
|
||||
t.swapKB += st.swapKB;
|
||||
t.cpuMs += st.cpuMs;
|
||||
trees.set(fp, t);
|
||||
// 浏览器树整体保持「易杀」(主进程已设 200,子进程继承;这里给漏网进程补救)
|
||||
try { fs.writeFileSync(`/proc/${pid}/oom_score_adj`, "200"); } catch { /* ignore */ }
|
||||
} else {
|
||||
// 非浏览器进程:OOM 保护(排除自身/内核线程写失败忽略)
|
||||
const p = Number(pid);
|
||||
if (SELF_PIDS.has(p)) continue;
|
||||
if (cmd.includes(mySig) || cmd.includes("sampler.ts") || cmd.includes("run-stress")) continue;
|
||||
try { fs.writeFileSync(`/proc/${pid}/oom_score_adj`, "-600"); } catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
const swapUsedMB = (get("SwapTotal") - get("SwapFree")) / 1024;
|
||||
const availMB = get("MemAvailable") / 1024;
|
||||
return { trees, availMB, swapUsedMB, load, cpuCount };
|
||||
}
|
||||
|
||||
/** # 函数:读单进程 RSS/PSS/swap/CPU(kB、ms) **/
|
||||
function readStat(pid: string): { rssKB: number; pssKB: number; swapKB: number; cpuMs: number } {
|
||||
let rssKB = 0, pssKB = 0, swapKB = 0, cpuMs = 0;
|
||||
try {
|
||||
const s = fs.readFileSync(`/proc/${pid}/stat`, "utf8");
|
||||
// stat 第 14/15 列为 utime/stime(tick);comm 含空格,取最后一个 ')' 之后按列号回算
|
||||
const tail = s.slice(s.lastIndexOf(")") + 2).split(" ");
|
||||
const utime = Number(tail[11]) || 0, stime = Number(tail[12]) || 0;
|
||||
cpuMs = ((utime + stime) * 1000) / 100; // CLK_TCK=100
|
||||
} catch { /* 进程已消失 */ }
|
||||
try {
|
||||
// smaps_rollup 是内核快速汇总:Pss = 独占内存(去共享重复),Swap 含交换的共享分摊
|
||||
const roll = fs.readFileSync(`/proc/${pid}/smaps_rollup`, "utf8");
|
||||
pssKB = Number(/^Pss:\s+(\d+)/m.exec(roll)?.[1] ?? 0);
|
||||
rssKB = Number(/^Rss:\s+(\d+)/m.exec(roll)?.[1] ?? 0);
|
||||
const swapPss = Number(/^SwapPss:\s+(\d+)/m.exec(roll)?.[1] ?? 0);
|
||||
const swapDirect = Number(/^Swap:\s+(\d+)/m.exec(roll)?.[1] ?? 0);
|
||||
swapKB = swapPss || swapDirect;
|
||||
} catch { /* ignore */ }
|
||||
return { rssKB, pssKB, swapKB, cpuMs };
|
||||
}
|
||||
|
||||
/** # 函数:CPU 核数 **/
|
||||
function osCpus(): number {
|
||||
try { return require("os").cpus().length; } catch { return 8; }
|
||||
}
|
||||
|
||||
// 主循环
|
||||
while (true) {
|
||||
const t0 = Date.now();
|
||||
const { trees, availMB, swapUsedMB, load, cpuCount } = scanOnce();
|
||||
const treesArr = [...trees.values()];
|
||||
const line = JSON.stringify({
|
||||
ts: new Date().toISOString(),
|
||||
browsers: treesArr.length,
|
||||
totalRssMB: +(treesArr.reduce((a, b) => a + b.rssKB, 0) / 1024).toFixed(1),
|
||||
totalPssMB: +(treesArr.reduce((a, b) => a + b.pssKB, 0) / 1024).toFixed(1),
|
||||
totalSwapMB: +(treesArr.reduce((a, b) => a + b.swapKB, 0) / 1024).toFixed(1),
|
||||
totalProcs: treesArr.reduce((a, b) => a + b.procs, 0),
|
||||
availMB: +availMB.toFixed(1), swapUsedMB: +swapUsedMB.toFixed(1),
|
||||
load, cpuCount,
|
||||
detail: treesArr,
|
||||
});
|
||||
try { fs.appendFileSync(OUT, line + "\n"); } catch { /* ignore */ }
|
||||
const wait = Math.max(0, INTERVAL - (Date.now() - t0));
|
||||
await new Promise((r) => setTimeout(r, wait));
|
||||
}
|
||||
Reference in New Issue
Block a user