use a fasthttp request for metrics push
All checks were successful
Build and push image for doorman-homeassistant / docker (push) Successful in 28s
Build and push Doorman UI / API / docker (push) Successful in 1m24s
Build and push image for doorman-homeassistant / deploy-gitainer (push) Successful in 3s

This commit is contained in:
Martin Dimitrov 2025-01-01 16:22:33 -08:00
parent e31451bae5
commit c424476f55
3 changed files with 96 additions and 7 deletions

View File

@ -3,6 +3,7 @@ import { PrometheusContentType, Registry, Pushgateway, Summary, Counter, registe
import { DoormanLambdaContext } from "./DoormanHandlerContext"; import { DoormanLambdaContext } from "./DoormanHandlerContext";
import { shouldBlockRequest } from "../utils/blockUserAgent"; import { shouldBlockRequest } from "../utils/blockUserAgent";
import { RequestOptions } from "https"; import { RequestOptions } from "https";
import { FastHttpPushgateway } from "../metrics/FastHttpPromGateway";
export type BaseEvent = { request: { cookies: {}; headers: {}; }; } export type BaseEvent = { request: { cookies: {}; headers: {}; }; }
@ -44,7 +45,7 @@ export function withMetrics<T extends DoormanLambdaContext, U extends BaseEvent>
}, },
}; };
const pushGateway = new Pushgateway(context.PUSHGATEWAY_URL, requestOptions, metricsRegistry); const pushGateway = new FastHttpPushgateway(context.PUSHGATEWAY_URL, requestOptions, metricsRegistry);
metricsRegistry.registerMetric(new Summary({ metricsRegistry.registerMetric(new Summary({
name: CommonMetrics.RUNTIME, name: CommonMetrics.RUNTIME,
@ -111,7 +112,7 @@ export function withMetrics<T extends DoormanLambdaContext, U extends BaseEvent>
console.log(`[CommonHandler] there is ${remainingTime} ms left to send metrics`); console.log(`[CommonHandler] there is ${remainingTime} ms left to send metrics`);
let metricsTimeout = setTimeout(() => { let metricsTimeout = setTimeout(() => {
console.error("[CommonHandler] cutting it too close, abandoning metrics"); console.log("[CommonHandler] cutting it too close, abandoning metrics");
callback(...result); callback(...result);
}, remainingTime - 250); }, remainingTime - 250);
@ -127,11 +128,9 @@ export function withMetrics<T extends DoormanLambdaContext, U extends BaseEvent>
}); });
console.log("[CommonHandler] pushed metrics successfully"); console.log("[CommonHandler] pushed metrics successfully");
} catch (e: any) { } catch (e: any) {
console.error("[CommonHandler] failed to push metrics, quietly discarding them", e); console.log("[CommonHandler] failed to push metrics, quietly discarding them", e);
} }
clearTimeout(metricsTimeout); clearTimeout(metricsTimeout);
callback(...result); callback(...result);
}; };

View File

@ -0,0 +1,90 @@
import { Pushgateway, Registry, RegistryContentType } from "prom-client";
import url from "url";
import http from "http";
import https from "https";
import { gzipSync } from "zlib";
// copied mostly from pushgateway.js implementation. Just swapping out the http call to not await a response
export class FastHttpPushgateway<T extends RegistryContentType> extends Pushgateway<T> {
constructor(url: string, options?: any, registry?: Registry<T>) {
super(url, options, registry);
}
override push(params: Pushgateway.Parameters): Promise<{ resp?: unknown; body?: unknown; }> {
let method = "PUT";
let job = params.jobName;
let groupings = params.groupings;
// @ts-ignore
let gatewayUrl: string = this.gatewayUrl;
// @ts-ignore
let requestOptions: any = this.requestOptions;
// @ts-ignore
let registry: Registry = this.registry;
// `URL` first added in v6.13.0
// eslint-disable-next-line n/no-deprecated-api
const gatewayUrlParsed = url.parse(gatewayUrl);
const gatewayUrlPath =
gatewayUrlParsed.pathname && gatewayUrlParsed.pathname !== '/'
? gatewayUrlParsed.pathname
: '';
const jobPath = job
? `/job/${encodeURIComponent(job)}${generateGroupings(groupings)}`
: '';
const path = `${gatewayUrlPath}/metrics${jobPath}`;
// eslint-disable-next-line n/no-deprecated-api
const target = url.resolve(gatewayUrl, path);
// eslint-disable-next-line n/no-deprecated-api
const requestParams = url.parse(target);
const httpModule = isHttps(requestParams.href) ? https : http;
const options = Object.assign(requestParams, requestOptions, {
method,
});
return new Promise((resolve, reject) => {
if (method === 'DELETE' && options.headers) {
delete options.headers['Content-Encoding'];
}
const req = httpModule.request(options);
// write metrics
registry
.metrics()
.then(metrics => {
if (
options.headers &&
options.headers['Content-Encoding'] === 'gzip'
) {
metrics = gzipSync(metrics) as any;
}
// write metrics, and once thats done, end req. Don't wait for response
req.write(metrics, () => {
req.end(() => {
resolve({});
});
});
})
});
}
}
function generateGroupings(groupings: any) {
if (!groupings) {
return '';
}
return Object.keys(groupings)
.map(
key =>
`/${encodeURIComponent(key)}/${encodeURIComponent(groupings[key])}`,
)
.join('');
}
function isHttps(href: string) {
return href.search(/^https/) !== -1;
}

View File

@ -135,11 +135,11 @@ export const handler: ServerlessFunctionSignature<TwilioContext, BuzzerDialEvent
.inc({ door: config.door }, 1); .inc({ door: config.door }, 1);
const twiml = dialFallbackTwiml(config); const twiml = dialFallbackTwiml(config);
console.error( console.log(
invokeId, "UngracefulFallbackPromise: Cutting it too close to timeout! Skipping notifying users and calling fallback" invokeId, "UngracefulFallbackPromise: Cutting it too close to timeout! Skipping notifying users and calling fallback"
); );
resolve(twiml); resolve(twiml);
}, 9500)); }, 9000));
}); });
const twiml = await Promise.race([unlockPromise, gracefulFallbackPromise, ungracefulFallbackPromise]); const twiml = await Promise.race([unlockPromise, gracefulFallbackPromise, ungracefulFallbackPromise]);