Download src/proxy/captcha-cpu-governor.ts from bolikoto/zcode-api: direct link, hf CLI and curl.
- Browser
- Download file 6.13 kB
-
https://huggingface.co/spaces/bolikoto/zcode-api/resolve/main/src/proxy/captcha-cpu-governor.ts
- Command line
-
hf download hf://spaces/bolikoto/zcode-api/src/proxy/captcha-cpu-governor.ts
-
curl -L -o captcha-cpu-governor.ts https://huggingface.co/spaces/bolikoto/zcode-api/resolve/main/src/proxy/captcha-cpu-governor.ts
6.13 kB
| import fs from "node:fs/promises"; | |
| import os from "node:os"; | |
| export type CpuGovernorConfig = { | |
| /** Host CPU ceiling — 100 = all vCPUs saturated (0 disables governor). */ | |
| cpuLimitPercent: number; | |
| /** Hysteresis band below limit before ramping up again. */ | |
| hysteresisPercent: number; | |
| poolSizeMin: number; | |
| poolSizeMax: number; | |
| maxSolveConcurrency: number; | |
| intervalMs: number; | |
| }; | |
| export type CpuGovernorSnapshot = { | |
| cpuPercent: number; | |
| limitPercent: number; | |
| concurrency: number; | |
| maxEffectiveTarget: number; | |
| throttled: boolean; | |
| }; | |
| /** 100 = entire host (all vCPUs) may be used; governor prevents exceeding that. */ | |
| const DEFAULT_CPU_LIMIT = Number(process.env.CAPTCHA_CPU_LIMIT_PCT || 100); | |
| const DEFAULT_INTERVAL_MS = Number(process.env.CAPTCHA_CPU_GOVERNOR_INTERVAL_MS || 2_000); | |
| const TARGET_STEP = 15; | |
| type CpuCounters = { idle: number; total: number }; | |
| async function readCpuCounters(): Promise<CpuCounters> { | |
| try { | |
| const stat = await fs.readFile("/proc/stat", "utf8"); | |
| const line = stat.split("\n")[0] ?? ""; | |
| const parts = line.split(/\s+/).slice(1).map((v) => Number(v) || 0); | |
| const idle = (parts[3] ?? 0) + (parts[4] ?? 0); | |
| const total = parts.reduce((sum, n) => sum + n, 0); | |
| return { idle, total }; | |
| } catch { | |
| const load = os.loadavg()[0] ?? 0; | |
| const cpus = Math.max(1, os.cpus().length); | |
| const pct = (load / cpus) * 100; | |
| return { idle: 100 - pct, total: 100 }; | |
| } | |
| } | |
| /** Samples host-wide CPU via /proc/stat (100% = all cores busy). */ | |
| export class CaptchaCpuGovernor { | |
| private prevCpu: CpuCounters | null = null; | |
| private lastCpuPercent = 0; | |
| private concurrency: number; | |
| private maxEffectiveTarget: number; | |
| private timer: ReturnType<typeof setInterval> | null = null; | |
| constructor(private cfg: CpuGovernorConfig) { | |
| this.concurrency = 1; | |
| this.maxEffectiveTarget = cfg.poolSizeMin; | |
| } | |
| get enabled(): boolean { | |
| return this.cfg.cpuLimitPercent > 0; | |
| } | |
| configure(partial: Partial<CpuGovernorConfig>): void { | |
| this.cfg = { ...this.cfg, ...partial }; | |
| this.maxEffectiveTarget = Math.max( | |
| this.cfg.poolSizeMin, | |
| Math.min(this.maxEffectiveTarget, this.cfg.poolSizeMax), | |
| ); | |
| this.concurrency = Math.min(this.concurrency, this.cfg.maxSolveConcurrency); | |
| } | |
| start(): void { | |
| if (!this.enabled || this.timer) return; | |
| void this.tick(); | |
| this.timer = setInterval(() => void this.tick(), this.cfg.intervalMs); | |
| } | |
| stop(): void { | |
| if (this.timer) { | |
| clearInterval(this.timer); | |
| this.timer = null; | |
| } | |
| } | |
| snapshot(): CpuGovernorSnapshot { | |
| const limit = this.cfg.cpuLimitPercent; | |
| return { | |
| cpuPercent: Math.round(this.lastCpuPercent * 10) / 10, | |
| limitPercent: limit, | |
| concurrency: this.concurrency, | |
| maxEffectiveTarget: this.maxEffectiveTarget, | |
| throttled: this.lastCpuPercent >= limit - 3, | |
| }; | |
| } | |
| /** Parallel background solves allowed right now (may be 0 when pool is warm). */ | |
| backgroundConcurrency(readyTokens: number): number { | |
| if (!this.enabled) return this.cfg.maxSolveConcurrency; | |
| const cpu = this.lastCpuPercent; | |
| const limit = this.cfg.cpuLimitPercent; | |
| let allowed = this.concurrency; | |
| if (cpu >= limit) { | |
| allowed = 0; | |
| } else if (cpu >= limit - 4) { | |
| allowed = Math.min(allowed, 1); | |
| } else if (cpu >= limit - 10) { | |
| allowed = Math.min(allowed, 2); | |
| } | |
| if (readyTokens < this.cfg.poolSizeMin) { | |
| return Math.max(1, allowed); | |
| } | |
| return allowed; | |
| } | |
| maxPoolTarget(): number { | |
| return this.maxEffectiveTarget; | |
| } | |
| private async tick(): Promise<void> { | |
| const cpu = await this.sampleCpuPercent(); | |
| this.lastCpuPercent = cpu; | |
| const limit = this.cfg.cpuLimitPercent; | |
| const rampThreshold = limit - this.cfg.hysteresisPercent; | |
| if (cpu >= limit) { | |
| this.concurrency = 1; | |
| this.maxEffectiveTarget = this.cfg.poolSizeMin; | |
| } else if (cpu >= limit - 4) { | |
| if (this.concurrency > 1) this.concurrency -= 1; | |
| this.maxEffectiveTarget = this.cfg.poolSizeMin; | |
| } else if (cpu >= limit - 10) { | |
| if (this.concurrency > 2) this.concurrency -= 1; | |
| this.maxEffectiveTarget = Math.max( | |
| this.cfg.poolSizeMin, | |
| this.maxEffectiveTarget - TARGET_STEP, | |
| ); | |
| } else if (cpu <= rampThreshold) { | |
| if (this.concurrency < this.cfg.maxSolveConcurrency) { | |
| this.concurrency += 1; | |
| } | |
| if (this.maxEffectiveTarget < this.cfg.poolSizeMax) { | |
| this.maxEffectiveTarget = Math.min( | |
| this.cfg.poolSizeMax, | |
| this.maxEffectiveTarget + TARGET_STEP, | |
| ); | |
| } | |
| } | |
| } | |
| private async sampleCpuPercent(): Promise<number> { | |
| const cur = await readCpuCounters(); | |
| if (!this.prevCpu) { | |
| this.prevCpu = cur; | |
| return this.lastCpuPercent; | |
| } | |
| const idleDelta = cur.idle - this.prevCpu.idle; | |
| const totalDelta = cur.total - this.prevCpu.total; | |
| this.prevCpu = cur; | |
| if (totalDelta <= 0) return this.lastCpuPercent; | |
| const used = ((totalDelta - idleDelta) / totalDelta) * 100; | |
| return Math.max(0, Math.min(100, used)); | |
| } | |
| } | |
| export function resolveCpuGovernorConfig(opts: { | |
| poolSizeMin?: number; | |
| poolSizeMax?: number; | |
| solveConcurrency?: number; | |
| cpuLimitPercent?: number; | |
| cpuGovernorIntervalSec?: number; | |
| }): CpuGovernorConfig { | |
| const poolSizeMin = opts.poolSizeMin ?? Number(process.env.CAPTCHA_POOL_MIN || 40); | |
| const poolSizeMax = Math.max( | |
| poolSizeMin, | |
| opts.poolSizeMax ?? Number(process.env.CAPTCHA_POOL_MAX || 120), | |
| ); | |
| const maxSolveConcurrency = | |
| opts.solveConcurrency ?? | |
| Number( | |
| process.env.CAPTCHA_SOLVE_CONCURRENCY || | |
| (process.env.ZCODE_CAPTCHA_LOW_CPU === "1" ? 3 : 6), | |
| ); | |
| const cpuLimitPercent = | |
| opts.cpuLimitPercent ?? | |
| (process.env.CAPTCHA_CPU_GOVERNOR === "0" ? 0 : DEFAULT_CPU_LIMIT); | |
| const intervalMs = | |
| opts.cpuGovernorIntervalSec != null | |
| ? opts.cpuGovernorIntervalSec * 1000 | |
| : DEFAULT_INTERVAL_MS; | |
| return { | |
| cpuLimitPercent, | |
| hysteresisPercent: 5, | |
| poolSizeMin, | |
| poolSizeMax, | |
| maxSolveConcurrency, | |
| intervalMs, | |
| }; | |
| } | |