libp2p_metrics/
kad.rs

1// Copyright 2021 Protocol Labs.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation
6// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7// and/or sell copies of the Software, and to permit persons to whom the
8// Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19// DEALINGS IN THE SOFTWARE.
20
21use 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}