@torrent-tv/proxy 2.89.0 → 2.89.2
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/.claude/commands/opsx/apply.md +155 -155
- package/.claude/commands/opsx/archive.md +160 -160
- package/.claude/commands/opsx/explore.md +174 -174
- package/.claude/commands/opsx/propose.md +109 -109
- package/.claude/commands/opsx/sync.md +143 -143
- package/.claude/skills/openspec-apply-change/SKILL.md +159 -159
- package/.claude/skills/openspec-archive-change/SKILL.md +117 -117
- package/.claude/skills/openspec-explore/SKILL.md +289 -289
- package/.claude/skills/openspec-propose/SKILL.md +113 -113
- package/.claude/skills/openspec-sync-specs/SKILL.md +147 -147
- package/.dockerignore +5 -5
- package/AGENTS.md +6 -1
- package/CHANGELOG.md +15 -0
- package/Dockerfile +30 -30
- package/LICENSE +16 -16
- package/README.md +369 -369
- package/assets/calibration/NOTICE.md +100 -100
- package/assets/diagnostics/sctpstate.gdb +31 -31
- package/bin/cli.js +684 -684
- package/bin/thread-pool.js +30 -30
- package/docs/download-architecture.md +261 -261
- package/docs/encode-architecture.md +729 -729
- package/docs/encode-run-state.md +96 -96
- package/docs/logs.md +97 -97
- package/docs/playback-start-forecast.md +42 -8
- package/docs/socks5-egress-research.md +114 -114
- package/docs/storage-architecture.md +267 -267
- package/knip.json +14 -14
- package/openspec/changes/adaptive-bitrate/design.md +92 -92
- package/openspec/changes/adaptive-bitrate/proposal.md +56 -56
- package/openspec/changes/adaptive-bitrate/specs/adaptive-bitrate/spec.md +51 -51
- package/openspec/changes/adaptive-bitrate/tasks.md +40 -40
- package/openspec/changes/binary-distribution/.openspec.yaml +2 -2
- package/openspec/changes/binary-distribution/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/design.md +81 -81
- package/openspec/changes/chunked-request-bodies/proposal.md +53 -53
- package/openspec/changes/chunked-request-bodies/specs/chunked-request-bodies/spec.md +30 -30
- package/openspec/changes/chunked-request-bodies/tasks.md +40 -40
- package/openspec/changes/cold-start/design.md +98 -98
- package/openspec/changes/cold-start/proposal.md +62 -62
- package/openspec/changes/cold-start/specs/cold-start/spec.md +47 -47
- package/openspec/changes/cold-start/tasks.md +48 -48
- package/openspec/changes/disk-cap/.openspec.yaml +2 -2
- package/openspec/changes/disk-cap/proposal.md +37 -37
- package/openspec/changes/disk-cap/specs/disk-cap/spec.md +25 -25
- package/openspec/changes/disk-cap/tasks.md +19 -19
- package/openspec/changes/proxy-observability/.openspec.yaml +2 -2
- package/openspec/changes/proxy-observability/design.md +38 -38
- package/openspec/changes/proxy-observability/proposal.md +49 -49
- package/openspec/changes/proxy-observability/specs/observability/spec.md +33 -33
- package/openspec/changes/proxy-observability/tasks.md +21 -21
- package/openspec/changes/seek-debounce/design.md +118 -118
- package/openspec/changes/seek-debounce/proposal.md +59 -59
- package/openspec/changes/seek-debounce/specs/seek-debounce/spec.md +41 -41
- package/openspec/changes/seek-debounce/tasks.md +33 -33
- package/openspec/changes/subtitle-language/.openspec.yaml +2 -2
- package/openspec/changes/subtitle-language/proposal.md +50 -50
- package/openspec/changes/subtitle-language/specs/subtitle-language/spec.md +34 -34
- package/openspec/changes/subtitle-language/tasks.md +26 -26
- package/openspec/changes/track-selection/.openspec.yaml +2 -2
- package/openspec/changes/track-selection/design.md +47 -47
- package/openspec/changes/track-selection/proposal.md +45 -45
- package/openspec/changes/track-selection/specs/track-selection/spec.md +41 -41
- package/openspec/changes/track-selection/tasks.md +20 -20
- package/openspec/changes/transcode-quality/.openspec.yaml +2 -2
- package/openspec/changes/transcode-quality/proposal.md +51 -51
- package/openspec/changes/transcode-quality/specs/transcode-quality/spec.md +139 -139
- package/openspec/changes/transcode-quality/tasks.md +63 -63
- package/openspec/config.yaml +38 -38
- package/package.json +1 -1
- package/public/.well-known/appspecific/com.chrome.devtools.json +1 -1
- package/research/delivery-wedge-stand-ladder-2026-09-13.md +329 -329
- package/research/double-spawn-2026-09-10.md +171 -171
- package/research/handover-2026-09-13.md +209 -209
- package/research/handover-reader-claims-removal-2026-09-12.md +166 -166
- package/research/piece-withdrawn-but-still-claimed-2026-09-12.md +175 -175
- package/research/preset-benchmark-size-scaling-2026-08-22.md +55 -55
- package/research/priority-map-is-the-truth-2026-09-12.md +225 -225
- package/routes/api/client-logs/post.js +86 -86
- package/routes/api/delivery-sink/get.js +72 -72
- package/routes/api/link-probe/get.js +51 -51
- package/routes/api/playback-plan/audio-tracks/post.js +29 -29
- package/routes/api/playback-plan/post.js +53 -53
- package/routes/api/sources/post.js +36 -36
- package/routes/api/sources/stats/get.js +132 -132
- package/routes/api/sources/warm/post.js +217 -217
- package/routes/api/subtitles/get.js +230 -230
- package/routes/api/transcode-sessions/fragment-far/post.js +60 -60
- package/routes/api/transcode-sessions/net-report/post.js +138 -138
- package/routes/api/transcode-sessions/post.js +178 -178
- package/routes/api/transcode-sessions/progress/get.js +27 -27
- package/routes/api/transcode-sessions/release/post.js +41 -41
- package/routes/api/transcode-sessions/seek/post.js +63 -63
- package/routes/health/get.js +13 -13
- package/routes/healthz/get.js +13 -13
- package/routes/stream/get.js +380 -380
- package/routes/transcode/audio-file/get.js +64 -64
- package/routes/transcode/audio-warm/get.js +95 -95
- package/routes/transcode/session-file/get.js +52 -52
- package/routes/transcode/variant-file/get.js +95 -95
- package/routes/transcode/variant-warm/get.js +84 -84
- package/scripts/render-run-graph.js +173 -173
- package/services/encode/CoverageMap.js +428 -428
- package/services/encode/CushionReport.js +345 -345
- package/services/encode/EncodeAdmission.js +376 -376
- package/services/encode/EncodeOrchestrator.js +1217 -1217
- package/services/encode/EncodePlan.js +1366 -1366
- package/services/encode/EncodeRun.js +813 -813
- package/services/encode/Encoder.js +142 -142
- package/services/encode/EncoderSelection.js +38 -38
- package/services/encode/LocalObservations.js +359 -359
- package/services/encode/NvencEncoder.js +50 -50
- package/services/encode/OutputTimes.js +579 -579
- package/services/encode/QsvEncoder.js +58 -58
- package/services/encode/Renditions.js +2282 -2282
- package/services/encode/RunProgress.js +84 -84
- package/services/encode/SegmentDemand.js +95 -95
- package/services/encode/SoftwareEncoder.js +121 -121
- package/services/encode/V4l2m2mEncoder.js +53 -53
- package/services/encode/VaapiEncoder.js +66 -66
- package/services/encode/args.js +418 -418
- package/services/encode/available-share.js +89 -89
- package/services/encode/calibration.js +361 -361
- package/services/encode/contention.js +128 -128
- package/services/encode/decode-cost-fit.js +363 -363
- package/services/encode/encode-exit.js +114 -114
- package/services/encode/encode-run-state.js +551 -551
- package/services/encode/encoder-readings.js +45 -45
- package/services/encode/fingerprint.js +322 -322
- package/services/encode/host-load.js +174 -174
- package/services/encode/index.js +9 -9
- package/services/encode/learned-median.js +90 -90
- package/services/encode/output/EncodedOutput.js +57 -57
- package/services/encode/output/Output.js +131 -131
- package/services/encode/output/OutputCatalog.js +414 -414
- package/services/encode/output/OutputSpec.js +390 -390
- package/services/encode/output/Timeline.js +308 -308
- package/services/encode/output/cut-grid.js +107 -107
- package/services/encode/output/ladder.js +32 -32
- package/services/encode/output/playlists.js +284 -284
- package/services/encode/output/rates.js +125 -125
- package/services/encode/output-key-format.js +21 -21
- package/services/encode/quality/HostLoad.js +525 -525
- package/services/encode/quality/HostTimings.js +249 -249
- package/services/encode/quality/OutputQualityState.js +28 -28
- package/services/encode/quality/QualityController.js +935 -935
- package/services/encode/quality/QualityOffer.js +416 -416
- package/services/encode/quality/drain-threat.js +47 -47
- package/services/encode/quality/link-budget.js +349 -349
- package/services/encode/quality/output-format.js +319 -319
- package/services/encode/quality/serving-output.js +216 -216
- package/services/encode/quality/visible-rung.js +57 -57
- package/services/encode/run-budget.js +97 -97
- package/services/encode/run-costs.js +155 -155
- package/services/encode/run-inputs.js +42 -42
- package/services/encode/segment-formats/fmp4.js +476 -458
- package/services/encode/segment-formats/index.js +103 -85
- package/services/encode/segment-formats/init-compat.js +324 -324
- package/services/encode/segment-formats/mp4-boxes.js +604 -541
- package/services/encode/segment-formats/mpegts.js +134 -134
- package/services/encode/start-stop-cost.js +178 -178
- package/services/encode/throughput.js +97 -97
- package/services/encode/torrent-cost.js +88 -88
- package/services/identity/installation-id.js +38 -38
- package/services/media/SourceFile.js +298 -298
- package/services/media/SubtitleOrchestrator.js +167 -167
- package/services/media/container/KeyframeTable.js +272 -272
- package/services/media/container/ebml-reader.js +159 -159
- package/services/media/ffmpeg-banner.js +260 -260
- package/services/media/keyframe-probe.js +157 -157
- package/services/media/media-info-probe.js +93 -93
- package/services/media/playback-planner.js +931 -931
- package/services/media/tracks/AudioTrack.js +143 -143
- package/services/media/tracks/ImageSubtitleTrack.js +19 -19
- package/services/media/tracks/SubtitleTrack.js +38 -38
- package/services/media/tracks/VideoTrack.js +33 -33
- package/services/media/tracks/audio-bound.js +233 -233
- package/services/media/tracks/index.js +15 -15
- package/services/media/tracks/language-detect.js +174 -174
- package/services/server/OutputLifecycle.js +418 -418
- package/services/server/SegmentServing.js +1150 -1150
- package/services/server/ViewerRequests.js +790 -786
- package/services/server/WaitLedger.js +142 -142
- package/services/server/controllers/PlaybackController.js +40 -40
- package/services/server/controllers/SubtitleController.js +109 -109
- package/services/server/http-responses.js +60 -60
- package/services/server/transcode-session-files.js +325 -325
- package/services/server/wire-outputs.js +1024 -1020
- package/services/storage/Diagnostics.js +202 -202
- package/services/storage/MachineBudget.js +235 -235
- package/services/storage/allowance.js +107 -107
- package/services/storage/core-dumps.js +106 -106
- package/services/storage/files/CompletedFiles.js +391 -391
- package/services/storage/files/piece-from-whole-file.js +118 -118
- package/services/storage/free.js +71 -71
- package/services/storage/keep.js +48 -48
- package/services/storage/machine-memory.js +56 -56
- package/services/storage/memory-report.js +575 -575
- package/services/storage/piece-store/piece-disk-store.js +539 -539
- package/services/storage/piece-store/piece-lru.js +495 -495
- package/services/storage/returns.js +103 -103
- package/services/storage/segment-store/SegmentStore.js +1255 -1255
- package/services/storage/wire.js +175 -175
- package/services/torrent/demand/DemandRegister.js +202 -202
- package/services/torrent/demand/Urgency.js +137 -137
- package/services/torrent/demand/Window.js +118 -118
- package/services/torrent/demand/index.js +8 -8
- package/services/torrent/demand/pieces.js +140 -140
- package/services/torrent/download/SwarmSelection.js +386 -386
- package/services/torrent/download/registry.js +102 -102
- package/services/torrent/download/withdraw-claim.js +80 -80
- package/services/torrent/files.js +455 -455
- package/services/torrent/naming.js +619 -619
- package/services/torrent/piece-store-of.js +29 -29
- package/services/torrent/supply-margin.js +262 -262
- package/services/torrent/worker/background-fill.js +213 -213
- package/services/torrent/worker/channel.js +271 -271
- package/services/torrent/worker/client.js +67 -0
- package/services/torrent/worker/fastest-wires.js +342 -342
- package/services/torrent/worker/handle-state.js +23 -23
- package/services/torrent/worker/install-webrtc-shim.js +32 -32
- package/services/torrent/worker/piece-reader.js +1078 -1078
- package/services/torrent/worker/pool-adapter.js +371 -371
- package/services/torrent/worker/protocol.js +184 -184
- package/services/torrent/worker/resume-warm.js +107 -107
- package/services/torrent/worker/webrtc-shim.js +219 -219
- package/services/transport/delivery-probe.js +842 -842
- package/services/transport/health-collector.js +53 -53
- package/services/transport/nat-classifier.js +231 -231
- package/services/transport/packet-witness.js +808 -808
- package/services/transport/port-mapper.js +235 -235
- package/services/transport/registry-api.js +81 -81
- package/services/transport/tunnel-client.js +447 -447
- package/services/transport/usrsctp-state.js +175 -175
- package/services/transport/webrtc-manager.js +484 -484
- package/services/viewer/Assignments.js +340 -340
- package/services/viewer/PriorityOrchestrator.js +325 -325
- package/services/viewer/Viewer.js +691 -691
- package/services/viewer/Viewers.js +368 -368
- package/services/viewer/active-output.js +37 -37
- package/services/viewer/audio-start.js +46 -46
- package/services/viewer/buffer-trend.js +81 -81
- package/services/viewer/choices.js +647 -647
- package/services/viewer/media-time.js +355 -0
- package/services/{server → viewer}/playback-readiness.js +635 -516
- package/services/viewer/positions.js +126 -126
- package/services/viewer/report-intake.js +46 -46
- package/store/source-registry.js +71 -71
- package/test/asset-paths.test.js +28 -28
- package/test/audio-buffer-continuity.test.js +77 -70
- package/test/audio-inventory.test.js +212 -212
- package/test/audio-track-bitrate.test.js +40 -40
- package/test/auto-quality-step.test.js +707 -707
- package/test/available-share.test.js +76 -76
- package/test/background-fill.test.js +190 -190
- package/test/client-log-file.test.js +110 -110
- package/test/completed-files.test.js +115 -115
- package/test/concurrent-cost.test.js +148 -148
- package/test/container-media-info.test.js +228 -228
- package/test/contention.test.js +76 -76
- package/test/core-dumps.test.js +57 -57
- package/test/coverage-follows-the-disk.test.js +228 -228
- package/test/coverage-map.test.js +195 -195
- package/test/cue-push-carries-what-the-walk-found.test.js +58 -58
- package/test/cut-times-timeline.test.js +65 -65
- package/test/cuts-follow-published-grid.test.js +109 -109
- package/test/data-channel-frame.test.js +63 -63
- package/test/data-channel-viewer-lifecycle.test.js +92 -92
- package/test/dead-torrent-handle.test.js +52 -52
- package/test/declared-rates.test.js +116 -116
- package/test/declared-tracks.test.js +43 -43
- package/test/decode-cost-fit.test.js +202 -202
- package/test/decode-cost.test.js +462 -462
- package/test/delivery-probe.test.js +393 -393
- package/test/demand-register.test.js +206 -206
- package/test/dht-bootstrap.test.js +72 -72
- package/test/drain-threat.test.js +65 -65
- package/test/duplicate-add.test.js +48 -48
- package/test/encode-admission.test.js +146 -146
- package/test/encode-cost.test.js +283 -283
- package/test/encode-exit.test.js +111 -111
- package/test/encode-orchestrator.test.js +419 -419
- package/test/encode-plan-viewers.test.js +718 -718
- package/test/encode-plan.test.js +545 -545
- package/test/encode-run-state.test.js +314 -314
- package/test/encode-run.test.js +162 -162
- package/test/encode-slope.test.js +46 -46
- package/test/encoded-output.test.js +39 -39
- package/test/encoder-kinds.test.js +122 -122
- package/test/encoder-readings.test.js +46 -46
- package/test/every-claimant-is-told-its-share.test.js +152 -152
- package/test/failed-encoder-still-serves-what-exists.test.js +354 -354
- package/test/failed-start-breaker.test.js +202 -202
- package/test/fastest-wires.test.js +143 -143
- package/test/ffmpeg-stream-counts.test.js +78 -78
- package/test/file-edges.test.js +191 -191
- package/test/fmp4-presentation-clock.test.js +146 -162
- package/test/fmp4-self-contained.test.js +80 -80
- package/test/fmp4-timestamps.test.js +140 -140
- package/test/fmp4-track-completeness.test.js +41 -41
- package/test/format-keyed-outputs.test.js +78 -78
- package/test/health-metrics.test.js +38 -38
- package/test/held-request-width.test.js +157 -157
- package/test/helpers/encode-run.js +136 -136
- package/test/helpers/manager.js +57 -57
- package/test/helpers/output-spec.js +63 -63
- package/test/host-load.test.js +68 -68
- package/test/index-accuracy-is-about-the-file.test.js +128 -128
- package/test/init-compat.test.js +219 -219
- package/test/input-lost-quiets-the-plan.test.js +261 -261
- package/test/input-unavailable.test.js +48 -48
- package/test/installation-id.test.js +39 -39
- package/test/keeping-period.test.js +83 -83
- package/test/keyframe-index-accuracy.test.js +188 -188
- package/test/keyframes-belong-to-the-file.test.js +249 -249
- package/test/learned-median.test.js +37 -37
- package/test/letting-go-asks-who-is-watching.test.js +191 -191
- package/test/limit-row-by-area.test.js +60 -60
- package/test/link-budget.test.js +112 -112
- package/test/link-probe.test.js +56 -56
- package/test/local-encoding-data.test.js +273 -273
- package/test/logger-repeats.test.js +134 -134
- package/test/look-ahead-coverage.test.js +56 -56
- package/test/manager-surface.test.js +292 -292
- package/test/matroska-cues-track.test.js +192 -192
- package/test/media-time.test.js +62 -0
- package/test/memory-budget.test.js +390 -390
- package/test/move-cost.test.js +216 -216
- package/test/mp4-subtitles.test.js +173 -173
- package/test/one-authority.test.js +285 -285
- package/test/one-budget.test.js +149 -149
- package/test/one-resource-divided.test.js +185 -185
- package/test/one-serving-output-per-height.test.js +458 -458
- package/test/orchestrator-wired.test.js +206 -206
- package/test/output-catalog.test.js +39 -39
- package/test/output-shape.test.js +21 -21
- package/test/output-spec.test.js +251 -251
- package/test/output-speed.test.js +88 -88
- package/test/outputs-being-prepared.test.js +40 -40
- package/test/packet-witness-ring.test.js +236 -236
- package/test/packet-witness.test.js +148 -148
- package/test/piece-disk-store.test.js +401 -401
- package/test/piece-from-whole-file.test.js +129 -129
- package/test/piece-lru.test.js +319 -319
- package/test/piece-reader.test.js +162 -162
- package/test/piece-selection-offset.test.js +85 -85
- package/test/piece-store-eviction.test.js +595 -595
- package/test/piece-store-never-refuses.test.js +153 -153
- package/test/piece-store-reservations.test.js +403 -403
- package/test/piece-store-slow-disk.test.js +159 -159
- package/test/piece-tail.test.js +97 -97
- package/test/plan-host-timings.test.js +68 -68
- package/test/plan-is-steady.test.js +225 -225
- package/test/playback-readiness.test.js +518 -483
- package/test/playback-start-regressions.test.js +101 -99
- package/test/priority-map-download.test.js +174 -174
- package/test/priority-map-emptied.test.js +204 -204
- package/test/priority-map-per-output.test.js +215 -215
- package/test/priority-map.test.js +163 -163
- package/test/probe-wedge-certainty.test.js +60 -60
- package/test/produced-copy-choice.test.js +213 -213
- package/test/published-timeline.test.js +83 -83
- package/test/quality-offer.test.js +162 -162
- package/test/quality-variants.test.js +1257 -1257
- package/test/rate-control-in-the-output.test.js +180 -180
- package/test/read-survives-withdrawal.test.js +164 -164
- package/test/read-window.test.js +365 -365
- package/test/resume-warm.test.js +39 -39
- package/test/run-graph-drift.test.js +37 -37
- package/test/run-inputs.test.js +80 -80
- package/test/run-intervals.test.js +100 -100
- package/test/run-position-follows-published-grid.test.js +89 -89
- package/test/run-progress.test.js +65 -65
- package/test/same-height-switch.test.js +664 -664
- package/test/seek-frees-the-viewers-requests.test.js +217 -217
- package/test/seek-landing.test.js +109 -109
- package/test/seek-target-not-superseded.test.js +195 -195
- package/test/segment-closed-on-both-branches.test.js +96 -96
- package/test/segment-cut-times.test.js +63 -63
- package/test/segment-demand.test.js +80 -80
- package/test/segment-hold-supersede.test.js +91 -91
- package/test/segment-judged-by-its-own-header.test.js +221 -221
- package/test/segment-store-eviction.test.js +247 -247
- package/test/segment-store.test.js +352 -352
- package/test/segment-true-start.test.js +86 -86
- package/test/segments-are-shared.test.js +176 -176
- package/test/send-chunk-bytes.test.js +86 -86
- package/test/serving-existing-output.test.js +105 -105
- package/test/serving-output.test.js +102 -102
- package/test/shared-piece-store.test.js +310 -310
- package/test/sidecar-naming.test.js +142 -142
- package/test/soundtrack-rate-bound.test.js +289 -289
- package/test/source-file.test.js +133 -133
- package/test/source-registry.test.js +18 -18
- package/test/split-a-stretch.test.js +139 -139
- package/test/stale-request-after-seek.test.js +158 -158
- package/test/startup-readings.test.js +124 -124
- package/test/stream-route.test.js +209 -209
- package/test/subtitle-cue-framing.test.js +200 -200
- package/test/subtitle-cue-source.test.js +102 -102
- package/test/subtitle-cursor.test.js +63 -63
- package/test/subtitle-defaults.test.js +118 -118
- package/test/subtitle-language.test.js +252 -252
- package/test/supply-margin.test.js +177 -177
- package/test/swarm-follows-readers.test.js +174 -174
- package/test/swarm-reach.test.js +130 -130
- package/test/swarm-selection.test.js +220 -220
- package/test/tail-duplication.test.js +167 -167
- package/test/the-channel-has-both-ends.test.js +91 -91
- package/test/the-torrent-thread-serves-bytes.test.js +107 -107
- package/test/thread-pool.test.js +53 -53
- package/test/timeline.test.js +84 -84
- package/test/torrent-contents.test.js +235 -235
- package/test/torrent-cost.test.js +71 -71
- package/test/torrent-files.test.js +221 -221
- package/test/torrent-naming.test.js +255 -255
- package/test/tracks-begin-together.test.js +209 -209
- package/test/tunnel-renewal.test.js +167 -167
- package/test/two-viewers-one-picture.test.js +504 -504
- package/test/upload-hurry.test.js +186 -186
- package/test/usrsctp-state.test.js +131 -131
- package/test/video-facts.test.js +102 -102
- package/test/viewer-assignments.test.js +143 -143
- package/test/viewer-outputs.test.js +285 -285
- package/test/viewer-presence.test.js +116 -116
- package/test/viewer-requests-admission.test.js +89 -89
- package/test/viewer-states.test.js +189 -189
- package/test/viewer-subtitle-subscription.test.js +96 -96
- package/test/viewer.test.js +139 -139
- package/test/viewing-generations.test.js +390 -390
- package/test/visible-rung.test.js +37 -37
- package/test/wait-ledger.test.js +100 -100
- package/test/webrtc-shim.test.js +149 -149
- package/test/wedge-certainty.test.js +131 -131
- package/test/whole-file-held-read.test.js +30 -0
- package/test/withdraw-piece-claim.test.js +212 -212
- package/test/worker-channel.test.js +158 -158
- package/utils/client-log-file.js +151 -151
- package/utils/logger.js +312 -312
- package/utils/parse-range.js +25 -25
- package/utils/perf.js +59 -59
- package/utils/torrent-source-key.js +38 -38
|
@@ -1,235 +1,235 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file The one budget: how much of this machine the proxy may take, and how
|
|
3
|
-
* that is divided between everything that holds bytes.
|
|
4
|
-
*
|
|
5
|
-
* THERE IS ONE BUDGET, AND IT IS NOT ONE NUMBER. Memory cannot be paid for with
|
|
6
|
-
* disk, so what is divided is divided per RESOURCE; what there is one of is the
|
|
7
|
-
* OWNER, the POLICY, and the place that reads the machine. Until 2026-09-14
|
|
8
|
-
* there were two owners — the memory store divided memory inside the torrent
|
|
9
|
-
* thread, `DiskSpace` divided disk on the main one — each applying the same rule
|
|
10
|
-
* from `allowance.js` to its own reading, neither able to see the other.
|
|
11
|
-
*
|
|
12
|
-
* **Why one owner rather than two, and it is measured rather than argued.** The
|
|
13
|
-
* claimants TRADE ACROSS RESOURCES. Pieces held in memory that do not fit are
|
|
14
|
-
* spilled to disk — the same bytes, in whichever resource there is room for —
|
|
15
|
-
* so how much memory the piece store is given decides how much disk it needs.
|
|
16
|
-
* Whole files exist on disk so that pieces need not be held at all; produced
|
|
17
|
-
* segments exist on disk because making them again is dear. Field 2026-08-31:
|
|
18
|
-
* **14 400 MB spilled to disk in fifty minutes** while the memory store held
|
|
19
|
-
* 312-424 MB. Give it memory and there is no spill to bound. Two owners cannot
|
|
20
|
-
* make that trade, because neither sees both sides of it.
|
|
21
|
-
*
|
|
22
|
-
* **A RESOURCE IS WHAT CANNOT BE SUBSTITUTED, and disk is not one of them.**
|
|
23
|
-
* Measured on the addon host 2026-09-05: `/tmp`, where the segments and the
|
|
24
|
-
* spill live, is the overlay filesystem (`dev=68`); `/data`, where the
|
|
25
|
-
* diagnostics live, is ext4 on the nvme (`dev=66305`). One "disk" figure
|
|
26
|
-
* divided between claimants on two devices gives each of them a share of a disk
|
|
27
|
-
* it is not writing to — which is what `DiskSpace` did, reading the free space
|
|
28
|
-
* of the segment root alone. So a resource is named by the DEVICE its
|
|
29
|
-
* claimants' directories are on, read with `statSync(dir).dev` rather than
|
|
30
|
-
* guessed.
|
|
31
|
-
*
|
|
32
|
-
* **The policy is the operator's, stated once at startup.** Three shapes, and
|
|
33
|
-
* the default is the measured one:
|
|
34
|
-
*
|
|
35
|
-
* - `adaptive` (default) — what is free now, plus what we already hold, less
|
|
36
|
-
* what everything that is not us has recently been seen to need. Nothing
|
|
37
|
-
* chosen: `allowance.js` computes every term from readings;
|
|
38
|
-
* - `share` — a fraction of what is free. A chosen number, and legitimately
|
|
39
|
-
* so: it is the machine's owner saying what of their machine we may use;
|
|
40
|
-
* - `fixed` — a hard ceiling, the same;
|
|
41
|
-
*
|
|
42
|
-
* plus a FLOOR per resource, which is the operator saying "below this the proxy
|
|
43
|
-
* is not worth running". A floor is not a licence to take: it bounds from
|
|
44
|
-
* below what the policy hands out, and a claimant below its own minimum says so
|
|
45
|
-
* rather than working badly in silence.
|
|
46
|
-
*
|
|
47
|
-
* The distinction matters because everything else in this codebase refuses
|
|
48
|
-
* chosen constants. These are chosen BY THE PERSON WHOSE MACHINE IT IS, which
|
|
49
|
-
* is the one kind of number that is not ours to derive.
|
|
50
|
-
*/
|
|
51
|
-
|
|
52
|
-
import { OtherDemand, divideAllowance } from "./allowance.js";
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* One thing that holds bytes of one resource.
|
|
56
|
-
*
|
|
57
|
-
* @typedef {object} Claimant
|
|
58
|
-
* @property {string} name - What it is called in the reading.
|
|
59
|
-
* @property {string} resource - Which resource it takes.
|
|
60
|
-
* @property {() => number} held - What it holds right now, in bytes.
|
|
61
|
-
* @property {() => number} wanted - What it would take if it could.
|
|
62
|
-
* @property {(allowanceBytes: number) => void} allow - Told its share.
|
|
63
|
-
* @property {number} [minimum] - Below this it cannot do its job, and says so.
|
|
64
|
-
*/
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* What the operator has said we may take.
|
|
68
|
-
*
|
|
69
|
-
* @typedef {object} BudgetPolicy
|
|
70
|
-
* @property {"adaptive" | "share" | "fixed"} [kind]
|
|
71
|
-
* @property {number} [share] - For `share`: the fraction of free space, 0..1.
|
|
72
|
-
* @property {number} [bytes] - For `fixed`: the ceiling.
|
|
73
|
-
* @property {Record<string, number>} [floors] - Per resource, the least this
|
|
74
|
-
* proxy should have. Absent, the policy's own answer stands.
|
|
75
|
-
*/
|
|
76
|
-
|
|
77
|
-
export class MachineBudget {
|
|
78
|
-
/** Resource name → how to read what is free, and what others have needed. */
|
|
79
|
-
#resources = new Map();
|
|
80
|
-
|
|
81
|
-
/** @type {Claimant[]} */
|
|
82
|
-
#claimants = [];
|
|
83
|
-
|
|
84
|
-
/** @type {BudgetPolicy} */
|
|
85
|
-
#policy;
|
|
86
|
-
|
|
87
|
-
#logger;
|
|
88
|
-
|
|
89
|
-
/** The last division, for the reading. @type {Map<string, { freeBytes: number, allowanceBytes: number, shares: { name: string, held: number, allowed: number, short: boolean }[] }>} */
|
|
90
|
-
#last = new Map();
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* @param {object} params
|
|
94
|
-
* @param {BudgetPolicy} [params.policy]
|
|
95
|
-
* @param {{ info: (line: string) => void, warn?: (line: string) => void }} [params.logger]
|
|
96
|
-
*/
|
|
97
|
-
constructor({ policy = {}, logger = null } = {}) {
|
|
98
|
-
this.#policy = { kind: "adaptive", ...policy };
|
|
99
|
-
this.#logger = logger;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Say that a resource exists and how to read what is free of it.
|
|
104
|
-
*
|
|
105
|
-
* @param {object} params
|
|
106
|
-
* @param {string} params.name
|
|
107
|
-
* @param {() => Promise<number | null> | number | null} params.readFree
|
|
108
|
-
* @returns {void}
|
|
109
|
-
*/
|
|
110
|
-
defineResource({ name, readFree }) {
|
|
111
|
-
if (!this.#resources.has(name)) {
|
|
112
|
-
this.#resources.set(name, { readFree, otherDemand: new OtherDemand() });
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
this.#resources.get(name).readFree = readFree;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Register something that holds bytes. Registering twice under one name
|
|
120
|
-
* replaces it.
|
|
121
|
-
*
|
|
122
|
-
* @param {Claimant} claimant
|
|
123
|
-
* @returns {void}
|
|
124
|
-
*/
|
|
125
|
-
register(claimant) {
|
|
126
|
-
this.#claimants = this.#claimants.filter((one) => one.name !== claimant.name);
|
|
127
|
-
this.#claimants.push(claimant);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* @param {string} name
|
|
132
|
-
* @returns {void}
|
|
133
|
-
*/
|
|
134
|
-
forget(name) {
|
|
135
|
-
this.#claimants = this.#claimants.filter((one) => one.name !== name);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Read every resource, divide each, and tell each claimant its share.
|
|
140
|
-
*
|
|
141
|
-
* @returns {Promise<Map<string, { freeBytes: number, allowanceBytes: number, shares: object[] }>>}
|
|
142
|
-
*/
|
|
143
|
-
async revise() {
|
|
144
|
-
for (const [name, resource] of this.#resources) {
|
|
145
|
-
const claimants = this.#claimants.filter((one) => one.resource === name);
|
|
146
|
-
if (claimants.length === 0) {
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
const held = claimants.reduce((sum, one) => sum + Math.max(0, one.held()), 0);
|
|
150
|
-
const free = await resource.readFree();
|
|
151
|
-
// WITHOUT A READING, NOTHING IS ALLOWED TO GROW. A resource whose free
|
|
152
|
-
// space cannot be read is not a resource with room; answering "unbounded"
|
|
153
|
-
// there is how the spill file came to have no limit in the first place.
|
|
154
|
-
const freeBytes = Number.isFinite(free) && free !== null ? Math.max(0, free) : 0;
|
|
155
|
-
const allowanceBytes = this.#allowanceFor(name, resource, freeBytes, held);
|
|
156
|
-
const shares = divideAllowance(claimants.map((one) => Math.max(0, one.wanted())), allowanceBytes);
|
|
157
|
-
const reading = claimants.map((one, position) => ({
|
|
158
|
-
name: one.name,
|
|
159
|
-
held: one.held(),
|
|
160
|
-
allowed: shares[position],
|
|
161
|
-
short: Number.isFinite(one.minimum) && shares[position] < one.minimum
|
|
162
|
-
}));
|
|
163
|
-
this.#last.set(name, { freeBytes, allowanceBytes, shares: reading });
|
|
164
|
-
for (const [position, one] of claimants.entries()) {
|
|
165
|
-
one.allow(shares[position]);
|
|
166
|
-
}
|
|
167
|
-
for (const share of reading.filter((one) => one.short)) {
|
|
168
|
-
// SAID, because a claimant working below what it needs is a fault of
|
|
169
|
-
// the machine and not of the code, and it is invisible otherwise.
|
|
170
|
-
this.#logger?.warn?.(
|
|
171
|
-
`budget: ${share.name} has ${megabytes(share.allowed)} of ${name}, ` +
|
|
172
|
-
`below the ${megabytes(claimants.find((one) => one.name === share.name)?.minimum ?? 0)} it needs`
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
this.#logger?.info?.(this.describe());
|
|
177
|
-
return this.#last;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* What the policy allows of one resource.
|
|
182
|
-
*
|
|
183
|
-
* @param {string} name
|
|
184
|
-
* @param {{ otherDemand: OtherDemand }} resource
|
|
185
|
-
* @param {number} freeBytes
|
|
186
|
-
* @param {number} heldBytes
|
|
187
|
-
* @returns {number}
|
|
188
|
-
*/
|
|
189
|
-
#allowanceFor(name, resource, freeBytes, heldBytes) {
|
|
190
|
-
// Noted whatever the policy, because the reading of what others need is a
|
|
191
|
-
// measurement of the machine and does not stop being true when the operator
|
|
192
|
-
// has named a number.
|
|
193
|
-
const reserve = resource.otherDemand.note(freeBytes, heldBytes);
|
|
194
|
-
const floor = Math.max(0, Number(this.#policy.floors?.[name]) || 0);
|
|
195
|
-
let allowance;
|
|
196
|
-
if (this.#policy.kind === "fixed") {
|
|
197
|
-
allowance = Math.max(0, Number(this.#policy.bytes) || 0);
|
|
198
|
-
} else if (this.#policy.kind === "share") {
|
|
199
|
-
const share = Math.min(1, Math.max(0, Number(this.#policy.share) || 0));
|
|
200
|
-
allowance = Math.floor((freeBytes + heldBytes) * share);
|
|
201
|
-
} else {
|
|
202
|
-
// What is free now, plus what we already hold, less what everything that
|
|
203
|
-
// is not us has recently been seen to need.
|
|
204
|
-
allowance = Math.max(0, freeBytes + heldBytes - reserve);
|
|
205
|
-
}
|
|
206
|
-
return Math.max(allowance, Math.min(floor, freeBytes + heldBytes));
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* One line per resource: what it has, what each claimant holds and may hold.
|
|
211
|
-
*
|
|
212
|
-
* @returns {string}
|
|
213
|
-
*/
|
|
214
|
-
describe() {
|
|
215
|
-
if (this.#last.size === 0) {
|
|
216
|
-
return "budget: nothing has claimed anything yet";
|
|
217
|
-
}
|
|
218
|
-
const lines = [];
|
|
219
|
-
for (const [name, reading] of this.#last) {
|
|
220
|
-
const parts = reading.shares.map(
|
|
221
|
-
(share) => `${share.name} ${megabytes(share.held)} of ${megabytes(share.allowed)}${share.short ? " (short)" : ""}`
|
|
222
|
-
);
|
|
223
|
-
lines.push(`${name}: ${megabytes(reading.freeBytes)} free; ${parts.join(", ")}`);
|
|
224
|
-
}
|
|
225
|
-
return `budget — ${lines.join(" | ")}`;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* @param {number} bytes
|
|
231
|
-
* @returns {string}
|
|
232
|
-
*/
|
|
233
|
-
function megabytes(bytes) {
|
|
234
|
-
return `${Math.round(Math.max(0, bytes) / (1024 * 1024))}MB`;
|
|
235
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @file The one budget: how much of this machine the proxy may take, and how
|
|
3
|
+
* that is divided between everything that holds bytes.
|
|
4
|
+
*
|
|
5
|
+
* THERE IS ONE BUDGET, AND IT IS NOT ONE NUMBER. Memory cannot be paid for with
|
|
6
|
+
* disk, so what is divided is divided per RESOURCE; what there is one of is the
|
|
7
|
+
* OWNER, the POLICY, and the place that reads the machine. Until 2026-09-14
|
|
8
|
+
* there were two owners — the memory store divided memory inside the torrent
|
|
9
|
+
* thread, `DiskSpace` divided disk on the main one — each applying the same rule
|
|
10
|
+
* from `allowance.js` to its own reading, neither able to see the other.
|
|
11
|
+
*
|
|
12
|
+
* **Why one owner rather than two, and it is measured rather than argued.** The
|
|
13
|
+
* claimants TRADE ACROSS RESOURCES. Pieces held in memory that do not fit are
|
|
14
|
+
* spilled to disk — the same bytes, in whichever resource there is room for —
|
|
15
|
+
* so how much memory the piece store is given decides how much disk it needs.
|
|
16
|
+
* Whole files exist on disk so that pieces need not be held at all; produced
|
|
17
|
+
* segments exist on disk because making them again is dear. Field 2026-08-31:
|
|
18
|
+
* **14 400 MB spilled to disk in fifty minutes** while the memory store held
|
|
19
|
+
* 312-424 MB. Give it memory and there is no spill to bound. Two owners cannot
|
|
20
|
+
* make that trade, because neither sees both sides of it.
|
|
21
|
+
*
|
|
22
|
+
* **A RESOURCE IS WHAT CANNOT BE SUBSTITUTED, and disk is not one of them.**
|
|
23
|
+
* Measured on the addon host 2026-09-05: `/tmp`, where the segments and the
|
|
24
|
+
* spill live, is the overlay filesystem (`dev=68`); `/data`, where the
|
|
25
|
+
* diagnostics live, is ext4 on the nvme (`dev=66305`). One "disk" figure
|
|
26
|
+
* divided between claimants on two devices gives each of them a share of a disk
|
|
27
|
+
* it is not writing to — which is what `DiskSpace` did, reading the free space
|
|
28
|
+
* of the segment root alone. So a resource is named by the DEVICE its
|
|
29
|
+
* claimants' directories are on, read with `statSync(dir).dev` rather than
|
|
30
|
+
* guessed.
|
|
31
|
+
*
|
|
32
|
+
* **The policy is the operator's, stated once at startup.** Three shapes, and
|
|
33
|
+
* the default is the measured one:
|
|
34
|
+
*
|
|
35
|
+
* - `adaptive` (default) — what is free now, plus what we already hold, less
|
|
36
|
+
* what everything that is not us has recently been seen to need. Nothing
|
|
37
|
+
* chosen: `allowance.js` computes every term from readings;
|
|
38
|
+
* - `share` — a fraction of what is free. A chosen number, and legitimately
|
|
39
|
+
* so: it is the machine's owner saying what of their machine we may use;
|
|
40
|
+
* - `fixed` — a hard ceiling, the same;
|
|
41
|
+
*
|
|
42
|
+
* plus a FLOOR per resource, which is the operator saying "below this the proxy
|
|
43
|
+
* is not worth running". A floor is not a licence to take: it bounds from
|
|
44
|
+
* below what the policy hands out, and a claimant below its own minimum says so
|
|
45
|
+
* rather than working badly in silence.
|
|
46
|
+
*
|
|
47
|
+
* The distinction matters because everything else in this codebase refuses
|
|
48
|
+
* chosen constants. These are chosen BY THE PERSON WHOSE MACHINE IT IS, which
|
|
49
|
+
* is the one kind of number that is not ours to derive.
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
import { OtherDemand, divideAllowance } from "./allowance.js";
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* One thing that holds bytes of one resource.
|
|
56
|
+
*
|
|
57
|
+
* @typedef {object} Claimant
|
|
58
|
+
* @property {string} name - What it is called in the reading.
|
|
59
|
+
* @property {string} resource - Which resource it takes.
|
|
60
|
+
* @property {() => number} held - What it holds right now, in bytes.
|
|
61
|
+
* @property {() => number} wanted - What it would take if it could.
|
|
62
|
+
* @property {(allowanceBytes: number) => void} allow - Told its share.
|
|
63
|
+
* @property {number} [minimum] - Below this it cannot do its job, and says so.
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* What the operator has said we may take.
|
|
68
|
+
*
|
|
69
|
+
* @typedef {object} BudgetPolicy
|
|
70
|
+
* @property {"adaptive" | "share" | "fixed"} [kind]
|
|
71
|
+
* @property {number} [share] - For `share`: the fraction of free space, 0..1.
|
|
72
|
+
* @property {number} [bytes] - For `fixed`: the ceiling.
|
|
73
|
+
* @property {Record<string, number>} [floors] - Per resource, the least this
|
|
74
|
+
* proxy should have. Absent, the policy's own answer stands.
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
export class MachineBudget {
|
|
78
|
+
/** Resource name → how to read what is free, and what others have needed. */
|
|
79
|
+
#resources = new Map();
|
|
80
|
+
|
|
81
|
+
/** @type {Claimant[]} */
|
|
82
|
+
#claimants = [];
|
|
83
|
+
|
|
84
|
+
/** @type {BudgetPolicy} */
|
|
85
|
+
#policy;
|
|
86
|
+
|
|
87
|
+
#logger;
|
|
88
|
+
|
|
89
|
+
/** The last division, for the reading. @type {Map<string, { freeBytes: number, allowanceBytes: number, shares: { name: string, held: number, allowed: number, short: boolean }[] }>} */
|
|
90
|
+
#last = new Map();
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {object} params
|
|
94
|
+
* @param {BudgetPolicy} [params.policy]
|
|
95
|
+
* @param {{ info: (line: string) => void, warn?: (line: string) => void }} [params.logger]
|
|
96
|
+
*/
|
|
97
|
+
constructor({ policy = {}, logger = null } = {}) {
|
|
98
|
+
this.#policy = { kind: "adaptive", ...policy };
|
|
99
|
+
this.#logger = logger;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Say that a resource exists and how to read what is free of it.
|
|
104
|
+
*
|
|
105
|
+
* @param {object} params
|
|
106
|
+
* @param {string} params.name
|
|
107
|
+
* @param {() => Promise<number | null> | number | null} params.readFree
|
|
108
|
+
* @returns {void}
|
|
109
|
+
*/
|
|
110
|
+
defineResource({ name, readFree }) {
|
|
111
|
+
if (!this.#resources.has(name)) {
|
|
112
|
+
this.#resources.set(name, { readFree, otherDemand: new OtherDemand() });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
this.#resources.get(name).readFree = readFree;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Register something that holds bytes. Registering twice under one name
|
|
120
|
+
* replaces it.
|
|
121
|
+
*
|
|
122
|
+
* @param {Claimant} claimant
|
|
123
|
+
* @returns {void}
|
|
124
|
+
*/
|
|
125
|
+
register(claimant) {
|
|
126
|
+
this.#claimants = this.#claimants.filter((one) => one.name !== claimant.name);
|
|
127
|
+
this.#claimants.push(claimant);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @param {string} name
|
|
132
|
+
* @returns {void}
|
|
133
|
+
*/
|
|
134
|
+
forget(name) {
|
|
135
|
+
this.#claimants = this.#claimants.filter((one) => one.name !== name);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Read every resource, divide each, and tell each claimant its share.
|
|
140
|
+
*
|
|
141
|
+
* @returns {Promise<Map<string, { freeBytes: number, allowanceBytes: number, shares: object[] }>>}
|
|
142
|
+
*/
|
|
143
|
+
async revise() {
|
|
144
|
+
for (const [name, resource] of this.#resources) {
|
|
145
|
+
const claimants = this.#claimants.filter((one) => one.resource === name);
|
|
146
|
+
if (claimants.length === 0) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const held = claimants.reduce((sum, one) => sum + Math.max(0, one.held()), 0);
|
|
150
|
+
const free = await resource.readFree();
|
|
151
|
+
// WITHOUT A READING, NOTHING IS ALLOWED TO GROW. A resource whose free
|
|
152
|
+
// space cannot be read is not a resource with room; answering "unbounded"
|
|
153
|
+
// there is how the spill file came to have no limit in the first place.
|
|
154
|
+
const freeBytes = Number.isFinite(free) && free !== null ? Math.max(0, free) : 0;
|
|
155
|
+
const allowanceBytes = this.#allowanceFor(name, resource, freeBytes, held);
|
|
156
|
+
const shares = divideAllowance(claimants.map((one) => Math.max(0, one.wanted())), allowanceBytes);
|
|
157
|
+
const reading = claimants.map((one, position) => ({
|
|
158
|
+
name: one.name,
|
|
159
|
+
held: one.held(),
|
|
160
|
+
allowed: shares[position],
|
|
161
|
+
short: Number.isFinite(one.minimum) && shares[position] < one.minimum
|
|
162
|
+
}));
|
|
163
|
+
this.#last.set(name, { freeBytes, allowanceBytes, shares: reading });
|
|
164
|
+
for (const [position, one] of claimants.entries()) {
|
|
165
|
+
one.allow(shares[position]);
|
|
166
|
+
}
|
|
167
|
+
for (const share of reading.filter((one) => one.short)) {
|
|
168
|
+
// SAID, because a claimant working below what it needs is a fault of
|
|
169
|
+
// the machine and not of the code, and it is invisible otherwise.
|
|
170
|
+
this.#logger?.warn?.(
|
|
171
|
+
`budget: ${share.name} has ${megabytes(share.allowed)} of ${name}, ` +
|
|
172
|
+
`below the ${megabytes(claimants.find((one) => one.name === share.name)?.minimum ?? 0)} it needs`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
this.#logger?.info?.(this.describe());
|
|
177
|
+
return this.#last;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* What the policy allows of one resource.
|
|
182
|
+
*
|
|
183
|
+
* @param {string} name
|
|
184
|
+
* @param {{ otherDemand: OtherDemand }} resource
|
|
185
|
+
* @param {number} freeBytes
|
|
186
|
+
* @param {number} heldBytes
|
|
187
|
+
* @returns {number}
|
|
188
|
+
*/
|
|
189
|
+
#allowanceFor(name, resource, freeBytes, heldBytes) {
|
|
190
|
+
// Noted whatever the policy, because the reading of what others need is a
|
|
191
|
+
// measurement of the machine and does not stop being true when the operator
|
|
192
|
+
// has named a number.
|
|
193
|
+
const reserve = resource.otherDemand.note(freeBytes, heldBytes);
|
|
194
|
+
const floor = Math.max(0, Number(this.#policy.floors?.[name]) || 0);
|
|
195
|
+
let allowance;
|
|
196
|
+
if (this.#policy.kind === "fixed") {
|
|
197
|
+
allowance = Math.max(0, Number(this.#policy.bytes) || 0);
|
|
198
|
+
} else if (this.#policy.kind === "share") {
|
|
199
|
+
const share = Math.min(1, Math.max(0, Number(this.#policy.share) || 0));
|
|
200
|
+
allowance = Math.floor((freeBytes + heldBytes) * share);
|
|
201
|
+
} else {
|
|
202
|
+
// What is free now, plus what we already hold, less what everything that
|
|
203
|
+
// is not us has recently been seen to need.
|
|
204
|
+
allowance = Math.max(0, freeBytes + heldBytes - reserve);
|
|
205
|
+
}
|
|
206
|
+
return Math.max(allowance, Math.min(floor, freeBytes + heldBytes));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* One line per resource: what it has, what each claimant holds and may hold.
|
|
211
|
+
*
|
|
212
|
+
* @returns {string}
|
|
213
|
+
*/
|
|
214
|
+
describe() {
|
|
215
|
+
if (this.#last.size === 0) {
|
|
216
|
+
return "budget: nothing has claimed anything yet";
|
|
217
|
+
}
|
|
218
|
+
const lines = [];
|
|
219
|
+
for (const [name, reading] of this.#last) {
|
|
220
|
+
const parts = reading.shares.map(
|
|
221
|
+
(share) => `${share.name} ${megabytes(share.held)} of ${megabytes(share.allowed)}${share.short ? " (short)" : ""}`
|
|
222
|
+
);
|
|
223
|
+
lines.push(`${name}: ${megabytes(reading.freeBytes)} free; ${parts.join(", ")}`);
|
|
224
|
+
}
|
|
225
|
+
return `budget — ${lines.join(" | ")}`;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @param {number} bytes
|
|
231
|
+
* @returns {string}
|
|
232
|
+
*/
|
|
233
|
+
function megabytes(bytes) {
|
|
234
|
+
return `${Math.round(Math.max(0, bytes) / (1024 * 1024))}MB`;
|
|
235
|
+
}
|