Skip to content

Commit 67c2a55

Browse files
authored
feat: type safe metric labels (#479)
Type safe metric labels
1 parent c11b924 commit 67c2a55

File tree

1 file changed

+20
-34
lines changed

1 file changed

+20
-34
lines changed

src/metrics.ts

+20-34
Original file line numberDiff line numberDiff line change
@@ -19,58 +19,45 @@ export enum MessageSource {
1919
publish = 'publish'
2020
}
2121

22-
type LabelsGeneric = Record<string, string | undefined>
22+
type NoLabels = Record<string, never>
23+
type LabelsGeneric = Record<string, string | number>
24+
type LabelKeys<Labels extends LabelsGeneric> = Extract<keyof Labels, string>
2325
interface CollectFn<Labels extends LabelsGeneric> { (metric: Gauge<Labels>): void }
2426

25-
export interface Gauge<Labels extends LabelsGeneric = never> {
26-
// Sorry for this mess, `prom-client` API choices are not great
27-
// If the function signature was `inc(value: number, labels?: Labels)`, this would be simpler
28-
inc(value?: number): void
29-
inc(labels: Labels, value?: number): void
30-
inc(arg1?: Labels | number, arg2?: number): void
31-
32-
set(value: number): void
33-
set(labels: Labels, value: number): void
34-
set(arg1?: Labels | number, arg2?: number): void
27+
export interface Gauge<Labels extends LabelsGeneric = NoLabels> {
28+
inc: NoLabels extends Labels ? (value?: number) => void : (labels: Labels, value?: number) => void
29+
set: NoLabels extends Labels ? (value: number) => void : (labels: Labels, value: number) => void
3530

3631
addCollect(collectFn: CollectFn<Labels>): void
3732
}
3833

39-
export interface Histogram<Labels extends LabelsGeneric = never> {
34+
export interface Histogram<Labels extends LabelsGeneric = NoLabels> {
4035
startTimer(): () => void
4136

42-
observe(value: number): void
43-
observe(labels: Labels, values: number): void
44-
observe(arg1: Labels | number, arg2?: number): void
37+
observe: NoLabels extends Labels ? (value: number) => void : (labels: Labels, value: number) => void
4538

4639
reset(): void
4740
}
4841

49-
export interface AvgMinMax<Labels extends LabelsGeneric = never> {
50-
set(values: number[]): void
51-
set(labels: Labels, values: number[]): void
52-
set(arg1?: Labels | number[], arg2?: number[]): void
42+
export interface AvgMinMax<Labels extends LabelsGeneric = NoLabels> {
43+
set: NoLabels extends Labels ? (values: number[]) => void : (labels: Labels, values: number[]) => void
5344
}
5445

55-
export interface GaugeConfig<Labels extends LabelsGeneric> {
46+
export type GaugeConfig<Labels extends LabelsGeneric> = {
5647
name: string
5748
help: string
58-
labelNames?: keyof Labels extends string ? Array<keyof Labels> : undefined
59-
}
49+
} & (NoLabels extends Labels ? { labelNames?: never } : { labelNames: [LabelKeys<Labels>, ...Array<LabelKeys<Labels>>] })
6050

61-
export interface HistogramConfig<Labels extends LabelsGeneric> {
62-
name: string
63-
help: string
64-
labelNames?: Array<keyof Labels>
51+
export type HistogramConfig<Labels extends LabelsGeneric> = GaugeConfig<Labels> & {
6552
buckets?: number[]
6653
}
6754

6855
export type AvgMinMaxConfig<Labels extends LabelsGeneric> = GaugeConfig<Labels>
6956

7057
export interface MetricsRegister {
71-
gauge<T extends LabelsGeneric>(config: GaugeConfig<T>): Gauge<T>
72-
histogram<T extends LabelsGeneric>(config: HistogramConfig<T>): Histogram<T>
73-
avgMinMax<T extends LabelsGeneric>(config: AvgMinMaxConfig<T>): AvgMinMax<T>
58+
gauge<Labels extends LabelsGeneric = NoLabels>(config: GaugeConfig<Labels>): Gauge<Labels>
59+
histogram<Labels extends LabelsGeneric = NoLabels>(config: HistogramConfig<Labels>): Histogram<Labels>
60+
avgMinMax<Labels extends LabelsGeneric = NoLabels>(config: AvgMinMaxConfig<Labels>): AvgMinMax<Labels>
7461
}
7562

7663
export enum InclusionReason {
@@ -328,10 +315,9 @@ export function getMetrics (
328315
labelNames: ['hit']
329316
}),
330317

331-
asyncValidationDelayFromFirstSeenSec: register.histogram<{ topic: TopicLabel }>({
318+
asyncValidationDelayFromFirstSeenSec: register.histogram({
332319
name: 'gossipsub_async_validation_delay_from_first_seen',
333320
help: 'Async validation report delay from first seen in second',
334-
labelNames: ['topic'],
335321
buckets: [0.01, 0.03, 0.1, 0.3, 1, 3, 10]
336322
}),
337323

@@ -482,7 +468,7 @@ export function getMetrics (
482468
labelNames: ['topic']
483469
}),
484470
/** Track duplicate message delivery time */
485-
duplicateMsgDeliveryDelay: register.histogram({
471+
duplicateMsgDeliveryDelay: register.histogram<{ topic: TopicLabel }>({
486472
name: 'gossisub_duplicate_msg_delivery_delay_seconds',
487473
help: 'Time since the 1st duplicated message validated',
488474
labelNames: ['topic'],
@@ -883,9 +869,9 @@ export function getMetrics (
883869
},
884870

885871
onDuplicateMsgDelivery (topicStr: TopicStr, deliveryDelayMs: number, isLateDelivery: boolean): void {
886-
this.duplicateMsgDeliveryDelay.observe(deliveryDelayMs / 1000)
872+
const topic = this.toTopic(topicStr)
873+
this.duplicateMsgDeliveryDelay.observe({ topic }, deliveryDelayMs / 1000)
887874
if (isLateDelivery) {
888-
const topic = this.toTopic(topicStr)
889875
this.duplicateMsgLateDelivery.inc({ topic }, 1)
890876
}
891877
},

0 commit comments

Comments
 (0)