From 9d58a8327eb2dfc7eb3db06f41a6778570d28fa3 Mon Sep 17 00:00:00 2001 From: Fijxu Date: Mon, 14 Apr 2025 01:24:57 -0400 Subject: [PATCH 12/17] metrics: track unidentified innertube errors --- src/lib/helpers/metrics.ts | 142 +++++++++++++++++++++++++++++-------- 1 file changed, 111 insertions(+), 31 deletions(-) diff --git a/src/lib/helpers/metrics.ts b/src/lib/helpers/metrics.ts index 5dee540..2464fe7 100644 --- a/src/lib/helpers/metrics.ts +++ b/src/lib/helpers/metrics.ts @@ -5,11 +5,16 @@ export class Metrics { private METRICS_PREFIX = "invidious_companion_"; public register = new Registry(); - public createCounter(name: string, help?: string): Counter { + public createCounter( + name: string, + help?: string, + labels?: string[], + ): Counter { return new Counter({ name: `${this.METRICS_PREFIX}${name}`, help: help || "No help has been provided for this metric", registers: [this.register], + labelNames: Array.isArray(labels) ? labels : [], }); } @@ -26,6 +31,7 @@ export class Metrics { private innertubeErrorStatusUnknown = this.createCounter( "innertube_error_status_unknown_total", "Number of times that an unknown status has been returned by Innertube API", + ["error"], ); private innertubeErrorReasonSignIn = this.createCounter( @@ -41,11 +47,13 @@ export class Metrics { private innertubeErrorReasonUnknown = this.createCounter( "innertube_error_reason_unknown_total", "Number of times that an unknown reason has been returned by the Innertube API", + ["error"], ); private innertubeErrorSubreasonUnknown = this.createCounter( "innertube_error_subreason_unknown_total", "Number of times that an unknown subreason has been returned by the Innertube API", + ["error"], ); public innertubeSuccessfulRequest = this.createCounter( @@ -66,26 +74,70 @@ export class Metrics { private checkStatus(videoData: IRawResponse) { const status = videoData.playabilityStatus?.status; - return { - unplayable: status === - "UNPLAYABLE", - contentCheckRequired: status === - "CONTENT_CHECK_REQUIRED", - loginRequired: status === "LOGIN_REQUIRED", + interface Error { + unplayable: boolean; + contentCheckRequired: boolean; + loginRequired: boolean; + unknown: string | undefined; + } + + const error: Error = { + unplayable: false, + contentCheckRequired: false, + loginRequired: false, + unknown: undefined, }; + + switch (status) { + case "UNPLAYABLE": + error.unplayable = true; + return error; + // Sensitive content videos + // Example video id: `VuSU7PcEKpU` + case "CONTENT_CHECK_REQUIRED": + error.contentCheckRequired = true; + return error; + case "LOGIN_REQUIRED": + error.loginRequired = true; + return error; + default: + error.unknown = status; + return error; + } } private checkReason(videoData: IRawResponse) { - const reason = videoData.playabilityStatus?.reason; - - return { - signInToConfirmAge: reason?.includes( - "Sign in to confirm your age", - ), - SignInToConfirmBot: reason?.includes( - "Sign in to confirm you’re not a bot", - ), + // On specific status like `CONTENT_CHECK_REQUIRED`, the reason is + // contained inside `errorScreen`, just like how we check subReason. + const reason = videoData.playabilityStatus?.reason || + videoData.playabilityStatus?.errorScreen + ?.playerErrorMessageRenderer + ?.reason?.simpleText; + + interface Error { + signInToConfirmAge: boolean; + SignInToConfirmBot: boolean; + unknown: string | undefined; + } + + const error: Error = { + signInToConfirmAge: false, + SignInToConfirmBot: false, + unknown: undefined, }; + + switch (true) { + case reason?.includes("Sign in to confirm you’re not a bot"): + error.SignInToConfirmBot = true; + return error; + // Age restricted videos + case reason?.includes("Sign in to confirm your age"): + error.signInToConfirmAge = true; + return error; + default: + error.unknown = reason; + return error; + } } private checkSubreason(videoData: IRawResponse) { @@ -93,39 +145,67 @@ export class Metrics { ?.playerErrorMessageRenderer ?.subreason?.runs?.[0]?.text; - return { - thisHelpsProtectCommunity: subReason?.includes( - "This helps protect our community", - ), + interface Error { + thisHelpsProtectCommunity: boolean; + unknown: string | undefined; + } + + const error: Error = { + thisHelpsProtectCommunity: false, + unknown: undefined, }; + + switch (true) { + case subReason?.includes("This helps protect our community"): + error.thisHelpsProtectCommunity = true; + return error; + default: + error.unknown = subReason; + return error; + } } public checkInnertubeResponse(videoData: IRawResponse) { this.innertubeFailedRequest.inc(); - const status = this.checkStatus(videoData); + const status = this.checkStatus(videoData); if (status.contentCheckRequired || status.unplayable) return; + if (status?.unknown) { + this.innertubeErrorStatusUnknown.labels({ + error: status.unknown, + }).inc(); + } + + const reason = this.checkReason(videoData); + if (reason.signInToConfirmAge) return; + + if (reason.unknown) { + this.innertubeErrorReasonUnknown.labels({ + error: reason.unknown, + }).inc(); + } + + // On specific `playabilityStatus.status` like `CONTENT_CHECK_REQUIRED`, + // `subReason` doesn't come with a `playabilityStatus.reason` + // key. So we need to check this separately from `reason` + const subReason = this.checkSubreason(videoData); + if (subReason.unknown) { + this.innertubeErrorSubreasonUnknown.labels({ + error: subReason.unknown, + }).inc(); + } + if (status.loginRequired) { this.innertubeErrorStatusLoginRequired.inc(); - const reason = this.checkReason(videoData); - - if (reason.signInToConfirmAge) return; if (reason.SignInToConfirmBot) { this.innertubeErrorReasonSignIn.inc(); - const subReason = this.checkSubreason(videoData); if (subReason.thisHelpsProtectCommunity) { this.innertubeErrorSubreasonProtectCommunity.inc(); - } else { - this.innertubeErrorSubreasonUnknown.inc(); } - } else { - this.innertubeErrorReasonUnknown.inc(); } - } else { - this.innertubeErrorStatusUnknown.inc(); } } } -- 2.49.0