mslxdff 0.1.15 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mslxdff.js +43 -0
- package/package.json +1 -1
package/bin/mslxdff.js
CHANGED
|
@@ -200,6 +200,19 @@ if (groupCmd && groupCmd !== "create") {
|
|
|
200
200
|
if (names.length) {
|
|
201
201
|
for (const n of names) {
|
|
202
202
|
console.log(`${n} (${Object.keys(all[n].members || {}).length} members)`);
|
|
203
|
+
// probe every member endpoint concurrently to show reachability + latency
|
|
204
|
+
const probes = Object.entries(all[n].members || {}).map(([id, m]) => ({
|
|
205
|
+
id, url: m?.url,
|
|
206
|
+
}));
|
|
207
|
+
const joined = loadGroupsJoined().find((g) => g.name === n);
|
|
208
|
+
if (joined?.leaderUrl) probes.push({ id: "leader", url: joined.leaderUrl });
|
|
209
|
+
const results = await Promise.all(probes.map(probeHealth));
|
|
210
|
+
const rows = results.sort((a, b) => (a.rank ?? 9) - (b.rank ?? 9));
|
|
211
|
+
for (const r of rows) {
|
|
212
|
+
const state = r.fail ? ` fail ${r.fail}` : ` ok ${r.ms}ms`;
|
|
213
|
+
const via = r.alt ? " (v1/health)" : "";
|
|
214
|
+
console.log(` ${String(r.id || "?")} ${r.url}${state}${via}`);
|
|
215
|
+
}
|
|
203
216
|
}
|
|
204
217
|
} else {
|
|
205
218
|
console.log("no groups on this node");
|
|
@@ -273,6 +286,36 @@ function markJoined(entry) {
|
|
|
273
286
|
|
|
274
287
|
function errMsg(err) { return String(err?.message || err); }
|
|
275
288
|
|
|
289
|
+
// Probe a member's health endpoint concurrently (v1/health preferred,
|
|
290
|
+
// falling back to /health). Returns { id, url, ms, rank } or { id, url, fail }.
|
|
291
|
+
const HEALTH_TIMEOUT_MS = 4000;
|
|
292
|
+
async function probeHealth({ id, url } = {}) {
|
|
293
|
+
if (!url) return { id, url, fail: "no url", rank: 3 };
|
|
294
|
+
const forBase = async (base) => {
|
|
295
|
+
const target = `${base}/v1/health`;
|
|
296
|
+
const startedAt = Date.now();
|
|
297
|
+
try {
|
|
298
|
+
const res = await fetch(target, { signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS) });
|
|
299
|
+
if (!res.ok) return { ok: false, alt: true };
|
|
300
|
+
return { ok: true, alt: false, ms: Date.now() - startedAt, rank: 0 };
|
|
301
|
+
} catch {
|
|
302
|
+
return { ok: false, alt: true };
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
const base = String(url).replace(/\/+$/, "");
|
|
306
|
+
const first = await forBase(base);
|
|
307
|
+
if (first.ok) return { id, url: base, ms: first.ms, rank: 0 };
|
|
308
|
+
// v1/health 404/refused → try plain /health (older nodes)
|
|
309
|
+
const startedAt = Date.now();
|
|
310
|
+
try {
|
|
311
|
+
const res = await fetch(`${base}/health`, { signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS) });
|
|
312
|
+
if (res.ok) return { id, url: base, ms: Date.now() - startedAt, rank: 1, via: "health" };
|
|
313
|
+
} catch {
|
|
314
|
+
// fall through
|
|
315
|
+
}
|
|
316
|
+
return { id, url: base, fail: "unreachable", rank: 2 };
|
|
317
|
+
}
|
|
318
|
+
|
|
276
319
|
function readModelsCache(cacheFile) {
|
|
277
320
|
try {
|
|
278
321
|
return JSON.parse(readFileSync(cacheFile, "utf8"));
|