1use prometheus_client::{
22 encoding::{EncodeLabelSet, EncodeLabelValue},
23 metrics::{
24 counter::Counter,
25 family::Family,
26 histogram::{exponential_buckets, Histogram},
27 },
28 registry::{Registry, Unit},
29};
30
31pub(crate) struct Metrics {
32 query_result_get_record_ok: Counter,
33 query_result_get_record_error: Family<GetRecordResult, Counter>,
34
35 query_result_get_closest_peers_ok: Histogram,
36 query_result_get_closest_peers_error: Family<GetClosestPeersResult, Counter>,
37
38 query_result_get_providers_ok: Histogram,
39 query_result_get_providers_error: Family<GetProvidersResult, Counter>,
40
41 query_result_num_requests: Family<QueryResult, Histogram>,
42 query_result_num_success: Family<QueryResult, Histogram>,
43 query_result_num_failure: Family<QueryResult, Histogram>,
44 query_result_duration: Family<QueryResult, Histogram>,
45
46 routing_updated: Family<RoutingUpdated, Counter>,
47
48 inbound_requests: Family<InboundRequest, Counter>,
49}
50
51impl Metrics {
52 pub(crate) fn new(registry: &mut Registry) -> Self {
53 let sub_registry = registry.sub_registry_with_prefix("kad");
54
55 let query_result_get_record_ok = Counter::default();
56 sub_registry.register(
57 "query_result_get_record_ok",
58 "Number of records returned by a successful Kademlia get record query",
59 query_result_get_record_ok.clone(),
60 );
61
62 let query_result_get_record_error = Family::default();
63 sub_registry.register(
64 "query_result_get_record_error",
65 "Number of failed Kademlia get record queries",
66 query_result_get_record_error.clone(),
67 );
68
69 let query_result_get_closest_peers_ok = Histogram::new(exponential_buckets(1.0, 2.0, 10));
70 sub_registry.register(
71 "query_result_get_closest_peers_ok",
72 "Number of closest peers returned by a successful Kademlia get closest peers query",
73 query_result_get_closest_peers_ok.clone(),
74 );
75
76 let query_result_get_closest_peers_error = Family::default();
77 sub_registry.register(
78 "query_result_get_closest_peers_error",
79 "Number of failed Kademlia get closest peers queries",
80 query_result_get_closest_peers_error.clone(),
81 );
82
83 let query_result_get_providers_ok = Histogram::new(exponential_buckets(1.0, 2.0, 10));
84 sub_registry.register(
85 "query_result_get_providers_ok",
86 "Number of providers returned by a successful Kademlia get providers query",
87 query_result_get_providers_ok.clone(),
88 );
89
90 let query_result_get_providers_error = Family::default();
91 sub_registry.register(
92 "query_result_get_providers_error",
93 "Number of failed Kademlia get providers queries",
94 query_result_get_providers_error.clone(),
95 );
96
97 let query_result_num_requests: Family<_, _> =
98 Family::new_with_constructor(|| Histogram::new(exponential_buckets(1.0, 2.0, 10)));
99 sub_registry.register(
100 "query_result_num_requests",
101 "Number of requests started for a Kademlia query",
102 query_result_num_requests.clone(),
103 );
104
105 let query_result_num_success: Family<_, _> =
106 Family::new_with_constructor(|| Histogram::new(exponential_buckets(1.0, 2.0, 10)));
107 sub_registry.register(
108 "query_result_num_success",
109 "Number of successful requests of a Kademlia query",
110 query_result_num_success.clone(),
111 );
112
113 let query_result_num_failure: Family<_, _> =
114 Family::new_with_constructor(|| Histogram::new(exponential_buckets(1.0, 2.0, 10)));
115 sub_registry.register(
116 "query_result_num_failure",
117 "Number of failed requests of a Kademlia query",
118 query_result_num_failure.clone(),
119 );
120
121 let query_result_duration: Family<_, _> =
122 Family::new_with_constructor(|| Histogram::new(exponential_buckets(0.1, 2.0, 10)));
123 sub_registry.register_with_unit(
124 "query_result_duration",
125 "Duration of a Kademlia query",
126 Unit::Seconds,
127 query_result_duration.clone(),
128 );
129
130 let routing_updated = Family::default();
131 sub_registry.register(
132 "routing_updated",
133 "Number of peers added, updated or evicted to, in or from a specific kbucket in the routing table",
134 routing_updated.clone(),
135 );
136
137 let inbound_requests = Family::default();
138 sub_registry.register(
139 "inbound_requests",
140 "Number of inbound requests",
141 inbound_requests.clone(),
142 );
143
144 Self {
145 query_result_get_record_ok,
146 query_result_get_record_error,
147
148 query_result_get_closest_peers_ok,
149 query_result_get_closest_peers_error,
150
151 query_result_get_providers_ok,
152 query_result_get_providers_error,
153
154 query_result_num_requests,
155 query_result_num_success,
156 query_result_num_failure,
157 query_result_duration,
158
159 routing_updated,
160
161 inbound_requests,
162 }
163 }
164}
165
166impl super::Recorder<libp2p_kad::Event> for Metrics {
167 fn record(&self, event: &libp2p_kad::Event) {
168 match event {
169 libp2p_kad::Event::OutboundQueryProgressed { result, stats, .. } => {
170 self.query_result_num_requests
171 .get_or_create(&result.into())
172 .observe(stats.num_requests().into());
173 self.query_result_num_success
174 .get_or_create(&result.into())
175 .observe(stats.num_successes().into());
176 self.query_result_num_failure
177 .get_or_create(&result.into())
178 .observe(stats.num_failures().into());
179 if let Some(duration) = stats.duration() {
180 self.query_result_duration
181 .get_or_create(&result.into())
182 .observe(duration.as_secs_f64());
183 }
184
185 match result {
186 libp2p_kad::QueryResult::GetRecord(result) => match result {
187 Ok(libp2p_kad::GetRecordOk::FoundRecord(_)) => {
188 self.query_result_get_record_ok.inc();
189 }
190 Ok(libp2p_kad::GetRecordOk::FinishedWithNoAdditionalRecord { .. }) => {}
191 Err(error) => {
192 self.query_result_get_record_error
193 .get_or_create(&error.into())
194 .inc();
195 }
196 },
197 libp2p_kad::QueryResult::GetClosestPeers(result) => match result {
198 Ok(ok) => self
199 .query_result_get_closest_peers_ok
200 .observe(ok.peers.len() as f64),
201 Err(error) => {
202 self.query_result_get_closest_peers_error
203 .get_or_create(&error.into())
204 .inc();
205 }
206 },
207 libp2p_kad::QueryResult::GetProviders(result) => match result {
208 Ok(libp2p_kad::GetProvidersOk::FoundProviders { providers, .. }) => {
209 self.query_result_get_providers_ok
210 .observe(providers.len() as f64);
211 }
212 Ok(libp2p_kad::GetProvidersOk::FinishedWithNoAdditionalRecord {
213 ..
214 }) => {}
215 Err(error) => {
216 self.query_result_get_providers_error
217 .get_or_create(&error.into())
218 .inc();
219 }
220 },
221 _ => {}
222 }
223 }
224 libp2p_kad::Event::RoutingUpdated {
225 is_new_peer,
226 old_peer,
227 bucket_range: (low, _high),
228 ..
229 } => {
230 let bucket = low.ilog2().unwrap_or(0);
231 if *is_new_peer {
232 self.routing_updated
233 .get_or_create(&RoutingUpdated {
234 action: RoutingAction::Added,
235 bucket,
236 })
237 .inc();
238 } else {
239 self.routing_updated
240 .get_or_create(&RoutingUpdated {
241 action: RoutingAction::Updated,
242 bucket,
243 })
244 .inc();
245 }
246
247 if old_peer.is_some() {
248 self.routing_updated
249 .get_or_create(&RoutingUpdated {
250 action: RoutingAction::Evicted,
251 bucket,
252 })
253 .inc();
254 }
255 }
256
257 libp2p_kad::Event::InboundRequest { request } => {
258 self.inbound_requests.get_or_create(&request.into()).inc();
259 }
260 _ => {}
261 }
262 }
263}
264
265#[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)]
266struct QueryResult {
267 r#type: QueryType,
268}
269
270#[derive(EncodeLabelValue, Hash, Clone, Eq, PartialEq, Debug)]
271enum QueryType {
272 Bootstrap,
273 GetClosestPeers,
274 GetProviders,
275 StartProviding,
276 RepublishProvider,
277 GetRecord,
278 PutRecord,
279 RepublishRecord,
280}
281
282impl From<&libp2p_kad::QueryResult> for QueryResult {
283 fn from(result: &libp2p_kad::QueryResult) -> Self {
284 match result {
285 libp2p_kad::QueryResult::Bootstrap(_) => QueryResult {
286 r#type: QueryType::Bootstrap,
287 },
288 libp2p_kad::QueryResult::GetClosestPeers(_) => QueryResult {
289 r#type: QueryType::GetClosestPeers,
290 },
291 libp2p_kad::QueryResult::GetProviders(_) => QueryResult {
292 r#type: QueryType::GetProviders,
293 },
294 libp2p_kad::QueryResult::StartProviding(_) => QueryResult {
295 r#type: QueryType::StartProviding,
296 },
297 libp2p_kad::QueryResult::RepublishProvider(_) => QueryResult {
298 r#type: QueryType::RepublishProvider,
299 },
300 libp2p_kad::QueryResult::GetRecord(_) => QueryResult {
301 r#type: QueryType::GetRecord,
302 },
303 libp2p_kad::QueryResult::PutRecord(_) => QueryResult {
304 r#type: QueryType::PutRecord,
305 },
306 libp2p_kad::QueryResult::RepublishRecord(_) => QueryResult {
307 r#type: QueryType::RepublishRecord,
308 },
309 }
310 }
311}
312
313#[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)]
314struct GetRecordResult {
315 error: GetRecordError,
316}
317
318#[derive(EncodeLabelValue, Hash, Clone, Eq, PartialEq, Debug)]
319enum GetRecordError {
320 NotFound,
321 QuorumFailed,
322 Timeout,
323}
324
325impl From<&libp2p_kad::GetRecordError> for GetRecordResult {
326 fn from(error: &libp2p_kad::GetRecordError) -> Self {
327 match error {
328 libp2p_kad::GetRecordError::NotFound { .. } => GetRecordResult {
329 error: GetRecordError::NotFound,
330 },
331 libp2p_kad::GetRecordError::QuorumFailed { .. } => GetRecordResult {
332 error: GetRecordError::QuorumFailed,
333 },
334 libp2p_kad::GetRecordError::Timeout { .. } => GetRecordResult {
335 error: GetRecordError::Timeout,
336 },
337 }
338 }
339}
340
341#[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)]
342struct GetClosestPeersResult {
343 error: GetClosestPeersError,
344}
345
346#[derive(EncodeLabelValue, Hash, Clone, Eq, PartialEq, Debug)]
347enum GetClosestPeersError {
348 Timeout,
349}
350
351impl From<&libp2p_kad::GetClosestPeersError> for GetClosestPeersResult {
352 fn from(error: &libp2p_kad::GetClosestPeersError) -> Self {
353 match error {
354 libp2p_kad::GetClosestPeersError::Timeout { .. } => GetClosestPeersResult {
355 error: GetClosestPeersError::Timeout,
356 },
357 }
358 }
359}
360
361#[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)]
362struct GetProvidersResult {
363 error: GetProvidersError,
364}
365
366#[derive(EncodeLabelValue, Hash, Clone, Eq, PartialEq, Debug)]
367enum GetProvidersError {
368 Timeout,
369}
370
371impl From<&libp2p_kad::GetProvidersError> for GetProvidersResult {
372 fn from(error: &libp2p_kad::GetProvidersError) -> Self {
373 match error {
374 libp2p_kad::GetProvidersError::Timeout { .. } => GetProvidersResult {
375 error: GetProvidersError::Timeout,
376 },
377 }
378 }
379}
380
381#[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)]
382struct RoutingUpdated {
383 action: RoutingAction,
384 bucket: u32,
385}
386
387#[derive(EncodeLabelValue, Hash, Clone, Eq, PartialEq, Debug)]
388enum RoutingAction {
389 Added,
390 Updated,
391 Evicted,
392}
393
394#[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)]
395struct InboundRequest {
396 request: Request,
397}
398
399impl From<&libp2p_kad::InboundRequest> for InboundRequest {
400 fn from(request: &libp2p_kad::InboundRequest) -> Self {
401 Self {
402 request: match request {
403 libp2p_kad::InboundRequest::FindNode { .. } => Request::FindNode,
404 libp2p_kad::InboundRequest::GetProvider { .. } => Request::GetProvider,
405 libp2p_kad::InboundRequest::AddProvider { .. } => Request::AddProvider,
406 libp2p_kad::InboundRequest::GetRecord { .. } => Request::GetRecord,
407 libp2p_kad::InboundRequest::PutRecord { .. } => Request::PutRecord,
408 },
409 }
410 }
411}
412
413#[derive(EncodeLabelValue, Hash, Clone, Eq, PartialEq, Debug)]
414enum Request {
415 FindNode,
416 GetProvider,
417 AddProvider,
418 GetRecord,
419 PutRecord,
420}