libp2p_gossipsub/
metrics.rs

1// Copyright 2020 Sigma Prime Pty Ltd.
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
21//! A set of metrics used to help track and diagnose the network behaviour of the gossipsub
22//! protocol.
23
24use std::collections::HashMap;
25
26use prometheus_client::{
27    encoding::{EncodeLabelSet, EncodeLabelValue},
28    metrics::{
29        counter::Counter,
30        family::{Family, MetricConstructor},
31        gauge::Gauge,
32        histogram::{linear_buckets, Histogram},
33    },
34    registry::Registry,
35};
36
37use crate::{
38    topic::TopicHash,
39    types::{MessageAcceptance, PeerKind},
40};
41
42// Default value that limits for how many topics do we store metrics.
43const DEFAULT_MAX_TOPICS: usize = 300;
44
45// Default value that limits how many topics for which there has never been a subscription do we
46// store metrics.
47const DEFAULT_MAX_NEVER_SUBSCRIBED_TOPICS: usize = 50;
48
49#[derive(Debug, Clone)]
50pub struct Config {
51    /// This provides an upper bound to the number of mesh topics we create metrics for. It
52    /// prevents unbounded labels being created in the metrics.
53    pub max_topics: usize,
54    /// Mesh topics are controlled by the user via subscriptions whereas non-mesh topics are
55    /// determined by users on the network.  This limit permits a fixed amount of topics to allow,
56    /// in-addition to the mesh topics.
57    pub max_never_subscribed_topics: usize,
58    /// Buckets used for the score histograms.
59    pub score_buckets: Vec<f64>,
60}
61
62impl Config {
63    /// Create buckets for the score histograms based on score thresholds.
64    pub fn buckets_using_scoring_thresholds(&mut self, params: &crate::PeerScoreThresholds) {
65        self.score_buckets = vec![
66            params.graylist_threshold,
67            params.publish_threshold,
68            params.gossip_threshold,
69            params.gossip_threshold / 2.0,
70            params.gossip_threshold / 4.0,
71            0.0,
72            1.0,
73            10.0,
74            100.0,
75        ];
76    }
77}
78
79impl Default for Config {
80    fn default() -> Self {
81        // Some sensible defaults
82        let gossip_threshold = -4000.0;
83        let publish_threshold = -8000.0;
84        let graylist_threshold = -16000.0;
85        let score_buckets: Vec<f64> = vec![
86            graylist_threshold,
87            publish_threshold,
88            gossip_threshold,
89            gossip_threshold / 2.0,
90            gossip_threshold / 4.0,
91            0.0,
92            1.0,
93            10.0,
94            100.0,
95        ];
96        Config {
97            max_topics: DEFAULT_MAX_TOPICS,
98            max_never_subscribed_topics: DEFAULT_MAX_NEVER_SUBSCRIBED_TOPICS,
99            score_buckets,
100        }
101    }
102}
103
104/// Whether we have ever been subscribed to this topic.
105type EverSubscribed = bool;
106
107/// A collection of metrics used throughout the Gossipsub behaviour.
108pub(crate) struct Metrics {
109    // Configuration parameters
110    /// Maximum number of topics for which we store metrics. This helps keep the metrics bounded.
111    max_topics: usize,
112    /// Maximum number of topics for which we store metrics, where the topic in not one to which we
113    /// have subscribed at some point. This helps keep the metrics bounded, since these topics come
114    /// from received messages and not explicit application subscriptions.
115    max_never_subscribed_topics: usize,
116
117    // Auxiliary variables
118    /// Information needed to decide if a topic is allowed or not.
119    topic_info: HashMap<TopicHash, EverSubscribed>,
120
121    // Metrics per known topic
122    /// Status of our subscription to this topic. This metric allows analyzing other topic metrics
123    /// filtered by our current subscription status.
124    topic_subscription_status: Family<TopicHash, Gauge>,
125    /// Number of peers subscribed to each topic. This allows us to analyze a topic's behaviour
126    /// regardless of our subscription status.
127    topic_peers_count: Family<TopicHash, Gauge>,
128    /// The number of invalid messages received for a given topic.
129    invalid_messages: Family<TopicHash, Counter>,
130    /// The number of messages accepted by the application (validation result).
131    accepted_messages: Family<TopicHash, Counter>,
132    /// The number of messages ignored by the application (validation result).
133    ignored_messages: Family<TopicHash, Counter>,
134    /// The number of messages rejected by the application (validation result).
135    rejected_messages: Family<TopicHash, Counter>,
136    /// The number of publish messages dropped by the sender.
137    publish_messages_dropped: Family<TopicHash, Counter>,
138    /// The number of forward messages dropped by the sender.
139    forward_messages_dropped: Family<TopicHash, Counter>,
140    /// The number of messages that timed out and could not be sent.
141    timedout_messages_dropped: Family<TopicHash, Counter>,
142
143    // Metrics regarding mesh state
144    /// Number of peers in our mesh. This metric should be updated with the count of peers for a
145    /// topic in the mesh regardless of inclusion and churn events.
146    mesh_peer_counts: Family<TopicHash, Gauge>,
147    /// Number of times we include peers in a topic mesh for different reasons.
148    mesh_peer_inclusion_events: Family<InclusionLabel, Counter>,
149    /// Number of times we remove peers in a topic mesh for different reasons.
150    mesh_peer_churn_events: Family<ChurnLabel, Counter>,
151
152    // Metrics regarding messages sent/received
153    /// Number of gossip messages sent to each topic.
154    topic_msg_sent_counts: Family<TopicHash, Counter>,
155    /// Bytes from gossip messages sent to each topic.
156    topic_msg_sent_bytes: Family<TopicHash, Counter>,
157    /// Number of gossipsub messages published to each topic.
158    topic_msg_published: Family<TopicHash, Counter>,
159
160    /// Number of gossipsub messages received on each topic (without filtering duplicates).
161    topic_msg_recv_counts_unfiltered: Family<TopicHash, Counter>,
162    /// Number of gossipsub messages received on each topic (after filtering duplicates).
163    topic_msg_recv_counts: Family<TopicHash, Counter>,
164    /// Bytes received from gossip messages for each topic.
165    topic_msg_recv_bytes: Family<TopicHash, Counter>,
166
167    // Metrics related to scoring
168    /// Histogram of the scores for each mesh topic.
169    score_per_mesh: Family<TopicHash, Histogram, HistBuilder>,
170    /// A counter of the kind of penalties being applied to peers.
171    scoring_penalties: Family<PenaltyLabel, Counter>,
172
173    // General Metrics
174    /// Gossipsub supports floodsub, gossipsub v1.0 and gossipsub v1.1. Peers are classified based
175    /// on which protocol they support. This metric keeps track of the number of peers that are
176    /// connected of each type.
177    peers_per_protocol: Family<ProtocolLabel, Gauge>,
178    /// The time it takes to complete one iteration of the heartbeat.
179    heartbeat_duration: Histogram,
180
181    // Performance metrics
182    /// When the user validates a message, it tries to re propagate it to its mesh peers. If the
183    /// message expires from the memcache before it can be validated, we count this a cache miss
184    /// and it is an indicator that the memcache size should be increased.
185    memcache_misses: Counter,
186    /// The number of times we have decided that an IWANT control message is required for this
187    /// topic. A very high metric might indicate an underperforming network.
188    topic_iwant_msgs: Family<TopicHash, Counter>,
189
190    /// The number of times we have received an IDONTWANT control message.
191    idontwant_msgs: Counter,
192
193    /// The number of msg_id's we have received in every IDONTWANT control message.
194    idontwant_msgs_ids: Counter,
195
196    /// The size of the priority queue.
197    priority_queue_size: Histogram,
198    /// The size of the non-priority queue.
199    non_priority_queue_size: Histogram,
200}
201
202impl Metrics {
203    pub(crate) fn new(registry: &mut Registry, config: Config) -> Self {
204        // Destructure the config to be sure everything is used.
205        let Config {
206            max_topics,
207            max_never_subscribed_topics,
208            score_buckets,
209        } = config;
210
211        macro_rules! register_family {
212            ($name:expr, $help:expr) => {{
213                let fam = Family::default();
214                registry.register($name, $help, fam.clone());
215                fam
216            }};
217        }
218
219        let topic_subscription_status = register_family!(
220            "topic_subscription_status",
221            "Subscription status per known topic"
222        );
223        let topic_peers_count = register_family!(
224            "topic_peers_counts",
225            "Number of peers subscribed to each topic"
226        );
227
228        let invalid_messages = register_family!(
229            "invalid_messages_per_topic",
230            "Number of invalid messages received for each topic"
231        );
232
233        let accepted_messages = register_family!(
234            "accepted_messages_per_topic",
235            "Number of accepted messages received for each topic"
236        );
237
238        let ignored_messages = register_family!(
239            "ignored_messages_per_topic",
240            "Number of ignored messages received for each topic"
241        );
242
243        let rejected_messages = register_family!(
244            "rejected_messages_per_topic",
245            "Number of rejected messages received for each topic"
246        );
247
248        let publish_messages_dropped = register_family!(
249            "publish_messages_dropped_per_topic",
250            "Number of publish messages dropped per topic"
251        );
252
253        let forward_messages_dropped = register_family!(
254            "forward_messages_dropped_per_topic",
255            "Number of forward messages dropped per topic"
256        );
257
258        let timedout_messages_dropped = register_family!(
259            "timedout_messages_dropped_per_topic",
260            "Number of timedout messages dropped per topic"
261        );
262
263        let mesh_peer_counts = register_family!(
264            "mesh_peer_counts",
265            "Number of peers in each topic in our mesh"
266        );
267        let mesh_peer_inclusion_events = register_family!(
268            "mesh_peer_inclusion_events",
269            "Number of times a peer gets added to our mesh for different reasons"
270        );
271        let mesh_peer_churn_events = register_family!(
272            "mesh_peer_churn_events",
273            "Number of times a peer gets removed from our mesh for different reasons"
274        );
275        let topic_msg_sent_counts = register_family!(
276            "topic_msg_sent_counts",
277            "Number of gossip messages sent to each topic"
278        );
279        let topic_msg_published = register_family!(
280            "topic_msg_published",
281            "Number of gossip messages published to each topic"
282        );
283        let topic_msg_sent_bytes = register_family!(
284            "topic_msg_sent_bytes",
285            "Bytes from gossip messages sent to each topic"
286        );
287
288        let topic_msg_recv_counts_unfiltered = register_family!(
289            "topic_msg_recv_counts_unfiltered",
290            "Number of gossip messages received on each topic (without duplicates being filtered)"
291        );
292
293        let topic_msg_recv_counts = register_family!(
294            "topic_msg_recv_counts",
295            "Number of gossip messages received on each topic (after duplicates have been filtered)"
296        );
297        let topic_msg_recv_bytes = register_family!(
298            "topic_msg_recv_bytes",
299            "Bytes received from gossip messages for each topic"
300        );
301
302        let hist_builder = HistBuilder {
303            buckets: score_buckets,
304        };
305
306        let score_per_mesh: Family<_, _, HistBuilder> = Family::new_with_constructor(hist_builder);
307        registry.register(
308            "score_per_mesh",
309            "Histogram of scores per mesh topic",
310            score_per_mesh.clone(),
311        );
312
313        let scoring_penalties = register_family!(
314            "scoring_penalties",
315            "Counter of types of scoring penalties given to peers"
316        );
317        let peers_per_protocol = register_family!(
318            "peers_per_protocol",
319            "Number of connected peers by protocol type"
320        );
321
322        let heartbeat_duration = Histogram::new(linear_buckets(0.0, 50.0, 10));
323        registry.register(
324            "heartbeat_duration",
325            "Histogram of observed heartbeat durations",
326            heartbeat_duration.clone(),
327        );
328
329        let topic_iwant_msgs = register_family!(
330            "topic_iwant_msgs",
331            "Number of times we have decided an IWANT is required for this topic"
332        );
333
334        let idontwant_msgs = {
335            let metric = Counter::default();
336            registry.register(
337                "idontwant_msgs",
338                "The number of times we have received an IDONTWANT control message",
339                metric.clone(),
340            );
341            metric
342        };
343
344        let idontwant_msgs_ids = {
345            let metric = Counter::default();
346            registry.register(
347                "idontwant_msgs_ids",
348                "The number of msg_id's we have received in every total of all IDONTWANT control message.",
349                metric.clone(),
350            );
351            metric
352        };
353
354        let memcache_misses = {
355            let metric = Counter::default();
356            registry.register(
357                "memcache_misses",
358                "Number of times a message is not found in the duplicate cache when validating",
359                metric.clone(),
360            );
361            metric
362        };
363
364        let priority_queue_size = Histogram::new(linear_buckets(0.0, 25.0, 100));
365        registry.register(
366            "priority_queue_size",
367            "Histogram of observed priority queue sizes",
368            priority_queue_size.clone(),
369        );
370
371        let non_priority_queue_size = Histogram::new(linear_buckets(0.0, 25.0, 100));
372        registry.register(
373            "non_priority_queue_size",
374            "Histogram of observed non-priority queue sizes",
375            non_priority_queue_size.clone(),
376        );
377
378        Self {
379            max_topics,
380            max_never_subscribed_topics,
381            topic_info: HashMap::default(),
382            topic_subscription_status,
383            topic_peers_count,
384            invalid_messages,
385            accepted_messages,
386            ignored_messages,
387            rejected_messages,
388            publish_messages_dropped,
389            forward_messages_dropped,
390            timedout_messages_dropped,
391            mesh_peer_counts,
392            mesh_peer_inclusion_events,
393            mesh_peer_churn_events,
394            topic_msg_sent_counts,
395            topic_msg_sent_bytes,
396            topic_msg_published,
397            topic_msg_recv_counts_unfiltered,
398            topic_msg_recv_counts,
399            topic_msg_recv_bytes,
400            score_per_mesh,
401            scoring_penalties,
402            peers_per_protocol,
403            heartbeat_duration,
404            memcache_misses,
405            topic_iwant_msgs,
406            idontwant_msgs,
407            idontwant_msgs_ids,
408            priority_queue_size,
409            non_priority_queue_size,
410        }
411    }
412
413    fn non_subscription_topics_count(&self) -> usize {
414        self.topic_info
415            .values()
416            .filter(|&ever_subscribed| !ever_subscribed)
417            .count()
418    }
419
420    /// Registers a topic if not already known and if the bounds allow it.
421    fn register_topic(&mut self, topic: &TopicHash) -> Result<(), ()> {
422        if self.topic_info.contains_key(topic) {
423            Ok(())
424        } else if self.topic_info.len() < self.max_topics
425            && self.non_subscription_topics_count() < self.max_never_subscribed_topics
426        {
427            // This is a topic without an explicit subscription and we register it if we are within
428            // the configured bounds.
429            self.topic_info.entry(topic.clone()).or_insert(false);
430            self.topic_subscription_status.get_or_create(topic).set(0);
431            Ok(())
432        } else {
433            // We don't know this topic and there is no space left to store it
434            Err(())
435        }
436    }
437
438    /// Increase the number of peers that are subscribed to this topic.
439    pub(crate) fn inc_topic_peers(&mut self, topic: &TopicHash) {
440        if self.register_topic(topic).is_ok() {
441            self.topic_peers_count.get_or_create(topic).inc();
442        }
443    }
444
445    /// Decrease the number of peers that are subscribed to this topic.
446    pub(crate) fn dec_topic_peers(&mut self, topic: &TopicHash) {
447        if self.register_topic(topic).is_ok() {
448            self.topic_peers_count.get_or_create(topic).dec();
449        }
450    }
451
452    // Mesh related methods
453
454    /// Registers the subscription to a topic if the configured limits allow it.
455    /// Sets the registered number of peers in the mesh to 0.
456    pub(crate) fn joined(&mut self, topic: &TopicHash) {
457        if self.topic_info.contains_key(topic) || self.topic_info.len() < self.max_topics {
458            self.topic_info.insert(topic.clone(), true);
459            let was_subscribed = self.topic_subscription_status.get_or_create(topic).set(1);
460            debug_assert_eq!(was_subscribed, 0);
461            self.mesh_peer_counts.get_or_create(topic).set(0);
462        }
463    }
464
465    /// Registers the unsubscription to a topic if the topic was previously allowed.
466    /// Sets the registered number of peers in the mesh to 0.
467    pub(crate) fn left(&mut self, topic: &TopicHash) {
468        if self.topic_info.contains_key(topic) {
469            // Depending on the configured topic bounds we could miss a mesh topic.
470            // So, check first if the topic was previously allowed.
471            let was_subscribed = self.topic_subscription_status.get_or_create(topic).set(0);
472            debug_assert_eq!(was_subscribed, 1);
473            self.mesh_peer_counts.get_or_create(topic).set(0);
474        }
475    }
476
477    /// Register the inclusion of peers in our mesh due to some reason.
478    pub(crate) fn peers_included(&mut self, topic: &TopicHash, reason: Inclusion, count: usize) {
479        if self.register_topic(topic).is_ok() {
480            self.mesh_peer_inclusion_events
481                .get_or_create(&InclusionLabel {
482                    hash: topic.to_string(),
483                    reason,
484                })
485                .inc_by(count as u64);
486        }
487    }
488
489    /// Register the removal of peers in our mesh due to some reason.
490    pub(crate) fn peers_removed(&mut self, topic: &TopicHash, reason: Churn, count: usize) {
491        if self.register_topic(topic).is_ok() {
492            self.mesh_peer_churn_events
493                .get_or_create(&ChurnLabel {
494                    hash: topic.to_string(),
495                    reason,
496                })
497                .inc_by(count as u64);
498        }
499    }
500
501    /// Register the current number of peers in our mesh for this topic.
502    pub(crate) fn set_mesh_peers(&mut self, topic: &TopicHash, count: usize) {
503        if self.register_topic(topic).is_ok() {
504            // Due to limits, this topic could have not been allowed, so we check.
505            self.mesh_peer_counts.get_or_create(topic).set(count as i64);
506        }
507    }
508
509    /// Register that an invalid message was received on a specific topic.
510    pub(crate) fn register_invalid_message(&mut self, topic: &TopicHash) {
511        if self.register_topic(topic).is_ok() {
512            self.invalid_messages.get_or_create(topic).inc();
513        }
514    }
515
516    /// Register a score penalty.
517    pub(crate) fn register_score_penalty(&mut self, penalty: Penalty) {
518        self.scoring_penalties
519            .get_or_create(&PenaltyLabel { penalty })
520            .inc();
521    }
522
523    /// Registers that a message was published on a specific topic.
524    pub(crate) fn register_published_message(&mut self, topic: &TopicHash) {
525        if self.register_topic(topic).is_ok() {
526            self.topic_msg_published.get_or_create(topic).inc();
527        }
528    }
529
530    /// Register sending a message over a topic.
531    pub(crate) fn msg_sent(&mut self, topic: &TopicHash, bytes: usize) {
532        if self.register_topic(topic).is_ok() {
533            self.topic_msg_sent_counts.get_or_create(topic).inc();
534            self.topic_msg_sent_bytes
535                .get_or_create(topic)
536                .inc_by(bytes as u64);
537        }
538    }
539
540    /// Register dropping a Publish message over a topic.
541    pub(crate) fn publish_msg_dropped(&mut self, topic: &TopicHash) {
542        if self.register_topic(topic).is_ok() {
543            self.publish_messages_dropped.get_or_create(topic).inc();
544        }
545    }
546
547    /// Register dropping a Forward message over a topic.
548    pub(crate) fn forward_msg_dropped(&mut self, topic: &TopicHash) {
549        if self.register_topic(topic).is_ok() {
550            self.forward_messages_dropped.get_or_create(topic).inc();
551        }
552    }
553
554    /// Register dropping a message that timedout over a topic.
555    pub(crate) fn timeout_msg_dropped(&mut self, topic: &TopicHash) {
556        if self.register_topic(topic).is_ok() {
557            self.timedout_messages_dropped.get_or_create(topic).inc();
558        }
559    }
560
561    /// Register that a message was received (and was not a duplicate).
562    pub(crate) fn msg_recvd(&mut self, topic: &TopicHash) {
563        if self.register_topic(topic).is_ok() {
564            self.topic_msg_recv_counts.get_or_create(topic).inc();
565        }
566    }
567
568    /// Register that a message was received (could have been a duplicate).
569    pub(crate) fn msg_recvd_unfiltered(&mut self, topic: &TopicHash, bytes: usize) {
570        if self.register_topic(topic).is_ok() {
571            self.topic_msg_recv_counts_unfiltered
572                .get_or_create(topic)
573                .inc();
574            self.topic_msg_recv_bytes
575                .get_or_create(topic)
576                .inc_by(bytes as u64);
577        }
578    }
579
580    pub(crate) fn register_msg_validation(
581        &mut self,
582        topic: &TopicHash,
583        validation: &MessageAcceptance,
584    ) {
585        if self.register_topic(topic).is_ok() {
586            match validation {
587                MessageAcceptance::Accept => self.accepted_messages.get_or_create(topic).inc(),
588                MessageAcceptance::Ignore => self.ignored_messages.get_or_create(topic).inc(),
589                MessageAcceptance::Reject => self.rejected_messages.get_or_create(topic).inc(),
590            };
591        }
592    }
593
594    /// Register a memcache miss.
595    pub(crate) fn memcache_miss(&mut self) {
596        self.memcache_misses.inc();
597    }
598
599    /// Register sending an IWANT msg for this topic.
600    pub(crate) fn register_iwant(&mut self, topic: &TopicHash) {
601        if self.register_topic(topic).is_ok() {
602            self.topic_iwant_msgs.get_or_create(topic).inc();
603        }
604    }
605
606    /// Register receiving an IDONTWANT msg for this topic.
607    pub(crate) fn register_idontwant(&mut self, msgs: usize) {
608        self.idontwant_msgs.inc();
609        self.idontwant_msgs_ids.inc_by(msgs as u64);
610    }
611
612    /// Observes a heartbeat duration.
613    pub(crate) fn observe_heartbeat_duration(&mut self, millis: u64) {
614        self.heartbeat_duration.observe(millis as f64);
615    }
616
617    /// Observes a priority queue size.
618    pub(crate) fn observe_priority_queue_size(&mut self, len: usize) {
619        self.priority_queue_size.observe(len as f64);
620    }
621
622    /// Observes a non-priority queue size.
623    pub(crate) fn observe_non_priority_queue_size(&mut self, len: usize) {
624        self.non_priority_queue_size.observe(len as f64);
625    }
626
627    /// Observe a score of a mesh peer.
628    pub(crate) fn observe_mesh_peers_score(&mut self, topic: &TopicHash, score: f64) {
629        if self.register_topic(topic).is_ok() {
630            self.score_per_mesh.get_or_create(topic).observe(score);
631        }
632    }
633
634    /// Register a new peers connection based on its protocol.
635    pub(crate) fn peer_protocol_connected(&mut self, kind: PeerKind) {
636        self.peers_per_protocol
637            .get_or_create(&ProtocolLabel { protocol: kind })
638            .inc();
639    }
640
641    /// Removes a peer from the counter based on its protocol when it disconnects.
642    pub(crate) fn peer_protocol_disconnected(&mut self, kind: PeerKind) {
643        let metric = self
644            .peers_per_protocol
645            .get_or_create(&ProtocolLabel { protocol: kind });
646        if metric.get() != 0 {
647            // decrement the counter
648            metric.set(metric.get() - 1);
649        }
650    }
651}
652
653/// Reasons why a peer was included in the mesh.
654#[derive(PartialEq, Eq, Hash, EncodeLabelValue, Clone, Debug)]
655pub(crate) enum Inclusion {
656    /// Peer was a fanaout peer.
657    Fanout,
658    /// Included from random selection.
659    Random,
660    /// Peer subscribed.
661    Subscribed,
662    /// Peer was included to fill the outbound quota.
663    Outbound,
664}
665
666/// Reasons why a peer was removed from the mesh.
667#[derive(PartialEq, Eq, Hash, EncodeLabelValue, Clone, Debug)]
668pub(crate) enum Churn {
669    /// Peer disconnected.
670    Dc,
671    /// Peer had a bad score.
672    BadScore,
673    /// Peer sent a PRUNE.
674    Prune,
675    /// Peer unsubscribed.
676    Unsub,
677    /// Too many peers.
678    Excess,
679}
680
681/// Kinds of reasons a peer's score has been penalized
682#[derive(PartialEq, Eq, Hash, EncodeLabelValue, Clone, Debug)]
683pub(crate) enum Penalty {
684    /// A peer grafted before waiting the back-off time.
685    GraftBackoff,
686    /// A Peer did not respond to an IWANT request in time.
687    BrokenPromise,
688    /// A Peer did not send enough messages as expected.
689    MessageDeficit,
690    /// Too many peers under one IP address.
691    IPColocation,
692}
693
694/// Label for the mesh inclusion event metrics.
695#[derive(PartialEq, Eq, Hash, EncodeLabelSet, Clone, Debug)]
696struct InclusionLabel {
697    hash: String,
698    reason: Inclusion,
699}
700
701/// Label for the mesh churn event metrics.
702#[derive(PartialEq, Eq, Hash, EncodeLabelSet, Clone, Debug)]
703struct ChurnLabel {
704    hash: String,
705    reason: Churn,
706}
707
708/// Label for the kinds of protocols peers can connect as.
709#[derive(PartialEq, Eq, Hash, EncodeLabelSet, Clone, Debug)]
710struct ProtocolLabel {
711    protocol: PeerKind,
712}
713
714/// Label for the kinds of scoring penalties that can occur
715#[derive(PartialEq, Eq, Hash, EncodeLabelSet, Clone, Debug)]
716struct PenaltyLabel {
717    penalty: Penalty,
718}
719
720#[derive(Clone)]
721struct HistBuilder {
722    buckets: Vec<f64>,
723}
724
725impl MetricConstructor<Histogram> for HistBuilder {
726    fn new_metric(&self) -> Histogram {
727        Histogram::new(self.buckets.clone().into_iter())
728    }
729}