Add typing for serialized collector type

Setup TypeScript tyings and JSON schema for serializeResult and
deserializeResult and use these in the metrics related requests.
This commit is contained in:
Hornwitser committed 2023-11-19 04:50:07 +01:00
1 parent b66138587b
commit b78ca31b09
7 files changed
+46 -26

No files matched your search

+6 -12
View File
@@ -18,7 +18,7 @@ const finished = util.promisify(nodeStream.finished);
// Merges samples from sourceResult to destinationResult
function mergeSamples(destinationResult: any, sourceResult: any) {
function mergeSamples(destinationResult: lib.CollectorResultSerialized, sourceResult: lib.CollectorResultSerialized) {
let receivedSamples = new Map(sourceResult.samples);
for (let [suffix, suffixSamples] of destinationResult.samples) {
if (receivedSamples.has(suffix)) {
@@ -36,15 +36,16 @@ function mergeSamples(destinationResult: any, sourceResult: any) {
async function getMetrics(req: Request, res: Response, next: any) {
const controller: Controller = req.app.locals.controller;
let results: any[] = [];
let pluginResults: any[] = await lib.invokeHook(controller.plugins, "onMetrics");
let results: lib.CollectorResult[] = [];
type ResultIterator = AsyncIterable<lib.CollectorResult> | Iterable<lib.CollectorResult>
let pluginResults = await lib.invokeHook(controller.plugins, "onMetrics") as ResultIterator[];
for (let metricIterator of pluginResults) {
for await (let metric of metricIterator) {
results.push(metric);
}
}
let requests: Promise<any>[] = [];
let requests: Promise<InstanceType<typeof lib.HostMetricsRequest["Response"]> | null>[] = [];
let timeout = controller.config.get("controller.metrics_timeout") * 1000;
for (let [hostId, hostConnection] of controller.wsServer.hostConnections) {
if (!hostConnection.connected) {
@@ -65,7 +66,7 @@ async function getMetrics(req: Request, res: Response, next: any) {
results.push(result);
}
let resultMap: Map<string, any> = new Map();
let resultMap: Map<string, lib.CollectorResultSerialized> = new Map();
for (let response of await Promise.all(requests)) {
if (!response) {
// TODO: Log timeout occured?
@@ -83,17 +84,10 @@ async function getMetrics(req: Request, res: Response, next: any) {
}
}
// TODO: results seem to mix deserialized and serialized lib.CollectorResult ?
// mergeSamples imply samples be [string, [string, number][]]
// but lib.defaultRegistry.collect() imply Map<string, Map<string, number>>
for (let result of resultMap.values()) {
results.push(lib.deserializeResult(result));
}
// lib.exposition expect AsyncIterable<CollectorResult> as parameter.
// Should wrap results into a Generator functions ?
// @ts-ignore
let text = await lib.exposition(results);
res.set("Content-Type", lib.expositionContentType);
res.send(text);
+1 -1
View File
@@ -20,6 +20,7 @@
},
"dependencies": {
"@clusterio/lib": "^2.0.0-alpha.13",
"@sinclair/typebox": "^0.30.4",
"fs-extra": "^11.1.1",
"jimp": "^0.22.8",
"jszip": "^3.10.1",
@@ -36,7 +37,6 @@
"access": "public"
},
"devDependencies": {
"@sinclair/typebox": "^0.30.4",
"@types/fs-extra": "^11.0.1",
"@types/node": "^20.4.5",
"@types/pidusage": "^2.0.2",
+1 -1
View File
@@ -542,7 +542,7 @@ export default class Host extends lib.Link {
}
async handleHostMetricsRequest() {
let requests = [];
let requests: Promise<InstanceType<typeof lib.InstanceMetricsRequest["Response"]>>[] = [];
for (let instanceConnection of this.instanceConnections.values()) {
requests.push(instanceConnection.send(new lib.InstanceMetricsRequest()));
}
+4 -3
View File
@@ -962,11 +962,12 @@ rcon.print(game.table_to_json(players))`.replace(/\r?\n/g, " ");
}
async handleInstanceMetricsRequest() {
let results = [];
let results: ReturnType<typeof lib.serializeResult>[] = [];
if (!["stopped", "stopping"].includes(this._status)) {
let pluginResults = await lib.invokeHook(this.plugins, "onMetrics");
type ResultIterator = AsyncIterable<lib.CollectorResult> | Iterable<lib.CollectorResult>
let pluginResults = await lib.invokeHook(this.plugins, "onMetrics") as ResultIterator[];
for (let metricIterator of pluginResults) {
for await (let metric of metricIterator as any) {
for await (let metric of metricIterator) {
results.push(lib.serializeResult(metric));
}
}
+3 -2
View File
@@ -1,5 +1,6 @@
import { Type, Static } from "@sinclair/typebox";
import { jsonArray, StringEnum } from "./composites";
import { CollectorResultSerialized } from "../prometheus";
export class HostDetails {
constructor(
@@ -75,11 +76,11 @@ export class HostMetricsRequest {
static dst = "host" as const;
static Response = class Response { // TODO: Use JSON class pattern in Prometheus
constructor(
public results: object[],
public results: CollectorResultSerialized[],
) { }
static jsonSchema = Type.Object({
"results": Type.Array(Type.Object({})),
"results": Type.Array(CollectorResultSerialized),
});
static fromJSON(json: Static<typeof this.jsonSchema>) {
+3 -3
View File
@@ -4,7 +4,7 @@ import { JsonString, StringEnum, jsonArray } from "./composites";
import { RawConfig } from "../config";
import type { User } from "../users";
import type { MessageRequest } from "./messages_core";
import type { SerializedConfig } from "../config";
import { CollectorResultSerialized } from "../prometheus";
export type InstanceStatus =
@@ -216,11 +216,11 @@ export class InstanceMetricsRequest {
static Response = class Response { // TODO: Use JSON class pattern in Prometheus
constructor(
public results: object[],
public results: CollectorResultSerialized[],
) { }
static jsonSchema = Type.Object({
"results": Type.Array(Type.Object({})),
"results": Type.Array(CollectorResultSerialized),
});
static fromJSON(json: Static<typeof this.jsonSchema>) {
+28 -4
View File
@@ -38,6 +38,8 @@
* app.listen(9100);
* @module lib/prometheus
*/
import { Type, Static } from "@sinclair/typebox";
import { StringEnum } from "./data/composites";
/**
* Result from collecting a {@link Collector}
@@ -1611,7 +1613,9 @@ function escapeHelp(help: string) {
;
}
async function* expositionLines(resultsIterator: AsyncIterable<CollectorResult>) {
async function* expositionLines(
resultsIterator: AsyncIterable<CollectorResult> | Iterable<CollectorResult>
) {
let first = true;
for await (let result of resultsIterator) {
if (first) {
@@ -1665,7 +1669,9 @@ async function* expositionLines(resultsIterator: AsyncIterable<CollectorResult>)
* defaultRegistry}.
* @returns Prometheus exposition.
*/
export async function exposition(resultsIterator = defaultRegistry.collect()) {
export async function exposition(
resultsIterator: AsyncIterable<CollectorResult> | Iterable<CollectorResult> = defaultRegistry.collect()
) {
let lines = "";
for await (let line of expositionLines(resultsIterator)) {
lines += line;
@@ -1678,6 +1684,24 @@ export async function exposition(resultsIterator = defaultRegistry.collect()) {
*/
export const expositionContentType = "text/plain; version=0.0.4";
export const CollectorResultSerialized = Type.Object({
metric: Type.Object({
type: StringEnum(["counter", "gauge", "histogram", "summary", "untyped"]),
name: Type.String(),
help: Type.String(),
labels: Type.Array(Type.String()),
}),
samples: Type.Array(
Type.Tuple([
Type.String(),
Type.Array(
Type.Tuple([Type.String(), Type.Number()])
),
]),
),
});
export type CollectorResultSerialized = Static<typeof CollectorResultSerialized>;
/**
* Serialize CollectorResult into a plain object
*
@@ -1702,7 +1726,7 @@ export function serializeResult(
metricName?: string,
metricHelp?: string,
} = {}
) {
): CollectorResultSerialized {
let {
addLabels,
metricName = result.metric.name,
@@ -1756,7 +1780,7 @@ export function serializeResult(
* @returns deserialized result.
*/
export function deserializeResult(
serializedResult: ReturnType<typeof serializeResult>
serializedResult: CollectorResultSerialized
): CollectorResult {
return {
metric: new Metric(