@torrent-tv/proxy 2.101.35 → 2.101.36

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torrent-tv/proxy",
3
- "version": "2.101.35",
3
+ "version": "2.101.36",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -1,4 +1,5 @@
1
1
  import { logger } from "../../../../utils/logger.js";
2
+ import { performance } from "node:perf_hooks";
2
3
 
3
4
  /**
4
5
  * Return download statistics for a registered torrent source.
@@ -65,7 +66,13 @@ export async function handleApiSourceStatsGet(req, reply, { sourceRegistry, torr
65
66
  // Awaited: with the torrent on its own thread this is a round trip, not a
66
67
  // local lookup. Without the await the reply was the pending promise itself,
67
68
  // which serialises to `{}` — the empty stats seen in the field 2026-08-02.
68
- const stats = await torrentPool.getFileStats(torrent, fileIndex);
69
+ const statsStartedAt = performance.now();
70
+ let stats;
71
+ try {
72
+ stats = await torrentPool.getFileStats(torrent, fileIndex);
73
+ } finally {
74
+ logger.info(`[stats] ${sourceKey.slice(0, 8)} getFileStatsMs=${(performance.now() - statsStartedAt).toFixed(1)}`);
75
+ }
69
76
  if (stats?.pending || !stats) {
70
77
  const waitEstimate = initialEstimate();
71
78
  return reply.code(202).send({ pending: true, ...(waitEstimate ? { waitEstimate } : {}) });
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import { logger } from "../../../utils/logger.js";
13
+ import { performance } from "node:perf_hooks";
13
14
  import { OUTPUT_NO_CAPACITY, OUTPUT_UNAVAILABLE } from "../../../services/encode/output/index.js";
14
15
  import { replyNoCapacity, replyOutputUnavailable } from "../../../services/server/http-responses.js";
15
16
  import { waitForPlan } from "../../../services/media/await-plan.js";
@@ -81,38 +82,49 @@ export async function handleApiTranscodeSessionsPost(req, reply, { viewerRequest
81
82
  }
82
83
 
83
84
  const cancellation = new AbortController();
85
+ const timing = { startedAt: performance.now(), openMs: 0, openCalls: 0,
86
+ planWaitInclusiveMs: null, masterMs: null, offerMs: null, tracksMs: null };
84
87
  const cancelled = () => cancellation.abort();
85
88
  reply.raw?.once?.("close", cancelled);
86
89
  if (reply.raw?.destroyed) cancelled();
87
90
  try {
88
- const open = () => viewerRequests.createOrGetSession({
89
- signal: cancellation.signal,
90
- sourceKey,
91
- fileIndex,
92
- transcodeVideo,
93
- transcodeAudio,
94
- consumerId,
95
- fileName,
96
- targetWidth: Number.isInteger(targetWidth) && targetWidth > 0 ? targetWidth : 0,
97
- targetHeight: Number.isInteger(targetHeight) && targetHeight > 0 ? targetHeight : 0,
98
- exactSize,
99
- visiblePicture,
100
- audioRenditions,
101
- startPositionSeconds:
102
- Number.isFinite(startPositionSeconds) && startPositionSeconds > 0
103
- ? startPositionSeconds
104
- : 0,
105
- viewerLinkMbps: Number.isFinite(viewerLinkMbps) && viewerLinkMbps > 0 ? viewerLinkMbps : null,
106
- linkSampleMbps: Number.isFinite(linkSampleMbps) && linkSampleMbps > 0 ? linkSampleMbps : null,
107
- linkSampleAt: Number.isFinite(linkSampleAt) && linkSampleAt > 0 ? linkSampleAt : null,
108
- linkSampleAgeMs: Number.isFinite(linkSampleAgeMs) && linkSampleAgeMs >= 0 ? linkSampleAgeMs : null,
109
- bufferedAheadSec: Number.isFinite(bufferedAheadSec) && bufferedAheadSec >= 0 ? bufferedAheadSec : 0,
110
- bufferLimitSeconds:
111
- Number.isFinite(bufferLimitSeconds) && bufferLimitSeconds > 0 ? bufferLimitSeconds : null,
112
- audioTrackIndex:
113
- Number.isInteger(audioTrackIndex) && audioTrackIndex > 0 ? audioTrackIndex : 0,
114
- segmentFormatId
115
- });
91
+ const open = async () => {
92
+ const openStartedAt = performance.now();
93
+ timing.openCalls += 1;
94
+ try {
95
+ return await viewerRequests.createOrGetSession({
96
+ signal: cancellation.signal,
97
+ sourceKey,
98
+ fileIndex,
99
+ transcodeVideo,
100
+ transcodeAudio,
101
+ consumerId,
102
+ fileName,
103
+ targetWidth: Number.isInteger(targetWidth) && targetWidth > 0 ? targetWidth : 0,
104
+ targetHeight: Number.isInteger(targetHeight) && targetHeight > 0 ? targetHeight : 0,
105
+ exactSize,
106
+ visiblePicture,
107
+ audioRenditions,
108
+ startPositionSeconds:
109
+ Number.isFinite(startPositionSeconds) && startPositionSeconds > 0
110
+ ? startPositionSeconds
111
+ : 0,
112
+ viewerLinkMbps: Number.isFinite(viewerLinkMbps) && viewerLinkMbps > 0 ? viewerLinkMbps : null,
113
+ linkSampleMbps: Number.isFinite(linkSampleMbps) && linkSampleMbps > 0 ? linkSampleMbps : null,
114
+ linkSampleAt: Number.isFinite(linkSampleAt) && linkSampleAt > 0 ? linkSampleAt : null,
115
+ linkSampleAgeMs: Number.isFinite(linkSampleAgeMs) && linkSampleAgeMs >= 0 ? linkSampleAgeMs : null,
116
+ bufferedAheadSec: Number.isFinite(bufferedAheadSec) && bufferedAheadSec >= 0 ? bufferedAheadSec : 0,
117
+ bufferLimitSeconds:
118
+ Number.isFinite(bufferLimitSeconds) && bufferLimitSeconds > 0 ? bufferLimitSeconds : null,
119
+ audioTrackIndex:
120
+ Number.isInteger(audioTrackIndex) && audioTrackIndex > 0 ? audioTrackIndex : 0,
121
+ segmentFormatId
122
+ });
123
+ } finally {
124
+ timing.openMs += performance.now() - openStartedAt;
125
+ }
126
+ };
127
+ const planWaitStartedAt = performance.now();
116
128
  const session = subscribeSource ? (await waitForPlan({ signal: cancellation.signal,
117
129
  subscribe: listener => subscribeSource(sourceKey, fileIndex, listener),
118
130
  read: async () => {
@@ -123,13 +135,22 @@ export async function handleApiTranscodeSessionsPost(req, reply, { viewerRequest
123
135
  }
124
136
  }
125
137
  })).session : await open();
138
+ timing.planWaitInclusiveMs = performance.now() - planWaitStartedAt;
126
139
  // The index of quality variants, when this session has more than one to
127
140
  // offer. Its presence is what tells the browser it can change quality
128
141
  // without a new session: the player switches variants itself, appending the
129
142
  // new one after what is already buffered. Absent for a copied video, whose
130
143
  // segments are cut at the source's own keyframes and so cannot be spliced
131
144
  // with a re-encoded rung.
145
+ let stageStartedAt = performance.now();
132
146
  const hasVariants = renditions.buildMasterPlaylist(session.id) !== null;
147
+ timing.masterMs = performance.now() - stageStartedAt;
148
+ stageStartedAt = performance.now();
149
+ const offeredHeights = quality.offeredHeights(session);
150
+ timing.offerMs = performance.now() - stageStartedAt;
151
+ stageStartedAt = performance.now();
152
+ const tracks = renditions.declaredTracks(session);
153
+ timing.tracksMs = performance.now() - stageStartedAt;
133
154
  return reply.send({
134
155
  sessionId: session.id,
135
156
  playlistPath: `/transcode/${session.id}/index.m3u8`,
@@ -149,12 +170,12 @@ export async function handleApiTranscodeSessionsPost(req, reply, { viewerRequest
149
170
  // it, it fell back to a ladder of its own invention and offered rungs the
150
171
  // proxy had just refused, and picking one re-opened the session at a
151
172
  // height measured at a third of realtime.
152
- offeredHeights: quality.offeredHeights(session),
173
+ offeredHeights,
153
174
  // What this session's output will carry, stated rather than left to be
154
175
  // discovered. The browser checks what it actually got against this: a
155
176
  // track that never arrives is otherwise noticed only by its absence,
156
177
  // minutes later, as a black picture with working sound.
157
- tracks: renditions.declaredTracks(session),
178
+ tracks,
158
179
  // The soundtrack this viewer is sent and whether it is re-encoded — the
159
180
  // proxy's decision, which can differ from what the page asked for: a
160
181
  // track no figure states a rate for is re-encoded even for a browser that
@@ -196,5 +217,13 @@ export async function handleApiTranscodeSessionsPost(req, reply, { viewerRequest
196
217
  return reply.code(500).send({ error: `Failed to prepare transcode session: ${message}` });
197
218
  } finally {
198
219
  reply.raw?.removeListener?.("close", cancelled);
220
+ const elapsed = (key) => Number.isFinite(timing[key]) ? timing[key].toFixed(1) : "n/a";
221
+ logger.info(
222
+ `transcode-session timing outcome=${cancellation.signal.aborted ? "cancelled" : "handler-return"} ` +
223
+ `consumer=${consumerId || "unknown"} file=${fileIndex} openCalls=${timing.openCalls} ` +
224
+ `openMs=${timing.openMs.toFixed(1)} planWaitInclusiveMs=${elapsed("planWaitInclusiveMs")} ` +
225
+ `masterMs=${elapsed("masterMs")} offerMs=${elapsed("offerMs")} tracksMs=${elapsed("tracksMs")} ` +
226
+ `handlerMs=${(performance.now() - timing.startedAt).toFixed(1)}`
227
+ );
199
228
  }
200
229
  }