From c3c12e15919e41af993fa8e0f5d5ee838944b407 Mon Sep 17 00:00:00 2001 From: Martin Dimitrov Date: Sat, 21 Dec 2024 14:12:09 -0800 Subject: [PATCH] implement a helper to fetch metrics --- .../doorman-api/src/common/DoormanHandler.ts | 46 +++++++++++++++++-- .../src/functions/buzzer-activated.ts | 26 +++++++---- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/packages/doorman-api/src/common/DoormanHandler.ts b/packages/doorman-api/src/common/DoormanHandler.ts index a813a33..d7f4d48 100644 --- a/packages/doorman-api/src/common/DoormanHandler.ts +++ b/packages/doorman-api/src/common/DoormanHandler.ts @@ -1,6 +1,7 @@ import { ServerlessCallback, ServerlessFunctionSignature } from "@twilio-labs/serverless-runtime-types/types"; -import { PrometheusContentType, Registry, Pushgateway, Summary } from "prom-client"; +import { PrometheusContentType, Registry, Pushgateway, Summary, Counter } from "prom-client"; import { DoormanLambdaContext } from "./DoormanHandlerContext"; +import { shouldBlockRequest } from "../utils/blockUserAgent"; export type BaseEvent = { request: { cookies: {}; headers: {}; }; } @@ -13,6 +14,12 @@ export type DoormanLambda = export enum CommonMetrics { RUNTIME = "FunctionRuntime", + BLOCKED_REQUEST = "BlockedRequest", + HTTP_CLIENT_ERROR = "HttpClientError", +}; + +export function getMetricFromRegistry(metricsRegistry: Registry, metric: string): T { + return metricsRegistry.getSingleMetric(metric) as T; }; /** @@ -34,7 +41,18 @@ export function withMetrics help: "Runtime of the function", })); - const summaryTimer = (metricsRegistry.getSingleMetric(CommonMetrics.RUNTIME) as Summary).startTimer(); + metricsRegistry.registerMetric(new Counter({ + name: CommonMetrics.BLOCKED_REQUEST, + help: "Blocked requests", + })); + + metricsRegistry.registerMetric(new Counter({ + name: CommonMetrics.HTTP_CLIENT_ERROR, + help: "Client side HTTP error", + labelNames: [ "ErrorCode" ], + })); + + const summaryTimer = getMetricFromRegistry(metricsRegistry, CommonMetrics.RUNTIME).startTimer(); const startTime = Date.now(); console.log(`[CommonHandler] started handler at ${startTime}`); @@ -46,11 +64,28 @@ export function withMetrics callbackResult = [err, payload]; } - await handler(context, event, tempCallback, metricsRegistry); + // block requests before we even call the handler + if (shouldBlockRequest(event)) { + getMetricFromRegistry(metricsRegistry, CommonMetrics.BLOCKED_REQUEST).inc(1); + const response = new Twilio.Response(); + response.setStatusCode(200); + callbackResult = [null, response]; + } else { + await handler(context, event, tempCallback, metricsRegistry); + } if (!callbackResult) { reject("No callback was given"); } + + let statusCode: number | undefined = (callbackResult?.[1] as any)?.statusCode; + + if (statusCode && statusCode >= 400) { + getMetricFromRegistry(metricsRegistry, CommonMetrics.HTTP_CLIENT_ERROR).inc({ + ErrorCode: statusCode, + }, 1); + } + resolve(callbackResult as Parameters); }); @@ -66,7 +101,7 @@ export function withMetrics console.log(`[CommonHandler] there is ${remainingTime} ms left to send metrics`); let metricsTimeout = setTimeout(() => { - console.log("[CommonHandler] cutting it too close, abandoning metrics"); + console.error("[CommonHandler] cutting it too close, abandoning metrics"); callback(...result); }, remainingTime - 250); @@ -74,6 +109,7 @@ export function withMetrics console.log("[CommonHandler] attempting to push metrics..."); try { + console.log(await getMetricFromRegistry(metricsRegistry, CommonMetrics.RUNTIME).get()); await pushGateway.push({ jobName: functionName, groupings: { @@ -82,7 +118,7 @@ export function withMetrics }); console.log("[CommonHandler] pushed metrics successfully"); } catch (e: any) { - console.log("[CommonHandler] failed to push metrics, quietly discarding them", e); + console.error("[CommonHandler] failed to push metrics, quietly discarding them", e); } clearTimeout(metricsTimeout); diff --git a/packages/doorman-client/src/functions/buzzer-activated.ts b/packages/doorman-client/src/functions/buzzer-activated.ts index 2054232..27aa93f 100644 --- a/packages/doorman-client/src/functions/buzzer-activated.ts +++ b/packages/doorman-client/src/functions/buzzer-activated.ts @@ -15,7 +15,7 @@ import VoiceResponse from 'twilio/lib/twiml/VoiceResponse'; import { DoorStatus } from '../../../doorman-api/src/types/DoorStatus'; import { StatusResponse } from '../../../doorman-api/src/functions/api/door/status'; import { InfoResponseClient } from '../../../doorman-api/src/functions/api/door/info'; -import { withMetrics } from '../../../doorman-api/src/common/DoormanHandler'; +import { getMetricFromRegistry, withMetrics } from '../../../doorman-api/src/common/DoormanHandler'; import { Counter, Summary } from 'prom-client'; import { BuzzerActivatedMetrics, registerMetrics } from '../metrics/BuzzerActivatedMetrics'; @@ -43,7 +43,9 @@ export const handler: ServerlessFunctionSignature(metricsRegistry, BuzzerActivatedMetrics.CALL_REJECTED) + .inc({ From: event.From }, 1); callback(null, twiml); return; } @@ -62,8 +64,11 @@ export const handler: ServerlessFunctionSignature((resolve, reject) => { intervals.push(setInterval(() => { - (metricsRegistry.getSingleMetric(BuzzerActivatedMetrics.POLL_ATTEMPTS) as Counter).inc({ door: config.door }, 1); - const recordPollLatency = (metricsRegistry.getSingleMetric(BuzzerActivatedMetrics.POLL_LATENCY) as Summary).startTimer({ door: config.door }); + getMetricFromRegistry(metricsRegistry, BuzzerActivatedMetrics.POLL_ATTEMPTS) + .inc({ door: config.door }, 1); + + const recordPollLatency = getMetricFromRegistry(metricsRegistry, BuzzerActivatedMetrics.POLL_LATENCY) + .startTimer({ door: config.door }); fetch(context.DOORMAN_URL + `/api/door/status?door=${config.door}`) .then(res => res.json()) @@ -79,7 +84,8 @@ export const handler: ServerlessFunctionSignature(metricsRegistry, BuzzerActivatedMetrics.API_UNLOCK) + .inc({ door: config.door }, 1); await notifyAllDiscord(context, config, `🔓 Doorman buzzed someone up @ Door "${config.door}"`, metricsRegistry, JSON.stringify(body.fingerprint)); resolve(twiml); @@ -99,7 +105,8 @@ export const handler: ServerlessFunctionSignature(metricsRegistry, BuzzerActivatedMetrics.DIAL_THROUGH) + .inc({ door: config.door }, 1); console.log( invokeId, "GracefulFallbackPromise: I was the fastest, so I will attempt to notify discord users before resolving with a call" @@ -121,8 +128,11 @@ export const handler: ServerlessFunctionSignature((resolve, reject) => { timeouts.push(setTimeout(async () => { - (metricsRegistry.getSingleMetric(BuzzerActivatedMetrics.RESULT_NOTIFICATION_FATE_UNKNOWN) as Counter).inc({ door: config.door }, 1); - (metricsRegistry.getSingleMetric(BuzzerActivatedMetrics.DIAL_THROUGH) as Counter).inc({ door: config.door }, 1); + getMetricFromRegistry(metricsRegistry, BuzzerActivatedMetrics.RESULT_NOTIFICATION_FATE_UNKNOWN) + .inc({ door: config.door }, 1); + + getMetricFromRegistry(metricsRegistry, BuzzerActivatedMetrics.DIAL_THROUGH) + .inc({ door: config.door }, 1); const twiml = dialFallbackTwiml(config); console.error(