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