mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 07:04:53 +09:00
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:
1 parent
b66138587b
commit
b78ca31b09
7 files changed
+46
-26
No files matched your search
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>) {
|
||||
|
||||
@@ -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>) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in new issue
Block a user