@torrent-tv/proxy 2.98.13 → 2.98.14
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/package.json
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file When each piece was asked of the swarm, and how long its blocks were
|
|
3
|
+
* in flight (torrent-tv/meta#178).
|
|
4
|
+
*
|
|
5
|
+
* The order in which pieces were verified cannot say why one came last: it
|
|
6
|
+
* may have been asked for late, or asked for early with its last blocks held
|
|
7
|
+
* by slow peers, and the two need different fixes. Field 2026-10-14: the
|
|
8
|
+
* first input of a 1080p MKV waited 18.7 s for pieces 1 and 2, verified after
|
|
9
|
+
* pieces 3-33. WebTorrent states no outgoing request, so the torrent pool
|
|
10
|
+
* tells this record each block it asks and each block that arrives; the
|
|
11
|
+
* record answers once, when the piece is verified, and then forgets it.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {object} PieceTiming
|
|
16
|
+
* @property {number} askedAgoMs - From the first block asked to the verification.
|
|
17
|
+
* @property {number} asks - Blocks asked, duplicates included.
|
|
18
|
+
* @property {number} peers - Distinct peers asked.
|
|
19
|
+
* @property {number} slowestMs - The longest a block was in flight, from its first ask to its arrival.
|
|
20
|
+
* @property {string | null} slowestPeer - Who sent that block.
|
|
21
|
+
* @property {number} lastInFlightMs - How long the block that completed the piece was in flight.
|
|
22
|
+
* @property {string | null} lastPeer - Who sent it.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export class PieceTimings {
|
|
26
|
+
/** @type {Map<number, { firstAskAt: number, asks: number, peers: Set<string>, blocks: Map<number, number>, slowestMs: number, slowestPeer: string | null, lastInFlightMs: number, lastPeer: string | null }>} */
|
|
27
|
+
#pieces = new Map();
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A block of `piece` at `offset` was asked of `peer`.
|
|
31
|
+
*
|
|
32
|
+
* @param {number} piece
|
|
33
|
+
* @param {number} offset
|
|
34
|
+
* @param {string} peer
|
|
35
|
+
* @param {number} now
|
|
36
|
+
*/
|
|
37
|
+
asked(piece, offset, peer, now) {
|
|
38
|
+
let record = this.#pieces.get(piece);
|
|
39
|
+
if (!record) {
|
|
40
|
+
record = { firstAskAt: now, asks: 0, peers: new Set(), blocks: new Map(), slowestMs: 0, slowestPeer: null, lastInFlightMs: 0, lastPeer: null };
|
|
41
|
+
this.#pieces.set(piece, record);
|
|
42
|
+
}
|
|
43
|
+
record.asks += 1;
|
|
44
|
+
record.peers.add(peer);
|
|
45
|
+
if (!record.blocks.has(offset)) record.blocks.set(offset, now);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A block of `piece` at `offset` arrived from `peer`. A block never asked
|
|
50
|
+
* through this record — it is not one of ours — says nothing.
|
|
51
|
+
*
|
|
52
|
+
* @param {number} piece
|
|
53
|
+
* @param {number} offset
|
|
54
|
+
* @param {string} peer
|
|
55
|
+
* @param {number} now
|
|
56
|
+
*/
|
|
57
|
+
arrived(piece, offset, peer, now) {
|
|
58
|
+
const record = this.#pieces.get(piece);
|
|
59
|
+
const askedAt = record?.blocks.get(offset);
|
|
60
|
+
if (askedAt === undefined) return;
|
|
61
|
+
const inFlightMs = now - askedAt;
|
|
62
|
+
if (inFlightMs >= record.slowestMs) {
|
|
63
|
+
record.slowestMs = inFlightMs;
|
|
64
|
+
record.slowestPeer = peer;
|
|
65
|
+
}
|
|
66
|
+
record.lastInFlightMs = inFlightMs;
|
|
67
|
+
record.lastPeer = peer;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The timing of `piece`, verified at `now`, or null when no block of it was
|
|
72
|
+
* asked through this record; forgotten either way.
|
|
73
|
+
*
|
|
74
|
+
* @param {number} piece
|
|
75
|
+
* @param {number} now
|
|
76
|
+
* @returns {PieceTiming | null}
|
|
77
|
+
*/
|
|
78
|
+
verified(piece, now) {
|
|
79
|
+
const record = this.#pieces.get(piece);
|
|
80
|
+
this.#pieces.delete(piece);
|
|
81
|
+
if (!record) return null;
|
|
82
|
+
return {
|
|
83
|
+
askedAgoMs: now - record.firstAskAt,
|
|
84
|
+
asks: record.asks,
|
|
85
|
+
peers: record.peers.size,
|
|
86
|
+
slowestMs: record.slowestMs,
|
|
87
|
+
slowestPeer: record.slowestPeer,
|
|
88
|
+
lastInFlightMs: record.lastInFlightMs,
|
|
89
|
+
lastPeer: record.lastPeer
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The timing said after a verified piece.
|
|
96
|
+
*
|
|
97
|
+
* @param {PieceTiming | null} timing
|
|
98
|
+
* @returns {string}
|
|
99
|
+
*/
|
|
100
|
+
export function describePieceTiming(timing) {
|
|
101
|
+
if (!timing) return "";
|
|
102
|
+
return `; first block asked ${(timing.askedAgoMs / 1000).toFixed(1)}s before, ${timing.asks} block ask(s) to ${timing.peers} peer(s), ` +
|
|
103
|
+
`slowest block ${(timing.slowestMs / 1000).toFixed(1)}s in flight from ${timing.slowestPeer ?? "?"}, ` +
|
|
104
|
+
`the last one ${(timing.lastInFlightMs / 1000).toFixed(1)}s from ${timing.lastPeer ?? "?"}`;
|
|
105
|
+
}
|
|
@@ -24,6 +24,7 @@ import { withdrawClaim } from "./download/withdraw-claim.js";
|
|
|
24
24
|
import { deadlineOfMapZone, levelOfMapZone, priorityOfMapZone } from "./map-zones.js";
|
|
25
25
|
import { deriveSourceKey } from "../../utils/torrent-source-key.js";
|
|
26
26
|
import { noteTorrentDestroyed } from "./destroyed-torrents.js";
|
|
27
|
+
import { PieceTimings, describePieceTiming } from "./piece-timing.js";
|
|
27
28
|
|
|
28
29
|
/** How a window stated from the priority map names itself. */
|
|
29
30
|
const MAP_CLAIMANT = "priority-map";
|
|
@@ -1502,21 +1503,35 @@ export class TorrentPool {
|
|
|
1502
1503
|
const addedAt = Date.now();
|
|
1503
1504
|
this.#swarmTimingByTorrent.set(torrent, { addedAt, firstPeerAt: null });
|
|
1504
1505
|
|
|
1505
|
-
torrent.on("download-requested", ({ piece, deadlineAt, requestId, peer, queued }) => {
|
|
1506
|
-
logger.info(`download request=${requestId} piece=${piece} deadline=${deadlineAt} peer=${peer} outstanding=${queued}`);
|
|
1507
|
-
});
|
|
1508
1506
|
torrent.on("download-request-cancelled", ({ piece, offset, length, reason, requestId }) => {
|
|
1509
1507
|
logger.info(`download request=${requestId ?? `${torrent.infoHash ?? "unknown"}:${piece}:${offset}`} cancel bytes=${length} reason=${reason}`);
|
|
1510
1508
|
});
|
|
1511
1509
|
torrent.on("download-request-cancel-failed", ({ piece, offset, length, error }) => {
|
|
1512
1510
|
logger.warn(`download request=${torrent.infoHash ?? "unknown"}:${piece}:${offset} cancel bytes=${length} failed: ${error}`);
|
|
1513
1511
|
});
|
|
1514
|
-
// WHEN each piece arrived
|
|
1515
|
-
//
|
|
1516
|
-
//
|
|
1517
|
-
//
|
|
1512
|
+
// WHEN each piece arrived, which decides how far an encoder reading the
|
|
1513
|
+
// file can go before it meets a piece still missing (torrent-tv/meta#151),
|
|
1514
|
+
// and WHY it came when it did: when its first block was asked and how long
|
|
1515
|
+
// its blocks were in flight, and from whom (`piece-timing.js`). One line
|
|
1516
|
+
// per piece: a 2.3 GB film is 563.
|
|
1517
|
+
const timings = new PieceTimings();
|
|
1518
|
+
torrent.on("wire", (wire) => {
|
|
1519
|
+
// WebTorrent asks a wire for a block by calling `request` and states
|
|
1520
|
+
// nothing; a request the wire refuses (choked, closed) is not queued and
|
|
1521
|
+
// not counted.
|
|
1522
|
+
if (typeof wire?.request !== "function") return;
|
|
1523
|
+
const peer = String(wire.remoteAddress ?? "?");
|
|
1524
|
+
const request = wire.request;
|
|
1525
|
+
wire.request = function (index, offset, length, callback) {
|
|
1526
|
+
const queued = this.requests.length;
|
|
1527
|
+
const answer = request.call(this, index, offset, length, callback);
|
|
1528
|
+
if (this.requests.length > queued) timings.asked(index, offset, peer, Date.now());
|
|
1529
|
+
return answer;
|
|
1530
|
+
};
|
|
1531
|
+
wire.on("piece", (index, offset) => timings.arrived(index, offset, peer, Date.now()));
|
|
1532
|
+
});
|
|
1518
1533
|
torrent.on("verified", (piece) => {
|
|
1519
|
-
logger.info(`download ${label()} verified piece=${piece}`);
|
|
1534
|
+
logger.info(`download ${label()} verified piece=${piece}${describePieceTiming(timings.verified(piece, Date.now()))}`);
|
|
1520
1535
|
});
|
|
1521
1536
|
|
|
1522
1537
|
// The first connected peer, said once, because the wait for it can BE the
|