CH-J Server Manager通过 SSH 管理服务器
菜单
已发布的源代码

CH-J Server Manager

浏览特定应用版本的目录和文件。

下载源代码 ZIP
CH-J / Server Manager 377 个文件
core-20261019T111616Z-160a00bfcddc 添加时间: · 构建时间:
processTools.js 添加时间: 未记录日期 12.4 KB · 128 行
代码 下载文件
1"use strict";
2const { spawn } = require("node:child_process");
3const net = require("node:net");
4const { setTimeout: delay } = require("node:timers/promises");
5const { DiagnosticError, checkAbort, stats, now } = require("./common");
6const { parsePing } = require("../sessions/latencyMonitor");
7function runProcess(executable, args, { timeoutMs = 5000, signal, onLine, maxBytes = 256 * 1024, spawnImpl = spawn, env } = {}) {
8 checkAbort(signal);
9 return new Promise((resolve, reject) => {
10 const child = spawnImpl(executable, args, { shell: false, windowsHide: true, env: env || { ...process.env, LC_ALL: "C", LANG: "C" }, stdio: ["ignore", "pipe", "pipe"] });
11 let stdout = [], stderr = [], bytes = 0, pending = "", failure, settled = false, killTimer;
12 const terminate = (error) => { failure ||= error; child.kill(); killTimer ||= setTimeout(() => child.kill("SIGKILL"), 1000); killTimer.unref?.(); };
13 const abort = () => terminate(new DiagnosticError("CANCELLED"));
14 const timer = setTimeout(() => terminate(new DiagnosticError("TIMEOUT")), timeoutMs);
15 signal?.addEventListener("abort", abort, { once: true });
16 const finish = (error, code) => { if (settled) return; settled = true; clearTimeout(timer); clearTimeout(killTimer); signal?.removeEventListener("abort", abort); if (pending && onLine) onLine(pending); error || failure ? reject(error || failure) : resolve({ code, stdout: Buffer.concat(stdout), stderr: Buffer.concat(stderr).toString("utf8") }); };
17 child.once("error", (error) => finish(error));
18 child.stdout.on("data", (chunk) => { bytes += chunk.length; if (bytes > maxBytes) return terminate(new DiagnosticError("OUTPUT_LIMIT")); stdout.push(chunk); if (onLine) { pending += chunk.toString("utf8"); const lines = pending.split(/\r?\n/); pending = lines.pop(); lines.forEach(onLine); } });
19 child.stderr.on("data", (chunk) => { bytes += chunk.length; if (bytes > maxBytes) return terminate(new DiagnosticError("OUTPUT_LIMIT")); stderr.push(chunk); });
20 child.once("close", (code) => finish(null, code));
21 });
23// Optional ICMP tuning is passed only when set, so OS defaults stay unchanged.
24// Don't-fragment applies to IPv4 only; IPv6 routers never fragment in transit.
25function pingCommand(address, timeoutMs, platform = process.platform, tuning = {}) {
26 if (!net.isIP(address)) throw new DiagnosticError("INVALID_IP");
27 const v6 = net.isIP(address) === 6, { packetSize = null, ttl = null, dontFragment = false } = tuning;
28 const size = (flag) => Number.isInteger(packetSize) ? [flag, String(packetSize)] : [];
29 const hops = (flag) => Number.isInteger(ttl) ? [flag, String(ttl)] : [];
30 const df = (...flags) => dontFragment && !v6 ? flags : [];
31 if (platform === "win32") return { executable: "ping.exe", args: [v6 ? "-6" : "-4", "-n", "1", "-w", String(timeoutMs), ...size("-l"), ...hops("-i"), ...df("-f"), address] };
32 if (platform === "darwin") return { executable: v6 ? "/sbin/ping6" : "/sbin/ping", args: ["-n", "-c", "1", ...(v6 ? [] : ["-W", String(timeoutMs)]), ...size("-s"), ...hops(v6 ? "-h" : "-m"), ...df("-D"), address] };
33 if (platform === "linux") return { executable: "ping", args: [v6 ? "-6" : "-4", "-n", "-c", "1", "-W", String(Math.ceil(timeoutMs / 1000)), ...size("-s"), ...hops("-t"), ...df("-M", "do"), address] };
34 throw new DiagnosticError("PLATFORM_UNSUPPORTED");
36function parsePingReply(output) {
37 const rttMs = parsePing(output), upper = String(output).match(/<\s*(\d+(?:[.,]\d+)?)\s*ms/i), ttl = String(output).match(/(?:ttl|hlim|hop limit)[=:\s]+(\d+)/i);
38 return { received: rttMs !== null || Boolean(upper), rttMs, upperBoundMs: upper ? Number(upper[1].replace(",", ".")) : null, ttl: ttl ? Number(ttl[1]) : null };
40const MAX_PING_IN_FLIGHT = 32, MAX_CONSECUTIVE_PING_ERRORS = 50;
41const CONFIG_ERROR = /not permitted|permission denied|invalid option|unknown option|invalid argument|out of range|illegal|usage:/i;
42const TRANSIENT_ERROR = /network is unreachable|no route to host|general failure|transmit failed|no buffer space|resource temporarily unavailable|host is down/i;
43// Every probe is an independent single-packet OS ping, so sub-second intervals
44// need no root/flood privileges on any platform. Probes overlap up to a bound;
45// the OS tool measures each RTT itself, so spawn jitter does not skew latency.
46async function ping(address, options, signal, progress, runner = runProcess) {
47 const intervalMs = options.intervalMs || 1000, timeoutMs = options.pingTimeoutMs || options.timeoutMs;
48 const tuning = { packetSize: options.packetSize, ttl: options.ttl, dontFragment: options.dontFragment };
49 const total = options.continuous ? Infinity : options.count * options.repetitions;
50 const maxInFlight = Math.max(1, Math.min(MAX_PING_IN_FLIGHT, Math.ceil((timeoutMs + 500) / intervalMs) + 1));
51 const packets = [], rtts = [], inFlight = new Set();
52 let sent = 0, received = 0, launched = 0, throttled = 0, errorPackets = 0, consecutiveErrors = 0, failure = null;
53 const summary = () => ({ sent, received, errorPackets, lossPercent: sent ? 100 * (sent - received) / sent : null, statistics: stats(rtts) });
54 const probe = async (sequence) => {
55 let packet;
56 try {
57 const command = pingCommand(address, timeoutMs, process.platform, tuning);
58 const result = await runner(command.executable, command.args, { timeoutMs: timeoutMs + 500, signal });
59 const raw = result.stdout.toString("utf8"); const reply = parsePingReply(raw);
60 const explicitTimeout = /Request timed out|Request timeout|Zeitüberschreitung|Vypršel.*časov[ýy].*limit/i.test(raw);
61 // Successful replies keep a short raw line; failures keep more for diagnosis (bounded IPC/history size).
62 packet = { sequence, address, ...reply, status: reply.received ? "success" : explicitTimeout ? "timeout" : "no-reply", raw: (raw + result.stderr).slice(0, reply.received ? 300 : 4096) };
63 if (!reply.received && (result.code > (process.platform === "darwin" ? 2 : 1) || CONFIG_ERROR.test(result.stderr) || TRANSIENT_ERROR.test(result.stderr))) {
64 packet.status = "error"; packet.fatal = CONFIG_ERROR.test(result.stderr);
65 }
66 } catch (error) {
67 if (signal.aborted) return;
68 packet = { sequence, address, received: false, rttMs: null, status: error.code === "ENOENT" ? "unavailable" : error.code === "TIMEOUT" ? "timeout" : "error", code: error.code, raw: String(error.message).slice(0, 400) };
69 }
70 if (signal.aborted || failure) return;
71 packet.measuredAt = new Date().toISOString();
72 // A missing tool, an option/permission problem, or an error before any probe settled
73 // stops the run. Later transient errors (no buffer space, route flaps, ARP/Hello
74 // failures) count as lost packets, so a long run is not discarded by one bad probe.
75 const fatal = packet.status === "unavailable" || packet.status === "error" && (packet.fatal || sent === 0 || consecutiveErrors + 1 >= MAX_CONSECUTIVE_PING_ERRORS);
76 delete packet.fatal;
77 if (fatal) { if (packet.status === "error" && sent > 0) packet.code ||= "PING_REPEATED_ERRORS"; failure = packet; packets.push(packet); return; }
78 consecutiveErrors = packet.status === "error" ? consecutiveErrors + 1 : 0; if (packet.status === "error") errorPackets++;
79 sent++; if (packet.received) { received++; rtts[sequence - 1] = packet.rttMs; }
80 packets.push(packet); if (packets.length > 500) packets.shift();
81 progress({ kind: "ping-packet", ...packet, ...summary() });
82 };
83 const started = now(); let nextAt = started, lastLaunchAt = started;
84 try {
85 while (launched < total && !failure) {
86 checkAbort(signal);
87 if (inFlight.size >= maxInFlight) { throttled++; await Promise.race(inFlight); continue; }
88 const sequence = ++launched, task = probe(sequence).finally(() => inFlight.delete(task)); inFlight.add(task);
89 lastLaunchAt = now();
90 // Keep a steady cadence without a catch-up burst after the in-flight bound held probes back.
91 nextAt = Math.max(nextAt + intervalMs, lastLaunchAt - intervalMs / 2);
92 if (launched < total && nextAt > now()) await delay(nextAt - now(), undefined, { signal });
93 }
94 } finally { await Promise.allSettled(inFlight); }
95 checkAbort(signal);
96 packets.sort((a, b) => a.sequence - b.sequence);
97 const timing = { intervalMs, effectiveIntervalMs: launched > 1 ? (lastLaunchAt - started) / (launched - 1) : null, timeoutMs, maxInFlight, throttled, packetSize: options.packetSize ?? null, ttl: options.ttl ?? null, dontFragment: Boolean(options.dontFragment) };
98 if (failure) progress({ kind: "ping-packet", ...failure, ...summary() });
99 if (failure) return { status: failure.status, code: failure.code || "PING_TOOL_ERROR", reason: failure.raw, address, packets, ...summary(), timing, note: "ICMP failure does not establish server unavailability or prove a firewall." };
100 return { status: received ? "success" : "warning", address, packets, ...summary(), timing, note: "No ICMP reply may mean filtering, ICMP rate limiting or loss; it does not establish server unavailability." };
102function tracerouteCommand(address, options, platform = process.platform) {
103 const v6 = net.isIP(address) === 6;
104 if (!net.isIP(address)) throw new DiagnosticError("INVALID_IP");
105 if (platform === "win32") return { executable: "tracert.exe", args: [v6 ? "-6" : "-4", "-d", "-h", String(options.maxHops), "-w", String(options.timeoutMs), address] };
106 if (platform === "darwin") return { executable: v6 ? "/usr/sbin/traceroute6" : "/usr/sbin/traceroute", args: ["-n", "-m", String(options.maxHops), "-w", String(Math.ceil(options.timeoutMs / 1000)), address] };
107 if (platform === "linux") return { executable: "traceroute", args: [v6 ? "-6" : "-4", "-n", "-m", String(options.maxHops), "-w", String(options.timeoutMs / 1000), address] };
108 throw new DiagnosticError("PLATFORM_UNSUPPORTED");
110function parseHop(line) {
111 const match = String(line).match(/^\s*(\d{1,2})\s+(.+)/); if (!match) return null;
112 const tokens = match[2].split(/\s+/), addresses = tokens.map((token) => token.replace(/[()[\],]/g, "")).filter((value) => net.isIP(value));
113 const probes = [...match[2].matchAll(/(<\s*)?(\d+(?:[.,]\d+)?)\s*ms|\*/gi)].map((m) => m[0] === "*" ? { status: "timeout", rttMs: null } : { status: "success", rttMs: m[1] ? null : Number(m[2].replace(",", ".")), upperBoundMs: m[1] ? Number(m[2]) : null });
114 if (!probes.length && !addresses.length) return null;
115 return { hop: Number(match[1]), addresses: [...new Set(addresses)], probes, averageMs: stats(probes.map((p) => p.rttMs)).average, lossPercent: probes.length ? 100 * probes.filter((p) => p.status === "timeout").length / probes.length : null, raw: line.slice(0, 1024) };
117async function traceroute(address, options, signal, progress, runner = runProcess) {
118 const routes = [];
119 for (let iteration = 0; iteration < options.repetitions; iteration++) {
120 checkAbort(signal); const hops = [], command = tracerouteCommand(address, options);
121 try {
122 const result = await runner(command.executable, command.args, { signal, timeoutMs: Math.min(180000, options.maxHops * Math.ceil(options.timeoutMs / 1000) * 1000 * 3 + 2000), onLine(line) { const hop = parseHop(line); if (hop) { hops.push(hop); progress({ kind: "trace-hop", iteration, address, ...hop }); } } });
123 routes.push({ iteration, hops, exitCode: result.code, raw: result.stdout.toString("utf8").slice(0, 32000), reason: result.stderr.slice(0, 1000) });
124 } catch (error) { if (signal.aborted) throw error; if (hops.length) routes.push({ iteration, hops, incomplete: true, reason: error.message }); return { status: error.code === "ENOENT" ? "unavailable" : "error", code: error.code, reason: error.message, address, routes }; }
125 }
126 return { status: routes.some((route) => route.hops.some((hop) => hop.addresses.some((ip) => ip === address || net.isIP(ip) === 6 && net.isIP(address) === 6 && new URL(`http://[${ip}]/`).hostname === new URL(`http://[${address}]/`).hostname))) ? "success" : routes.every((route) => route.exitCode !== 0 && !route.hops.length) ? "error" : "warning", address, routes, effectiveProbeTimeoutMs: process.platform === "darwin" ? Math.ceil(options.timeoutMs / 1000) * 1000 : options.timeoutMs, routeChanged: routes.length > 1 ? routes.some((route) => JSON.stringify(route.hops.map((h) => h.addresses)) !== JSON.stringify(routes[0].hops.map((h) => h.addresses))) : null, method: process.platform === "win32" ? "ICMP tracert" : "System UDP traceroute", note: "Intermediate timeouts do not prove a broken route. Loss is the fraction of unanswered displayed probes, not end-to-end packet loss." };
128module.exports = { runProcess, pingCommand, parsePingReply, ping, tracerouteCommand, parseHop, traceroute };
SHA-256d305e248cd633473c5c2ee4b4e937bf2034b1279f88d96e0efd82f2868b2cb04

CH-J Proprietary Software License 1.14 · 源代码依据 CH-J Proprietary Software License 1.14 提供。公开源代码不会改变许可条款,也不会授予额外权利。

压缩包 SHA-256a9605c0870a223b6a8542bb0705edd51d8b04f3069582653417a8397fabcc09a
文件