UNPKG

@chainsafe/libp2p-gossipsub

Version:
868 lines 41.8 kB
import { TopicValidatorResult } from '@libp2p/interface'; import { MessageStatus, RejectReason } from './types.js'; export var MessageSource; (function (MessageSource) { MessageSource["forward"] = "forward"; MessageSource["publish"] = "publish"; })(MessageSource || (MessageSource = {})); export var InclusionReason; (function (InclusionReason) { /** Peer was a fanaout peer. */ InclusionReason["Fanout"] = "fanout"; /** Included from random selection. */ InclusionReason["Random"] = "random"; /** Peer subscribed. */ InclusionReason["Subscribed"] = "subscribed"; /** On heartbeat, peer was included to fill the outbound quota. */ InclusionReason["Outbound"] = "outbound"; /** On heartbeat, not enough peers in mesh */ InclusionReason["NotEnough"] = "not_enough"; /** On heartbeat opportunistic grafting due to low mesh score */ InclusionReason["Opportunistic"] = "opportunistic"; })(InclusionReason || (InclusionReason = {})); /// Reasons why a peer was removed from the mesh. export var ChurnReason; (function (ChurnReason) { /// Peer disconnected. ChurnReason["Dc"] = "disconnected"; /// Peer had a bad score. ChurnReason["BadScore"] = "bad_score"; /// Peer sent a PRUNE. ChurnReason["Prune"] = "prune"; /// Too many peers. ChurnReason["Excess"] = "excess"; })(ChurnReason || (ChurnReason = {})); /// Kinds of reasons a peer's score has been penalized export var ScorePenalty; (function (ScorePenalty) { /// A peer grafted before waiting the back-off time. ScorePenalty["GraftBackoff"] = "graft_backoff"; /// A Peer did not respond to an IWANT request in time. ScorePenalty["BrokenPromise"] = "broken_promise"; /// A Peer did not send enough messages as expected. ScorePenalty["MessageDeficit"] = "message_deficit"; /// Too many peers under one IP address. ScorePenalty["IPColocation"] = "IP_colocation"; })(ScorePenalty || (ScorePenalty = {})); export var IHaveIgnoreReason; (function (IHaveIgnoreReason) { IHaveIgnoreReason["LowScore"] = "low_score"; IHaveIgnoreReason["MaxIhave"] = "max_ihave"; IHaveIgnoreReason["MaxIasked"] = "max_iasked"; })(IHaveIgnoreReason || (IHaveIgnoreReason = {})); export var ScoreThreshold; (function (ScoreThreshold) { ScoreThreshold["graylist"] = "graylist"; ScoreThreshold["publish"] = "publish"; ScoreThreshold["gossip"] = "gossip"; ScoreThreshold["mesh"] = "mesh"; })(ScoreThreshold || (ScoreThreshold = {})); /** * A collection of metrics used throughout the Gossipsub behaviour. * NOTE: except for special reasons, do not add more than 1 label for frequent metrics, * there's a performance penalty as of June 2023. */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/explicit-function-return-type export function getMetrics(register, topicStrToLabel, opts) { // Using function style instead of class to prevent having to re-declare all MetricsPrometheus types. return { /* Metrics for static config */ protocolsEnabled: register.gauge({ name: 'gossipsub_protocol', help: 'Status of enabled protocols', labelNames: ['protocol'] }), /* Metrics per known topic */ /** * Status of our subscription to this topic. This metric allows analyzing other topic metrics * filtered by our current subscription status. * = rust-libp2p `topic_subscription_status` */ topicSubscriptionStatus: register.gauge({ name: 'gossipsub_topic_subscription_status', help: 'Status of our subscription to this topic', labelNames: ['topicStr'] }), /** Number of peers subscribed to each topic. This allows us to analyze a topic's behaviour * regardless of our subscription status. */ topicPeersCount: register.gauge({ name: 'gossipsub_topic_peer_count', help: 'Number of peers subscribed to each topic', labelNames: ['topicStr'] }), /* Metrics regarding mesh state */ /** * Number of peers in our mesh. This metric should be updated with the count of peers for a * topic in the mesh regardless of inclusion and churn events. * = rust-libp2p `mesh_peer_counts` */ meshPeerCounts: register.gauge({ name: 'gossipsub_mesh_peer_count', help: 'Number of peers in our mesh', labelNames: ['topicStr'] }), /** * Number of times we include peers in a topic mesh for different reasons. * = rust-libp2p `mesh_peer_inclusion_events` */ meshPeerInclusionEventsFanout: register.gauge({ name: 'gossipsub_mesh_peer_inclusion_events_fanout_total', help: 'Number of times we include peers in a topic mesh for fanout reasons', labelNames: ['topic'] }), meshPeerInclusionEventsRandom: register.gauge({ name: 'gossipsub_mesh_peer_inclusion_events_random_total', help: 'Number of times we include peers in a topic mesh for random reasons', labelNames: ['topic'] }), meshPeerInclusionEventsSubscribed: register.gauge({ name: 'gossipsub_mesh_peer_inclusion_events_subscribed_total', help: 'Number of times we include peers in a topic mesh for subscribed reasons', labelNames: ['topic'] }), meshPeerInclusionEventsOutbound: register.gauge({ name: 'gossipsub_mesh_peer_inclusion_events_outbound_total', help: 'Number of times we include peers in a topic mesh for outbound reasons', labelNames: ['topic'] }), meshPeerInclusionEventsNotEnough: register.gauge({ name: 'gossipsub_mesh_peer_inclusion_events_not_enough_total', help: 'Number of times we include peers in a topic mesh for not_enough reasons', labelNames: ['topic'] }), meshPeerInclusionEventsOpportunistic: register.gauge({ name: 'gossipsub_mesh_peer_inclusion_events_opportunistic_total', help: 'Number of times we include peers in a topic mesh for opportunistic reasons', labelNames: ['topic'] }), meshPeerInclusionEventsUnknown: register.gauge({ name: 'gossipsub_mesh_peer_inclusion_events_unknown_total', help: 'Number of times we include peers in a topic mesh for unknown reasons', labelNames: ['topic'] }), /** * Number of times we remove peers in a topic mesh for different reasons. * = rust-libp2p `mesh_peer_churn_events` */ meshPeerChurnEventsDisconnected: register.gauge({ name: 'gossipsub_peer_churn_events_disconnected_total', help: 'Number of times we remove peers in a topic mesh for disconnected reasons', labelNames: ['topic'] }), meshPeerChurnEventsBadScore: register.gauge({ name: 'gossipsub_peer_churn_events_bad_score_total', help: 'Number of times we remove peers in a topic mesh for bad_score reasons', labelNames: ['topic'] }), meshPeerChurnEventsPrune: register.gauge({ name: 'gossipsub_peer_churn_events_prune_total', help: 'Number of times we remove peers in a topic mesh for prune reasons', labelNames: ['topic'] }), meshPeerChurnEventsExcess: register.gauge({ name: 'gossipsub_peer_churn_events_excess_total', help: 'Number of times we remove peers in a topic mesh for excess reasons', labelNames: ['topic'] }), meshPeerChurnEventsUnknown: register.gauge({ name: 'gossipsub_peer_churn_events_unknown_total', help: 'Number of times we remove peers in a topic mesh for unknown reasons', labelNames: ['topic'] }), /* General Metrics */ /** * Gossipsub supports floodsub, gossipsub v1.0, v1.1, and v1.2. Peers are classified based * on which protocol they support. This metric keeps track of the number of peers that are * connected of each type. */ peersPerProtocol: register.gauge({ name: 'gossipsub_peers_per_protocol_count', help: 'Peers connected for each topic', labelNames: ['protocol'] }), /** The time it takes to complete one iteration of the heartbeat. */ heartbeatDuration: register.histogram({ name: 'gossipsub_heartbeat_duration_seconds', help: 'The time it takes to complete one iteration of the heartbeat', // Should take <10ms, over 1s it's a huge issue that needs debugging, since a heartbeat will be cancelled buckets: [0.01, 0.1, 1] }), /** Heartbeat run took longer than heartbeat interval so next is skipped */ heartbeatSkipped: register.gauge({ name: 'gossipsub_heartbeat_skipped', help: 'Heartbeat run took longer than heartbeat interval so next is skipped' }), /** * Message validation results for each topic. * Invalid == Reject? * = rust-libp2p `invalid_messages`, `accepted_messages`, `ignored_messages`, `rejected_messages` */ acceptedMessagesTotal: register.gauge({ name: 'gossipsub_accepted_messages_total', help: 'Total accepted messages for each topic', labelNames: ['topic'] }), ignoredMessagesTotal: register.gauge({ name: 'gossipsub_ignored_messages_total', help: 'Total ignored messages for each topic', labelNames: ['topic'] }), rejectedMessagesTotal: register.gauge({ name: 'gossipsub_rejected_messages_total', help: 'Total rejected messages for each topic', labelNames: ['topic'] }), unknownValidationResultsTotal: register.gauge({ name: 'gossipsub_unknown_validation_results_total', help: 'Total unknown validation results for each topic', labelNames: ['topic'] }), /** * When the user validates a message, it tries to re propagate it to its mesh peers. If the * message expires from the memcache before it can be validated, we count this a cache miss * and it is an indicator that the memcache size should be increased. * = rust-libp2p `mcache_misses` */ asyncValidationMcacheHit: register.gauge({ name: 'gossipsub_async_validation_mcache_hit_total', help: 'Async validation result reported by the user layer', labelNames: ['hit'] }), asyncValidationDelayFromFirstSeenSec: register.histogram({ name: 'gossipsub_async_validation_delay_from_first_seen', help: 'Async validation report delay from first seen in second', buckets: [0.01, 0.03, 0.1, 0.3, 1, 3, 10] }), asyncValidationUnknownFirstSeen: register.gauge({ name: 'gossipsub_async_validation_unknown_first_seen_count_total', help: 'Async validation report unknown first seen value for message' }), // peer stream peerReadStreamError: register.gauge({ name: 'gossipsub_peer_read_stream_err_count_total', help: 'Peer read stream error' }), // RPC outgoing. Track byte length + data structure sizes rpcRecvBytes: register.gauge({ name: 'gossipsub_rpc_recv_bytes_total', help: 'RPC recv' }), rpcRecvCount: register.gauge({ name: 'gossipsub_rpc_recv_count_total', help: 'RPC recv' }), rpcRecvSubscription: register.gauge({ name: 'gossipsub_rpc_recv_subscription_total', help: 'RPC recv' }), rpcRecvMessage: register.gauge({ name: 'gossipsub_rpc_recv_message_total', help: 'RPC recv' }), rpcRecvControl: register.gauge({ name: 'gossipsub_rpc_recv_control_total', help: 'RPC recv' }), rpcRecvIHave: register.gauge({ name: 'gossipsub_rpc_recv_ihave_total', help: 'RPC recv' }), rpcRecvIWant: register.gauge({ name: 'gossipsub_rpc_recv_iwant_total', help: 'RPC recv' }), rpcRecvGraft: register.gauge({ name: 'gossipsub_rpc_recv_graft_total', help: 'RPC recv' }), rpcRecvPrune: register.gauge({ name: 'gossipsub_rpc_recv_prune_total', help: 'RPC recv' }), rpcDataError: register.gauge({ name: 'gossipsub_rpc_data_err_count_total', help: 'RPC data error' }), rpcRecvError: register.gauge({ name: 'gossipsub_rpc_recv_err_count_total', help: 'RPC recv error' }), /** Total count of RPC dropped because acceptFrom() == false */ rpcRecvNotAccepted: register.gauge({ name: 'gossipsub_rpc_rcv_not_accepted_total', help: 'Total count of RPC dropped because acceptFrom() == false' }), // RPC incoming. Track byte length + data structure sizes rpcSentBytes: register.gauge({ name: 'gossipsub_rpc_sent_bytes_total', help: 'RPC sent' }), rpcSentCount: register.gauge({ name: 'gossipsub_rpc_sent_count_total', help: 'RPC sent' }), rpcSentSubscription: register.gauge({ name: 'gossipsub_rpc_sent_subscription_total', help: 'RPC sent' }), rpcSentMessage: register.gauge({ name: 'gossipsub_rpc_sent_message_total', help: 'RPC sent' }), rpcSentControl: register.gauge({ name: 'gossipsub_rpc_sent_control_total', help: 'RPC sent' }), rpcSentIHave: register.gauge({ name: 'gossipsub_rpc_sent_ihave_total', help: 'RPC sent' }), rpcSentIWant: register.gauge({ name: 'gossipsub_rpc_sent_iwant_total', help: 'RPC sent' }), rpcSentGraft: register.gauge({ name: 'gossipsub_rpc_sent_graft_total', help: 'RPC sent' }), rpcSentPrune: register.gauge({ name: 'gossipsub_rpc_sent_prune_total', help: 'RPC sent' }), rpcSentIDontWant: register.gauge({ name: 'gossipsub_rpc_sent_idontwant_total', help: 'RPC sent' }), // publish message. Track peers sent to and bytes /** Total count of msg published by topic */ msgPublishCount: register.gauge({ name: 'gossipsub_msg_publish_count_total', help: 'Total count of msg published by topic', labelNames: ['topic'] }), /** Total count of peers that we publish a msg to */ msgPublishPeersByTopic: register.gauge({ name: 'gossipsub_msg_publish_peers_total', help: 'Total count of peers that we publish a msg to', labelNames: ['topic'] }), /** Total count of peers (by group) that we publish a msg to */ directPeersPublishedTotal: register.gauge({ name: 'gossipsub_direct_peers_published_total', help: 'Total direct peers that we publish a msg to', labelNames: ['topic'] }), floodsubPeersPublishedTotal: register.gauge({ name: 'gossipsub_floodsub_peers_published_total', help: 'Total floodsub peers that we publish a msg to', labelNames: ['topic'] }), meshPeersPublishedTotal: register.gauge({ name: 'gossipsub_mesh_peers_published_total', help: 'Total mesh peers that we publish a msg to', labelNames: ['topic'] }), fanoutPeersPublishedTotal: register.gauge({ name: 'gossipsub_fanout_peers_published_total', help: 'Total fanout peers that we publish a msg to', labelNames: ['topic'] }), /** Total count of msg publish data.length bytes */ msgPublishBytes: register.gauge({ name: 'gossipsub_msg_publish_bytes_total', help: 'Total count of msg publish data.length bytes', labelNames: ['topic'] }), /** Total time in seconds to publish a message */ msgPublishTime: register.histogram({ name: 'gossipsub_msg_publish_seconds', help: 'Total time in seconds to publish a message', buckets: [0.001, 0.002, 0.005, 0.01, 0.1, 0.5, 1], labelNames: ['topic'] }), /** Total count of msg forwarded by topic */ msgForwardCount: register.gauge({ name: 'gossipsub_msg_forward_count_total', help: 'Total count of msg forwarded by topic', labelNames: ['topic'] }), /** Total count of peers that we forward a msg to */ msgForwardPeers: register.gauge({ name: 'gossipsub_msg_forward_peers_total', help: 'Total count of peers that we forward a msg to', labelNames: ['topic'] }), /** Total count of recv msgs before any validation */ msgReceivedPreValidation: register.gauge({ name: 'gossipsub_msg_received_prevalidation_total', help: 'Total count of recv msgs before any validation', labelNames: ['topic'] }), /** Total count of recv msgs error */ msgReceivedError: register.gauge({ name: 'gossipsub_msg_received_error_total', help: 'Total count of recv msgs error', labelNames: ['topic'] }), /** Tracks distribution of recv msgs by duplicate, invalid, valid */ prevalidationInvalidTotal: register.gauge({ name: 'gossipsub_pre_validation_invalid_total', help: 'Total count of invalid messages received', labelNames: ['topic'] }), prevalidationValidTotal: register.gauge({ name: 'gossipsub_pre_validation_valid_total', help: 'Total count of valid messages received', labelNames: ['topic'] }), prevalidationDuplicateTotal: register.gauge({ name: 'gossipsub_pre_validation_duplicate_total', help: 'Total count of duplicate messages received', labelNames: ['topic'] }), prevalidationUnknownTotal: register.gauge({ name: 'gossipsub_pre_validation_unknown_status_total', help: 'Total count of unknown_status messages received', labelNames: ['topic'] }), /** Tracks specific reason of invalid */ msgReceivedInvalid: register.gauge({ name: 'gossipsub_msg_received_invalid_total', help: 'Tracks specific reason of invalid', labelNames: ['error'] }), msgReceivedInvalidByTopic: register.gauge({ name: 'gossipsub_msg_received_invalid_by_topic_total', help: 'Tracks specific invalid message by topic', labelNames: ['topic'] }), /** Track duplicate message delivery time */ duplicateMsgDeliveryDelay: register.histogram({ name: 'gossisub_duplicate_msg_delivery_delay_seconds', help: 'Time since the 1st duplicated message validated', labelNames: ['topic'], buckets: [ 0.25 * opts.maxMeshMessageDeliveriesWindowSec, 0.5 * opts.maxMeshMessageDeliveriesWindowSec, Number(opts.maxMeshMessageDeliveriesWindowSec), 2 * opts.maxMeshMessageDeliveriesWindowSec, 4 * opts.maxMeshMessageDeliveriesWindowSec ] }), /** Total count of late msg delivery total by topic */ duplicateMsgLateDelivery: register.gauge({ name: 'gossisub_duplicate_msg_late_delivery_total', help: 'Total count of late duplicate message delivery by topic, which triggers P3 penalty', labelNames: ['topic'] }), duplicateMsgIgnored: register.gauge({ name: 'gossisub_ignored_published_duplicate_msgs_total', help: 'Total count of published duplicate message ignored by topic', labelNames: ['topic'] }), /* Metrics related to scoring */ /** Total times score() is called */ scoreFnCalls: register.gauge({ name: 'gossipsub_score_fn_calls_total', help: 'Total times score() is called' }), /** Total times score() call actually computed computeScore(), no cache */ scoreFnRuns: register.gauge({ name: 'gossipsub_score_fn_runs_total', help: 'Total times score() call actually computed computeScore(), no cache' }), scoreCachedDelta: register.histogram({ name: 'gossipsub_score_cache_delta', help: 'Delta of score between cached values that expired', buckets: [10, 100, 1000] }), /** Current count of peers by score threshold */ peersByScoreThreshold: register.gauge({ name: 'gossipsub_peers_by_score_threshold_count', help: 'Current count of peers by score threshold', labelNames: ['threshold'] }), score: register.avgMinMax({ name: 'gossipsub_score', help: 'Avg min max of gossip scores' }), /** * Separate score weights * Need to use 2-label metrics in this case to debug the score weights **/ scoreWeights: register.avgMinMax({ name: 'gossipsub_score_weights', help: 'Separate score weights', labelNames: ['topic', 'p'] }), /** Histogram of the scores for each mesh topic. */ // TODO: Not implemented scorePerMesh: register.avgMinMax({ name: 'gossipsub_score_per_mesh', help: 'Histogram of the scores for each mesh topic', labelNames: ['topic'] }), /** A counter of the kind of penalties being applied to peers. */ // TODO: Not fully implemented scoringPenalties: register.gauge({ name: 'gossipsub_scoring_penalties_total', help: 'A counter of the kind of penalties being applied to peers', labelNames: ['penalty'] }), behaviourPenalty: register.histogram({ name: 'gossipsub_peer_stat_behaviour_penalty', help: 'Current peer stat behaviour_penalty at each scrape', buckets: [ 0.25 * opts.behaviourPenaltyThreshold, 0.5 * opts.behaviourPenaltyThreshold, Number(opts.behaviourPenaltyThreshold), 2 * opts.behaviourPenaltyThreshold, 4 * opts.behaviourPenaltyThreshold ] }), // TODO: // - iasked per peer (on heartbeat) // - when promise is resolved, track messages from promises /** Total received IHAVE messages that we ignore for some reason */ ihaveRcvIgnored: register.gauge({ name: 'gossipsub_ihave_rcv_ignored_total', help: 'Total received IHAVE messages that we ignore for some reason', labelNames: ['reason'] }), /** Total received IHAVE messages by topic */ ihaveRcvMsgids: register.gauge({ name: 'gossipsub_ihave_rcv_msgids_total', help: 'Total received IHAVE messages by topic', labelNames: ['topic'] }), /** * Total messages per topic we don't have. Not actual requests. * The number of times we have decided that an IWANT control message is required for this * topic. A very high metric might indicate an underperforming network. * = rust-libp2p `topic_iwant_msgs` */ ihaveRcvNotSeenMsgids: register.gauge({ name: 'gossipsub_ihave_rcv_not_seen_msgids_total', help: 'Total messages per topic we do not have, not actual requests', labelNames: ['topic'] }), /** Total received IWANT messages by topic */ iwantRcvMsgids: register.gauge({ name: 'gossipsub_iwant_rcv_msgids_total', help: 'Total received IWANT messages by topic', labelNames: ['topic'] }), /** Total requested messageIDs that we don't have */ iwantRcvDonthaveMsgids: register.gauge({ name: 'gossipsub_iwant_rcv_dont_have_msgids_total', help: 'Total requested messageIDs that we do not have' }), /** Total received IDONTWANT messages */ idontwantRcvMsgids: register.gauge({ name: 'gossipsub_idontwant_rcv_msgids_total', help: 'Total received IDONTWANT messages' }), /** Total received IDONTWANT messageIDs that we don't have */ idontwantRcvDonthaveMsgids: register.gauge({ name: 'gossipsub_idontwant_rcv_dont_have_msgids_total', help: 'Total received IDONTWANT messageIDs that we do not have in mcache' }), iwantPromiseStarted: register.gauge({ name: 'gossipsub_iwant_promise_sent_total', help: 'Total count of started IWANT promises' }), /** Total count of resolved IWANT promises */ iwantPromiseResolved: register.gauge({ name: 'gossipsub_iwant_promise_resolved_total', help: 'Total count of resolved IWANT promises' }), /** Total count of resolved IWANT promises from duplicate messages */ iwantPromiseResolvedFromDuplicate: register.gauge({ name: 'gossipsub_iwant_promise_resolved_from_duplicate_total', help: 'Total count of resolved IWANT promises from duplicate messages' }), /** Total count of peers we have asked IWANT promises that are resolved */ iwantPromiseResolvedPeers: register.gauge({ name: 'gossipsub_iwant_promise_resolved_peers', help: 'Total count of peers we have asked IWANT promises that are resolved' }), iwantPromiseBroken: register.gauge({ name: 'gossipsub_iwant_promise_broken', help: 'Total count of broken IWANT promises' }), iwantMessagePruned: register.gauge({ name: 'gossipsub_iwant_message_pruned', help: 'Total count of pruned IWANT messages' }), /** Histogram of delivery time of resolved IWANT promises */ iwantPromiseDeliveryTime: register.histogram({ name: 'gossipsub_iwant_promise_delivery_seconds', help: 'Histogram of delivery time of resolved IWANT promises', buckets: [ 0.5 * opts.gossipPromiseExpireSec, Number(opts.gossipPromiseExpireSec), 2 * opts.gossipPromiseExpireSec, 4 * opts.gossipPromiseExpireSec ] }), iwantPromiseUntracked: register.gauge({ name: 'gossip_iwant_promise_untracked', help: 'Total count of untracked IWANT promise' }), /** Backoff time */ connectedPeersBackoffSec: register.histogram({ name: 'gossipsub_connected_peers_backoff_seconds', help: 'Backoff time in seconds', // Using 1 seconds as minimum as that's close to the heartbeat duration, no need for more resolution. // As per spec, backoff times are 10 seconds for UnsubscribeBackoff and 60 seconds for PruneBackoff. // Higher values of 60 seconds should not occur, but we add 120 seconds just in case // https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#overview-of-new-parameters buckets: [1, 2, 4, 10, 20, 60, 120] }), /* Data structure sizes */ /** Unbounded cache sizes */ cacheSize: register.gauge({ name: 'gossipsub_cache_size', help: 'Unbounded cache sizes', labelNames: ['cache'] }), /** Current mcache msg count */ mcacheSize: register.gauge({ name: 'gossipsub_mcache_size', help: 'Current mcache msg count' }), mcacheNotValidatedCount: register.gauge({ name: 'gossipsub_mcache_not_validated_count', help: 'Current mcache msg count not validated' }), fastMsgIdCacheCollision: register.gauge({ name: 'gossipsub_fastmsgid_cache_collision_total', help: 'Total count of key collisions on fastmsgid cache put' }), newConnectionCount: register.gauge({ name: 'gossipsub_new_connection_total', help: 'Total new connection by status', labelNames: ['status'] }), topicStrToLabel, toTopic(topicStr) { return this.topicStrToLabel.get(topicStr) ?? topicStr; }, /** We joined a topic */ onJoin(topicStr) { this.topicSubscriptionStatus.set({ topicStr }, 1); this.meshPeerCounts.set({ topicStr }, 0); // Reset count }, /** We left a topic */ onLeave(topicStr) { this.topicSubscriptionStatus.set({ topicStr }, 0); this.meshPeerCounts.set({ topicStr }, 0); // Reset count }, /** Register the inclusion of peers in our mesh due to some reason. */ onAddToMesh(topicStr, reason, count) { const topic = this.toTopic(topicStr); switch (reason) { case InclusionReason.Fanout: this.meshPeerInclusionEventsFanout.inc({ topic }, count); break; case InclusionReason.Random: this.meshPeerInclusionEventsRandom.inc({ topic }, count); break; case InclusionReason.Subscribed: this.meshPeerInclusionEventsSubscribed.inc({ topic }, count); break; case InclusionReason.Outbound: this.meshPeerInclusionEventsOutbound.inc({ topic }, count); break; case InclusionReason.NotEnough: this.meshPeerInclusionEventsNotEnough.inc({ topic }, count); break; case InclusionReason.Opportunistic: this.meshPeerInclusionEventsOpportunistic.inc({ topic }, count); break; default: this.meshPeerInclusionEventsUnknown.inc({ topic }, count); break; } }, /** Register the removal of peers in our mesh due to some reason */ // - remove_peer_from_mesh() // - heartbeat() Churn::BadScore // - heartbeat() Churn::Excess // - on_disconnect() Churn::Ds onRemoveFromMesh(topicStr, reason, count) { const topic = this.toTopic(topicStr); switch (reason) { case ChurnReason.Dc: this.meshPeerChurnEventsDisconnected.inc({ topic }, count); break; case ChurnReason.BadScore: this.meshPeerChurnEventsBadScore.inc({ topic }, count); break; case ChurnReason.Prune: this.meshPeerChurnEventsPrune.inc({ topic }, count); break; case ChurnReason.Excess: this.meshPeerChurnEventsExcess.inc({ topic }, count); break; default: this.meshPeerChurnEventsUnknown.inc({ topic }, count); break; } }, /** * Update validation result to metrics * * @param messageRecord - null means the message's mcache record was not known at the time of acceptance report */ onReportValidation(messageRecord, acceptance, firstSeenTimestampMs) { this.asyncValidationMcacheHit.inc({ hit: messageRecord != null ? 'hit' : 'miss' }); if (messageRecord != null) { const topic = this.toTopic(messageRecord.message.topic); switch (acceptance) { case TopicValidatorResult.Accept: this.acceptedMessagesTotal.inc({ topic }); break; case TopicValidatorResult.Ignore: this.ignoredMessagesTotal.inc({ topic }); break; case TopicValidatorResult.Reject: this.rejectedMessagesTotal.inc({ topic }); break; default: this.unknownValidationResultsTotal.inc({ topic }); break; } } if (firstSeenTimestampMs != null) { this.asyncValidationDelayFromFirstSeenSec.observe((Date.now() - firstSeenTimestampMs) / 1000); } else { this.asyncValidationUnknownFirstSeen.inc(); } }, /** * - in handle_graft() Penalty::GraftBackoff * - in apply_iwant_penalties() Penalty::BrokenPromise * - in metric_score() P3 Penalty::MessageDeficit * - in metric_score() P6 Penalty::IPColocation */ onScorePenalty(penalty) { // Can this be labeled by topic too? this.scoringPenalties.inc({ penalty }, 1); }, onIhaveRcv(topicStr, ihave, idonthave) { const topic = this.toTopic(topicStr); this.ihaveRcvMsgids.inc({ topic }, ihave); this.ihaveRcvNotSeenMsgids.inc({ topic }, idonthave); }, onIwantRcv(iwantByTopic, iwantDonthave) { for (const [topicStr, iwant] of iwantByTopic) { const topic = this.toTopic(topicStr); this.iwantRcvMsgids.inc({ topic }, iwant); } this.iwantRcvDonthaveMsgids.inc(iwantDonthave); }, onIdontwantRcv(idontwant, idontwantDonthave) { this.idontwantRcvMsgids.inc(idontwant); this.idontwantRcvDonthaveMsgids.inc(idontwantDonthave); }, onForwardMsg(topicStr, tosendCount) { const topic = this.toTopic(topicStr); this.msgForwardCount.inc({ topic }, 1); this.msgForwardPeers.inc({ topic }, tosendCount); }, onPublishMsg(topicStr, tosendGroupCount, tosendCount, dataLen, ms) { const topic = this.toTopic(topicStr); this.msgPublishCount.inc({ topic }, 1); this.msgPublishBytes.inc({ topic }, tosendCount * dataLen); this.msgPublishPeersByTopic.inc({ topic }, tosendCount); this.directPeersPublishedTotal.inc({ topic }, tosendGroupCount.direct); this.floodsubPeersPublishedTotal.inc({ topic }, tosendGroupCount.floodsub); this.meshPeersPublishedTotal.inc({ topic }, tosendGroupCount.mesh); this.fanoutPeersPublishedTotal.inc({ topic }, tosendGroupCount.fanout); this.msgPublishTime.observe({ topic }, ms / 1000); }, onMsgRecvPreValidation(topicStr) { const topic = this.toTopic(topicStr); this.msgReceivedPreValidation.inc({ topic }, 1); }, onMsgRecvError(topicStr) { const topic = this.toTopic(topicStr); this.msgReceivedError.inc({ topic }, 1); }, onPrevalidationResult(topicStr, status) { const topic = this.toTopic(topicStr); switch (status) { case MessageStatus.duplicate: this.prevalidationDuplicateTotal.inc({ topic }); break; case MessageStatus.invalid: this.prevalidationInvalidTotal.inc({ topic }); break; case MessageStatus.valid: this.prevalidationValidTotal.inc({ topic }); break; default: this.prevalidationUnknownTotal.inc({ topic }); break; } }, onMsgRecvInvalid(topicStr, reason) { const topic = this.toTopic(topicStr); const error = reason.reason === RejectReason.Error ? reason.error : reason.reason; this.msgReceivedInvalid.inc({ error }, 1); this.msgReceivedInvalidByTopic.inc({ topic }, 1); }, onDuplicateMsgDelivery(topicStr, deliveryDelayMs, isLateDelivery) { const topic = this.toTopic(topicStr); this.duplicateMsgDeliveryDelay.observe({ topic }, deliveryDelayMs / 1000); if (isLateDelivery) { this.duplicateMsgLateDelivery.inc({ topic }, 1); } }, onPublishDuplicateMsg(topicStr) { const topic = this.toTopic(topicStr); this.duplicateMsgIgnored.inc({ topic }, 1); }, onPeerReadStreamError() { this.peerReadStreamError.inc(1); }, onRpcRecvError() { this.rpcRecvError.inc(1); }, onRpcDataError() { this.rpcDataError.inc(1); }, onRpcRecv(rpc, rpcBytes) { this.rpcRecvBytes.inc(rpcBytes); this.rpcRecvCount.inc(1); if (rpc.subscriptions != null) this.rpcRecvSubscription.inc(rpc.subscriptions.length); if (rpc.messages != null) this.rpcRecvMessage.inc(rpc.messages.length); if (rpc.control != null) { this.rpcRecvControl.inc(1); if (rpc.control.ihave != null) this.rpcRecvIHave.inc(rpc.control.ihave.length); if (rpc.control.iwant != null) this.rpcRecvIWant.inc(rpc.control.iwant.length); if (rpc.control.graft != null) this.rpcRecvGraft.inc(rpc.control.graft.length); if (rpc.control.prune != null) this.rpcRecvPrune.inc(rpc.control.prune.length); } }, onRpcSent(rpc, rpcBytes) { this.rpcSentBytes.inc(rpcBytes); this.rpcSentCount.inc(1); if (rpc.subscriptions != null) this.rpcSentSubscription.inc(rpc.subscriptions.length); if (rpc.messages != null) this.rpcSentMessage.inc(rpc.messages.length); if (rpc.control != null) { const ihave = rpc.control.ihave?.length ?? 0; const iwant = rpc.control.iwant?.length ?? 0; const graft = rpc.control.graft?.length ?? 0; const prune = rpc.control.prune?.length ?? 0; const idontwant = rpc.control.idontwant?.length ?? 0; if (ihave > 0) this.rpcSentIHave.inc(ihave); if (iwant > 0) this.rpcSentIWant.inc(iwant); if (graft > 0) this.rpcSentGraft.inc(graft); if (prune > 0) this.rpcSentPrune.inc(prune); if (idontwant > 0) this.rpcSentIDontWant.inc(idontwant); if (ihave > 0 || iwant > 0 || graft > 0 || prune > 0 || idontwant > 0) this.rpcSentControl.inc(1); } }, registerScores(scores, scoreThresholds) { let graylist = 0; let publish = 0; let gossip = 0; let mesh = 0; for (const score of scores) { if (score >= scoreThresholds.graylistThreshold) graylist++; if (score >= scoreThresholds.publishThreshold) publish++; if (score >= scoreThresholds.gossipThreshold) gossip++; if (score >= 0) mesh++; } this.peersByScoreThreshold.set({ threshold: ScoreThreshold.graylist }, graylist); this.peersByScoreThreshold.set({ threshold: ScoreThreshold.publish }, publish); this.peersByScoreThreshold.set({ threshold: ScoreThreshold.gossip }, gossip); this.peersByScoreThreshold.set({ threshold: ScoreThreshold.mesh }, mesh); // Register full score too this.score.set(scores); }, registerScoreWeights(sw) { for (const [topic, wsTopic] of sw.byTopic) { this.scoreWeights.set({ topic, p: 'p1' }, wsTopic.p1w); this.scoreWeights.set({ topic, p: 'p2' }, wsTopic.p2w); this.scoreWeights.set({ topic, p: 'p3' }, wsTopic.p3w); this.scoreWeights.set({ topic, p: 'p3b' }, wsTopic.p3bw); this.scoreWeights.set({ topic, p: 'p4' }, wsTopic.p4w); } this.scoreWeights.set({ p: 'p5' }, sw.p5w); this.scoreWeights.set({ p: 'p6' }, sw.p6w); this.scoreWeights.set({ p: 'p7' }, sw.p7w); }, registerScorePerMesh(mesh, scoreByPeer) { const peersPerTopicLabel = new Map(); mesh.forEach((peers, topicStr) => { // Aggregate by known topicLabel or throw to 'unknown'. This prevent too high cardinality const topicLabel = this.topicStrToLabel.get(topicStr) ?? 'unknown'; let peersInMesh = peersPerTopicLabel.get(topicLabel); if (peersInMesh == null) { peersInMesh = new Set(); peersPerTopicLabel.set(topicLabel, peersInMesh); } peers.forEach((p) => peersInMesh?.add(p)); }); for (const [topic, peers] of peersPerTopicLabel) { const meshScores = []; peers.forEach((peer) => { meshScores.push(scoreByPeer.get(peer) ?? 0); }); this.scorePerMesh.set({ topic }, meshScores); } } }; } //# sourceMappingURL=metrics.js.map