@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
153 lines (125 loc) • 4.86 kB
text/typescript
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;
}
}