CH-J Server Managerإدارة الخوادم عبر SSH
القائمة
المصدر المنشور

CH-J Server Manager

تصفّح الأدلة والملفات الخاصة بإصدار محدد من التطبيق.

تنزيل الشيفرة المصدرية بصيغة ZIP
CH-J / Server Manager 377 ملفات
core-20261019T111616Z-160a00bfcddc تاريخ الإضافة: · تاريخ البناء:
latencyMonitor.js تاريخ الإضافة: لم يُسجَّل التاريخ ٧٫٤ KB · 138 أسطر
الشيفرة تنزيل الملف
1"use strict";
3const { execFile } = require("node:child_process");
4const net = require("node:net");
5const { performance } = require("node:perf_hooks");
7const PING_COUNT = 3;
8// Reply lines only (they carry TTL/hlim); statistics lines such as "Minimum = 5ms" are ignored.
9const REPLY_LINE = /\b(?:ttl|hlim)\s*=\s*\d+/i;
10const REPLY_TIME = /([=<])\s*(\d+(?:[.,]\d+)?)\s*ms\b/i;
12function median(values) {
13 const sorted = [...values].sort((left, right) => left - right);
14 if (!sorted.length) return null;
15 const middle = Math.floor(sorted.length / 2);
16 return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
19// Parses per-reply times from macOS, Linux (iputils) and localized Windows ping output.
20// "<1ms" (Windows) is an upper bound and is reported as 1 ms with lessThan=true.
21function parsePingReplies(stdout) {
22 const replies = [];
23 for (const line of String(stdout || "").split(/\r?\n/)) {
24 if (!REPLY_LINE.test(line)) continue;
25 const match = line.match(REPLY_TIME);
26 if (!match) continue;
27 const value = Number(match[2].replace(",", "."));
28 if (Number.isFinite(value) && value >= 0) replies.push({ ms: match[1] === "<" ? Math.max(value, 1) : value, lessThan: match[1] === "<" });
29 }
30 return replies;
33// First exact reply time (kept for callers that need a single value).
34function parsePing(stdout) {
35 const reply = parsePingReplies(stdout).find((entry) => !entry.lessThan && entry.ms > 0);
36 return reply ? reply.ms : null;
39function classifyFailure(error, output) {
40 const text = `${output || ""}\n${error?.message || ""}`;
41 if (error?.code === "ENOENT") return { status: "unsupported", reason: "The ping utility is not available on this computer." };
42 if (/Operation not permitted|Permission denied|Access is denied|not allowed/i.test(text)) return { status: "denied", reason: "The operating system denied sending ICMP (for example the macOS Local Network permission or a security policy)." };
43 if (/unknown host|cannot resolve|could not find host|Name or service not known/i.test(text)) return { status: "error", reason: "The address could not be resolved for ping." };
44 if (/No route to host|Network is unreachable|Destination (host|net) unreachable|unreachable|nedosažiteln|nicht erreichbar/i.test(text)) return { status: "unreachable", reason: "The target is unreachable for ICMP (no route or an unreachable reply)." };
45 return { status: "timeout", reason: "No ICMP echo reply before the timeout; the server or a firewall may block ICMP." };
48function pingCommand(address, platform) {
49 const ipv6 = net.isIP(address) === 6;
50 if (platform === "win32") return { executable: "ping.exe", args: [ipv6 ? "-6" : "-4", "-n", String(PING_COUNT), "-w", "2000", address] };
51 if (platform === "darwin") return { executable: ipv6 ? "/sbin/ping6" : "/sbin/ping", args: ["-n", "-c", String(PING_COUNT), ...(ipv6 ? [] : ["-W", "2000"]), address] };
52 return { executable: "ping", args: [ipv6 ? "-6" : "-4", "-n", "-c", String(PING_COUNT), "-W", "2", address] };
55// Local ICMP echo from the CH-J computer to the address actually used by the SSH connection (IPv4 or IPv6).
56// Arguments are passed separately without a shell; no root or sudo is needed on macOS, Linux or Windows.
57function measurePing(address, { signal, platform = process.platform, run = execFile } = {}) {
58 const family = net.isIP(address);
59 if (!family) return Promise.resolve({ ms: null, status: "error", reason: "No IP address is known for this session.", family: null, sent: 0, received: 0, loss: null });
60 if (!["win32", "darwin", "linux"].includes(platform)) return Promise.resolve({ ms: null, status: "unsupported", reason: `Ping is not supported on ${platform}.`, family, sent: 0, received: 0, loss: null });
61 const { executable, args } = pingCommand(address, platform);
62 return new Promise((resolve) => {
63 run(executable, args, { shell: false, windowsHide: true, timeout: 9000, maxBuffer: 64 * 1024, signal, env: { ...process.env, LC_ALL: "C", LANG: "C" } },
64 (error, stdout, stderr) => {
65 const replies = parsePingReplies(stdout);
66 const received = Math.min(replies.length, PING_COUNT);
67 if (received) {
68 resolve({ ms: median(replies.map((reply) => reply.ms)), lessThan: replies.every((reply) => reply.lessThan), status: "ok", reason: "", family, sent: PING_COUNT, received, loss: (PING_COUNT - received) / PING_COUNT });
69 return;
70 }
71 if (error?.name === "AbortError") { resolve({ ms: null, status: "aborted", reason: "", family, sent: 0, received: 0, loss: null }); return; }
72 resolve({ ms: null, ...classifyFailure(error, `${stdout || ""}\n${stderr || ""}`), family, sent: PING_COUNT, received: 0, loss: error?.killed ? null : 1 });
73 });
74 });
77class LatencyMonitor {
78 constructor({ ping = measurePing, now = () => performance.now(), intervalMs = 10000, sshSamples = 3 } = {}) {
79 this.ping = ping;
80 this.now = now;
81 this.intervalMs = intervalMs;
82 this.sshSamples = sshSamples;
83 }
85 // request() performs one SSH round trip. It may resolve with { ms, method }; otherwise the elapsed
86 // monotonic time around the call is used. Samples run sequentially so channels are never flooded.
87 async _ssh(request) {
88 const samples = [];
89 let method = null;
90 let error = null;
91 for (let index = 0; index < this.sshSamples; index += 1) {
92 const started = this.now();
93 try {
94 const result = await request();
95 const ms = typeof result?.ms === "number" ? result.ms : this.now() - started;
96 method = result?.method || method || "command";
97 if (Number.isFinite(ms) && ms > 0) samples.push(ms);
98 } catch (cause) { error = cause; break; }
99 }
100 return { ms: median(samples), method, samples: samples.length, error: samples.length ? null : (error?.message || "SSH round trip failed") };
101 }
103 start(record, request, publish) {
104 const controller = new AbortController();
105 let timer;
106 let stopped = false;
107 const sample = async () => {
108 // ICMP and SSH are independent: a blocked ping never hides the SSH measurement and vice versa.
109 const [ping, ssh] = await Promise.all([
110 Promise.resolve().then(() => this.ping(record.address, { signal: controller.signal })).catch((error) => ({ ms: null, status: "error", reason: error?.message || String(error) })),
111 this._ssh(request)
112 ]);
113 if (stopped) return;
114 const pingResult = typeof ping === "number" ? { ms: ping, status: "ok" } : (ping || { ms: null, status: "timeout" });
115 const valid = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
116 publish({
117 pingMs: valid(pingResult.ms),
118 pingStatus: pingResult.status || "error",
119 pingReason: pingResult.reason || "",
120 pingLoss: typeof pingResult.loss === "number" ? pingResult.loss : null,
121 pingFamily: pingResult.family || net.isIP(record.address) || null,
122 pingLessThan: pingResult.lessThan === true,
123 sshRttMs: valid(ssh.ms),
124 sshRttMethod: ssh.method,
125 sshRttSamples: ssh.samples,
126 sshRttError: ssh.error,
127 measuredAt: new Date().toISOString()
128 });
129 // Schedule only after both finish: no overlapping probes or intervals.
130 timer = setTimeout(sample, this.intervalMs);
131 timer.unref?.();
132 };
133 void sample();
134 return () => { stopped = true; clearTimeout(timer); controller.abort(); };
135 }
138module.exports = { LatencyMonitor, PING_COUNT, measurePing, median, parsePing, parsePingReplies, pingCommand };
SHA-2563ed95d5d82915d7ca2cfe3849f4d4a2fc033b8990f31748a0f1e1804a87da163

CH-J Proprietary Software License 1.14 · تُتاح الشيفرة المصدرية بموجب CH-J Proprietary Software License 1.14. وإتاحتها لا تغيّر شروط الترخيص ولا تمنح حقوقًا إضافية.

SHA-256 للأرشيفa9605c0870a223b6a8542bb0705edd51d8b04f3069582653417a8397fabcc09a
الملفات