@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
package/test/one-budget.test.js
CHANGED
|
@@ -1,149 +1,149 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file One budget: one owner, one policy, a share per resource.
|
|
3
|
-
*
|
|
4
|
-
* What it replaced was two owners applying one rule to two readings — memory
|
|
5
|
-
* divided inside the torrent thread, disk divided on the main one, neither able
|
|
6
|
-
* to see the other. The claimants TRADE ACROSS RESOURCES: pieces that do not
|
|
7
|
-
* fit in memory are spilled to disk, so how much memory the piece store is
|
|
8
|
-
* given decides how much disk it needs. Field 2026-08-31: 14 400 MB spilled in
|
|
9
|
-
* fifty minutes while the memory store held 312-424 MB.
|
|
10
|
-
*
|
|
11
|
-
* And "disk" is not one resource. Measured on the addon host 2026-09-05,
|
|
12
|
-
* `/tmp` is the overlay filesystem and `/data` is ext4 on the nvme — two
|
|
13
|
-
* devices, so one figure divided between claimants on both gives each a share
|
|
14
|
-
* of a disk it does not write to.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import test from "node:test";
|
|
18
|
-
import assert from "node:assert/strict";
|
|
19
|
-
import { MachineBudget } from "../services/storage/MachineBudget.js";
|
|
20
|
-
|
|
21
|
-
const MEGABYTE = 1024 * 1024;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @param {{ name: string, resource: string, held?: number, wanted: number, minimum?: number }} shape
|
|
25
|
-
* @returns {{ claimant: object, allowed: () => number }}
|
|
26
|
-
*/
|
|
27
|
-
function claimantOf({ name, resource, held = 0, wanted, minimum }) {
|
|
28
|
-
let allowed = 0;
|
|
29
|
-
return {
|
|
30
|
-
claimant: {
|
|
31
|
-
name,
|
|
32
|
-
resource,
|
|
33
|
-
minimum,
|
|
34
|
-
held: () => held,
|
|
35
|
-
wanted: () => wanted,
|
|
36
|
-
allow: (bytes) => {
|
|
37
|
-
allowed = bytes;
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
allowed: () => allowed
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
test("each resource is divided on its own — a share of memory is not a share of disk", async () => {
|
|
45
|
-
const budget = new MachineBudget({ logger: { info: () => {}, warn: () => {} } });
|
|
46
|
-
budget.defineResource({ name: "memory", readFree: () => 100 * MEGABYTE });
|
|
47
|
-
budget.defineResource({ name: "disk:tmp", readFree: () => 10 * MEGABYTE });
|
|
48
|
-
|
|
49
|
-
const pieces = claimantOf({ name: "pieces in memory", resource: "memory", wanted: Number.MAX_SAFE_INTEGER });
|
|
50
|
-
const segments = claimantOf({ name: "segments", resource: "disk:tmp", wanted: Number.MAX_SAFE_INTEGER });
|
|
51
|
-
budget.register(pieces.claimant);
|
|
52
|
-
budget.register(segments.claimant);
|
|
53
|
-
|
|
54
|
-
await budget.revise();
|
|
55
|
-
|
|
56
|
-
assert.equal(pieces.allowed(), 100 * MEGABYTE, "memory is divided from what memory has");
|
|
57
|
-
assert.equal(segments.allowed(), 10 * MEGABYTE, "and disk from what that disk has");
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("two devices are two resources — one is not cut to pay for the other", async () => {
|
|
61
|
-
const budget = new MachineBudget({ logger: { info: () => {}, warn: () => {} } });
|
|
62
|
-
budget.defineResource({ name: "disk:66305", readFree: () => 8 * MEGABYTE });
|
|
63
|
-
budget.defineResource({ name: "disk:68", readFree: () => 80 * MEGABYTE });
|
|
64
|
-
|
|
65
|
-
// The shape on the addon host: segments and spill on the overlay, the
|
|
66
|
-
// evidence on the nvme.
|
|
67
|
-
const segments = claimantOf({ name: "segments", resource: "disk:68", wanted: Number.MAX_SAFE_INTEGER });
|
|
68
|
-
const spill = claimantOf({ name: "spilled pieces", resource: "disk:68", wanted: Number.MAX_SAFE_INTEGER });
|
|
69
|
-
const evidence = claimantOf({ name: "diagnostics", resource: "disk:66305", wanted: Number.MAX_SAFE_INTEGER });
|
|
70
|
-
for (const one of [segments, spill, evidence]) {
|
|
71
|
-
budget.register(one.claimant);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
await budget.revise();
|
|
75
|
-
|
|
76
|
-
assert.equal(evidence.allowed(), 8 * MEGABYTE, "the evidence gets the disk it actually writes to, whole");
|
|
77
|
-
assert.equal(segments.allowed() + spill.allowed(), 80 * MEGABYTE, "and the other device is divided among its own");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("a fixed ceiling is the operator's, and it binds", async () => {
|
|
81
|
-
const budget = new MachineBudget({
|
|
82
|
-
policy: { kind: "fixed", bytes: 5 * MEGABYTE },
|
|
83
|
-
logger: { info: () => {}, warn: () => {} }
|
|
84
|
-
});
|
|
85
|
-
budget.defineResource({ name: "disk", readFree: () => 1000 * MEGABYTE });
|
|
86
|
-
const segments = claimantOf({ name: "segments", resource: "disk", wanted: Number.MAX_SAFE_INTEGER });
|
|
87
|
-
budget.register(segments.claimant);
|
|
88
|
-
|
|
89
|
-
await budget.revise();
|
|
90
|
-
|
|
91
|
-
assert.equal(segments.allowed(), 5 * MEGABYTE, "a disk with a gigabyte free is still capped at what was asked for");
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("a share of free is the operator's too", async () => {
|
|
95
|
-
const budget = new MachineBudget({
|
|
96
|
-
policy: { kind: "share", share: 0.25 },
|
|
97
|
-
logger: { info: () => {}, warn: () => {} }
|
|
98
|
-
});
|
|
99
|
-
budget.defineResource({ name: "disk", readFree: () => 40 * MEGABYTE });
|
|
100
|
-
const segments = claimantOf({ name: "segments", resource: "disk", held: 0, wanted: Number.MAX_SAFE_INTEGER });
|
|
101
|
-
budget.register(segments.claimant);
|
|
102
|
-
|
|
103
|
-
await budget.revise();
|
|
104
|
-
|
|
105
|
-
assert.equal(segments.allowed(), 10 * MEGABYTE);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("a floor lifts a policy that would allow less, and cannot invent room", async () => {
|
|
109
|
-
const budget = new MachineBudget({
|
|
110
|
-
policy: { kind: "fixed", bytes: MEGABYTE, floors: { disk: 6 * MEGABYTE } },
|
|
111
|
-
logger: { info: () => {}, warn: () => {} }
|
|
112
|
-
});
|
|
113
|
-
budget.defineResource({ name: "disk", readFree: () => 4 * MEGABYTE });
|
|
114
|
-
const segments = claimantOf({ name: "segments", resource: "disk", wanted: Number.MAX_SAFE_INTEGER });
|
|
115
|
-
budget.register(segments.claimant);
|
|
116
|
-
|
|
117
|
-
await budget.revise();
|
|
118
|
-
|
|
119
|
-
assert.equal(
|
|
120
|
-
segments.allowed(),
|
|
121
|
-
4 * MEGABYTE,
|
|
122
|
-
"the floor lifts the ceiling above the fixed one, but never past what the machine has"
|
|
123
|
-
);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("a resource that cannot be read allows nothing to grow", async () => {
|
|
127
|
-
const budget = new MachineBudget({ logger: { info: () => {}, warn: () => {} } });
|
|
128
|
-
budget.defineResource({ name: "disk", readFree: () => null });
|
|
129
|
-
const segments = claimantOf({ name: "segments", resource: "disk", held: 0, wanted: Number.MAX_SAFE_INTEGER });
|
|
130
|
-
budget.register(segments.claimant);
|
|
131
|
-
|
|
132
|
-
await budget.revise();
|
|
133
|
-
|
|
134
|
-
assert.equal(segments.allowed(), 0, "unreadable is not the same as unbounded");
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("a claimant below its own minimum says so", async () => {
|
|
138
|
-
const said = [];
|
|
139
|
-
const budget = new MachineBudget({ logger: { info: () => {}, warn: (line) => said.push(line) } });
|
|
140
|
-
budget.defineResource({ name: "memory", readFree: () => MEGABYTE });
|
|
141
|
-
budget.register(claimantOf({
|
|
142
|
-
name: "pieces in memory", resource: "memory", wanted: Number.MAX_SAFE_INTEGER, minimum: 64 * MEGABYTE
|
|
143
|
-
}).claimant);
|
|
144
|
-
|
|
145
|
-
await budget.revise();
|
|
146
|
-
|
|
147
|
-
assert.equal(said.length, 1, "working below what it needs is a fault of the machine, and invisible otherwise");
|
|
148
|
-
assert.match(said[0], /below the 64MB it needs/);
|
|
149
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @file One budget: one owner, one policy, a share per resource.
|
|
3
|
+
*
|
|
4
|
+
* What it replaced was two owners applying one rule to two readings — memory
|
|
5
|
+
* divided inside the torrent thread, disk divided on the main one, neither able
|
|
6
|
+
* to see the other. The claimants TRADE ACROSS RESOURCES: pieces that do not
|
|
7
|
+
* fit in memory are spilled to disk, so how much memory the piece store is
|
|
8
|
+
* given decides how much disk it needs. Field 2026-08-31: 14 400 MB spilled in
|
|
9
|
+
* fifty minutes while the memory store held 312-424 MB.
|
|
10
|
+
*
|
|
11
|
+
* And "disk" is not one resource. Measured on the addon host 2026-09-05,
|
|
12
|
+
* `/tmp` is the overlay filesystem and `/data` is ext4 on the nvme — two
|
|
13
|
+
* devices, so one figure divided between claimants on both gives each a share
|
|
14
|
+
* of a disk it does not write to.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import test from "node:test";
|
|
18
|
+
import assert from "node:assert/strict";
|
|
19
|
+
import { MachineBudget } from "../services/storage/MachineBudget.js";
|
|
20
|
+
|
|
21
|
+
const MEGABYTE = 1024 * 1024;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {{ name: string, resource: string, held?: number, wanted: number, minimum?: number }} shape
|
|
25
|
+
* @returns {{ claimant: object, allowed: () => number }}
|
|
26
|
+
*/
|
|
27
|
+
function claimantOf({ name, resource, held = 0, wanted, minimum }) {
|
|
28
|
+
let allowed = 0;
|
|
29
|
+
return {
|
|
30
|
+
claimant: {
|
|
31
|
+
name,
|
|
32
|
+
resource,
|
|
33
|
+
minimum,
|
|
34
|
+
held: () => held,
|
|
35
|
+
wanted: () => wanted,
|
|
36
|
+
allow: (bytes) => {
|
|
37
|
+
allowed = bytes;
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
allowed: () => allowed
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
test("each resource is divided on its own — a share of memory is not a share of disk", async () => {
|
|
45
|
+
const budget = new MachineBudget({ logger: { info: () => {}, warn: () => {} } });
|
|
46
|
+
budget.defineResource({ name: "memory", readFree: () => 100 * MEGABYTE });
|
|
47
|
+
budget.defineResource({ name: "disk:tmp", readFree: () => 10 * MEGABYTE });
|
|
48
|
+
|
|
49
|
+
const pieces = claimantOf({ name: "pieces in memory", resource: "memory", wanted: Number.MAX_SAFE_INTEGER });
|
|
50
|
+
const segments = claimantOf({ name: "segments", resource: "disk:tmp", wanted: Number.MAX_SAFE_INTEGER });
|
|
51
|
+
budget.register(pieces.claimant);
|
|
52
|
+
budget.register(segments.claimant);
|
|
53
|
+
|
|
54
|
+
await budget.revise();
|
|
55
|
+
|
|
56
|
+
assert.equal(pieces.allowed(), 100 * MEGABYTE, "memory is divided from what memory has");
|
|
57
|
+
assert.equal(segments.allowed(), 10 * MEGABYTE, "and disk from what that disk has");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("two devices are two resources — one is not cut to pay for the other", async () => {
|
|
61
|
+
const budget = new MachineBudget({ logger: { info: () => {}, warn: () => {} } });
|
|
62
|
+
budget.defineResource({ name: "disk:66305", readFree: () => 8 * MEGABYTE });
|
|
63
|
+
budget.defineResource({ name: "disk:68", readFree: () => 80 * MEGABYTE });
|
|
64
|
+
|
|
65
|
+
// The shape on the addon host: segments and spill on the overlay, the
|
|
66
|
+
// evidence on the nvme.
|
|
67
|
+
const segments = claimantOf({ name: "segments", resource: "disk:68", wanted: Number.MAX_SAFE_INTEGER });
|
|
68
|
+
const spill = claimantOf({ name: "spilled pieces", resource: "disk:68", wanted: Number.MAX_SAFE_INTEGER });
|
|
69
|
+
const evidence = claimantOf({ name: "diagnostics", resource: "disk:66305", wanted: Number.MAX_SAFE_INTEGER });
|
|
70
|
+
for (const one of [segments, spill, evidence]) {
|
|
71
|
+
budget.register(one.claimant);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await budget.revise();
|
|
75
|
+
|
|
76
|
+
assert.equal(evidence.allowed(), 8 * MEGABYTE, "the evidence gets the disk it actually writes to, whole");
|
|
77
|
+
assert.equal(segments.allowed() + spill.allowed(), 80 * MEGABYTE, "and the other device is divided among its own");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("a fixed ceiling is the operator's, and it binds", async () => {
|
|
81
|
+
const budget = new MachineBudget({
|
|
82
|
+
policy: { kind: "fixed", bytes: 5 * MEGABYTE },
|
|
83
|
+
logger: { info: () => {}, warn: () => {} }
|
|
84
|
+
});
|
|
85
|
+
budget.defineResource({ name: "disk", readFree: () => 1000 * MEGABYTE });
|
|
86
|
+
const segments = claimantOf({ name: "segments", resource: "disk", wanted: Number.MAX_SAFE_INTEGER });
|
|
87
|
+
budget.register(segments.claimant);
|
|
88
|
+
|
|
89
|
+
await budget.revise();
|
|
90
|
+
|
|
91
|
+
assert.equal(segments.allowed(), 5 * MEGABYTE, "a disk with a gigabyte free is still capped at what was asked for");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("a share of free is the operator's too", async () => {
|
|
95
|
+
const budget = new MachineBudget({
|
|
96
|
+
policy: { kind: "share", share: 0.25 },
|
|
97
|
+
logger: { info: () => {}, warn: () => {} }
|
|
98
|
+
});
|
|
99
|
+
budget.defineResource({ name: "disk", readFree: () => 40 * MEGABYTE });
|
|
100
|
+
const segments = claimantOf({ name: "segments", resource: "disk", held: 0, wanted: Number.MAX_SAFE_INTEGER });
|
|
101
|
+
budget.register(segments.claimant);
|
|
102
|
+
|
|
103
|
+
await budget.revise();
|
|
104
|
+
|
|
105
|
+
assert.equal(segments.allowed(), 10 * MEGABYTE);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("a floor lifts a policy that would allow less, and cannot invent room", async () => {
|
|
109
|
+
const budget = new MachineBudget({
|
|
110
|
+
policy: { kind: "fixed", bytes: MEGABYTE, floors: { disk: 6 * MEGABYTE } },
|
|
111
|
+
logger: { info: () => {}, warn: () => {} }
|
|
112
|
+
});
|
|
113
|
+
budget.defineResource({ name: "disk", readFree: () => 4 * MEGABYTE });
|
|
114
|
+
const segments = claimantOf({ name: "segments", resource: "disk", wanted: Number.MAX_SAFE_INTEGER });
|
|
115
|
+
budget.register(segments.claimant);
|
|
116
|
+
|
|
117
|
+
await budget.revise();
|
|
118
|
+
|
|
119
|
+
assert.equal(
|
|
120
|
+
segments.allowed(),
|
|
121
|
+
4 * MEGABYTE,
|
|
122
|
+
"the floor lifts the ceiling above the fixed one, but never past what the machine has"
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("a resource that cannot be read allows nothing to grow", async () => {
|
|
127
|
+
const budget = new MachineBudget({ logger: { info: () => {}, warn: () => {} } });
|
|
128
|
+
budget.defineResource({ name: "disk", readFree: () => null });
|
|
129
|
+
const segments = claimantOf({ name: "segments", resource: "disk", held: 0, wanted: Number.MAX_SAFE_INTEGER });
|
|
130
|
+
budget.register(segments.claimant);
|
|
131
|
+
|
|
132
|
+
await budget.revise();
|
|
133
|
+
|
|
134
|
+
assert.equal(segments.allowed(), 0, "unreadable is not the same as unbounded");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("a claimant below its own minimum says so", async () => {
|
|
138
|
+
const said = [];
|
|
139
|
+
const budget = new MachineBudget({ logger: { info: () => {}, warn: (line) => said.push(line) } });
|
|
140
|
+
budget.defineResource({ name: "memory", readFree: () => MEGABYTE });
|
|
141
|
+
budget.register(claimantOf({
|
|
142
|
+
name: "pieces in memory", resource: "memory", wanted: Number.MAX_SAFE_INTEGER, minimum: 64 * MEGABYTE
|
|
143
|
+
}).claimant);
|
|
144
|
+
|
|
145
|
+
await budget.revise();
|
|
146
|
+
|
|
147
|
+
assert.equal(said.length, 1, "working below what it needs is a fault of the machine, and invisible otherwise");
|
|
148
|
+
assert.match(said[0], /below the 64MB it needs/);
|
|
149
|
+
});
|