@torrent-tv/proxy 2.101.36 → 2.101.38

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.36",
3
+ "version": "2.101.38",
4
4
  "description": "Torrent proxy client that exposes webseed-like HTTP stream endpoint.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -84,6 +84,8 @@ export async function handleApiTranscodeSessionsPost(req, reply, { viewerRequest
84
84
  const cancellation = new AbortController();
85
85
  const timing = { startedAt: performance.now(), openMs: 0, openCalls: 0,
86
86
  planWaitInclusiveMs: null, masterMs: null, offerMs: null, tracksMs: null };
87
+ const forwardedRequestId = typeof req.headers?.["x-ttv-forwarded-request-id"] === "string"
88
+ ? req.headers["x-ttv-forwarded-request-id"] : "unknown";
87
89
  const cancelled = () => cancellation.abort();
88
90
  reply.raw?.once?.("close", cancelled);
89
91
  if (reply.raw?.destroyed) cancelled();
@@ -217,13 +219,16 @@ export async function handleApiTranscodeSessionsPost(req, reply, { viewerRequest
217
219
  return reply.code(500).send({ error: `Failed to prepare transcode session: ${message}` });
218
220
  } finally {
219
221
  reply.raw?.removeListener?.("close", cancelled);
222
+ const handlerReturnAt = performance.now();
220
223
  const elapsed = (key) => Number.isFinite(timing[key]) ? timing[key].toFixed(1) : "n/a";
221
224
  logger.info(
222
225
  `transcode-session timing outcome=${cancellation.signal.aborted ? "cancelled" : "handler-return"} ` +
226
+ `requestId=${forwardedRequestId} ` +
223
227
  `consumer=${consumerId || "unknown"} file=${fileIndex} openCalls=${timing.openCalls} ` +
224
228
  `openMs=${timing.openMs.toFixed(1)} planWaitInclusiveMs=${elapsed("planWaitInclusiveMs")} ` +
225
229
  `masterMs=${elapsed("masterMs")} offerMs=${elapsed("offerMs")} tracksMs=${elapsed("tracksMs")} ` +
226
- `handlerMs=${(performance.now() - timing.startedAt).toFixed(1)}`
230
+ `handlerReturnAtMs=${handlerReturnAt.toFixed(3)} ` +
231
+ `handlerMs=${(handlerReturnAt - timing.startedAt).toFixed(1)}`
227
232
  );
228
233
  }
229
234
  }
package/server.js CHANGED
@@ -11,6 +11,7 @@ import fastifyCors from "@fastify/cors";
11
11
  import fastifyHelmet from "@fastify/helmet";
12
12
  import getPort from "get-port";
13
13
  import { createRequire } from "node:module";
14
+ import { performance } from "node:perf_hooks";
14
15
  import { handleHealthGet } from "./routes/health/get.js";
15
16
  import { handleMediaGet } from "./routes/media/get.js";
16
17
  import { ProbeRequests } from "./services/media/ProbeRequests.js";
@@ -152,6 +153,15 @@ export async function startProxyServer({
152
153
  // receives torrent source payloads that may be arbitrarily large.
153
154
  bodyLimit: 256 * 1024 * 1024 // 256 MB
154
155
  });
156
+ const forwardedSessionResponseStarts = new WeakMap();
157
+ const logForwardedSessionResponseStage = (stage, req) => {
158
+ const startedAt = forwardedSessionResponseStarts.get(req);
159
+ const requestId = req.headers["x-ttv-forwarded-request-id"];
160
+ if (startedAt === undefined || typeof requestId !== "string") return;
161
+ const at = performance.now();
162
+ logger.info(`[forecast-http] stage=${stage} requestId=${requestId} atMs=${at.toFixed(3)} ` +
163
+ `elapsedMs=${(at - startedAt).toFixed(3)}`);
164
+ };
155
165
 
