@bridge4dev/runner 0.70.1 → 0.71.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/host-load.d.ts +53 -5
- package/dist/host-load.js +39 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/host-load.d.ts
CHANGED
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
* is on its knees», so it said nothing, and the first symptom a user saw was a
|
|
7
7
|
* session that had gone quiet (plan `agent-sessions-host-resources.md` §5.3).
|
|
8
8
|
*
|
|
9
|
-
* Two files and nothing else. That is a security
|
|
10
|
-
* implementation detail: this runner reports to a server, so
|
|
11
|
-
* for telemetry is listed here in full — `/proc/loadavg` and
|
|
12
|
-
* both world-readable, neither containing a path, a command
|
|
13
|
-
* environment variable or a process name
|
|
9
|
+
* Two files and one filesystem call, and nothing else. That is a security
|
|
10
|
+
* property, not an implementation detail: this runner reports to a server, so
|
|
11
|
+
* what it may read for telemetry is listed here in full — `/proc/loadavg` and
|
|
12
|
+
* `/proc/meminfo`, both world-readable, neither containing a path, a command
|
|
13
|
+
* line, an environment variable or a process name; and `statfs('/')` (0.71.0,
|
|
14
|
+
* #485), which answers four sizes of the root filesystem and nothing about what
|
|
15
|
+
* is on it. No `/proc/<pid>` walk, no `ps`, no directory listing, no `du`.
|
|
14
16
|
*
|
|
15
17
|
* **One module is allowed past that line, and it is named here so the rule and
|
|
16
18
|
* the code cannot drift apart:** `session-stall.ts` reads `/proc/<pid>` for the
|
|
@@ -89,6 +91,13 @@ export interface HostLoadFrame {
|
|
|
89
91
|
/** Zero on a machine with no swap; consumers must not divide blindly. */
|
|
90
92
|
swapTotalBytes: number;
|
|
91
93
|
swapFreeBytes: number;
|
|
94
|
+
/**
|
|
95
|
+
* The root filesystem (0.71.0, #485) – or `null` when it could not be read,
|
|
96
|
+
* which is «disk unknown» and never a reason to hold the rest of the frame
|
|
97
|
+
* back. It rides the same minute-long heartbeat as everything else here: a
|
|
98
|
+
* disk fills over hours, and a trigger of its own would buy nothing.
|
|
99
|
+
*/
|
|
100
|
+
disk: HostDiskFrame | null;
|
|
92
101
|
/**
|
|
93
102
|
* When THIS MACHINE measured it, ISO.
|
|
94
103
|
*
|
|
@@ -98,6 +107,31 @@ export interface HostLoadFrame {
|
|
|
98
107
|
*/
|
|
99
108
|
at: string;
|
|
100
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Free space on the root filesystem – what a full disk stops the machine at.
|
|
112
|
+
*
|
|
113
|
+
* Mirror of `HostDiskFrameSchema` in `@devbridge/shared`. The same arithmetic as
|
|
114
|
+
* the DevBridge host's own reading (`admin-host.service.ts`), so «free» means one
|
|
115
|
+
* thing on the Fleet screen whichever machine it describes: `freeBytes` is
|
|
116
|
+
* `bavail` (what an unprivileged writer can still use, not `bfree`), `usedBytes`
|
|
117
|
+
* is total − `bfree` (what `df` calls used).
|
|
118
|
+
*/
|
|
119
|
+
export interface HostDiskFrame {
|
|
120
|
+
/** Always {@link HOST_DISK_PATH} today; carried so the reading describes itself. */
|
|
121
|
+
path: string;
|
|
122
|
+
totalBytes: number;
|
|
123
|
+
freeBytes: number;
|
|
124
|
+
usedBytes: number;
|
|
125
|
+
}
|
|
126
|
+
/** The filesystem measured: the root, where running out stops everything. */
|
|
127
|
+
export declare const HOST_DISK_PATH = "/";
|
|
128
|
+
/** What `fs.statfsSync` answers, as far as a size is concerned. */
|
|
129
|
+
export interface DiskStats {
|
|
130
|
+
bsize: number;
|
|
131
|
+
blocks: number;
|
|
132
|
+
bfree: number;
|
|
133
|
+
bavail: number;
|
|
134
|
+
}
|
|
101
135
|
/** Test seams. Production calls `readHostLoad()` with nothing. */
|
|
102
136
|
export interface HostLoadSources {
|
|
103
137
|
/**
|
|
@@ -111,7 +145,21 @@ export interface HostLoadSources {
|
|
|
111
145
|
cpuCount?: number;
|
|
112
146
|
/** The clock behind `at`. */
|
|
113
147
|
now?: () => Date;
|
|
148
|
+
/**
|
|
149
|
+
* `statfs`, for the same reason as `procDir`: a full disk, a filesystem that
|
|
150
|
+
* refuses the call and a reading that does not add up are exactly the states a
|
|
151
|
+
* test machine is not in.
|
|
152
|
+
*/
|
|
153
|
+
statfs?: (target: string) => DiskStats;
|
|
114
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Measure the root filesystem, or say honestly that we cannot.
|
|
157
|
+
*
|
|
158
|
+
* Every number has to be a whole, non-negative, safely representable count of
|
|
159
|
+
* bytes that fits inside the total – the API's schema holds the same line, and a
|
|
160
|
+
* reading it would refuse is better not sent than sent. Any doubt is `null`.
|
|
161
|
+
*/
|
|
162
|
+
export declare function readRootDisk(statfs?: (target: string) => DiskStats): HostDiskFrame | null;
|
|
115
163
|
/**
|
|
116
164
|
* Measure this machine, or say honestly that we cannot.
|
|
117
165
|
*
|
package/dist/host-load.js
CHANGED
|
@@ -9,11 +9,13 @@ import path from 'node:path';
|
|
|
9
9
|
* is on its knees», so it said nothing, and the first symptom a user saw was a
|
|
10
10
|
* session that had gone quiet (plan `agent-sessions-host-resources.md` §5.3).
|
|
11
11
|
*
|
|
12
|
-
* Two files and nothing else. That is a security
|
|
13
|
-
* implementation detail: this runner reports to a server, so
|
|
14
|
-
* for telemetry is listed here in full — `/proc/loadavg` and
|
|
15
|
-
* both world-readable, neither containing a path, a command
|
|
16
|
-
* environment variable or a process name
|
|
12
|
+
* Two files and one filesystem call, and nothing else. That is a security
|
|
13
|
+
* property, not an implementation detail: this runner reports to a server, so
|
|
14
|
+
* what it may read for telemetry is listed here in full — `/proc/loadavg` and
|
|
15
|
+
* `/proc/meminfo`, both world-readable, neither containing a path, a command
|
|
16
|
+
* line, an environment variable or a process name; and `statfs('/')` (0.71.0,
|
|
17
|
+
* #485), which answers four sizes of the root filesystem and nothing about what
|
|
18
|
+
* is on it. No `/proc/<pid>` walk, no `ps`, no directory listing, no `du`.
|
|
17
19
|
*
|
|
18
20
|
* **One module is allowed past that line, and it is named here so the rule and
|
|
19
21
|
* the code cannot drift apart:** `session-stall.ts` reads `/proc/<pid>` for the
|
|
@@ -66,7 +68,36 @@ export const HOST_LOAD_HEARTBEAT_MS = 60_000;
|
|
|
66
68
|
export const HOST_LOAD_MIN_DELTA_LOAD1 = 0.5;
|
|
67
69
|
/** The same idea for memory, as a fraction — see `hostLoadChangedEnough`. */
|
|
68
70
|
export const HOST_LOAD_MIN_DELTA_MEM_RATIO = 0.05;
|
|
71
|
+
/** The filesystem measured: the root, where running out stops everything. */
|
|
72
|
+
export const HOST_DISK_PATH = '/';
|
|
69
73
|
const PROC_DIR = '/proc';
|
|
74
|
+
/**
|
|
75
|
+
* Measure the root filesystem, or say honestly that we cannot.
|
|
76
|
+
*
|
|
77
|
+
* Every number has to be a whole, non-negative, safely representable count of
|
|
78
|
+
* bytes that fits inside the total – the API's schema holds the same line, and a
|
|
79
|
+
* reading it would refuse is better not sent than sent. Any doubt is `null`.
|
|
80
|
+
*/
|
|
81
|
+
export function readRootDisk(statfs = fs.statfsSync) {
|
|
82
|
+
try {
|
|
83
|
+
const stat = statfs(HOST_DISK_PATH);
|
|
84
|
+
const totalBytes = stat.blocks * stat.bsize;
|
|
85
|
+
const freeBytes = stat.bavail * stat.bsize;
|
|
86
|
+
const usedBytes = Math.max(0, totalBytes - stat.bfree * stat.bsize);
|
|
87
|
+
const whole = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
88
|
+
if (!whole(totalBytes) || totalBytes === 0 || !whole(freeBytes) || !whole(usedBytes)) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
if (freeBytes > totalBytes || usedBytes > totalBytes)
|
|
92
|
+
return null;
|
|
93
|
+
return { path: HOST_DISK_PATH, totalBytes, freeBytes, usedBytes };
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// ENOSYS in an odd container, EACCES under a locked-down profile: the disk
|
|
97
|
+
// is unknown, and the load beside it is still worth sending.
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
70
101
|
/** kB in `/proc/meminfo` means kibibytes, and has since the field was added. */
|
|
71
102
|
const MEMINFO_UNIT_BYTES = 1024;
|
|
72
103
|
/**
|
|
@@ -158,6 +189,9 @@ export function readHostLoad(sources = {}) {
|
|
|
158
189
|
memAvailableBytes,
|
|
159
190
|
swapTotalBytes,
|
|
160
191
|
swapFreeBytes,
|
|
192
|
+
// Its own failure is `null` inside the frame, never a missing frame: a
|
|
193
|
+
// machine whose filesystem refuses `statfs` still has a load worth showing.
|
|
194
|
+
disk: readRootDisk(sources.statfs),
|
|
161
195
|
at,
|
|
162
196
|
};
|
|
163
197
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const RUNNER_VERSION = "0.
|
|
1
|
+
export declare const RUNNER_VERSION = "0.71.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
package/package.json
CHANGED