harnery 0.38.0 → 0.40.0
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/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +32 -8
- package/dist/commands/artifacts.d.ts.map +1 -1
- package/dist/commands/artifacts.js +97 -6
- package/dist/commands/backup.d.ts +16 -0
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +239 -33
- package/dist/commands/browse.d.ts +8 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +466 -231
- package/dist/commands/diagnostics.d.ts.map +1 -1
- package/dist/commands/diagnostics.js +6 -3
- package/dist/commands/ledger-v3.d.ts.map +1 -1
- package/dist/commands/ledger-v3.js +20 -4
- package/dist/commands/qa-run.d.ts.map +1 -1
- package/dist/commands/qa-run.js +26 -3
- package/dist/commands/qa-status.d.ts.map +1 -1
- package/dist/commands/qa-status.js +7 -2
- package/dist/commands/resources.d.ts.map +1 -1
- package/dist/commands/resources.js +23 -3
- package/dist/commands/review-pack.d.ts.map +1 -1
- package/dist/commands/review-pack.js +98 -23
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +2 -2
- package/dist/core/adapters/profiles.d.ts +1 -1
- package/dist/core/agents/canonical-emit.d.ts +3 -2
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +2 -0
- package/dist/core/agents/cli.js +13 -6
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +5 -3
- package/dist/core/agents/live-lifecycle-v3.d.ts +2 -2
- package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.d.ts +7 -5
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +63 -8
- package/dist/core/agents/session-finalizer-v3.d.ts +2 -2
- package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.d.ts +3 -3
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +2 -1
- package/dist/core/agents/state/names.d.ts +35 -2
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +80 -5
- package/dist/core/agents/state/stale-sweep.d.ts +2 -1
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +23 -6
- package/dist/core/agents/turn-elapsed.d.ts +51 -0
- package/dist/core/agents/turn-elapsed.d.ts.map +1 -0
- package/dist/core/agents/turn-elapsed.js +91 -0
- package/dist/core/artifacts/activity.d.ts +14 -0
- package/dist/core/artifacts/activity.d.ts.map +1 -0
- package/dist/core/artifacts/activity.js +52 -0
- package/dist/core/artifacts/delivery-card.d.ts +39 -0
- package/dist/core/artifacts/delivery-card.d.ts.map +1 -0
- package/dist/core/artifacts/delivery-card.js +282 -0
- package/dist/core/artifacts/index.d.ts +42 -7
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +288 -76
- package/dist/core/artifacts/mutation-lock.d.ts +2 -0
- package/dist/core/artifacts/mutation-lock.d.ts.map +1 -0
- package/dist/core/artifacts/mutation-lock.js +91 -0
- package/dist/core/backup/host-snapshot.d.ts +18 -0
- package/dist/core/backup/host-snapshot.d.ts.map +1 -0
- package/dist/core/backup/host-snapshot.js +64 -0
- package/dist/core/config.d.ts +20 -5
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +44 -10
- package/dist/core/diagnostics/advice.d.ts +38 -3
- package/dist/core/diagnostics/advice.d.ts.map +1 -1
- package/dist/core/diagnostics/advice.js +103 -99
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +111 -47
- package/dist/core/diagnostics/comparison.d.ts.map +1 -1
- package/dist/core/diagnostics/comparison.js +3 -2
- package/dist/core/diagnostics/contract.d.ts +15 -31
- package/dist/core/diagnostics/contract.d.ts.map +1 -1
- package/dist/core/diagnostics/contract.js +2 -5
- package/dist/core/diagnostics/index.d.ts +2 -0
- package/dist/core/diagnostics/index.d.ts.map +1 -1
- package/dist/core/diagnostics/index.js +2 -0
- package/dist/core/diagnostics/pressure-contract.d.ts +260 -0
- package/dist/core/diagnostics/pressure-contract.d.ts.map +1 -0
- package/dist/core/diagnostics/pressure-contract.js +70 -0
- package/dist/core/diagnostics/pressure.d.ts +25 -0
- package/dist/core/diagnostics/pressure.d.ts.map +1 -0
- package/dist/core/diagnostics/pressure.js +1138 -0
- package/dist/core/diagnostics/replay.d.ts.map +1 -1
- package/dist/core/diagnostics/replay.js +90 -10
- package/dist/core/events/v3/adapter-id.d.ts +6 -0
- package/dist/core/events/v3/adapter-id.d.ts.map +1 -0
- package/dist/core/events/v3/adapter-id.js +7 -0
- package/dist/core/events/v3/archive-retention.d.ts.map +1 -1
- package/dist/core/events/v3/archive-retention.js +3 -2
- package/dist/core/events/v3/bootstrap.d.ts +41 -4
- package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
- package/dist/core/events/v3/bootstrap.js +120 -17
- package/dist/core/events/v3/capabilities.d.ts +16 -9
- package/dist/core/events/v3/capabilities.d.ts.map +1 -1
- package/dist/core/events/v3/capabilities.js +37 -2
- package/dist/core/events/v3/capability-drift.d.ts +2 -2
- package/dist/core/events/v3/capability-drift.d.ts.map +1 -1
- package/dist/core/events/v3/control-witness.d.ts +48 -5
- package/dist/core/events/v3/control-witness.d.ts.map +1 -1
- package/dist/core/events/v3/control-witness.js +91 -36
- package/dist/core/events/v3/control.d.ts.map +1 -1
- package/dist/core/events/v3/control.js +22 -2
- package/dist/core/events/v3/index.d.ts +2 -1
- package/dist/core/events/v3/index.d.ts.map +1 -1
- package/dist/core/events/v3/index.js +2 -1
- package/dist/core/events/v3/live-feed.d.ts +7 -1
- package/dist/core/events/v3/live-feed.d.ts.map +1 -1
- package/dist/core/events/v3/live-feed.js +21 -3
- package/dist/core/events/v3/live-routing.d.ts.map +1 -1
- package/dist/core/events/v3/live-routing.js +10 -2
- package/dist/core/events/v3/producers/command-recorder.d.ts +2 -2
- package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/command-recorder.js +2 -1
- package/dist/core/events/v3/producers/coordination-recorder.d.ts +2 -2
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +2 -1
- package/dist/core/events/v3/producers/hook-base.d.ts +2 -2
- package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook-base.js +15 -5
- package/dist/core/events/v3/producers/hook.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook.js +17 -4
- package/dist/core/events/v3/producers/intake.d.ts +4 -4
- package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
- package/dist/core/events/v3/producers/intake.js +3 -2
- package/dist/core/events/v3/producers/producer-state-observer.d.ts.map +1 -1
- package/dist/core/events/v3/producers/producer-state-observer.js +2 -1
- package/dist/core/events/v3/producers/recorder.d.ts +16 -5
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +31 -9
- package/dist/core/events/v3/reader.d.ts.map +1 -1
- package/dist/core/events/v3/reader.js +10 -13
- package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
- package/dist/core/events/v3/runtime-build.js +3 -2
- package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts +2 -2
- package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts.map +1 -1
- package/dist/core/events/v3/turn-telemetry.d.ts +2 -2
- package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.d.ts +16 -14
- package/dist/core/hooks/adapter/output.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.js +36 -38
- package/dist/core/hooks/backup-schedule.d.ts +34 -0
- package/dist/core/hooks/backup-schedule.d.ts.map +1 -0
- package/dist/core/hooks/backup-schedule.js +114 -0
- package/dist/core/hooks/cli.js +61 -18
- package/dist/core/hooks/codex-wsl-bridge.d.ts +2 -0
- package/dist/core/hooks/codex-wsl-bridge.d.ts.map +1 -1
- package/dist/core/hooks/codex-wsl-bridge.js +18 -4
- package/dist/core/hooks/index.d.ts +1 -0
- package/dist/core/hooks/index.d.ts.map +1 -1
- package/dist/core/hooks/index.js +1 -0
- package/dist/core/hooks/prompt-context/contract.d.ts +61 -0
- package/dist/core/hooks/prompt-context/contract.d.ts.map +1 -0
- package/dist/core/hooks/prompt-context/contract.js +102 -0
- package/dist/core/hooks/prompt-context/index.d.ts +5 -0
- package/dist/core/hooks/prompt-context/index.d.ts.map +1 -0
- package/dist/core/hooks/prompt-context/index.js +2 -0
- package/dist/core/hooks/prompt-context/runner.d.ts +24 -0
- package/dist/core/hooks/prompt-context/runner.d.ts.map +1 -0
- package/dist/core/hooks/prompt-context/runner.js +249 -0
- package/dist/core/qa-artifacts.d.ts +3 -0
- package/dist/core/qa-artifacts.d.ts.map +1 -1
- package/dist/core/qa-artifacts.js +25 -3
- package/dist/core/resources/contract.d.ts +69 -3
- package/dist/core/resources/contract.d.ts.map +1 -1
- package/dist/core/resources/contract.js +1 -1
- package/dist/core/resources/index.d.ts +1 -0
- package/dist/core/resources/index.d.ts.map +1 -1
- package/dist/core/resources/index.js +1 -0
- package/dist/core/resources/native.d.ts +36 -0
- package/dist/core/resources/native.d.ts.map +1 -0
- package/dist/core/resources/native.js +418 -0
- package/dist/core/resources/sampler.d.ts +2 -0
- package/dist/core/resources/sampler.d.ts.map +1 -1
- package/dist/core/resources/sampler.js +31 -1
- package/dist/core/resources/status.d.ts +61 -0
- package/dist/core/resources/status.d.ts.map +1 -0
- package/dist/core/resources/status.js +536 -0
- package/dist/core/resources/system.d.ts +27 -0
- package/dist/core/resources/system.d.ts.map +1 -0
- package/dist/core/resources/system.js +439 -0
- package/dist/core/semantic/contract.d.ts +13 -13
- package/dist/core/semantic/service.d.ts +2 -0
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +27 -4
- package/dist/core/storage/builtins.js +41 -0
- package/dist/core/supervisor/contract.d.ts +41 -3
- package/dist/core/supervisor/contract.d.ts.map +1 -1
- package/dist/core/supervisor/contract.js +3 -2
- package/dist/core/supervisor/findings.d.ts +3 -0
- package/dist/core/supervisor/findings.d.ts.map +1 -1
- package/dist/core/supervisor/findings.js +88 -0
- package/dist/core/supervisor/history.d.ts.map +1 -1
- package/dist/core/supervisor/history.js +14 -0
- package/dist/core/supervisor/ledger-state.d.ts +55 -0
- package/dist/core/supervisor/ledger-state.d.ts.map +1 -0
- package/dist/core/supervisor/ledger-state.js +0 -0
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +120 -7
- package/dist/core/supervisor/storage.d.ts +3 -1
- package/dist/core/supervisor/storage.d.ts.map +1 -1
- package/dist/core/supervisor/storage.js +4 -0
- package/dist/core/workflow/admission.d.ts +3 -0
- package/dist/core/workflow/admission.d.ts.map +1 -1
- package/dist/core/workflow/admission.js +28 -16
- package/dist/core/workflow/engine.js +2 -2
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +6 -1
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +4 -3
- package/dist/lib/browser/capture-fidelity.d.ts +7 -0
- package/dist/lib/browser/capture-fidelity.d.ts.map +1 -1
- package/dist/lib/browser/capture-fidelity.js +17 -0
- package/dist/lib/browser/client.d.ts +9 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +85 -37
- package/dist/lib/browser/index.d.ts +3 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +2 -1
- package/dist/lib/browser/page-review-budget.d.ts +13 -0
- package/dist/lib/browser/page-review-budget.d.ts.map +1 -0
- package/dist/lib/browser/page-review-budget.js +251 -0
- package/dist/lib/browser/page-review-contracts.d.ts +127 -0
- package/dist/lib/browser/page-review-contracts.d.ts.map +1 -0
- package/dist/lib/browser/page-review-contracts.js +5 -0
- package/dist/lib/browser/page-review-evidence.d.ts +48 -0
- package/dist/lib/browser/page-review-evidence.d.ts.map +1 -0
- package/dist/lib/browser/page-review-evidence.js +340 -0
- package/dist/lib/browser/page-review-judge.d.ts +1 -1
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
- package/dist/lib/browser/page-review-judge.js +46 -38
- package/dist/lib/browser/page-review-pack.d.ts +44 -22
- package/dist/lib/browser/page-review-pack.d.ts.map +1 -1
- package/dist/lib/browser/page-review-pack.js +116 -36
- package/dist/lib/browser/page-review-source.d.ts +12 -0
- package/dist/lib/browser/page-review-source.d.ts.map +1 -0
- package/dist/lib/browser/page-review-source.js +22 -0
- package/dist/lib/browser/qa-run-contracts.d.ts +35 -9
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
- package/dist/lib/browser/qa-run-contracts.js +48 -14
- package/dist/lib/browser/qa-run.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.js +215 -68
- package/dist/lib/browser/qa-snapshot.d.ts +6 -0
- package/dist/lib/browser/qa-snapshot.d.ts.map +1 -1
- package/dist/lib/browser/qa-snapshot.js +259 -48
- package/dist/lib/browser/review-capture-transaction.d.ts +13 -0
- package/dist/lib/browser/review-capture-transaction.d.ts.map +1 -0
- package/dist/lib/browser/review-capture-transaction.js +79 -0
- package/dist/lib/browser/standalone-html.d.ts.map +1 -1
- package/dist/lib/browser/standalone-html.js +4 -2
- package/dist/lib/browser/thumbnail-association.d.ts +34 -0
- package/dist/lib/browser/thumbnail-association.d.ts.map +1 -0
- package/dist/lib/browser/thumbnail-association.js +158 -0
- package/dist/lib/browser/tiling.d.ts +30 -0
- package/dist/lib/browser/tiling.d.ts.map +1 -1
- package/dist/lib/browser/tiling.js +38 -0
- package/dist/lib/docs-metadata-managed.d.ts.map +1 -1
- package/dist/lib/docs-metadata-managed.js +4 -1
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +7 -1
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +1 -0
- package/dist/lib/local-file-url.d.ts +12 -0
- package/dist/lib/local-file-url.d.ts.map +1 -1
- package/dist/lib/local-file-url.js +16 -2
- package/dist/lib/tunnel/state.d.ts +19 -4
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +53 -13
- package/package.json +7 -4
- package/schemas/config.schema.json +69 -2
- package/src/commands/agents.ts +42 -9
- package/src/commands/artifacts.ts +130 -6
- package/src/commands/backup.ts +287 -34
- package/src/commands/browse.ts +564 -267
- package/src/commands/diagnostics.ts +6 -3
- package/src/commands/ledger-v3.ts +20 -3
- package/src/commands/qa-run.ts +37 -3
- package/src/commands/qa-status.ts +11 -2
- package/src/commands/resources.ts +23 -3
- package/src/commands/review-pack.ts +165 -62
- package/src/commands/tunnel.ts +2 -1
- package/src/core/agents/canonical-emit.ts +4 -2
- package/src/core/agents/cli.ts +14 -6
- package/src/core/agents/live-authority-v3.ts +7 -3
- package/src/core/agents/live-lifecycle-v3.ts +2 -2
- package/src/core/agents/render/prompt-context.ts +68 -14
- package/src/core/agents/session-finalizer-v3.ts +2 -2
- package/src/core/agents/state/live-coordination-view.ts +4 -4
- package/src/core/agents/state/names.ts +92 -7
- package/src/core/agents/state/stale-sweep.ts +22 -8
- package/src/core/agents/turn-elapsed.ts +135 -0
- package/src/core/artifacts/activity.ts +71 -0
- package/src/core/artifacts/delivery-card.ts +375 -0
- package/src/core/artifacts/index.ts +385 -91
- package/src/core/artifacts/mutation-lock.ts +106 -0
- package/src/core/backup/host-snapshot.ts +76 -0
- package/src/core/config.ts +66 -13
- package/src/core/diagnostics/advice.ts +148 -120
- package/src/core/diagnostics/bundle.ts +138 -52
- package/src/core/diagnostics/comparison.ts +6 -2
- package/src/core/diagnostics/contract.ts +39 -42
- package/src/core/diagnostics/index.ts +2 -0
- package/src/core/diagnostics/pressure-contract.ts +294 -0
- package/src/core/diagnostics/pressure.ts +1361 -0
- package/src/core/diagnostics/replay.ts +116 -11
- package/src/core/events/v3/adapter-id.ts +12 -0
- package/src/core/events/v3/archive-retention.ts +3 -6
- package/src/core/events/v3/bootstrap.ts +163 -16
- package/src/core/events/v3/capabilities.ts +75 -25
- package/src/core/events/v3/capability-drift.ts +2 -2
- package/src/core/events/v3/control-witness.ts +172 -41
- package/src/core/events/v3/control.ts +34 -2
- package/src/core/events/v3/index.ts +3 -0
- package/src/core/events/v3/live-feed.ts +23 -5
- package/src/core/events/v3/live-routing.ts +14 -2
- package/src/core/events/v3/producers/command-recorder.ts +4 -4
- package/src/core/events/v3/producers/coordination-recorder.ts +4 -4
- package/src/core/events/v3/producers/hook-base.ts +21 -16
- package/src/core/events/v3/producers/hook.ts +21 -4
- package/src/core/events/v3/producers/intake.ts +6 -6
- package/src/core/events/v3/producers/producer-state-observer.ts +2 -1
- package/src/core/events/v3/producers/recorder.ts +50 -21
- package/src/core/events/v3/reader.ts +14 -13
- package/src/core/events/v3/runtime-build.ts +3 -2
- package/src/core/events/v3/runtime-telemetry-capabilities.ts +3 -3
- package/src/core/events/v3/turn-telemetry.ts +4 -4
- package/src/core/hooks/adapter/output.ts +50 -36
- package/src/core/hooks/backup-schedule.ts +147 -0
- package/src/core/hooks/cli.ts +65 -14
- package/src/core/hooks/codex-wsl-bridge.ts +21 -5
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/prompt-context/contract.ts +177 -0
- package/src/core/hooks/prompt-context/index.ts +22 -0
- package/src/core/hooks/prompt-context/runner.ts +303 -0
- package/src/core/qa-artifacts.ts +29 -3
- package/src/core/resources/contract.ts +76 -3
- package/src/core/resources/index.ts +1 -0
- package/src/core/resources/native.ts +481 -0
- package/src/core/resources/sampler.ts +46 -1
- package/src/core/resources/status.ts +717 -0
- package/src/core/resources/system.ts +530 -0
- package/src/core/semantic/service.ts +37 -3
- package/src/core/storage/builtins.ts +41 -0
- package/src/core/supervisor/contract.ts +45 -3
- package/src/core/supervisor/findings.ts +159 -0
- package/src/core/supervisor/history.ts +15 -0
- package/src/core/supervisor/ledger-state.ts +0 -0
- package/src/core/supervisor/service.ts +133 -5
- package/src/core/supervisor/storage.ts +7 -0
- package/src/core/workflow/admission.ts +33 -17
- package/src/core/workflow/engine.ts +2 -2
- package/src/core/workflow/live-session-v3.ts +6 -8
- package/src/core/workflow/proof.ts +4 -3
- package/src/lib/browser/capture-fidelity.ts +23 -0
- package/src/lib/browser/client.ts +96 -44
- package/src/lib/browser/index.ts +15 -1
- package/src/lib/browser/page-review-budget.ts +309 -0
- package/src/lib/browser/page-review-contracts.ts +129 -0
- package/src/lib/browser/page-review-evidence.ts +465 -0
- package/src/lib/browser/page-review-judge.ts +56 -40
- package/src/lib/browser/page-review-pack.ts +181 -43
- package/src/lib/browser/page-review-source.ts +30 -0
- package/src/lib/browser/qa-run-contracts.ts +62 -18
- package/src/lib/browser/qa-run.ts +259 -71
- package/src/lib/browser/qa-snapshot.ts +289 -48
- package/src/lib/browser/review-capture-transaction.ts +106 -0
- package/src/lib/browser/standalone-html.ts +4 -2
- package/src/lib/browser/thumbnail-association.ts +190 -0
- package/src/lib/browser/tiling.ts +63 -0
- package/src/lib/docs-metadata-managed.ts +4 -1
- package/src/lib/identities/assume.ts +12 -4
- package/src/lib/instructions/templates.ts +1 -0
- package/src/lib/local-file-url.ts +17 -2
- package/src/lib/tunnel/state.ts +70 -13
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { execFile, spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import {
|
|
5
|
+
RESOURCE_SNAPSHOT_SCHEMA_VERSION,
|
|
6
|
+
type ResourceDiskSample,
|
|
7
|
+
type ResourceHostSample,
|
|
8
|
+
type ResourceMachineSample,
|
|
9
|
+
type ResourceSampleResult,
|
|
10
|
+
type ResourceSamplerState,
|
|
11
|
+
} from "./contract.ts";
|
|
12
|
+
|
|
13
|
+
export type NativeResourceCommand = (file: string, args: readonly string[]) => string | null;
|
|
14
|
+
|
|
15
|
+
export interface NativeResourceSamplerOptions {
|
|
16
|
+
nowMs?: number;
|
|
17
|
+
platform?: "darwin" | "win32";
|
|
18
|
+
os?: Partial<
|
|
19
|
+
Pick<typeof os, "cpus" | "totalmem" | "freemem" | "availableParallelism" | "loadavg">
|
|
20
|
+
>;
|
|
21
|
+
command?: NativeResourceCommand;
|
|
22
|
+
backgroundHost?: boolean;
|
|
23
|
+
/** Supervisors use the background collector to keep native CPU sampling on cadence. */
|
|
24
|
+
hostCollector?: () => ResourceHostSample;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const WINDOWS_CACHE_MS = 15_000;
|
|
28
|
+
const COMMAND_TIMEOUT_MS = 4_000;
|
|
29
|
+
|
|
30
|
+
// Queries are static and read-only. Individual optional providers may be absent.
|
|
31
|
+
const WINDOWS_QUERY = `
|
|
32
|
+
$ErrorActionPreference = 'Stop'
|
|
33
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
34
|
+
$osInfo = Get-CimInstance Win32_OperatingSystem -Property TotalVisibleMemorySize,FreePhysicalMemory,NumberOfProcesses
|
|
35
|
+
$processors = $null; $pagefiles = $null; $disks = $null
|
|
36
|
+
try { $processors = @(Get-CimInstance Win32_Processor -Property NumberOfLogicalProcessors,LoadPercentage | Select-Object NumberOfLogicalProcessors,LoadPercentage) } catch {}
|
|
37
|
+
try { $pagefiles = @(Get-CimInstance Win32_PageFileUsage -Property AllocatedBaseSize,CurrentUsage | Select-Object AllocatedBaseSize,CurrentUsage) } catch {}
|
|
38
|
+
try { $disks = @(Get-CimInstance Win32_LogicalDisk -Filter 'DriveType = 3' -Property DeviceID,Size,FreeSpace | Select-Object DeviceID,Size,FreeSpace) } catch {}
|
|
39
|
+
[pscustomobject]@{
|
|
40
|
+
sampled_at = [DateTime]::UtcNow.ToString('o')
|
|
41
|
+
memory_total_kib = $osInfo.TotalVisibleMemorySize
|
|
42
|
+
memory_available_kib = $osInfo.FreePhysicalMemory
|
|
43
|
+
process_count = $osInfo.NumberOfProcesses
|
|
44
|
+
processors = $processors
|
|
45
|
+
pagefiles = $pagefiles
|
|
46
|
+
disks = $disks
|
|
47
|
+
} | ConvertTo-Json -Depth 4 -Compress
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
function runNativeCommand(file: string, args: readonly string[]): string | null {
|
|
51
|
+
const macTools: Record<string, string> = {
|
|
52
|
+
vm_stat: "/usr/bin/vm_stat",
|
|
53
|
+
sysctl: "/usr/sbin/sysctl",
|
|
54
|
+
ps: "/bin/ps",
|
|
55
|
+
};
|
|
56
|
+
const executable = os.platform() === "darwin" ? (macTools[file] ?? file) : file;
|
|
57
|
+
const result = spawnSync(executable, [...args], {
|
|
58
|
+
encoding: "utf8",
|
|
59
|
+
timeout: COMMAND_TIMEOUT_MS,
|
|
60
|
+
maxBuffer: 1_048_576,
|
|
61
|
+
windowsHide: true,
|
|
62
|
+
env: os.platform() === "darwin" ? { ...process.env, LC_ALL: "C" } : undefined,
|
|
63
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
64
|
+
});
|
|
65
|
+
return result.status === 0 && !result.error ? result.stdout : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function readCommand(
|
|
69
|
+
command: NativeResourceCommand,
|
|
70
|
+
file: string,
|
|
71
|
+
args: readonly string[],
|
|
72
|
+
): string | null {
|
|
73
|
+
try {
|
|
74
|
+
return command(file, args);
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function sampleNativeResources(
|
|
81
|
+
_coordRoot: string,
|
|
82
|
+
previous?: ResourceSamplerState,
|
|
83
|
+
options: NativeResourceSamplerOptions = {},
|
|
84
|
+
): ResourceSampleResult {
|
|
85
|
+
const started = performance.now();
|
|
86
|
+
const cpuBefore = process.cpuUsage();
|
|
87
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
88
|
+
const platform = options.platform ?? os.platform();
|
|
89
|
+
if (platform !== "darwin" && platform !== "win32") {
|
|
90
|
+
throw new Error("native_resource_platform_invalid");
|
|
91
|
+
}
|
|
92
|
+
const nativeOs = { ...os, ...options.os };
|
|
93
|
+
const command = options.command ?? runNativeCommand;
|
|
94
|
+
const cpuRows = nativeOs.cpus();
|
|
95
|
+
const cpu = sumCpuCounters(cpuRows);
|
|
96
|
+
const reasons = ["native_process_attribution_unavailable"];
|
|
97
|
+
let available = nonnegative(nativeOs.freemem());
|
|
98
|
+
const total = positive(nativeOs.totalmem());
|
|
99
|
+
let swap: { total: number; used: number } | null = null;
|
|
100
|
+
let processCount: number | null = null;
|
|
101
|
+
if (platform === "darwin") {
|
|
102
|
+
const vm = parseMacVmStat(readCommand(command, "vm_stat", []) ?? "");
|
|
103
|
+
if (vm) {
|
|
104
|
+
available = vm.available_bytes;
|
|
105
|
+
reasons.push("memory_available_estimated_from_free_inactive_speculative_pages");
|
|
106
|
+
} else {
|
|
107
|
+
reasons.push("vm_stat_unavailable_using_free_memory");
|
|
108
|
+
}
|
|
109
|
+
swap = parseMacSwapUsage(readCommand(command, "sysctl", ["-n", "vm.swapusage"]) ?? "");
|
|
110
|
+
processCount = parseProcessCount(readCommand(command, "ps", ["-axo", "pid="]));
|
|
111
|
+
} else {
|
|
112
|
+
const host =
|
|
113
|
+
options.hostCollector?.() ??
|
|
114
|
+
(options.backgroundHost
|
|
115
|
+
? collectWindowsHostInBackground({ nowMs })
|
|
116
|
+
: collectWindowsHost({ nowMs, command }));
|
|
117
|
+
if (host.machine) {
|
|
118
|
+
swap =
|
|
119
|
+
host.machine.swap_total_bytes !== null && host.machine.swap_used_bytes !== null
|
|
120
|
+
? { total: host.machine.swap_total_bytes, used: host.machine.swap_used_bytes }
|
|
121
|
+
: null;
|
|
122
|
+
processCount = host.machine.process_count;
|
|
123
|
+
} else {
|
|
124
|
+
reasons.push(host.reason ?? "windows_auxiliary_metrics_unavailable");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (total !== null && available !== null && available > total) available = null;
|
|
128
|
+
const used = total !== null && available !== null ? total - available : null;
|
|
129
|
+
const cpuPercent = cpu ? cpuDeltaPercent(cpu, previous, nowMs) : null;
|
|
130
|
+
if (!cpu) reasons.push("cpu_counters_unavailable");
|
|
131
|
+
if (!swap) reasons.push("swap_unavailable");
|
|
132
|
+
if (processCount === null) reasons.push("process_count_unavailable");
|
|
133
|
+
const collectorCpu = process.cpuUsage(cpuBefore);
|
|
134
|
+
return {
|
|
135
|
+
snapshot: {
|
|
136
|
+
schema_version: RESOURCE_SNAPSHOT_SCHEMA_VERSION,
|
|
137
|
+
sampled_at: new Date(nowMs).toISOString(),
|
|
138
|
+
interval_ms: previous ? Math.max(0, nowMs - previous.sampled_at_ms) : null,
|
|
139
|
+
sample_duration_ms: round1(performance.now() - started),
|
|
140
|
+
collector_cpu_ms: round1((collectorCpu.user + collectorCpu.system) / 1_000),
|
|
141
|
+
platform,
|
|
142
|
+
namespace: "host",
|
|
143
|
+
support: { state: "partial", sampler: platform, reason: reasons.join("; ") },
|
|
144
|
+
machine: {
|
|
145
|
+
cpu_percent: cpuPercent,
|
|
146
|
+
cpu_logical_count: cpuRows.length,
|
|
147
|
+
cpu_available_parallelism: nativeOs.availableParallelism(),
|
|
148
|
+
load_average:
|
|
149
|
+
platform === "darwin" ? (nativeOs.loadavg() as [number, number, number]) : null,
|
|
150
|
+
memory_total_bytes: total,
|
|
151
|
+
memory_available_bytes: available,
|
|
152
|
+
memory_used_bytes: used,
|
|
153
|
+
memory_percent: used !== null && total !== null ? round1((used / total) * 100) : null,
|
|
154
|
+
swap_total_bytes: swap?.total ?? null,
|
|
155
|
+
swap_used_bytes: swap?.used ?? null,
|
|
156
|
+
process_count: processCount,
|
|
157
|
+
},
|
|
158
|
+
groups: [],
|
|
159
|
+
processes: [],
|
|
160
|
+
visible_process_count: 0,
|
|
161
|
+
omitted_process_count: processCount ?? 0,
|
|
162
|
+
unattributed_process_count: 0,
|
|
163
|
+
},
|
|
164
|
+
...(cpu
|
|
165
|
+
? {
|
|
166
|
+
state: {
|
|
167
|
+
sampled_at_ms: nowMs,
|
|
168
|
+
cpu_total_ticks: cpu.total,
|
|
169
|
+
cpu_idle_ticks: cpu.idle,
|
|
170
|
+
process_ticks: new Map(),
|
|
171
|
+
process_owners: new Map(),
|
|
172
|
+
},
|
|
173
|
+
}
|
|
174
|
+
: {}),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function parseMacVmStat(output: string): { available_bytes: number } | null {
|
|
179
|
+
const pageSize = positive(output.match(/page size of (\d+) bytes/)?.[1]);
|
|
180
|
+
if (pageSize === null) return null;
|
|
181
|
+
const fields = new Map<string, number>();
|
|
182
|
+
for (const line of output.split(/\r?\n/)) {
|
|
183
|
+
const match = /^Pages (free|inactive|speculative):\s*(\d+)\.?\s*$/.exec(line.trim());
|
|
184
|
+
if (match) fields.set(match[1]!, Number(match[2]));
|
|
185
|
+
else if (/^Pages (free|inactive|speculative):/.test(line.trim())) return null;
|
|
186
|
+
}
|
|
187
|
+
const free = fields.get("free");
|
|
188
|
+
const inactive = fields.get("inactive");
|
|
189
|
+
if (free === undefined || inactive === undefined) return null;
|
|
190
|
+
// Purgeable pages overlap other queues; adding them again would double count.
|
|
191
|
+
const available = (free + inactive + (fields.get("speculative") ?? 0)) * pageSize;
|
|
192
|
+
return Number.isSafeInteger(available) ? { available_bytes: available } : null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function parseMacSwapUsage(output: string): { total: number; used: number } | null {
|
|
196
|
+
const values = new Map<string, number>();
|
|
197
|
+
for (const match of output.matchAll(/\b(total|used)\s*=\s*([\d.]+)([KMGT]?)\b/g)) {
|
|
198
|
+
const multiplier = 1_024 ** (match[3] ? "KMGT".indexOf(match[3]) + 1 : 0);
|
|
199
|
+
const value = nonnegative(Number(match[2]) * multiplier);
|
|
200
|
+
if (value !== null) values.set(match[1]!, Math.round(value));
|
|
201
|
+
}
|
|
202
|
+
const total = values.get("total");
|
|
203
|
+
const used = values.get("used");
|
|
204
|
+
return total !== undefined && used !== undefined && used <= total ? { total, used } : null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function parseProcessCount(output: string | null): number | null {
|
|
208
|
+
if (!output?.trim()) return null;
|
|
209
|
+
const rows = output.trim().split(/\r?\n/);
|
|
210
|
+
return rows.every((row) => /^\s*\d+\s*$/.test(row)) ? rows.length : null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function sumCpuCounters(rows: readonly os.CpuInfo[]): { total: number; idle: number } | null {
|
|
214
|
+
let total = 0;
|
|
215
|
+
let idle = 0;
|
|
216
|
+
if (rows.length === 0) return null;
|
|
217
|
+
for (const row of rows) {
|
|
218
|
+
const times = [row.times.user, row.times.nice, row.times.sys, row.times.idle, row.times.irq];
|
|
219
|
+
if (times.some((time) => nonnegative(time) === null)) return null;
|
|
220
|
+
total += times.reduce((sum, value) => sum + value, 0);
|
|
221
|
+
idle += row.times.idle;
|
|
222
|
+
}
|
|
223
|
+
return Number.isSafeInteger(total) && total > 0 ? { total, idle } : null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function cpuDeltaPercent(
|
|
227
|
+
cpu: { total: number; idle: number },
|
|
228
|
+
previous: ResourceSamplerState | undefined,
|
|
229
|
+
nowMs: number,
|
|
230
|
+
): number | null {
|
|
231
|
+
if (!previous || nowMs <= previous.sampled_at_ms) return null;
|
|
232
|
+
const totalDelta = cpu.total - previous.cpu_total_ticks;
|
|
233
|
+
const idleDelta = cpu.idle - previous.cpu_idle_ticks;
|
|
234
|
+
if (totalDelta <= 0 || idleDelta < 0 || idleDelta > totalDelta) return null;
|
|
235
|
+
return round1(((totalDelta - idleDelta) / totalDelta) * 100);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface WindowsHostOptions {
|
|
239
|
+
nowMs?: number;
|
|
240
|
+
command?: NativeResourceCommand;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function createWindowsHostCollector(): (options?: WindowsHostOptions) => ResourceHostSample {
|
|
244
|
+
const cache = new WeakMap<NativeResourceCommand, { at: number; sample: ResourceHostSample }>();
|
|
245
|
+
return (options = {}) => {
|
|
246
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
247
|
+
const command = options.command ?? runNativeCommand;
|
|
248
|
+
const cached = cache.get(command);
|
|
249
|
+
if (cached && nowMs >= cached.at && nowMs - cached.at < WINDOWS_CACHE_MS) return cached.sample;
|
|
250
|
+
let sample: ResourceHostSample;
|
|
251
|
+
try {
|
|
252
|
+
const executable =
|
|
253
|
+
command === runNativeCommand ? windowsPowerShellExecutable() : "powershell.exe";
|
|
254
|
+
const output = command(executable, windowsPowerShellArgs());
|
|
255
|
+
sample =
|
|
256
|
+
output === null
|
|
257
|
+
? unavailableWindowsHost(nowMs, "windows_host_query_failed")
|
|
258
|
+
: parseWindowsResourceOutput(output, nowMs);
|
|
259
|
+
} catch {
|
|
260
|
+
sample = unavailableWindowsHost(nowMs, "windows_host_query_failed");
|
|
261
|
+
}
|
|
262
|
+
// Cache failures too: missing interop/CIM must not launch PowerShell every tick.
|
|
263
|
+
cache.set(command, { at: nowMs, sample });
|
|
264
|
+
return sample;
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export const collectWindowsHost = createWindowsHostCollector();
|
|
269
|
+
|
|
270
|
+
export type NativeResourceAsyncCommand = (
|
|
271
|
+
file: string,
|
|
272
|
+
args: readonly string[],
|
|
273
|
+
) => Promise<string | null>;
|
|
274
|
+
|
|
275
|
+
export interface WindowsHostBackgroundOptions {
|
|
276
|
+
nowMs?: number;
|
|
277
|
+
command?: NativeResourceAsyncCommand;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function runWindowsCommandAsync(file: string, args: readonly string[]): Promise<string | null> {
|
|
281
|
+
return new Promise((resolve) => {
|
|
282
|
+
execFile(
|
|
283
|
+
file,
|
|
284
|
+
[...args],
|
|
285
|
+
{
|
|
286
|
+
encoding: "utf8",
|
|
287
|
+
timeout: COMMAND_TIMEOUT_MS,
|
|
288
|
+
maxBuffer: 1_048_576,
|
|
289
|
+
windowsHide: true,
|
|
290
|
+
},
|
|
291
|
+
(error, stdout) => resolve(error ? null : stdout),
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Return immediately; one process-wide probe refreshes all callers' cached host reading. */
|
|
297
|
+
export function createWindowsHostBackgroundCollector(): (
|
|
298
|
+
options?: WindowsHostBackgroundOptions,
|
|
299
|
+
) => ResourceHostSample {
|
|
300
|
+
const cache = new WeakMap<
|
|
301
|
+
NativeResourceAsyncCommand,
|
|
302
|
+
{ at: number; sample: ResourceHostSample; pending: boolean }
|
|
303
|
+
>();
|
|
304
|
+
return (options = {}) => {
|
|
305
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
306
|
+
const command = options.command ?? runWindowsCommandAsync;
|
|
307
|
+
let entry = cache.get(command);
|
|
308
|
+
if (entry?.pending || (entry && nowMs >= entry.at && nowMs - entry.at < WINDOWS_CACHE_MS))
|
|
309
|
+
return entry.sample;
|
|
310
|
+
entry ??= {
|
|
311
|
+
at: nowMs,
|
|
312
|
+
sample: { ...unavailableWindowsHost(nowMs, "windows_host_query_pending"), state: "partial" },
|
|
313
|
+
pending: false,
|
|
314
|
+
};
|
|
315
|
+
entry.at = nowMs;
|
|
316
|
+
entry.pending = true;
|
|
317
|
+
cache.set(command, entry);
|
|
318
|
+
const current = entry;
|
|
319
|
+
const executable =
|
|
320
|
+
command === runWindowsCommandAsync ? windowsPowerShellExecutable() : "powershell.exe";
|
|
321
|
+
// Resolve on a microtask so even an injected synchronous throw cannot interrupt a tick.
|
|
322
|
+
void Promise.resolve()
|
|
323
|
+
.then(() => command(executable, windowsPowerShellArgs()))
|
|
324
|
+
.then((output) => {
|
|
325
|
+
current.sample =
|
|
326
|
+
output === null
|
|
327
|
+
? unavailableWindowsHost(nowMs, "windows_host_query_failed")
|
|
328
|
+
: parseWindowsResourceOutput(output, nowMs);
|
|
329
|
+
})
|
|
330
|
+
.catch(() => {
|
|
331
|
+
current.sample = unavailableWindowsHost(nowMs, "windows_host_query_failed");
|
|
332
|
+
})
|
|
333
|
+
.finally(() => {
|
|
334
|
+
current.pending = false;
|
|
335
|
+
});
|
|
336
|
+
return current.sample;
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export const collectWindowsHostInBackground = createWindowsHostBackgroundCollector();
|
|
341
|
+
|
|
342
|
+
function windowsPowerShellExecutable(): string {
|
|
343
|
+
// A WSL toolchain may deliberately exclude Windows executables from PATH.
|
|
344
|
+
const wslPowerShell = "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe";
|
|
345
|
+
return os.platform() === "linux" && existsSync(wslPowerShell) ? wslPowerShell : "powershell.exe";
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function windowsPowerShellArgs(): string[] {
|
|
349
|
+
// Preserve the static multiline program across both Win32 and WSL argument transports.
|
|
350
|
+
return [
|
|
351
|
+
"-NoLogo",
|
|
352
|
+
"-NoProfile",
|
|
353
|
+
"-NonInteractive",
|
|
354
|
+
"-EncodedCommand",
|
|
355
|
+
Buffer.from(WINDOWS_QUERY, "utf16le").toString("base64"),
|
|
356
|
+
];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export function parseWindowsResourceOutput(output: string, nowMs = Date.now()): ResourceHostSample {
|
|
360
|
+
let row: Record<string, unknown>;
|
|
361
|
+
try {
|
|
362
|
+
const parsed: unknown = JSON.parse(output.replace(/^\uFEFF/, "").trim());
|
|
363
|
+
if (!isRecord(parsed)) throw new Error("invalid");
|
|
364
|
+
row = parsed;
|
|
365
|
+
} catch {
|
|
366
|
+
return unavailableWindowsHost(nowMs, "windows_host_output_invalid");
|
|
367
|
+
}
|
|
368
|
+
const totalKiB = positive(row.memory_total_kib);
|
|
369
|
+
const availableKiB = nonnegative(row.memory_available_kib);
|
|
370
|
+
if (totalKiB === null || availableKiB === null || availableKiB > totalKiB) {
|
|
371
|
+
return unavailableWindowsHost(nowMs, "windows_host_memory_invalid");
|
|
372
|
+
}
|
|
373
|
+
const processors = records(row.processors);
|
|
374
|
+
let logical = 0;
|
|
375
|
+
let weightedCpu = 0;
|
|
376
|
+
let cpuValid = processors !== null && processors.length > 0;
|
|
377
|
+
for (const processor of processors ?? []) {
|
|
378
|
+
const count = positive(processor.NumberOfLogicalProcessors);
|
|
379
|
+
const percent = nonnegative(processor.LoadPercentage);
|
|
380
|
+
if (count === null || !Number.isInteger(count)) cpuValid = false;
|
|
381
|
+
else logical += count;
|
|
382
|
+
if (count === null || percent === null || percent > 100) cpuValid = false;
|
|
383
|
+
else weightedCpu += count * percent;
|
|
384
|
+
}
|
|
385
|
+
const pagefiles = records(row.pagefiles);
|
|
386
|
+
let swapTotal = 0;
|
|
387
|
+
let swapUsed = 0;
|
|
388
|
+
let swapValid = pagefiles !== null;
|
|
389
|
+
for (const file of pagefiles ?? []) {
|
|
390
|
+
const total = nonnegative(file.AllocatedBaseSize);
|
|
391
|
+
const used = nonnegative(file.CurrentUsage);
|
|
392
|
+
if (total === null || used === null || used > total) swapValid = false;
|
|
393
|
+
else {
|
|
394
|
+
swapTotal += total * 1_048_576;
|
|
395
|
+
swapUsed += used * 1_048_576;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const disks = (records(row.disks) ?? []).map(parseWindowsDisk);
|
|
399
|
+
const processCount = nonnegative(row.process_count);
|
|
400
|
+
const machine: ResourceMachineSample = {
|
|
401
|
+
cpu_percent: cpuValid && logical > 0 ? round1(weightedCpu / logical) : null,
|
|
402
|
+
cpu_logical_count: logical,
|
|
403
|
+
load_average: null,
|
|
404
|
+
memory_total_bytes: totalKiB * 1_024,
|
|
405
|
+
memory_available_bytes: availableKiB * 1_024,
|
|
406
|
+
memory_used_bytes: (totalKiB - availableKiB) * 1_024,
|
|
407
|
+
memory_percent: round1(((totalKiB - availableKiB) / totalKiB) * 100),
|
|
408
|
+
swap_total_bytes: swapValid ? swapTotal : null,
|
|
409
|
+
swap_used_bytes: swapValid ? swapUsed : null,
|
|
410
|
+
process_count: processCount !== null && Number.isInteger(processCount) ? processCount : null,
|
|
411
|
+
};
|
|
412
|
+
const complete =
|
|
413
|
+
cpuValid &&
|
|
414
|
+
swapValid &&
|
|
415
|
+
machine.process_count !== null &&
|
|
416
|
+
disks.length > 0 &&
|
|
417
|
+
disks.every((disk) => disk.state === "supported");
|
|
418
|
+
return {
|
|
419
|
+
platform: "win32",
|
|
420
|
+
sampled_at:
|
|
421
|
+
typeof row.sampled_at === "string" && Number.isFinite(Date.parse(row.sampled_at))
|
|
422
|
+
? new Date(row.sampled_at).toISOString()
|
|
423
|
+
: new Date(nowMs).toISOString(),
|
|
424
|
+
state: complete ? "supported" : "partial",
|
|
425
|
+
machine,
|
|
426
|
+
disks,
|
|
427
|
+
...(!complete ? { reason: "windows_host_optional_metrics_unavailable" } : {}),
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function parseWindowsDisk(row: Record<string, unknown>): ResourceDiskSample {
|
|
432
|
+
const total = positive(row.Size);
|
|
433
|
+
const available = nonnegative(row.FreeSpace);
|
|
434
|
+
const valid =
|
|
435
|
+
typeof row.DeviceID === "string" && total !== null && available !== null && available <= total;
|
|
436
|
+
return {
|
|
437
|
+
path: typeof row.DeviceID === "string" ? `${row.DeviceID}\\` : "unknown",
|
|
438
|
+
state: valid ? "supported" : "error",
|
|
439
|
+
total_bytes: valid ? total : null,
|
|
440
|
+
available_bytes: valid ? available : null,
|
|
441
|
+
used_percent: valid ? round1(((total - available) / total) * 100) : null,
|
|
442
|
+
...(!valid ? { reason: "windows_disk_counters_invalid" } : {}),
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function unavailableWindowsHost(nowMs: number, reason: string): ResourceHostSample {
|
|
447
|
+
return {
|
|
448
|
+
platform: "win32",
|
|
449
|
+
sampled_at: new Date(nowMs).toISOString(),
|
|
450
|
+
state: "error",
|
|
451
|
+
machine: null,
|
|
452
|
+
disks: [],
|
|
453
|
+
reason,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
458
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function records(value: unknown): Record<string, unknown>[] | null {
|
|
462
|
+
return Array.isArray(value) && value.every(isRecord) ? value : null;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function nonnegative(value: unknown): number | null {
|
|
466
|
+
if (typeof value !== "number" && !(typeof value === "string" && /^\d+(\.\d+)?$/.test(value)))
|
|
467
|
+
return null;
|
|
468
|
+
const number = Number(value);
|
|
469
|
+
return Number.isFinite(number) && number >= 0 && number <= Number.MAX_SAFE_INTEGER
|
|
470
|
+
? number
|
|
471
|
+
: null;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function positive(value: unknown): number | null {
|
|
475
|
+
const number = nonnegative(value);
|
|
476
|
+
return number !== null && number > 0 ? number : null;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function round1(value: number): number {
|
|
480
|
+
return Math.round(value * 10) / 10;
|
|
481
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { existsSync, readdirSync, readFileSync, readlinkSync } from "node:fs";
|
|
3
|
-
import { cpus, loadavg, platform, release } from "node:os";
|
|
3
|
+
import { availableParallelism, cpus, loadavg, platform, release } from "node:os";
|
|
4
4
|
import { basename, join } from "node:path";
|
|
5
5
|
import { performance } from "node:perf_hooks";
|
|
6
6
|
import { readLiveCoordinationRows } from "../agents/state/live-coordination-view.ts";
|
|
@@ -15,6 +15,12 @@ import {
|
|
|
15
15
|
type ResourceSamplerState,
|
|
16
16
|
type ResourceSnapshot,
|
|
17
17
|
} from "./contract.ts";
|
|
18
|
+
import {
|
|
19
|
+
collectWindowsHost,
|
|
20
|
+
collectWindowsHostInBackground,
|
|
21
|
+
sampleNativeResources,
|
|
22
|
+
} from "./native.ts";
|
|
23
|
+
import { collectSystemResources } from "./system.ts";
|
|
18
24
|
|
|
19
25
|
const DEFAULT_CLOCK_TICKS = 100;
|
|
20
26
|
const DEFAULT_PAGE_SIZE = 4_096;
|
|
@@ -41,7 +47,9 @@ interface LinuxCpuReading {
|
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
interface ResourceSamplerOptions {
|
|
50
|
+
backgroundHost?: boolean;
|
|
44
51
|
procRoot?: string;
|
|
52
|
+
sysRoot?: string;
|
|
45
53
|
nowMs?: number;
|
|
46
54
|
clockTicks?: number;
|
|
47
55
|
pageSize?: number;
|
|
@@ -66,6 +74,43 @@ export function sampleResources(
|
|
|
66
74
|
coordRoot: string,
|
|
67
75
|
previous?: ResourceSamplerState,
|
|
68
76
|
options: ResourceSamplerOptions = {},
|
|
77
|
+
): ResourceSampleResult {
|
|
78
|
+
const started = performance.now();
|
|
79
|
+
const cpuBefore = process.cpuUsage();
|
|
80
|
+
const currentPlatform = platform();
|
|
81
|
+
const result =
|
|
82
|
+
currentPlatform === "darwin" || currentPlatform === "win32"
|
|
83
|
+
? sampleNativeResources(coordRoot, previous, {
|
|
84
|
+
nowMs: options.nowMs,
|
|
85
|
+
backgroundHost: options.backgroundHost,
|
|
86
|
+
})
|
|
87
|
+
: sampleProcessResources(coordRoot, previous, options);
|
|
88
|
+
Object.assign(
|
|
89
|
+
result.snapshot,
|
|
90
|
+
collectSystemResources(coordRoot, {
|
|
91
|
+
procRoot: options.procRoot,
|
|
92
|
+
sysRoot: options.sysRoot,
|
|
93
|
+
nowMs: options.nowMs,
|
|
94
|
+
namespace: result.snapshot.namespace,
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
result.snapshot.machine.cpu_available_parallelism = availableParallelism();
|
|
98
|
+
// A fixture procfs has no relationship to the actual Windows host.
|
|
99
|
+
if (result.snapshot.namespace === "wsl" && !options.procRoot) {
|
|
100
|
+
result.snapshot.host = options.backgroundHost
|
|
101
|
+
? collectWindowsHostInBackground()
|
|
102
|
+
: collectWindowsHost();
|
|
103
|
+
}
|
|
104
|
+
result.snapshot.sample_duration_ms = round1(performance.now() - started);
|
|
105
|
+
const cpuUsed = process.cpuUsage(cpuBefore);
|
|
106
|
+
result.snapshot.collector_cpu_ms = round1((cpuUsed.user + cpuUsed.system) / 1_000);
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function sampleProcessResources(
|
|
111
|
+
coordRoot: string,
|
|
112
|
+
previous?: ResourceSamplerState,
|
|
113
|
+
options: ResourceSamplerOptions = {},
|
|
69
114
|
): ResourceSampleResult {
|
|
70
115
|
const started = performance.now();
|
|
71
116
|
const cpuBefore = process.cpuUsage();
|