@nimbus-sh/core 0.5.0 → 0.6.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.
Files changed (62) hide show
  1. package/dist/_shared/cache-stats.d.ts +23 -1
  2. package/dist/_shared/cache-stats.d.ts.map +1 -1
  3. package/dist/_shared/cache-stats.js +13 -1
  4. package/dist/_shared/exports-resolver.d.ts +1 -1
  5. package/dist/_shared/exports-resolver.js +1 -1
  6. package/dist/_shared/retry.js +1 -1
  7. package/dist/constants.d.ts +5 -11
  8. package/dist/constants.d.ts.map +1 -1
  9. package/dist/constants.js +16 -56
  10. package/dist/runtime/assets-loader.js +1 -1
  11. package/dist/runtime/virtual-socket-kernel.generated.d.ts +1 -1
  12. package/dist/runtime/virtual-socket-kernel.generated.js +1 -1
  13. package/dist/vfs/seed-project.d.ts.map +1 -1
  14. package/dist/vfs/seed-project.js +1 -1
  15. package/dist/vfs/sqlite-vfs.d.ts +2 -30
  16. package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
  17. package/dist/vfs/sqlite-vfs.js +7 -6
  18. package/package.json +3 -2
  19. package/src/_shared/cache-stats.ts +27 -1
  20. package/src/_shared/exports-resolver.ts +1 -1
  21. package/src/_shared/retry.ts +1 -1
  22. package/src/constants.ts +30 -60
  23. package/src/runtime/assets-loader.ts +1 -1
  24. package/src/runtime/virtual-socket-kernel.generated.ts +1 -1
  25. package/src/vfs/seed-project.ts +3 -3
  26. package/src/vfs/sqlite-vfs.ts +24 -41
  27. package/dist/_shared/install-phase.d.ts +0 -47
  28. package/dist/_shared/install-phase.d.ts.map +0 -1
  29. package/dist/_shared/install-phase.js +0 -1
  30. package/dist/_shared/rpc-dispose.d.ts +0 -4
  31. package/dist/_shared/rpc-dispose.d.ts.map +0 -1
  32. package/dist/_shared/rpc-dispose.js +0 -30
  33. package/dist/_shared/w7-frame.d.ts +0 -79
  34. package/dist/_shared/w7-frame.d.ts.map +0 -1
  35. package/dist/_shared/w7-frame.js +0 -840
  36. package/dist/_shared/weighted-credit-pool.d.ts +0 -61
  37. package/dist/_shared/weighted-credit-pool.d.ts.map +0 -1
  38. package/dist/_shared/weighted-credit-pool.js +0 -196
  39. package/dist/observability/diag-counters.d.ts +0 -236
  40. package/dist/observability/diag-counters.d.ts.map +0 -1
  41. package/dist/observability/diag-counters.js +0 -195
  42. package/dist/observability/heap-estimate.d.ts +0 -225
  43. package/dist/observability/heap-estimate.d.ts.map +0 -1
  44. package/dist/observability/heap-estimate.js +0 -185
  45. package/dist/observability/heavy-alloc-coord.d.ts +0 -93
  46. package/dist/observability/heavy-alloc-coord.d.ts.map +0 -1
  47. package/dist/observability/heavy-alloc-coord.js +0 -181
  48. package/dist/observability/oom-classify.d.ts +0 -138
  49. package/dist/observability/oom-classify.d.ts.map +0 -1
  50. package/dist/observability/oom-classify.js +0 -268
  51. package/dist/observability/oom-discriminator.d.ts +0 -176
  52. package/dist/observability/oom-discriminator.d.ts.map +0 -1
  53. package/dist/observability/oom-discriminator.js +0 -272
  54. package/src/_shared/install-phase.ts +0 -56
  55. package/src/_shared/rpc-dispose.ts +0 -31
  56. package/src/_shared/w7-frame.ts +0 -1013
  57. package/src/_shared/weighted-credit-pool.ts +0 -244
  58. package/src/observability/diag-counters.ts +0 -343
  59. package/src/observability/heap-estimate.ts +0 -331
  60. package/src/observability/heavy-alloc-coord.ts +0 -241
  61. package/src/observability/oom-classify.ts +0 -260
  62. package/src/observability/oom-discriminator.ts +0 -405
