CH-J Server Managerserver management over SSH
Menu
Published source

CH-J Server Manager

Browse directories and files for a specific application release.

Download source ZIP
CH-J Proprietary Software License 1.14

Source is provided under the CH-J Proprietary Software License 1.14. Its availability does not change the license terms or grant additional rights.

2,9 KB · 61 linesDownload file
1"use strict";
3const { execFile } = require("node:child_process");
4const net = require("node:net");
5const { performance } = require("node:perf_hooks");
7function parsePing(stdout) {
8 // Parse the reply, not the average or a process duration. Windows localizes
9 // "time", so accept the numeric '=…ms' field without relying on that word.
10 const match = String(stdout).match(/(?:time\s*[=<]|[=<])\s*(\d+(?:[.,]\d+)?)\s*ms\b/i);
11 if (!match) return null;
12 const value = Number(match[1].replace(",", "."));
13 // '<1ms' is an upper bound, not an exact measurement.
14 return Number.isFinite(value) && value > 0 && !/<\s*\d+(?:[.,]\d+)?\s*ms/i.test(stdout) ? value : null;
17function measurePing(address, { signal, platform = process.platform, run = execFile } = {}) {
18 if (!net.isIP(address) || !["win32", "darwin", "linux"].includes(platform)) return Promise.resolve(null);
19 const ipv6 = net.isIP(address) === 6;
20 const executable = platform === "darwin" ? (ipv6 ? "/sbin/ping6" : "/sbin/ping") : platform === "win32" ? "ping.exe" : "ping";
21 const args = platform === "win32" ? [ipv6 ? "-6" : "-4", "-n", "1", "-w", "4000", address]
22 : platform === "darwin" ? ["-n", "-c", "1", ...(ipv6 ? [] : ["-W", "4000"]), address]
23 : [ipv6 ? "-6" : "-4", "-n", "-c", "1", "-W", "4", address];
24 return new Promise((resolve) => {
25 run(executable, args, { shell: false, windowsHide: true, timeout: 5000, maxBuffer: 32 * 1024, signal, env: { ...process.env, LC_ALL: "C", LANG: "C" } },
26 (error, stdout) => resolve(error ? null : parsePing(stdout)));
27 });
30class LatencyMonitor {
31 constructor({ ping = measurePing, now = () => performance.now(), intervalMs = 12000 } = {}) {
32 this.ping = ping;
33 this.now = now;
34 this.intervalMs = intervalMs;
35 }
37 start(record, request, publish) {
38 const controller = new AbortController();
39 let timer;
40 let stopped = false;
41 const sample = async () => {
42 const started = this.now();
43 const ssh = Promise.resolve().then(request).then(() => {
44 const elapsed = this.now() - started;
45 return Number.isFinite(elapsed) && elapsed > 0 ? elapsed : null;
46 }).catch(() => null);
47 const ping = Promise.resolve().then(() => this.ping(record.address, { signal: controller.signal })).catch(() => null);
48 const [pingMs, sshRttMs] = await Promise.all([ping, ssh]);
49 if (stopped) return;
50 const valid = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
51 publish({ pingMs: valid(pingMs), sshRttMs: valid(sshRttMs), measuredAt: new Date().toISOString() });
52 // Schedule only after both finish: no overlapping probes or intervals.
53 timer = setTimeout(sample, this.intervalMs);
54 timer.unref?.();
55 };
56 void sample();
57 return () => { stopped = true; clearTimeout(timer); controller.abort(); };
58 }
61module.exports = { LatencyMonitor, measurePing, parsePing };

SHA-256: 750792eb3fda2fd855a5fdbfa87ead97a99f5fe4e2adc12f0c37488961f795ef

Archive SHA-256: 5ac91caf4fa32a6fdb114f2430deed486fbe7489d5eea343d1f034169fafb5e0