156
166
  await app.register(fastifyHelmet, {
157
167
  // Proxy serves media to a different origin (registry UI), so CORP must allow cross-origin usage.
@@ -170,6 +180,34 @@ export async function startProxyServer({
170
180
  app.addHook("onRequest", async (_req, reply) => {
171
181
  reply.header("Access-Control-Allow-Private-Network", "true");
172
182
  });
183
+ app.addHook("onRequest", async (req, reply) => {
184
+ const requestId = req.headers["x-ttv-forwarded-request-id"];
185
+ if (typeof requestId !== "string" || req.method !== "POST" || req.url !== "/api/transcode-sessions") return;
186
+ const startedAt = performance.now();
187
+ forwardedSessionResponseStarts.set(req, startedAt);
188
+ logForwardedSessionResponseStage("onRequest", req);
189
+ reply.raw.once("finish", () => {
190
+ logForwardedSessionResponseStage("finish", req);
191
+ logger.info(`[http-response] requestId=${requestId} method=${req.method} url=${req.url} ` +
192
+ `responseMs=${(performance.now() - startedAt).toFixed(1)} status=${reply.statusCode} ` +
193
+ `contentLength=${reply.raw.getHeader("content-length") ?? "unknown"}`);
194
+ });
195
+ reply.raw.once("close", () => {
196
+ if (!reply.raw.writableFinished) {
197
+ logForwardedSessionResponseStage("close", req);
198
+ logger.warn(`[http-response] requestId=${requestId} method=${req.method} url=${req.url} ` +
199
+ `closedMs=${(performance.now() - startedAt).toFixed(1)} status=${reply.statusCode}`);
200
+ }
201
+ });
202
+ });
203
+ app.addHook("preSerialization", async (req, _reply, payload) => {
204
+ logForwardedSessionResponseStage("preSerialization", req);
205
+ return payload;
206
+ });
207
+ app.addHook("onSend", async (req, _reply, payload) => {
208
+ logForwardedSessionResponseStage("onSend", req);
209
+ return payload;
210
+ });
173
211
 
174
212
  const sourceRegistry = createSourceRegistry(200);
175
213
  // The torrent runs on its own thread. Profiling a live seek (2026-08-02)
@@ -276,13 +276,16 @@ export class LocalObservations {
276
276
  */
277
277
  noteFirstFrame({ seconds, features, featureSnapshots = null, stages = null }) {
278
278
  if (!Number.isFinite(seconds) || seconds < 0 || !features || typeof features !== "object") return;
279
+ const measuredStages = stagesOf(stages);
280
+ if (!measuredStages) return;
279
281
  const key = this.#firstFrameKey();
280
282
  const snapshots = [features, ...(Array.isArray(featureSnapshots) ? featureSnapshots : [])]
281
283
  .filter(value => value && typeof value === "object").slice(0, 2).map(firstFrameFeatures);
282
- const readings = [...(this.#entries.firstFrame[key] ?? []), {
284
+ const usableReadings = (this.#entries.firstFrame[key] ?? []).filter(reading => stagesOf(reading.stages));
285
+ const readings = [...usableReadings, {
283
286
  ...snapshots[0],
284
287
  featureSnapshots: snapshots,
285
- stages: stagesOf(stages),
288
+ stages: measuredStages,
286
289
  at: this.#now()
287
290
  }].slice(-READINGS_KEPT);
288
291
  this.#entries.firstFrame[key] = readings;
@@ -1171,13 +1171,24 @@ export function createDataChannelHandler({
1171
1171
  log(`[dc] ${method} ${path}${queryInfo}${bodyInfo}`);
1172
1172
 
1173
1173
  const targetUrl = `http://127.0.0.1:${proxyPort}${path}${query ? `?${query}` : ""}`;
1174
- const requestHeaders = { ...(forwardedHeaders ?? {}), host: `127.0.0.1:${proxyPort}` };
1174
+ const requestHeaders = {
1175
+ ...(forwardedHeaders ?? {}),
1176
+ host: `127.0.0.1:${proxyPort}`,
1177
+ "x-ttv-forwarded-request-id": requestId
1178
+ };
1175
1179
 
1176
1180
  let response;
1177
1181
  // [net-debug] TEMPORARY: time spent in the local fetch (waiting for the
1178
1182
  // route to return a response — e.g. long-polling until an HLS segment is
1179
1183
  // finalized by ffmpeg) vs. the body transfer over the data channel.
1180
1184
  const fetchStartedAt = Date.now();
1185
+ const traceSessionResponse = method === "POST" && path === "/api/transcode-sessions" &&
1186
+ typeof requestId === "string";
1187
+ const fetchStartedAtMonotonic = traceSessionResponse ? performance.now() : null;
1188
+ if (traceSessionResponse) {
1189
+ log(`[forecast-http] stage=local-fetch-start requestId=${requestId} ` +
1190
+ `atMs=${fetchStartedAtMonotonic.toFixed(3)}`);
1191
+ }
1181
1192
  try {
1182
1193
  response = await fetch(targetUrl, {
1183
1194
  method,
@@ -1193,6 +1204,12 @@ export function createDataChannelHandler({
1193
1204
  send(channel, { type: "response-error", requestId, error: fetchError?.message ?? String(fetchError) });
1194
1205
  return;
1195
1206
  }
1207
+ if (traceSessionResponse) {
1208
+ const headersAt = performance.now();
1209
+ log(`[forecast-http] stage=local-fetch-headers requestId=${requestId} ` +
1210
+ `atMs=${headersAt.toFixed(3)} elapsedMs=${(headersAt - fetchStartedAtMonotonic).toFixed(3)} ` +
1211
+ `status=${response.status}`);
1212
+ }
1196
1213
 
1197
1214
  if (response.status !== 200 && response.status !== 206) {
1198
1215
  log(`[dc] ${method} ${path}${queryInfo} → ${response.status}`);