@@ -1,195 +0,0 @@
1
- /**
2
- * diag-counters.ts — application-level memory + phase observability.
3
- *
4
- * Why: workerd's `process.memoryUsage()` returns 0 for all fields inside
5
- * a Durable Object class context (only dynamic-worker isolates under
6
- * nodejs_compat get the real implementation). Without a working heap
7
- * probe we have no way to confirm OOM hypotheses or verify a fix.
8
- *
9
- * Replacement: deterministic counters bumped at known allocation sites.
10
- * Cumulative bytes decoded, in-flight stub counts, and phase markers
11
- * give us the same operational signal as a heap probe — and they're
12
- * exact, not estimated.
13
- *
14
- * Singleton-per-isolate. Lives at module scope so any code path in the
15
- * supervisor bundle can write to it (installer and RPC handlers) and
16
- * the request handler in nimbus-session.ts:/api/_diag/memory can read
17
- * it. Survives across requests within the same isolate; resets on DO
18
- * reboot — itself a useful signal (counters at 0 immediately after the
19
- * banner reprinted = the killed isolate took its state with it).
20
- */
21
- const _counters = {
22
- installPhase: 'idle',
23
- inFlightRpcPayloadBytes: 0,
24
- prefetchBundleBytes: 0,
25
- prefetchCacheBytes: 0,
26
- installFacet: {
27
- tarballsCompleted: 0,
28
- cumulativeBytesDecoded: 0,
29
- peakInFlight: 0,
30
- },
31
- preBundleFacet: {
32
- attempted: 0,
33
- bundlesCompleted: 0,
34
- errors: 0,
35
- skipped: 0,
36
- wasmBootBytes: 0,
37
- errorsByModule: {},
38
- },
39
- r2: {
40
- pipelinedTarballRaceWins: 0,
41
- pipelinedTarballRaceLosses: 0,
42
- pipelinedPackumentRaceWins: 0,
43
- pipelinedPackumentRaceLosses: 0,
44
- },
45
- };
46
- /** Read a snapshot — caller-side mutations don't affect the singleton. */
47
- export function readDiagCounters() {
48
- return { ..._counters };
49
- }
50
- /** Set the install phase. */
51
- export function setInstallPhase(p) {
52
- _counters.installPhase = p;
53
- }
54
- /**
55
- * Track an in-flight supervisor RPC payload [Phase 2 A'.2].
56
- *
57
- * Call at RPC entry, BEFORE awaiting any work that depends on
58
- * `payload`. Pair with `rpcPayloadEnd(bytes)` in the matching `finally`
59
- * so the counter goes back to zero on both success and failure.
60
- *
61
- * `bytes` is the supervisor-resident byte cost of the RPC's argument
62
- * (or return value, whichever is bigger). For structured-clone RPCs
63
- * this is the size of the cloned payload in bytes.
64
- *
65
- * Streamed payloads (ReadableStream-over-RPC) flow with backpressure
66
- * and the supervisor-resident bound is the chunk size, not the total
67
- * payload. Pass the chunk-size estimate (typically ≤ 1 MiB) — never
68
- * the unknown total. -1 is silently coerced to 0 to keep the counter
69
- * non-negative; callers that don't know the size should call this
70
- * with 0 explicitly rather than relying on coercion.
71
- */
72
- export function rpcPayloadStart(bytes) {
73
- const n = Number(bytes);
74
- if (!Number.isFinite(n) || n <= 0)
75
- return;
76
- _counters.inFlightRpcPayloadBytes += n;
77
- }
78
- /**
79
- * Release an in-flight supervisor RPC payload [Phase 2 A'.2].
80
- * Pass the same byte count given to the matching `rpcPayloadStart`.
81
- *
82
- * Floors at 0 to absorb arithmetic drift (e.g. if a payload-byte
83
- * counter rounding gave a slightly different number on entry vs.
84
- * exit). Drift in routine paths should be zero — a non-zero floor
85
- * hit is a bug worth investigating.
86
- */
87
- export function rpcPayloadEnd(bytes) {
88
- const n = Number(bytes);
89
- if (!Number.isFinite(n) || n <= 0)
90
- return;
91
- _counters.inFlightRpcPayloadBytes -= n;
92
- if (_counters.inFlightRpcPayloadBytes < 0) {
93
- _counters.inFlightRpcPayloadBytes = 0;
94
- }
95
- }
96
- /**
97
- * Track an in-flight prefetch-bundle build. Call with the byte reservation the
98
- * build took from the supervisor allocation budget, and pair with
99
- * `prefetchBundleEnd` in the matching `finally` so it returns to zero whether
100
- * the build succeeded or threw.
101
- */
102
- export function prefetchBundleStart(bytes) {
103
- const n = Number(bytes);
104
- if (!Number.isFinite(n) || n <= 0)
105
- return;
106
- _counters.prefetchBundleBytes += n;
107
- }
108
- /** Release an in-flight prefetch-bundle reservation. Floors at 0. */
109
- export function prefetchBundleEnd(bytes) {
110
- const n = Number(bytes);
111
- if (!Number.isFinite(n) || n <= 0)
112
- return;
113
- _counters.prefetchBundleBytes -= n;
114
- if (_counters.prefetchBundleBytes < 0)
115
- _counters.prefetchBundleBytes = 0;
116
- }
117
- /**
118
- * Publish the prefetch-bundle LRU's live retained total. A gauge: the cache
119
- * owns the number and reports it whenever it admits or evicts an entry.
120
- */
121
- export function setPrefetchCacheBytes(bytes) {
122
- const n = Number(bytes);
123
- _counters.prefetchCacheBytes = Number.isFinite(n) && n > 0 ? n : 0;
124
- }
125
- /** Fold facet-returned counters into the supervisor's diag state.
126
- * Called by npm-installer after the batch-facet returns; aggregates
127
- * rather than replaces so multiple install runs in the same DO
128
- * lifetime accumulate in cumulativeBytesDecoded. */
129
- export function recordInstallFacetCounters(c) {
130
- _counters.installFacet.tarballsCompleted += c.tarballsCompleted;
131
- _counters.installFacet.cumulativeBytesDecoded += c.cumulativeBytesDecoded;
132
- if (c.peakInFlight > _counters.installFacet.peakInFlight) {
133
- _counters.installFacet.peakInFlight = c.peakInFlight;
134
- }
135
- }
136
- /** Record pre-bundle phase summary. Aggregates lifetime totals across
137
- * all phases run in this DO's lifetime, but REPLACES the
138
- * errorsByModule map every call so it reflects only the most recent
139
- * batch. Losing prior-batch errors is fine — they're already aggregated
140
- * into `errors` (count); the map is for "which modules failed this
141
- * time." */
142
- export function recordPreBundleSummary(s) {
143
- _counters.preBundleFacet.attempted += s.attempted;
144
- _counters.preBundleFacet.bundlesCompleted += s.bundlesCompleted;
145
- _counters.preBundleFacet.errors += s.errors;
146
- _counters.preBundleFacet.skipped += s.skipped;
147
- if (s.wasmBootBytes && _counters.preBundleFacet.wasmBootBytes === 0) {
148
- _counters.preBundleFacet.wasmBootBytes = s.wasmBootBytes;
149
- }
150
- // Replace (not merge) so we capture only the most recent batch.
151
- // Bounded by batch size; truncate each value to 200 chars.
152
- if (s.errorsByModule) {
153
- const trimmed = {};
154
- for (const [name, msg] of Object.entries(s.errorsByModule)) {
155
- trimmed[name] = String(msg).slice(0, 200);
156
- }
157
- _counters.preBundleFacet.errorsByModule = trimmed;
158
- }
159
- }
160
- /** Bump pipelined-RPC race outcome counters. The facet returns these
161
- * in its result counters; the supervisor folds them in alongside the
162
- * existing installFacet counters. */
163
- export function recordR2RaceCounters(c) {
164
- _counters.r2.pipelinedTarballRaceWins += c.pipelinedTarballRaceWins;
165
- _counters.r2.pipelinedTarballRaceLosses += c.pipelinedTarballRaceLosses;
166
- _counters.r2.pipelinedPackumentRaceWins += c.pipelinedPackumentRaceWins;
167
- _counters.r2.pipelinedPackumentRaceLosses += c.pipelinedPackumentRaceLosses;
168
- }
169
- /**
170
- * cache-obs-2: fold facet-collected per-tier cache events into the
171
- * DO-side cache-stats singleton. Called from installer.ts after a
172
- * batch-facet / resolve-facet returns — mirrors recordR2RaceCounters
173
- * (the facet collects metrics in its result and the supervisor folds
174
- * them into the DO isolate).
175
- *
176
- * Each event has shape:
177
- * { kind: 'hit', tier: 'L2'|'L3'|'L4', cacheKind: 'tarball'|'packument'|'asset', bytes: number }
178
- * { kind: 'miss', tier: ..., cacheKind: ... }
179
- *
180
- * The recordHit/recordMiss surface is defined in
181
- * src/_shared/cache-stats.ts.
182
- */
183
- import { recordHit as _cacheRecordHit, recordMiss as _cacheRecordMiss, } from '../_shared/cache-stats.js';
184
- export function recordCacheStatEvents(events) {
185
- if (!events || events.length === 0)
186
- return;
187
- for (const e of events) {
188
- if (e.kind === 'hit') {
189
- _cacheRecordHit(e.tier, e.cacheKind, e.bytes);
190
- }
191
- else {
192
- _cacheRecordMiss(e.tier, e.cacheKind);
193
- }
194
- }
195
- }
@@ -1,225 +0,0 @@
1
- /**
2
- * heap-estimate.ts — deterministic supervisor-heap estimator [C'.1]
3
- *
4
- * Why this module exists
5
- * ──────────────────────
6
- * `process.memoryUsage()` returns 0 for every field inside a Durable
7
- * Object class context (only dynamic-worker isolates under nodejs_compat
8
- * get the real implementation). The previous `readNodeMem` /
9
- * `sampleMemory` helpers in nimbus-session-diag.ts called it anyway and
10
- * therefore reported zero forever — useless for verifying memory-
11
- * containment work in plan §3 Track A'.
12
- *
13
- * Replacement: a deterministic estimator that sums the INSTRUMENTED
14
- * supervisor heap allocation sources — runtime counters that ARE accurate
15
- * (diag-counters.ts singleton + SqliteVFS.getStats()). Every byte it
16
- * reports has a named contributor, so a regression in any one component
17
- * is locatable.
18
- *
19
- * `estimatedBytes` is a LOWER BOUND, not a total
20
- * ─────────────────────────────────────────────
21
- * Within each instrumented component the estimator over-reports on
22
- * purpose: components carry a peak-or-current value, in-flight bytes are
23
- * counted before they are freed, and LRU bytes are the cap rather than the
24
- * live footprint.
25
- *
26
- * Across components it can only sum what is instrumented. Allocation sites
27
- * that neither bump a diag counter nor take a lease via
28
- * `acquireSupervisorAllocation` are invisible to it, and it has no way to
29
- * infer their size — so any site known to be uninstrumented is named in
30
- * HEAP_BLIND_SPOTS rather than left silently absent.
31
- *
32
- * The prefetch-bundle path used to be exactly that gap, and a large one: the
33
- * supervisor DO was reset three times under bundle construction while this
34
- * estimator reported a 9.4 MiB baseline and 35 KB of LRU. Both of its sites
35
- * are now instrumented where they allocate — the per-exec build leases the
36
- * budget it spends (`prefetchBundleBytes`) and the cross-exec cache is bounded
37
- * by bytes and reports its retained total (`prefetchCacheBytes`) — so they are
38
- * components of the breakdown rather than caveats beside it.
39
- *
40
- * `blindSpots` is still returned alongside `estimatedBytes` because a list
41
- * being empty is a claim that has to be re-earned as the system grows, not a
42
- * property of the estimator. Closing a blind spot always means instrumenting
43
- * it at its own allocation site and adding it to the breakdown — never
44
- * estimating it from here.
45
- *
46
- * Eviction-label taxonomy
47
- * ───────────────────────
48
- * Workerd distinguishes five labelled eviction reasons:
49
- * - lru → memory pressure on the runtime process
50
- * - condemned → kill (operator / abuse pipeline)
51
- * - inactive → idle eviction (70-140 s of no traffic)
52
- * - dynamic_worker → per-owner LRU cap (default 50)
53
- * - dynamic_worker_banned → Dice abuse-detection ban
54
- *
55
- * Nimbus surfaces the labels here so any tool that reads
56
- * /api/_diag/memory has a fixed, well-known taxonomy to count against.
57
- * The actual count of evictions Nimbus has observed lives in the C'.2
58
- * recovery_event ring, separate from this module.
59
- */
60
- import type { DiagCounters } from './diag-counters.js';
61
- import { type SupervisorAllocationBudgetStats } from './heavy-alloc-coord.js';
62
- /**
63
- * Five labelled workerd eviction reasons. Surfaced as a constant
64
- * taxonomy in /api/_diag/memory so any consumer can count observed
65
- * events against the well-known set.
66
- *
67
- */
68
- export declare const WORKERD_EVICTION_LABELS: readonly ["lru", "condemned", "inactive", "dynamic_worker", "dynamic_worker_banned"];
69
- /**
70
- * A supervisor allocation site the estimator cannot see.
71
- *
72
- * Listing one here is a statement of fact about instrumentation, not a
73
- * measurement: the site allocates on the supervisor heap without bumping a
74
- * diag counter or taking an `acquireSupervisorAllocation` lease, so its
75
- * live size is unknown to this module. Only `capBytes` — the bound the
76
- * site enforces on itself — is knowable from here.
77
- */
78
- export interface HeapBlindSpot {
79
- /** Module-qualified allocation site. */
80
- source: string;
81
- /**
82
- * Upper bound the site enforces per retained item, or null when nothing
83
- * bounds its total bytes. A null here is the important case: it means no
84
- * finite worst case can be stated for the supervisor at all.
85
- */
86
- capBytes: number | null;
87
- /** What allocates, and why the estimator cannot observe it. */
88
- reason: string;
89
- }
90
- /**
91
- * Supervisor allocation sites known to be unaccounted for.
92
- *
93
- * Empty: both former entries were the prefetch-bundle path in
94
- * facets/manager.ts, and both are now instrumented at their own allocation
95
- * sites rather than described from here. `buildPrefetchBundle` takes an
96
- * `acquireSupervisorAllocation` lease and reports
97
- * `breakdown.prefetchBundleBytes`; `prefetchBundleCache` is bounded by
98
- * PREFETCH_CACHE_MAX_BYTES instead of by entry count and reports its live
99
- * retained total as `breakdown.prefetchCacheBytes`.
100
- *
101
- * An empty list says nothing is CURRENTLY known to be missing — it is not a
102
- * proof of completeness, which no list can be. Adding an entry is how a newly
103
- * discovered gap stays visible in /api/_diag/memory until it is closed the
104
- * same way: instrumented where it allocates, then folded into HeapBreakdown.
105
- */
106
- export declare const HEAP_BLIND_SPOTS: readonly HeapBlindSpot[];
107
- /**
108
- * Supervisor heap snapshot. Components are PEAK-OR-CURRENT bytes from
109
- * runtime counters; the estimator never calls process.memoryUsage().
110
- */
111
- export interface HeapEstimate {
112
- /**
113
- * Sum of the instrumented components below. Always equals the breakdown
114
- * sum, and is a LOWER BOUND on supervisor heap — see `blindSpots`.
115
- */
116
- estimatedBytes: number;
117
- /** SUPERVISOR_HEAP_CEILING_BYTES (constant — 64 MiB by design). */
118
- ceilingBytes: number;
119
- /**
120
- * estimatedBytes / ceilingBytes × 100, one decimal place. Shares
121
- * estimatedBytes' lower-bound caveat: a low value does NOT mean the
122
- * supervisor has headroom, only that instrumented sources are small.
123
- */
124
- percentOfCeiling: number;
125
- /** Per-source byte attribution for the instrumented sources. */
126
- breakdown: HeapBreakdown;
127
- /**
128
- * Allocation sites this estimate does not cover. Non-empty means
129
- * `estimatedBytes` cannot be read as a total. See HEAP_BLIND_SPOTS.
130
- */
131
- blindSpots: readonly HeapBlindSpot[];
132
- /**
133
- * Worst-case bytes the blind spots could add, or null when any of them
134
- * is unbounded — in which case no finite worst case exists and only
135
- * direct instrumentation can answer the headroom question.
136
- */
137
- blindSpotCeilingBytes: number | null;
138
- /**
139
- * Shared transient-allocation budget occupancy. Named contributors overlap
140
- * this total; any otherwise-unattributed occupancy is included in
141
- * breakdown.unattributedReservationBytes.
142
- */
143
- allocationBudget: SupervisorAllocationBudgetStats;
144
- }
145
- export interface HeapBreakdown {
146
- /** Static module bundle + runtime baseline. Constant per build. */
147
- supervisorBaselineBytes: number;
148
- /** SqliteVFS LRU cache hot-byte count (sqlite-vfs.ts cache.hotBytes). */
149
- vfsLruBytes: number;
150
- /** SqliteVFS current logical retained write payload bytes. */
151
- vfsInFlightBytes: number;
152
- /** Pre-bundle slice bytes resident in supervisor heap (peak across
153
- * in-flight pool slots × SLICE_CAP_BYTES). Drops to 0 once
154
- * A'.2/A'.3 stream the slice through ReadableStream-over-RPC. */
155
- preBundleSliceBytes: number;
156
- /**
157
- * In-flight supervisor RPC payload bytes (Phase 2 A'.2).
158
- *
159
- * Sum of bytes claimed by DO-side byte-returning filesystem RPC handlers
160
- * between payload allocation and exit. Tracked by
161
- * `inFlightRpcPayloadBytes` in src/observability/diag-counters.ts; bumped
162
- * after shared-budget admission and debited in `finally`.
163
- *
164
- * At idle this is 0. Under load it should stay bounded by the
165
- * shared allocation budget.
166
- * Persistent non-zero readings here mean an RPC handler isn't
167
- * decrementing on its failure path — a leak worth fixing.
168
- */
169
- streamingBuffersBytes: number;
170
- /**
171
- * Bytes reserved by prefetch-bundle builds in flight (diag-counters
172
- * `prefetchBundleBytes`). Transient: each build leases the enrichment budget
173
- * for its duration and releases it on the way out, success or failure.
174
- */
175
- prefetchBundleBytes: number;
176
- /**
177
- * Bytes retained by the FacetManager's prefetch-bundle LRU (diag-counters
178
- * `prefetchCacheBytes`). Unlike every other component here these persist
179
- * across execs, and unlike the transient ones they are a measurement of what
180
- * the cache holds rather than a reservation it might use.
181
- */
182
- prefetchCacheBytes: number;
183
- /**
184
- * Shared-budget occupancy not already represented by the named transient
185
- * counters. This covers full-budget owners and keeps cross-DO module-local
186
- * reservations visible without double-counting read/write payloads.
187
- */
188
- unattributedReservationBytes: number;
189
- }
190
- /**
191
- * Inputs the estimator needs from the SqliteVFS layer. Kept narrow
192
- * (just the two fields we actually consume) to avoid a circular import
193
- * between observability/ and the VFS module.
194
- */
195
- export interface VfsHeapInputs {
196
- /** SqliteVFS.getStats().cache.hotBytes — actual LRU memory in use. */
197
- cacheHotBytes: number;
198
- /** Current sum of in-flight write payloads (or 0 if none). */
199
- inFlightWriteBytes: number;
200
- }
201
- /**
202
- * Estimate pre-bundle slice bytes resident in the supervisor heap.
203
- *
204
- * Pre A'.2: the supervisor builds the slice in heap (up to
205
- * SLICE_CAP_BYTES = 28 MiB per concurrent slot) before passing to the
206
- * pre-bundle facet. With PRE_BUNDLE_CONCURRENCY = 1 the cap is 28 MiB.
207
- *
208
- * Diag counters track pre-bundle attempts/completions but not
209
- * in-flight slice bytes specifically. Until A'.2 streams the slice,
210
- * we treat the slice as PRESENT in supervisor heap iff the pre-bundle
211
- * phase is active AND a slot is in flight (no completion yet for the
212
- * current attempted count).
213
- *
214
- * After A'.2 lands the slice flows via ReadableStream-over-RPC and
215
- * supervisor never holds the bytes — this contribution drops to 0.
216
- */
217
- export declare function estimatePreBundleSliceBytes(c: DiagCounters): number;
218
- /**
219
- * Build a heap estimate from runtime counters + VFS inputs.
220
- *
221
- * Deterministic and I/O-free. Called from the /api/_diag/memory request
222
- * handler; the allocation-budget snapshot is process-local state.
223
- */
224
- export declare function estimateSupervisorHeap(c: DiagCounters, vfs: VfsHeapInputs): HeapEstimate;
225
- //# sourceMappingURL=heap-estimate.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"heap-estimate.d.ts","sourceRoot":"","sources":["../../src/observability/heap-estimate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAOH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,wBAAwB,CAAC;AAEhC;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,sFAM1B,CAAC;AAEX;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,aAAa,EAAO,CAAC;AAE7D;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB,gEAAgE;IAChE,SAAS,EAAE,aAAa,CAAC;IACzB;;;OAGG;IACH,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;IACrC;;;;OAIG;IACH,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC;;;;OAIG;IACH,gBAAgB,EAAE,+BAA+B,CAAC;CACnD;AAED,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,uBAAuB,EAAE,MAAM,CAAC;IAChC,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,gBAAgB,EAAE,MAAM,CAAC;IACzB;;sEAEkE;IAClE,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;;;OAYG;IACH,qBAAqB,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,4BAA4B,EAAE,MAAM,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,aAAa,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAyBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAQnE;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,CAAC,EAAE,YAAY,EACf,GAAG,EAAE,aAAa,GACjB,YAAY,CAkDd"}
@@ -1,185 +0,0 @@
1
- /**
2
- * heap-estimate.ts — deterministic supervisor-heap estimator [C'.1]
3
- *
4
- * Why this module exists
5
- * ──────────────────────
6
- * `process.memoryUsage()` returns 0 for every field inside a Durable
7
- * Object class context (only dynamic-worker isolates under nodejs_compat
8
- * get the real implementation). The previous `readNodeMem` /
9
- * `sampleMemory` helpers in nimbus-session-diag.ts called it anyway and
10
- * therefore reported zero forever — useless for verifying memory-
11
- * containment work in plan §3 Track A'.
12
- *
13
- * Replacement: a deterministic estimator that sums the INSTRUMENTED
14
- * supervisor heap allocation sources — runtime counters that ARE accurate
15
- * (diag-counters.ts singleton + SqliteVFS.getStats()). Every byte it
16
- * reports has a named contributor, so a regression in any one component
17
- * is locatable.
18
- *
19
- * `estimatedBytes` is a LOWER BOUND, not a total
20
- * ─────────────────────────────────────────────
21
- * Within each instrumented component the estimator over-reports on
22
- * purpose: components carry a peak-or-current value, in-flight bytes are
23
- * counted before they are freed, and LRU bytes are the cap rather than the
24
- * live footprint.
25
- *
26
- * Across components it can only sum what is instrumented. Allocation sites
27
- * that neither bump a diag counter nor take a lease via
28
- * `acquireSupervisorAllocation` are invisible to it, and it has no way to
29
- * infer their size — so any site known to be uninstrumented is named in
30
- * HEAP_BLIND_SPOTS rather than left silently absent.
31
- *
32
- * The prefetch-bundle path used to be exactly that gap, and a large one: the
33
- * supervisor DO was reset three times under bundle construction while this
34
- * estimator reported a 9.4 MiB baseline and 35 KB of LRU. Both of its sites
35
- * are now instrumented where they allocate — the per-exec build leases the
36
- * budget it spends (`prefetchBundleBytes`) and the cross-exec cache is bounded
37
- * by bytes and reports its retained total (`prefetchCacheBytes`) — so they are
38
- * components of the breakdown rather than caveats beside it.
39
- *
40
- * `blindSpots` is still returned alongside `estimatedBytes` because a list
41
- * being empty is a claim that has to be re-earned as the system grows, not a
42
- * property of the estimator. Closing a blind spot always means instrumenting
43
- * it at its own allocation site and adding it to the breakdown — never
44
- * estimating it from here.
45
- *
46
- * Eviction-label taxonomy
47
- * ───────────────────────
48
- * Workerd distinguishes five labelled eviction reasons:
49
- * - lru → memory pressure on the runtime process
50
- * - condemned → kill (operator / abuse pipeline)
51
- * - inactive → idle eviction (70-140 s of no traffic)
52
- * - dynamic_worker → per-owner LRU cap (default 50)
53
- * - dynamic_worker_banned → Dice abuse-detection ban
54
- *
55
- * Nimbus surfaces the labels here so any tool that reads
56
- * /api/_diag/memory has a fixed, well-known taxonomy to count against.
57
- * The actual count of evictions Nimbus has observed lives in the C'.2
58
- * recovery_event ring, separate from this module.
59
- */
60
- import { PRE_BUNDLE_CONCURRENCY, PRE_BUNDLE_SLICE_CAP_BYTES, SUPERVISOR_HEAP_CEILING_BYTES, } from '../constants.js';
61
- import { readSupervisorAllocationBudget, } from './heavy-alloc-coord.js';
62
- /**
63
- * Five labelled workerd eviction reasons. Surfaced as a constant
64
- * taxonomy in /api/_diag/memory so any consumer can count observed
65
- * events against the well-known set.
66
- *
67
- */
68
- export const WORKERD_EVICTION_LABELS = [
69
- 'lru',
70
- 'condemned',
71
- 'inactive',
72
- 'dynamic_worker',
73
- 'dynamic_worker_banned',
74
- ];
75
- /**
76
- * Supervisor allocation sites known to be unaccounted for.
77
- *
78
- * Empty: both former entries were the prefetch-bundle path in
79
- * facets/manager.ts, and both are now instrumented at their own allocation
80
- * sites rather than described from here. `buildPrefetchBundle` takes an
81
- * `acquireSupervisorAllocation` lease and reports
82
- * `breakdown.prefetchBundleBytes`; `prefetchBundleCache` is bounded by
83
- * PREFETCH_CACHE_MAX_BYTES instead of by entry count and reports its live
84
- * retained total as `breakdown.prefetchCacheBytes`.
85
- *
86
- * An empty list says nothing is CURRENTLY known to be missing — it is not a
87
- * proof of completeness, which no list can be. Adding an entry is how a newly
88
- * discovered gap stays visible in /api/_diag/memory until it is closed the
89
- * same way: instrumented where it allocates, then folded into HeapBreakdown.
90
- */
91
- export const HEAP_BLIND_SPOTS = [];
92
- // ── Architectural constants for non-counter contributors ────────────────
93
- //
94
- // Each constant below has a comment explaining the source-of-truth and
95
- // when it is expected to change (typically: when a Track A' wave lands).
96
- // They are NOT runtime-measured because the underlying values aren't
97
- // observable from JS — they're properties of the worker bundle itself.
98
- /**
99
- * Static supervisor baseline. The compiled worker bundle resident in V8
100
- * isolate memory: module sources, class definitions, top-level imports,
101
- * lookup tables.
102
- *
103
- * Phase 2 A'.5 dropped the 16 MiB esbuild-wasm base64 string from the
104
- * generated module (it now lives in env.ASSETS), shrinking the worker
105
- * bundle by ~21 MiB UTF-16-resident. Empirical baseline post-A'.5 is
106
- * ~9 MiB (verified via the heap-estimator probe + bundle size).
107
- *
108
- * This is constant across a deploy. A bundle-size shrink (removing an
109
- * unused dependency, splitting another big constant out to assets)
110
- * would lower this further.
111
- */
112
- const SUPERVISOR_BASELINE_BYTES = 9 * 1024 * 1024;
113
- /**
114
- * Estimate pre-bundle slice bytes resident in the supervisor heap.
115
- *
116
- * Pre A'.2: the supervisor builds the slice in heap (up to
117
- * SLICE_CAP_BYTES = 28 MiB per concurrent slot) before passing to the
118
- * pre-bundle facet. With PRE_BUNDLE_CONCURRENCY = 1 the cap is 28 MiB.
119
- *
120
- * Diag counters track pre-bundle attempts/completions but not
121
- * in-flight slice bytes specifically. Until A'.2 streams the slice,
122
- * we treat the slice as PRESENT in supervisor heap iff the pre-bundle
123
- * phase is active AND a slot is in flight (no completion yet for the
124
- * current attempted count).
125
- *
126
- * After A'.2 lands the slice flows via ReadableStream-over-RPC and
127
- * supervisor never holds the bytes — this contribution drops to 0.
128
- */
129
- export function estimatePreBundleSliceBytes(c) {
130
- // Conservative: if the most recent batch attempted >0 specs and the
131
- // batch hasn't fully completed, assume one slice's worth of bytes
132
- // are in flight.
133
- const f = c.preBundleFacet;
134
- const inFlight = f.attempted - f.bundlesCompleted - f.errors - f.skipped;
135
- if (inFlight <= 0)
136
- return 0;
137
- return Math.min(inFlight, PRE_BUNDLE_CONCURRENCY) * PRE_BUNDLE_SLICE_CAP_BYTES;
138
- }
139
- /**
140
- * Build a heap estimate from runtime counters + VFS inputs.
141
- *
142
- * Deterministic and I/O-free. Called from the /api/_diag/memory request
143
- * handler; the allocation-budget snapshot is process-local state.
144
- */
145
- export function estimateSupervisorHeap(c, vfs) {
146
- const allocationBudget = readSupervisorAllocationBudget();
147
- const preBundleSliceBytes = estimatePreBundleSliceBytes(c);
148
- // The prefetch build holds a lease, so its bytes are already inside
149
- // allocationBudget.current — naming it here moves it out of the
150
- // unattributed remainder rather than adding to the total twice.
151
- const transientAttributedBytes = vfs.inFlightWriteBytes +
152
- preBundleSliceBytes +
153
- c.inFlightRpcPayloadBytes +
154
- c.prefetchBundleBytes;
155
- const breakdown = {
156
- supervisorBaselineBytes: SUPERVISOR_BASELINE_BYTES,
157
- vfsLruBytes: vfs.cacheHotBytes,
158
- vfsInFlightBytes: vfs.inFlightWriteBytes,
159
- preBundleSliceBytes,
160
- streamingBuffersBytes: c.inFlightRpcPayloadBytes,
161
- prefetchBundleBytes: c.prefetchBundleBytes,
162
- prefetchCacheBytes: c.prefetchCacheBytes,
163
- unattributedReservationBytes: Math.max(0, allocationBudget.current - transientAttributedBytes),
164
- };
165
- const estimatedBytes = breakdown.supervisorBaselineBytes +
166
- breakdown.vfsLruBytes +
167
- breakdown.vfsInFlightBytes +
168
- breakdown.preBundleSliceBytes +
169
- breakdown.streamingBuffersBytes +
170
- breakdown.prefetchBundleBytes +
171
- breakdown.prefetchCacheBytes +
172
- breakdown.unattributedReservationBytes;
173
- const percentOfCeiling = Math.round((estimatedBytes / SUPERVISOR_HEAP_CEILING_BYTES) * 1000) / 10;
174
- return {
175
- estimatedBytes,
176
- ceilingBytes: SUPERVISOR_HEAP_CEILING_BYTES,
177
- percentOfCeiling,
178
- breakdown,
179
- blindSpots: HEAP_BLIND_SPOTS,
180
- blindSpotCeilingBytes: HEAP_BLIND_SPOTS.some((s) => s.capBytes === null)
181
- ? null
182
- : HEAP_BLIND_SPOTS.reduce((sum, s) => sum + (s.capBytes ?? 0), 0),
183
- allocationBudget,
184
- };
185
- }