UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

153 lines (125 loc) 4.86 kB
import {MetricObjectWithValues, MetricValue as MetricValueObject, Registry} from "prom-client"; import {JsonRecord, JsonType, MetricValue, RecordValue} from "./types.js"; type PropertyDefinition = { /** Key of value to be sent to remote service */ jsonKey: string; /** Description of the property */ description?: string; }; type StaticPropertyDefinition<T extends RecordValue> = PropertyDefinition & { /** Static value */ value: T; }; type DynamicPropertyDefinition<T extends RecordValue> = PropertyDefinition & { /** Value provider function */ provider: () => T; }; type MetricPropertyDefinition<T extends RecordValue> = PropertyDefinition & { /** Type of value to be sent to remote service */ jsonType: JsonType; /** Name of the metric */ metricName: string; /** Get value from metric with label */ withLabel?: {name: string; value: string}; /** Get value from label instead of metric value */ fromLabel?: string; /** Range value to evaluate to true */ rangeValue?: number; /** Evaluate to true if value is greater than or equal to threshold */ threshold?: number; /** Function to format retrieved metric value */ formatter?: (value: MetricValue) => MetricValue; /** Only fetch metric once and then use cached value */ cacheResult?: boolean; /** Default value if metric does not exist */ defaultValue: T; }; /** * Interface to be implemented by client stats properties */ export interface ClientStatsProperty<T extends RecordValue> { readonly definition: PropertyDefinition; getRecord(register: Registry): JsonRecord<T> | Promise<JsonRecord<T>>; } /** * Static property that can be used to define hard-coded values */ export class StaticProperty<T extends RecordValue> implements ClientStatsProperty<T> { constructor(readonly definition: StaticPropertyDefinition<T>) {} getRecord(): JsonRecord<T> { return {key: this.definition.jsonKey, value: this.definition.value}; } } /** * Dynamic property that can be used to get value from a provider function */ export class DynamicProperty<T extends RecordValue> implements ClientStatsProperty<T> { constructor(readonly definition: DynamicPropertyDefinition<T>) {} getRecord(): JsonRecord<T> { return {key: this.definition.jsonKey, value: this.definition.provider()}; } } /** * Metric property that can be used to get value from an existing prometheus metric */ export class MetricProperty<T extends RecordValue> implements ClientStatsProperty<T> { private cachedValue?: T; constructor(readonly definition: MetricPropertyDefinition<T>) {} async getRecord(register: Registry): Promise<JsonRecord<T>> { if (this.cachedValue != null) { return {key: this.definition.jsonKey, value: this.cachedValue}; } const metric = register.getSingleMetric(this.definition.metricName); if (metric) { const metricObject = await metric.get(); const metricValue = this.extractMetricValue(metricObject); if (metricValue != null) { const formattedValue = this.formatMetricValue(metricValue); const typedValue = this.convertMetricValue(formattedValue) as T; if (this.definition.cacheResult) { this.cachedValue = typedValue; } return {key: this.definition.jsonKey, value: typedValue}; } } return {key: this.definition.jsonKey, value: this.definition.defaultValue}; } private extractMetricValue(metricObject: MetricObjectWithValues<MetricValueObject<string>>): MetricValue | undefined { const {withLabel, fromLabel} = this.definition; if (withLabel) { // get value from metric with specific label, e.g. protocol="global received" return metricObject.values.find((v) => v.labels[withLabel.name] === withLabel.value)?.value; } if (fromLabel) { // get value from label, e.g. lodestar_version{version="v1.3.0/2d0938e"} => v1.3.0/2d0938e return metricObject.values[0].labels[fromLabel]; } // metric value e.g. beacon_head_slot 5603174 => 5603174 return metricObject.values[0].value; } private formatMetricValue(value: MetricValue): MetricValue { if (!this.definition.formatter) { return value; } return this.definition.formatter(value); } private convertMetricValue(value: MetricValue): RecordValue { if (typeof value === "number") { switch (this.definition.jsonType) { case JsonType.String: return value.toString(); case JsonType.Number: return Math.round(value); case JsonType.Boolean: if (this.definition.rangeValue != null) { return value === this.definition.rangeValue; } if (this.definition.threshold != null) { return value >= this.definition.threshold; } return value > 0; } } return value; } }