@bridge4dev/runner 0.52.0 → 0.54.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/adapters/claude.js +111 -33
- package/dist/adapters/codex-protocol.d.ts +11 -0
- package/dist/adapters/codex-protocol.js +41 -3
- package/dist/adapters/codex.js +14 -26
- package/dist/adapters/types.d.ts +45 -1
- package/dist/adapters/types.js +53 -0
- package/dist/checkpoints.d.ts +62 -0
- package/dist/checkpoints.js +50 -1
- package/dist/git.d.ts +64 -0
- package/dist/git.js +487 -36
- package/dist/gitops.d.ts +5 -0
- package/dist/gitops.js +7 -8
- package/dist/host-load.d.ts +156 -0
- package/dist/host-load.js +223 -0
- package/dist/index.js +190 -40
- package/dist/policy.d.ts +38 -0
- package/dist/policy.js +228 -7
- package/dist/process-priority.d.ts +55 -0
- package/dist/process-priority.js +99 -0
- package/dist/protocol.d.ts +42 -20
- package/dist/recipe-schema.d.ts +6 -6
- package/dist/self-update.js +43 -2
- package/dist/service-unit.d.ts +232 -10
- package/dist/service-unit.js +372 -43
- package/dist/session-cage.d.ts +297 -0
- package/dist/session-cage.js +755 -0
- package/dist/supervisor.d.ts +156 -3
- package/dist/supervisor.js +351 -32
- package/dist/systemd-memory.d.ts +35 -0
- package/dist/systemd-memory.js +115 -0
- package/dist/verify.js +28 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What this machine can say about its own load, read straight from `/proc`.
|
|
3
|
+
*
|
|
4
|
+
* Until this existed, an overloaded dev server was visible in exactly one
|
|
5
|
+
* place: `sar` on the machine itself. The product could not say «this machine
|
|
6
|
+
* is on its knees», so it said nothing, and the first symptom a user saw was a
|
|
7
|
+
* session that had gone quiet (plan `agent-sessions-host-resources.md` §5.3).
|
|
8
|
+
*
|
|
9
|
+
* Two files and nothing else. That is a security property, not an
|
|
10
|
+
* implementation detail: this runner reports to a server, so what it may read
|
|
11
|
+
* for telemetry is listed here in full — `/proc/loadavg` and `/proc/meminfo`,
|
|
12
|
+
* both world-readable, neither containing a path, a command line, an
|
|
13
|
+
* environment variable or a process name. No `/proc/<pid>` walk, no `ps`.
|
|
14
|
+
*
|
|
15
|
+
* Mirror of `packages/shared/src/schemas/host-load.ts` and the `HOST_LOAD_*`
|
|
16
|
+
* block of `packages/shared/src/constants/runner.ts` — the DevBridge side is
|
|
17
|
+
* the source of truth, exactly like `levels.ts` mirrors the level thresholds
|
|
18
|
+
* and `protocol.ts` mirrors the API's wire types. Copied rather than imported
|
|
19
|
+
* on purpose: this package is published to npm on its own and installed by
|
|
20
|
+
* users who have no DevBridge workspace, so a `@devbridge/shared` import would
|
|
21
|
+
* make the published tarball unresolvable (see `recipe-schema.ts`).
|
|
22
|
+
*
|
|
23
|
+
* CHECKED: `host-load.test.ts` reads the shared file and compares every number
|
|
24
|
+
* below against it. A threshold edited on one side only would make the runner
|
|
25
|
+
* send frames the API throws away — or go silent while a card waits for one.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* How often the runner LOOKS at `/proc`. Not how often it sends.
|
|
29
|
+
*
|
|
30
|
+
* Fine enough that a card is never more than half a minute stale, coarse
|
|
31
|
+
* enough that reading two small files costs nothing measurable.
|
|
32
|
+
*/
|
|
33
|
+
export declare const HOST_LOAD_SAMPLE_INTERVAL_MS = 30000;
|
|
34
|
+
/**
|
|
35
|
+
* The heartbeat: an unchanged machine still reports this often.
|
|
36
|
+
*
|
|
37
|
+
* Without it a quiet runner would go silent and the API's key would expire,
|
|
38
|
+
* turning «nothing is happening» into «we have no idea» on the card. With it,
|
|
39
|
+
* silence really does mean the runner stopped talking.
|
|
40
|
+
*
|
|
41
|
+
* Bound to the API's `HOST_LOAD_TTL_MS` (120 s), and the order is the design:
|
|
42
|
+
* a heartbeat LONGER than the TTL means a healthy quiet machine shows «no
|
|
43
|
+
* data» for the gap between them. The first draft had 5 minutes against a
|
|
44
|
+
* 2-minute TTL — three silent minutes out of every five.
|
|
45
|
+
*/
|
|
46
|
+
export declare const HOST_LOAD_HEARTBEAT_MS = 60000;
|
|
47
|
+
/**
|
|
48
|
+
* How much load1 must move before an otherwise unchanged sample earns a frame.
|
|
49
|
+
*
|
|
50
|
+
* Same principle as `publishSlots`: a tick that would repeat what the server
|
|
51
|
+
* already knows is not sent at all. 0.5 is below the resolution at which a
|
|
52
|
+
* person reads the number and well above the noise of an idle machine.
|
|
53
|
+
*/
|
|
54
|
+
export declare const HOST_LOAD_MIN_DELTA_LOAD1 = 0.5;
|
|
55
|
+
/** The same idea for memory, as a fraction — see `hostLoadChangedEnough`. */
|
|
56
|
+
export declare const HOST_LOAD_MIN_DELTA_MEM_RATIO = 0.05;
|
|
57
|
+
/**
|
|
58
|
+
* One measurement of this machine. Only the moving parts.
|
|
59
|
+
*
|
|
60
|
+
* `machine: {cpuCount, memTotalBytes, memAvailableBytes}` already travels in
|
|
61
|
+
* `hello` (`index.ts`), so nothing static is repeated here. `cpuCount` is the
|
|
62
|
+
* exception and it earns its place: load1 is meaningless without it, and a
|
|
63
|
+
* consumer that had to join two sources to answer «is 12.4 a lot?» would
|
|
64
|
+
* eventually paint one machine's load against another's core count.
|
|
65
|
+
*/
|
|
66
|
+
export interface HostLoadFrame {
|
|
67
|
+
/** `/proc/loadavg`, the three windows as the kernel reports them. */
|
|
68
|
+
load1: number;
|
|
69
|
+
load5: number;
|
|
70
|
+
load15: number;
|
|
71
|
+
/** Processors, so load1 can be read as a ratio without a second lookup. */
|
|
72
|
+
cpuCount: number;
|
|
73
|
+
/**
|
|
74
|
+
* `MemAvailable`, not `MemFree` — the kernel's own estimate of what a new
|
|
75
|
+
* allocation could actually get. `MemFree` on a healthy Linux box is near
|
|
76
|
+
* zero by design (page cache), and a card drawn from it would cry wolf on
|
|
77
|
+
* every machine, every minute.
|
|
78
|
+
*/
|
|
79
|
+
memAvailableBytes: number;
|
|
80
|
+
/** Zero on a machine with no swap; consumers must not divide blindly. */
|
|
81
|
+
swapTotalBytes: number;
|
|
82
|
+
swapFreeBytes: number;
|
|
83
|
+
/**
|
|
84
|
+
* When THIS MACHINE measured it, ISO.
|
|
85
|
+
*
|
|
86
|
+
* Load-bearing, same as in `agent_versions`: frames without a session id take
|
|
87
|
+
* the gateway's unordered fast path, so two of these can be handled out of
|
|
88
|
+
* order. The card dates the number by this field, never by arrival.
|
|
89
|
+
*/
|
|
90
|
+
at: string;
|
|
91
|
+
}
|
|
92
|
+
/** Test seams. Production calls `readHostLoad()` with nothing. */
|
|
93
|
+
export interface HostLoadSources {
|
|
94
|
+
/**
|
|
95
|
+
* Where `/proc` is mounted. A seam so the parser can be driven by fixtures:
|
|
96
|
+
* a test that had to arrange the real kernel into a state would not be
|
|
97
|
+
* written, and the states worth covering (no swap, no `MemAvailable`, no
|
|
98
|
+
* file at all) cannot be arranged at all.
|
|
99
|
+
*/
|
|
100
|
+
procDir?: string;
|
|
101
|
+
/** Processors. Seam for the same reason — a test cannot change its own. */
|
|
102
|
+
cpuCount?: number;
|
|
103
|
+
/** The clock behind `at`. */
|
|
104
|
+
now?: () => Date;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Measure this machine, or say honestly that we cannot.
|
|
108
|
+
*
|
|
109
|
+
* `null` is a first-class answer and every caller must treat it as «send
|
|
110
|
+
* nothing». There is deliberately no `process.platform` check in front of it:
|
|
111
|
+
* the absence of the files IS the check, and it is the wider one — it also
|
|
112
|
+
* covers a Linux container with `/proc` masked or mounted `hidepid`, which a
|
|
113
|
+
* platform test would sail straight past into an exception. The runner is
|
|
114
|
+
* Linux-only by its installer, but «only runs on Linux» must never mean
|
|
115
|
+
* «crashes anywhere else»: this is called from a timer, and an exception here
|
|
116
|
+
* would take the whole session supervisor with it.
|
|
117
|
+
*/
|
|
118
|
+
export declare function readHostLoad(sources?: HostLoadSources): HostLoadFrame | null;
|
|
119
|
+
/**
|
|
120
|
+
* Is the heartbeat due, given how long ago the last frame actually went out?
|
|
121
|
+
*
|
|
122
|
+
* The second half of the answer is the whole reason this is a function: a
|
|
123
|
+
* backwards clock step makes «how long ago» NEGATIVE, and a machine whose load
|
|
124
|
+
* never moves would then say nothing for the entire offset — an hour of silence
|
|
125
|
+
* on a machine that may be on fire. A clock correction is due, not early.
|
|
126
|
+
*
|
|
127
|
+
* The API makes the same allowance for the same step, and has to: it drops
|
|
128
|
+
* frames older than the one it holds unless the step is larger than a
|
|
129
|
+
* measurement's lifetime (`HOST_LOAD_TTL_MS`, `runner-gateway.ts`). Half of this
|
|
130
|
+
* fix on one side of the socket is no fix at all.
|
|
131
|
+
*/
|
|
132
|
+
export declare function hostLoadHeartbeatDue(sinceLastSentMs: number, heartbeatMs: number): boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Is this sample worth a frame at all? The «a quiet runner says nothing» rule.
|
|
135
|
+
*
|
|
136
|
+
* Same shape as `publishSlots`: what the server already knows is not repeated.
|
|
137
|
+
* The alternative is 2 880 identical frames a day per machine, each one waking
|
|
138
|
+
* the gateway, a Redis write and every open dashboard socket.
|
|
139
|
+
*
|
|
140
|
+
* The memory threshold is 5 % of the machine's TOTAL memory, not 5 % of what
|
|
141
|
+
* is currently available. Two reasons, and both were the deciding one:
|
|
142
|
+
*
|
|
143
|
+
* - a fraction of `memAvailable` shrinks as the machine fills, so the frames
|
|
144
|
+
* would get chattiest exactly when the machine is least able to afford it —
|
|
145
|
+
* 5 % of the last 200 MB is a 10 MB trigger;
|
|
146
|
+
* - `memTotal` is fixed per machine, so «the card moves when a GB moves» reads
|
|
147
|
+
* the same on every server instead of meaning something different on each.
|
|
148
|
+
*
|
|
149
|
+
* Swap counts too, though the plan names only load and memory. It is one of the
|
|
150
|
+
* three verdicts behind «overloaded», and it is the only one with no other
|
|
151
|
+
* trigger: a machine that starts paging with its load and its `MemAvailable`
|
|
152
|
+
* both flat would otherwise wait up to five minutes for the heartbeat to say
|
|
153
|
+
* so, and paging is the state whose wall-clock cost is unbounded.
|
|
154
|
+
*/
|
|
155
|
+
export declare function hostLoadChangedEnough(previous: HostLoadFrame | null, next: HostLoadFrame, memTotalBytes?: number): boolean;
|
|
156
|
+
//# sourceMappingURL=host-load.d.ts.map
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* What this machine can say about its own load, read straight from `/proc`.
|
|
6
|
+
*
|
|
7
|
+
* Until this existed, an overloaded dev server was visible in exactly one
|
|
8
|
+
* place: `sar` on the machine itself. The product could not say «this machine
|
|
9
|
+
* is on its knees», so it said nothing, and the first symptom a user saw was a
|
|
10
|
+
* session that had gone quiet (plan `agent-sessions-host-resources.md` §5.3).
|
|
11
|
+
*
|
|
12
|
+
* Two files and nothing else. That is a security property, not an
|
|
13
|
+
* implementation detail: this runner reports to a server, so what it may read
|
|
14
|
+
* for telemetry is listed here in full — `/proc/loadavg` and `/proc/meminfo`,
|
|
15
|
+
* both world-readable, neither containing a path, a command line, an
|
|
16
|
+
* environment variable or a process name. No `/proc/<pid>` walk, no `ps`.
|
|
17
|
+
*
|
|
18
|
+
* Mirror of `packages/shared/src/schemas/host-load.ts` and the `HOST_LOAD_*`
|
|
19
|
+
* block of `packages/shared/src/constants/runner.ts` — the DevBridge side is
|
|
20
|
+
* the source of truth, exactly like `levels.ts` mirrors the level thresholds
|
|
21
|
+
* and `protocol.ts` mirrors the API's wire types. Copied rather than imported
|
|
22
|
+
* on purpose: this package is published to npm on its own and installed by
|
|
23
|
+
* users who have no DevBridge workspace, so a `@devbridge/shared` import would
|
|
24
|
+
* make the published tarball unresolvable (see `recipe-schema.ts`).
|
|
25
|
+
*
|
|
26
|
+
* CHECKED: `host-load.test.ts` reads the shared file and compares every number
|
|
27
|
+
* below against it. A threshold edited on one side only would make the runner
|
|
28
|
+
* send frames the API throws away — or go silent while a card waits for one.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* How often the runner LOOKS at `/proc`. Not how often it sends.
|
|
32
|
+
*
|
|
33
|
+
* Fine enough that a card is never more than half a minute stale, coarse
|
|
34
|
+
* enough that reading two small files costs nothing measurable.
|
|
35
|
+
*/
|
|
36
|
+
export const HOST_LOAD_SAMPLE_INTERVAL_MS = 30_000;
|
|
37
|
+
/**
|
|
38
|
+
* The heartbeat: an unchanged machine still reports this often.
|
|
39
|
+
*
|
|
40
|
+
* Without it a quiet runner would go silent and the API's key would expire,
|
|
41
|
+
* turning «nothing is happening» into «we have no idea» on the card. With it,
|
|
42
|
+
* silence really does mean the runner stopped talking.
|
|
43
|
+
*
|
|
44
|
+
* Bound to the API's `HOST_LOAD_TTL_MS` (120 s), and the order is the design:
|
|
45
|
+
* a heartbeat LONGER than the TTL means a healthy quiet machine shows «no
|
|
46
|
+
* data» for the gap between them. The first draft had 5 minutes against a
|
|
47
|
+
* 2-minute TTL — three silent minutes out of every five.
|
|
48
|
+
*/
|
|
49
|
+
export const HOST_LOAD_HEARTBEAT_MS = 60_000;
|
|
50
|
+
/**
|
|
51
|
+
* How much load1 must move before an otherwise unchanged sample earns a frame.
|
|
52
|
+
*
|
|
53
|
+
* Same principle as `publishSlots`: a tick that would repeat what the server
|
|
54
|
+
* already knows is not sent at all. 0.5 is below the resolution at which a
|
|
55
|
+
* person reads the number and well above the noise of an idle machine.
|
|
56
|
+
*/
|
|
57
|
+
export const HOST_LOAD_MIN_DELTA_LOAD1 = 0.5;
|
|
58
|
+
/** The same idea for memory, as a fraction — see `hostLoadChangedEnough`. */
|
|
59
|
+
export const HOST_LOAD_MIN_DELTA_MEM_RATIO = 0.05;
|
|
60
|
+
const PROC_DIR = '/proc';
|
|
61
|
+
/** kB in `/proc/meminfo` means kibibytes, and has since the field was added. */
|
|
62
|
+
const MEMINFO_UNIT_BYTES = 1024;
|
|
63
|
+
/**
|
|
64
|
+
* `MemTotal: 16307180 kB` → `['MemTotal', 16307180 * 1024]`.
|
|
65
|
+
*
|
|
66
|
+
* The `kB` suffix is required rather than assumed. A handful of `/proc/meminfo`
|
|
67
|
+
* rows carry no unit at all (`HugePages_Total`), and multiplying one of those
|
|
68
|
+
* by 1024 because it happened to be named like the row we wanted would produce
|
|
69
|
+
* a number that is wrong by three orders of magnitude and looks perfectly
|
|
70
|
+
* plausible on a card.
|
|
71
|
+
*/
|
|
72
|
+
const MEMINFO_LINE = /^([A-Za-z0-9_()]+):\s+(\d+)\s+kB$/;
|
|
73
|
+
function parseMeminfo(text) {
|
|
74
|
+
const values = new Map();
|
|
75
|
+
for (const line of text.split('\n')) {
|
|
76
|
+
const match = MEMINFO_LINE.exec(line.trim());
|
|
77
|
+
if (!match)
|
|
78
|
+
continue;
|
|
79
|
+
const [, key, amount] = match;
|
|
80
|
+
if (!key || amount === undefined)
|
|
81
|
+
continue;
|
|
82
|
+
const parsed = Number(amount);
|
|
83
|
+
if (!Number.isFinite(parsed))
|
|
84
|
+
continue;
|
|
85
|
+
values.set(key, parsed * MEMINFO_UNIT_BYTES);
|
|
86
|
+
}
|
|
87
|
+
return values;
|
|
88
|
+
}
|
|
89
|
+
/** `0.42 0.53 0.60 2/1234 56789` → the three windows, or `null` if it is not that. */
|
|
90
|
+
function parseLoadavg(text) {
|
|
91
|
+
const parts = text.trim().split(/\s+/);
|
|
92
|
+
const windows = parts.slice(0, 3).map(Number);
|
|
93
|
+
if (windows.length < 3)
|
|
94
|
+
return null;
|
|
95
|
+
for (const value of windows) {
|
|
96
|
+
if (!Number.isFinite(value) || value < 0)
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const [load1, load5, load15] = windows;
|
|
100
|
+
return { load1, load5, load15 };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Measure this machine, or say honestly that we cannot.
|
|
104
|
+
*
|
|
105
|
+
* `null` is a first-class answer and every caller must treat it as «send
|
|
106
|
+
* nothing». There is deliberately no `process.platform` check in front of it:
|
|
107
|
+
* the absence of the files IS the check, and it is the wider one — it also
|
|
108
|
+
* covers a Linux container with `/proc` masked or mounted `hidepid`, which a
|
|
109
|
+
* platform test would sail straight past into an exception. The runner is
|
|
110
|
+
* Linux-only by its installer, but «only runs on Linux» must never mean
|
|
111
|
+
* «crashes anywhere else»: this is called from a timer, and an exception here
|
|
112
|
+
* would take the whole session supervisor with it.
|
|
113
|
+
*/
|
|
114
|
+
export function readHostLoad(sources = {}) {
|
|
115
|
+
const procDir = sources.procDir ?? PROC_DIR;
|
|
116
|
+
try {
|
|
117
|
+
const load = parseLoadavg(fs.readFileSync(path.join(procDir, 'loadavg'), 'utf8'));
|
|
118
|
+
if (!load)
|
|
119
|
+
return null;
|
|
120
|
+
const meminfo = parseMeminfo(fs.readFileSync(path.join(procDir, 'meminfo'), 'utf8'));
|
|
121
|
+
const memAvailableBytes = meminfo.get('MemAvailable');
|
|
122
|
+
// No `MemAvailable` means a kernel older than 3.14 (2014). Reporting
|
|
123
|
+
// `MemFree` in its place would be worse than reporting nothing: on a
|
|
124
|
+
// healthy machine it is near zero by design, so every such card would
|
|
125
|
+
// permanently read «out of memory».
|
|
126
|
+
if (memAvailableBytes === undefined)
|
|
127
|
+
return null;
|
|
128
|
+
// Swap is reported only when BOTH halves are there. A kernel built without
|
|
129
|
+
// swap support has neither, and 0/0 is the truthful answer for it — but
|
|
130
|
+
// one half without the other is a shape we do not understand, and guessing
|
|
131
|
+
// `free = 0` for a real swap area would paint that machine as permanently
|
|
132
|
+
// paging, which is the one verdict this frame exists to make trustworthy.
|
|
133
|
+
const swapTotalRaw = meminfo.get('SwapTotal');
|
|
134
|
+
const swapFreeRaw = meminfo.get('SwapFree');
|
|
135
|
+
const swapKnown = swapTotalRaw !== undefined && swapFreeRaw !== undefined;
|
|
136
|
+
const swapTotalBytes = swapKnown ? swapTotalRaw : 0;
|
|
137
|
+
const swapFreeBytes = swapKnown ? swapFreeRaw : 0;
|
|
138
|
+
const cpuCount = sources.cpuCount ?? os.cpus().length;
|
|
139
|
+
// A frame the API's schema would reject is worse than no frame, and
|
|
140
|
+
// `os.cpus()` has been seen returning an empty array in containers. Nor may
|
|
141
|
+
// it be quietly replaced by 1: load1 is read as a ratio of this number, so
|
|
142
|
+
// a made-up core count would paint a working eight-core machine red.
|
|
143
|
+
if (!Number.isInteger(cpuCount) || cpuCount < 1)
|
|
144
|
+
return null;
|
|
145
|
+
const at = (sources.now?.() ?? new Date()).toISOString();
|
|
146
|
+
return {
|
|
147
|
+
...load,
|
|
148
|
+
cpuCount,
|
|
149
|
+
memAvailableBytes,
|
|
150
|
+
swapTotalBytes,
|
|
151
|
+
swapFreeBytes,
|
|
152
|
+
at,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// ENOENT on a Mac, EACCES in a locked-down container, EIO on a machine
|
|
157
|
+
// that is already having a very bad day. All of them mean the same thing
|
|
158
|
+
// to the caller, and none of them is worth a log line every 30 seconds.
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Is the heartbeat due, given how long ago the last frame actually went out?
|
|
164
|
+
*
|
|
165
|
+
* The second half of the answer is the whole reason this is a function: a
|
|
166
|
+
* backwards clock step makes «how long ago» NEGATIVE, and a machine whose load
|
|
167
|
+
* never moves would then say nothing for the entire offset — an hour of silence
|
|
168
|
+
* on a machine that may be on fire. A clock correction is due, not early.
|
|
169
|
+
*
|
|
170
|
+
* The API makes the same allowance for the same step, and has to: it drops
|
|
171
|
+
* frames older than the one it holds unless the step is larger than a
|
|
172
|
+
* measurement's lifetime (`HOST_LOAD_TTL_MS`, `runner-gateway.ts`). Half of this
|
|
173
|
+
* fix on one side of the socket is no fix at all.
|
|
174
|
+
*/
|
|
175
|
+
export function hostLoadHeartbeatDue(sinceLastSentMs, heartbeatMs) {
|
|
176
|
+
return sinceLastSentMs >= heartbeatMs || sinceLastSentMs < 0;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Is this sample worth a frame at all? The «a quiet runner says nothing» rule.
|
|
180
|
+
*
|
|
181
|
+
* Same shape as `publishSlots`: what the server already knows is not repeated.
|
|
182
|
+
* The alternative is 2 880 identical frames a day per machine, each one waking
|
|
183
|
+
* the gateway, a Redis write and every open dashboard socket.
|
|
184
|
+
*
|
|
185
|
+
* The memory threshold is 5 % of the machine's TOTAL memory, not 5 % of what
|
|
186
|
+
* is currently available. Two reasons, and both were the deciding one:
|
|
187
|
+
*
|
|
188
|
+
* - a fraction of `memAvailable` shrinks as the machine fills, so the frames
|
|
189
|
+
* would get chattiest exactly when the machine is least able to afford it —
|
|
190
|
+
* 5 % of the last 200 MB is a 10 MB trigger;
|
|
191
|
+
* - `memTotal` is fixed per machine, so «the card moves when a GB moves» reads
|
|
192
|
+
* the same on every server instead of meaning something different on each.
|
|
193
|
+
*
|
|
194
|
+
* Swap counts too, though the plan names only load and memory. It is one of the
|
|
195
|
+
* three verdicts behind «overloaded», and it is the only one with no other
|
|
196
|
+
* trigger: a machine that starts paging with its load and its `MemAvailable`
|
|
197
|
+
* both flat would otherwise wait up to five minutes for the heartbeat to say
|
|
198
|
+
* so, and paging is the state whose wall-clock cost is unbounded.
|
|
199
|
+
*/
|
|
200
|
+
export function hostLoadChangedEnough(previous, next, memTotalBytes = os.totalmem()) {
|
|
201
|
+
// Nothing said yet — the first measurement after a (re)connect is always news.
|
|
202
|
+
if (!previous)
|
|
203
|
+
return true;
|
|
204
|
+
if (Math.abs(next.load1 - previous.load1) >= HOST_LOAD_MIN_DELTA_LOAD1)
|
|
205
|
+
return true;
|
|
206
|
+
// A machine that cannot say how much memory it has still gets a working
|
|
207
|
+
// threshold: the sample's own availability is a poor denominator but a
|
|
208
|
+
// finite one, and the alternative is dividing by zero forever.
|
|
209
|
+
const memBase = memTotalBytes > 0 ? memTotalBytes : next.memAvailableBytes;
|
|
210
|
+
const memDelta = Math.abs(next.memAvailableBytes - previous.memAvailableBytes);
|
|
211
|
+
if (memBase > 0 && memDelta >= memBase * HOST_LOAD_MIN_DELTA_MEM_RATIO)
|
|
212
|
+
return true;
|
|
213
|
+
// Swap size itself can change (`swapon`), and that is news by the same rule.
|
|
214
|
+
if (next.swapTotalBytes !== previous.swapTotalBytes)
|
|
215
|
+
return true;
|
|
216
|
+
if (next.swapTotalBytes > 0) {
|
|
217
|
+
const swapDelta = Math.abs(next.swapFreeBytes - previous.swapFreeBytes);
|
|
218
|
+
if (swapDelta >= next.swapTotalBytes * HOST_LOAD_MIN_DELTA_MEM_RATIO)
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=host-load.js.map
|