Skip to content

Commit 45f2d92

Browse files
committed
refactor(API): restructure /diagnostics output for clarity
- Promote network match to top-level "network" key with "chia"/"cadt" sub-keys instead of "chia.network.actual"/"configured" - Rename "processes" to "runningProcesses", drop redundant "supported" and "platform" fields (already in system section) - Add "percentUsed" to both memory and disk sections - Rename disk "path" to "chiaRootPath" for clarity - Add "runningLocally" to fullNode section; skip full-node RPC calls when process scan confirms no local chia_full_node (falls back to optimistic probe when scan is unsupported or fails)
1 parent 83e3c7b commit 45f2d92

5 files changed

Lines changed: 116 additions & 105 deletions

File tree

src/routes/diagnostics.js

Lines changed: 66 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
* home org IDs) stripped, matching the convention from wallet-health.js.
1313
*/
1414

15-
import os from 'os';
16-
1715
import _ from 'lodash';
1816

1917
import { getConfig, getConfigV2 } from '../utils/config-loader.js';
@@ -248,24 +246,32 @@ export const getDiagnosticsResponse = async ({ readOnly = false } = {}) => {
248246
const { OrganizationsV2 } = await import('../models/v2/index.js');
249247
const fullNodeModule = await import('../datalayer/fullNode.js');
250248

251-
// Kick off every external call in parallel. None of these can throw.
252-
const [
253-
activeNetworkRes,
254-
walletSyncedRes,
255-
walletBalanceRes,
256-
walletConnectionsRes,
257-
walletHealthRes,
258-
fullNodeStateRes,
259-
fullNodeConnectionsRes,
260-
chiaConfigRes,
261-
datalayerAvailableRes,
262-
subscriptionsRes,
263-
homeOrgV1Res,
264-
homeOrgV2Res,
265-
systemInfoRes,
266-
chiaProcessesRes,
267-
chiaToolsRes,
268-
] = await Promise.all([
249+
// Phase 1: fast local probes (process scan, system info, chia-tools).
250+
// We need the process scan result before deciding whether to probe the
251+
// full node RPC, so these run first.
252+
const [chiaProcessesRes, systemInfoRes, chiaToolsRes] = await Promise.all([
253+
settle('scanChiaProcesses', () => scanChiaProcesses(), DEFAULT_TIMEOUT_MS),
254+
settle('getSystemInfo', () => getSystemInfo(), DEFAULT_TIMEOUT_MS),
255+
settle('probeChiaTools', () => probeChiaTools(), DEFAULT_TIMEOUT_MS),
256+
]);
257+
258+
const processesValue = chiaProcessesRes.ok
259+
? chiaProcessesRes.value
260+
: { matches: [], installPaths: [], multipleVersionsDetected: false, error: chiaProcessesRes.error };
261+
262+
// If the scan succeeded and found processes, check for chia_full_node.
263+
// If the scan failed or is unsupported (Windows, minimal Docker image
264+
// without ps, /proc restrictions), we can't tell whether the full node
265+
// is running, so default to true and let the RPC timeout be the backstop.
266+
const scanReliable = chiaProcessesRes.ok && !processesValue.note;
267+
const fullNodeRunningLocally = scanReliable
268+
? processesValue.matches.some((m) => /chia_full_node/i.test(m.command))
269+
: true;
270+
271+
// Phase 2: network RPCs in parallel. Full-node RPCs are skipped when the
272+
// process scan shows no local full node — the calls would just timeout or
273+
// ECONNREFUSED, wasting wall-clock for no diagnostic value.
274+
const rpcSettles = [
269275
settle('wallet.getActiveNetwork', () => wallet.getActiveNetwork(), DEFAULT_TIMEOUT_MS),
270276
settle('wallet.walletIsSynced', () => wallet.walletIsSynced(), DEFAULT_TIMEOUT_MS),
271277
settle('wallet.getWalletBalance', () => wallet.getWalletBalance(), DEFAULT_TIMEOUT_MS),
@@ -275,12 +281,12 @@ export const getDiagnosticsResponse = async ({ readOnly = false } = {}) => {
275281
() => getWalletHealthResponse(wallet, { readOnly: false }),
276282
DEFAULT_TIMEOUT_MS,
277283
),
278-
settle('fullNode.getBlockchainState', () => fullNodeRpc.getBlockchainState(), DEFAULT_TIMEOUT_MS),
279-
settle(
280-
'fullNode.getFullNodeConnections',
281-
() => fullNodeRpc.getFullNodeConnections(),
282-
DEFAULT_TIMEOUT_MS,
283-
),
284+
fullNodeRunningLocally
285+
? settle('fullNode.getBlockchainState', () => fullNodeRpc.getBlockchainState(), DEFAULT_TIMEOUT_MS)
286+
: Promise.resolve({ ok: false, error: 'full node not running locally' }),
287+
fullNodeRunningLocally
288+
? settle('fullNode.getFullNodeConnections', () => fullNodeRpc.getFullNodeConnections(), DEFAULT_TIMEOUT_MS)
289+
: Promise.resolve({ ok: false, error: 'full node not running locally' }),
284290
settle('getChiaConfig', () => Promise.resolve(fullNodeModule.getChiaConfig()), 1000),
285291
settle('persistance.dataLayerAvailable', () => persistance.dataLayerAvailable(), DEFAULT_TIMEOUT_MS),
286292
settle(
@@ -290,10 +296,22 @@ export const getDiagnosticsResponse = async ({ readOnly = false } = {}) => {
290296
),
291297
settle('Organization.getHomeOrg', () => readHomeOrgId(Organization), DEFAULT_TIMEOUT_MS),
292298
settle('OrganizationsV2.getHomeOrg', () => readHomeOrgId(OrganizationsV2), DEFAULT_TIMEOUT_MS),
293-
settle('getSystemInfo', () => getSystemInfo(), DEFAULT_TIMEOUT_MS),
294-
settle('scanChiaProcesses', () => scanChiaProcesses(), DEFAULT_TIMEOUT_MS),
295-
settle('probeChiaTools', () => probeChiaTools(), DEFAULT_TIMEOUT_MS),
296-
]);
299+
];
300+
301+
const [
302+
activeNetworkRes,
303+
walletSyncedRes,
304+
walletBalanceRes,
305+
walletConnectionsRes,
306+
walletHealthRes,
307+
fullNodeStateRes,
308+
fullNodeConnectionsRes,
309+
chiaConfigRes,
310+
datalayerAvailableRes,
311+
subscriptionsRes,
312+
homeOrgV1Res,
313+
homeOrgV2Res,
314+
] = await Promise.all(rpcSettles);
297315

298316
// walletReachable: derived from the get_network_info RPC return value, NOT
299317
// from settle().ok -- walletIsSynced and getWalletBalance both swallow
@@ -399,13 +417,17 @@ export const getDiagnosticsResponse = async ({ readOnly = false } = {}) => {
399417

400418
// ---- Chia: full node ----------------------------------------------------
401419
const fullNodeSection = (() => {
420+
if (!fullNodeRunningLocally) {
421+
return { runningLocally: false, reachable: false };
422+
}
402423
const base = fullNodeStateRes.ok
403424
? fullNodeStateRes.value
404425
: { reachable: false, error: fullNodeStateRes.error };
405426
const connections = fullNodeConnectionsRes.ok
406427
? fullNodeConnectionsRes.value
407428
: { reachable: false, error: fullNodeConnectionsRes.error };
408429
return {
430+
runningLocally: true,
409431
...base,
410432
peerCount: connections.connections ? connections.connections.length : null,
411433
connectionsError: connections.error || null,
@@ -431,24 +453,16 @@ export const getDiagnosticsResponse = async ({ readOnly = false } = {}) => {
431453
// ---- Chia: services / chia-tools / processes ---------------------------
432454
const servicesSection = {
433455
walletReachable,
434-
fullNodeReachable: fullNodeStateRes.ok ? !!fullNodeStateRes.value?.reachable : false,
456+
fullNodeReachable: fullNodeRunningLocally && fullNodeStateRes.ok
457+
? !!fullNodeStateRes.value?.reachable
458+
: false,
435459
datalayerReachable: datalayerAvailableRes.ok ? datalayerAvailableRes.value === true : false,
436460
};
437461

438462
const chiaToolsSection = chiaToolsRes.ok
439463
? chiaToolsRes.value
440464
: { installed: false, version: null, error: chiaToolsRes.error, note: 'probe failed' };
441465

442-
const processesSection = chiaProcessesRes.ok
443-
? chiaProcessesRes.value
444-
: {
445-
supported: false,
446-
platform: os.platform(),
447-
matches: [],
448-
multipleVersionsDetected: false,
449-
error: chiaProcessesRes.error,
450-
};
451-
452466
// ---- System -------------------------------------------------------------
453467
const systemSection = systemInfoRes.ok
454468
? systemInfoRes.value
@@ -459,18 +473,18 @@ export const getDiagnosticsResponse = async ({ readOnly = false } = {}) => {
459473
timestamp,
460474
readOnly: false,
461475
cadt: cadtSection,
476+
network: {
477+
chia: actualNetwork,
478+
cadt: configuredNetwork,
479+
matches: networkMatches,
480+
},
462481
chia: {
463-
network: {
464-
actual: actualNetwork,
465-
configured: configuredNetwork,
466-
matches: networkMatches,
467-
},
468482
wallet: walletSection,
469483
fullNode: fullNodeSection,
470484
datalayer: datalayerSection,
471485
services: servicesSection,
472486
chiaTools: chiaToolsSection,
473-
processes: processesSection,
487+
runningProcesses: processesValue,
474488
},
475489
system: systemSection,
476490
};
@@ -495,9 +509,9 @@ const buildReadOnlyResponse = async ({ timestamp, configV1, configV2, appConfig
495509
settle('probeChiaTools', () => probeChiaTools(), DEFAULT_TIMEOUT_MS),
496510
]);
497511

498-
const processesSection = chiaProcessesRes.ok
512+
const processesValue = chiaProcessesRes.ok
499513
? chiaProcessesRes.value
500-
: { supported: false, matches: [], multipleVersionsDetected: false, error: chiaProcessesRes.error };
514+
: { matches: [], installPaths: [], multipleVersionsDetected: false, error: chiaProcessesRes.error };
501515
const chiaToolsSection = chiaToolsRes.ok
502516
? chiaToolsRes.value
503517
: { installed: false, version: null, error: chiaToolsRes.error };
@@ -529,13 +543,11 @@ const buildReadOnlyResponse = async ({ timestamp, configV1, configV2, appConfig
529543
governanceBodyId: configV2.GOVERNANCE?.GOVERNANCE_BODY_ID || null,
530544
},
531545
},
546+
network: { cadt: appConfig.CHIA_NETWORK || null },
532547
chia: {
533-
network: { configured: appConfig.CHIA_NETWORK || null },
534548
chiaTools: { installed: chiaToolsSection.installed, version: chiaToolsSection.version },
535-
processes: {
536-
supported: processesSection.supported,
537-
platform: processesSection.platform || null,
538-
multipleVersionsDetected: processesSection.multipleVersionsDetected,
549+
runningProcesses: {
550+
multipleVersionsDetected: processesValue.multipleVersionsDetected,
539551
},
540552
},
541553
system: systemSection,

src/utils/chia-process-scan.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,15 @@ const runPs = () =>
6464

6565
/**
6666
* Scan running processes for chia binaries.
67-
* @returns {Promise<{supported: boolean, platform: string, matches: Array, multipleVersionsDetected: boolean, error?: string}>}
67+
* @returns {Promise<{matches: Array, installPaths: string[], multipleVersionsDetected: boolean, note?: string, error?: string}>}
6868
*/
6969
export const scanChiaProcesses = async () => {
7070
const platform = os.platform();
7171

7272
if (platform === 'win32') {
7373
return {
74-
supported: false,
75-
platform,
7674
matches: [],
75+
installPaths: [],
7776
multipleVersionsDetected: false,
7877
note: 'process scan is not supported on Windows',
7978
};
@@ -100,18 +99,15 @@ export const scanChiaProcesses = async () => {
10099
}
101100

102101
return {
103-
supported: true,
104-
platform,
105102
matches,
106103
installPaths: Array.from(installPaths),
107104
multipleVersionsDetected: installPaths.size > 1,
108105
};
109106
} catch (error) {
110107
logger.debug(`[diagnostics]: chia process scan failed: ${error.message}`);
111108
return {
112-
supported: true,
113-
platform,
114109
matches: [],
110+
installPaths: [],
115111
multipleVersionsDetected: false,
116112
error: error.message,
117113
};

src/utils/system-info.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,17 @@ const getCpuInfo = () => {
2626

2727
const getMemoryInfo = () => {
2828
try {
29+
const totalBytes = os.totalmem();
30+
const freeBytes = os.freemem();
31+
const usedBytes = totalBytes - freeBytes;
2932
return {
30-
totalBytes: os.totalmem(),
31-
freeBytes: os.freemem(),
33+
totalBytes,
34+
freeBytes,
35+
percentUsed: totalBytes > 0 ? Math.round((usedBytes / totalBytes) * 1000) / 10 : null,
3236
};
3337
} catch (error) {
3438
logger.debug(`[diagnostics]: failed to read memory info: ${error.message}`);
35-
return { totalBytes: null, freeBytes: null, error: error.message };
39+
return { totalBytes: null, freeBytes: null, percentUsed: null, error: error.message };
3640
}
3741
};
3842

@@ -53,9 +57,10 @@ const getDiskInfo = async (targetPath) => {
5357

5458
if (typeof fs.promises.statfs !== 'function') {
5559
return {
56-
path: resolvedPath,
60+
chiaRootPath: resolvedPath,
5761
totalBytes: null,
5862
freeBytes: null,
63+
percentUsed: null,
5964
error: 'fs.promises.statfs is not available in this Node runtime',
6065
};
6166
}
@@ -70,17 +75,22 @@ const getDiskInfo = async (targetPath) => {
7075
// hedge against environments like Docker+VirtioFS where the two can
7176
// differ.
7277
const blockSize = stats.frsize ?? stats.bsize;
78+
const totalBytes = stats.blocks * blockSize;
79+
const freeBytes = stats.bavail * blockSize;
80+
const usedBytes = totalBytes - freeBytes;
7381
return {
74-
path: resolvedPath,
75-
totalBytes: stats.blocks * blockSize,
76-
freeBytes: stats.bavail * blockSize,
82+
chiaRootPath: resolvedPath,
83+
totalBytes,
84+
freeBytes,
85+
percentUsed: totalBytes > 0 ? Math.round((usedBytes / totalBytes) * 1000) / 10 : null,
7786
};
7887
} catch (error) {
7988
logger.debug(`[diagnostics]: failed to stat disk for ${targetPath}: ${error.message}`);
8089
return {
81-
path: targetPath,
90+
chiaRootPath: targetPath,
8291
totalBytes: null,
8392
freeBytes: null,
93+
percentUsed: null,
8494
error: error.message,
8595
};
8696
}

0 commit comments

Comments
 (0)