Skip to content

Commit 9e55b0e

Browse files
committed
feat(fork_choice): add Prometheus metrics module
Add fork choice metrics aligned with Lodestar TypeScript implementation (packages/fork-choice/src/metrics.ts). Uses the same beacon_ prefix and metric names to ensure Grafana dashboard compatibility. Metrics include find_head histogram, reorg tracking, compute_deltas breakdown, and not_reorged_reason counters.
1 parent 9ee58ac commit 9e55b0e

File tree

1 file changed

+186
-0
lines changed

1 file changed

+186
-0
lines changed

src/fork_choice/metrics.zig

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
const std = @import("std");
2+
const Allocator = std.mem.Allocator;
3+
const m = @import("metrics");
4+
5+
const fork_choice = @import("fork_choice.zig");
6+
const NotReorgedReason = fork_choice.NotReorgedReason;
7+
const UpdateHeadOpt = fork_choice.UpdateHeadOpt;
8+
9+
/// Defaults to noop metrics, making this safe to use whether or not `metrics.init` is called.
10+
pub var fork_choice_metrics = m.initializeNoop(Metrics);
11+
12+
const CallerLabel = struct { caller: UpdateHeadOpt };
13+
const EntrypointLabel = struct { entrypoint: UpdateHeadOpt };
14+
const ReasonLabel = struct { reason: NotReorgedReason };
15+
16+
const Metrics = struct {
17+
find_head: FindHead,
18+
requests: CountGauge,
19+
errors: ErrorsGauge,
20+
changed_head: CountGauge,
21+
reorg: CountGauge,
22+
reorg_distance: ReorgDistance,
23+
votes: CountGauge,
24+
queued_attestations: CountGauge,
25+
validated_attestation_datas: CountGauge,
26+
balances_length: CountGauge,
27+
nodes: CountGauge,
28+
indices: CountGauge,
29+
not_reorged_reason: NotReorgedReasonCounter,
30+
compute_deltas_duration: ComputeDeltasDuration,
31+
compute_deltas_deltas_count: CountGauge,
32+
compute_deltas_zero_deltas_count: CountGauge,
33+
compute_deltas_equivocating_validators: CountGauge,
34+
compute_deltas_old_inactive_validators: CountGauge,
35+
compute_deltas_new_inactive_validators: CountGauge,
36+
compute_deltas_unchanged_vote_validators: CountGauge,
37+
compute_deltas_new_vote_validators: CountGauge,
38+
39+
const FindHead = m.HistogramVec(f64, CallerLabel, &.{ 0.1, 1, 10 });
40+
const CountGauge = m.Gauge(u64);
41+
const ErrorsGauge = m.GaugeVec(u64, EntrypointLabel);
42+
const ReorgDistance = m.Histogram(u64, &.{ 1, 2, 3, 5, 7, 10, 20, 30, 50, 100 });
43+
const NotReorgedReasonCounter = m.CounterVec(u64, ReasonLabel);
44+
const ComputeDeltasDuration = m.Histogram(f64, &.{ 0.01, 0.05, 0.1, 0.2 });
45+
46+
pub fn deinit(self: *Metrics) void {
47+
self.find_head.deinit();
48+
self.errors.deinit();
49+
self.not_reorged_reason.deinit();
50+
}
51+
};
52+
53+
/// Initializes all fork choice metrics. Requires an allocator for Vec metrics.
54+
///
55+
/// Meant to be called once on application startup.
56+
pub fn init(allocator: Allocator, comptime opts: m.RegistryOpts) !void {
57+
var find_head = try Metrics.FindHead.init(
58+
allocator,
59+
"beacon_fork_choice_find_head_seconds",
60+
.{ .help = "Time to find head in seconds" },
61+
opts,
62+
);
63+
errdefer find_head.deinit();
64+
65+
var errors = try Metrics.ErrorsGauge.init(
66+
allocator,
67+
"beacon_fork_choice_errors_total",
68+
.{ .help = "Count of fork choice errors" },
69+
opts,
70+
);
71+
errdefer errors.deinit();
72+
73+
var not_reorged_reason = try Metrics.NotReorgedReasonCounter.init(
74+
allocator,
75+
"beacon_fork_choice_not_reorged_reason_total",
76+
.{ .help = "Count of not reorged reasons" },
77+
opts,
78+
);
79+
errdefer not_reorged_reason.deinit();
80+
81+
fork_choice_metrics = .{
82+
.find_head = find_head,
83+
.requests = Metrics.CountGauge.init(
84+
"beacon_fork_choice_requests_total",
85+
.{ .help = "Count of fork choice head-finding attempts" },
86+
opts,
87+
),
88+
.errors = errors,
89+
.changed_head = Metrics.CountGauge.init(
90+
"beacon_fork_choice_changed_head_total",
91+
.{ .help = "Count of head changes" },
92+
opts,
93+
),
94+
.reorg = Metrics.CountGauge.init(
95+
"beacon_fork_choice_reorg_total",
96+
.{ .help = "Count of chain reorgs" },
97+
opts,
98+
),
99+
.reorg_distance = Metrics.ReorgDistance.init(
100+
"beacon_fork_choice_reorg_distance",
101+
.{ .help = "Histogram of reorg distances" },
102+
opts,
103+
),
104+
.votes = Metrics.CountGauge.init(
105+
"beacon_fork_choice_votes_count",
106+
.{ .help = "Current count of votes in fork choice" },
107+
opts,
108+
),
109+
.queued_attestations = Metrics.CountGauge.init(
110+
"beacon_fork_choice_queued_attestations_count",
111+
.{ .help = "Current count of queued attestations per slot" },
112+
opts,
113+
),
114+
.validated_attestation_datas = Metrics.CountGauge.init(
115+
"beacon_fork_choice_validated_attestation_datas_count",
116+
.{ .help = "Current count of validated attestation data" },
117+
opts,
118+
),
119+
.balances_length = Metrics.CountGauge.init(
120+
"beacon_fork_choice_balances_length",
121+
.{ .help = "Current balances array length" },
122+
opts,
123+
),
124+
.nodes = Metrics.CountGauge.init(
125+
"beacon_fork_choice_nodes_count",
126+
.{ .help = "Current number of nodes in fork choice" },
127+
opts,
128+
),
129+
.indices = Metrics.CountGauge.init(
130+
"beacon_fork_choice_indices_count",
131+
.{ .help = "Current number of indices in fork choice" },
132+
opts,
133+
),
134+
.not_reorged_reason = not_reorged_reason,
135+
.compute_deltas_duration = Metrics.ComputeDeltasDuration.init(
136+
"beacon_fork_choice_compute_deltas_seconds",
137+
.{ .help = "Time to compute deltas in seconds" },
138+
opts,
139+
),
140+
.compute_deltas_deltas_count = Metrics.CountGauge.init(
141+
"beacon_fork_choice_compute_deltas_deltas_count",
142+
.{ .help = "Number of deltas computed" },
143+
opts,
144+
),
145+
.compute_deltas_zero_deltas_count = Metrics.CountGauge.init(
146+
"beacon_fork_choice_compute_deltas_zero_deltas_count",
147+
.{ .help = "Number of zero deltas" },
148+
opts,
149+
),
150+
.compute_deltas_equivocating_validators = Metrics.CountGauge.init(
151+
"beacon_fork_choice_compute_deltas_equivocating_validators_count",
152+
.{ .help = "Number of equivocating validators" },
153+
opts,
154+
),
155+
.compute_deltas_old_inactive_validators = Metrics.CountGauge.init(
156+
"beacon_fork_choice_compute_deltas_old_inactive_validators_count",
157+
.{ .help = "Number of old inactive validators" },
158+
opts,
159+
),
160+
.compute_deltas_new_inactive_validators = Metrics.CountGauge.init(
161+
"beacon_fork_choice_compute_deltas_new_inactive_validators_count",
162+
.{ .help = "Number of new inactive validators" },
163+
opts,
164+
),
165+
.compute_deltas_unchanged_vote_validators = Metrics.CountGauge.init(
166+
"beacon_fork_choice_compute_deltas_unchanged_vote_validators_count",
167+
.{ .help = "Number of unchanged vote validators" },
168+
opts,
169+
),
170+
.compute_deltas_new_vote_validators = Metrics.CountGauge.init(
171+
"beacon_fork_choice_compute_deltas_new_vote_validators_count",
172+
.{ .help = "Number of new vote validators" },
173+
opts,
174+
),
175+
};
176+
}
177+
178+
/// Useful for conversion to seconds during isolated uses of `observe` that requires timing.
179+
pub fn readSeconds(timer: *std.time.Timer) f64 {
180+
return @as(f64, @floatFromInt(timer.read())) / std.time.ns_per_s;
181+
}
182+
183+
/// Writes all fork choice metrics to `writer`.
184+
pub fn write(writer: anytype) !void {
185+
try m.write(&fork_choice_metrics, writer);
186+
}

0 commit comments

Comments
 (0)