import { createHistogram } from 'perf_hooks';
/** Metrics class for proving-related benchmarks. */ export class ProvingMetrics {
    prefix;
    successfulTxs;
    proofDuration;
    activeAgents;
    avgQueueTime;
    jobRetries;
    jobDuration;
    timedOutJobs;
    resolvedJobs;
    rejectedJobs;
    epochProvingDuration;
    provenTransactions;
    provenBlocks;
    constructor(prefix){
        this.prefix = prefix;
    }
    recordSuccessfulTxs(count) {
        this.successfulTxs = count;
    }
    recordProofDuration(seconds) {
        this.proofDuration = seconds;
    }
    recordActiveAgents(count) {
        this.activeAgents = count;
    }
    recordAvgQueueTime(ms) {
        this.avgQueueTime = ms;
    }
    recordJobRetries(count) {
        this.jobRetries = count;
    }
    recordJobDuration(ms) {
        this.jobDuration = ms;
    }
    recordTimedOutJobs(count) {
        this.timedOutJobs = count;
    }
    recordResolvedJobs(count) {
        this.resolvedJobs = count;
    }
    recordRejectedJobs(count) {
        this.rejectedJobs = count;
    }
    recordEpochProvingDuration(seconds) {
        this.epochProvingDuration = seconds;
    }
    recordProvenTransactions(count) {
        this.provenTransactions = count;
    }
    recordProvenBlocks(count) {
        this.provenBlocks = count;
    }
    toGithubActionBenchmarkJSON() {
        const data = [];
        if (this.successfulTxs !== undefined) {
            data.push({
                name: `${this.prefix}/successful_txs`,
                unit: 'count',
                value: this.successfulTxs
            });
        }
        if (this.proofDuration !== undefined) {
            data.push({
                name: `${this.prefix}/proof_duration`,
                unit: 's',
                value: this.proofDuration
            });
        }
        if (this.activeAgents !== undefined) {
            data.push({
                name: `${this.prefix}/active_agents`,
                unit: 'count',
                value: this.activeAgents
            });
        }
        if (this.avgQueueTime !== undefined) {
            data.push({
                name: `${this.prefix}/avg_queue_time`,
                unit: 'ms',
                value: this.avgQueueTime
            });
        }
        if (this.jobRetries !== undefined) {
            data.push({
                name: `${this.prefix}/job_retries`,
                unit: 'count',
                value: this.jobRetries
            });
        }
        if (this.jobDuration !== undefined) {
            data.push({
                name: `${this.prefix}/job_duration`,
                unit: 'ms',
                value: this.jobDuration
            });
        }
        if (this.timedOutJobs !== undefined) {
            data.push({
                name: `${this.prefix}/timed_out_jobs`,
                unit: 'count',
                value: this.timedOutJobs
            });
        }
        if (this.resolvedJobs !== undefined) {
            data.push({
                name: `${this.prefix}/resolved_jobs`,
                unit: 'count',
                value: this.resolvedJobs
            });
        }
        if (this.rejectedJobs !== undefined) {
            data.push({
                name: `${this.prefix}/rejected_jobs`,
                unit: 'count',
                value: this.rejectedJobs
            });
        }
        if (this.epochProvingDuration !== undefined) {
            data.push({
                name: `${this.prefix}/epoch_proving_duration`,
                unit: 's',
                value: this.epochProvingDuration
            });
        }
        if (this.provenTransactions !== undefined) {
            data.push({
                name: `${this.prefix}/proven_transactions`,
                unit: 'count',
                value: this.provenTransactions
            });
        }
        if (this.provenBlocks !== undefined) {
            data.push({
                name: `${this.prefix}/proven_blocks`,
                unit: 'count',
                value: this.provenBlocks
            });
        }
        const scenario = process.env.BENCH_SCENARIO?.trim();
        if (!scenario) {
            return data;
        }
        const scenarioPrefix = `scenario/${scenario}/`;
        return data.map((entry)=>({
                ...entry,
                name: `${scenarioPrefix}${entry.name}`
            }));
    }
}
export class TxInclusionMetrics {
    aztecNode;
    logger;
    data;
    groups;
    blocks;
    p2pGossipLatencyByTopic;
    attestationLatency;
    attestationCounts;
    reqRespStats;
    peerStats;
    mempoolMinedDelay;
    constructor(aztecNode, logger){
        this.aztecNode = aztecNode;
        this.logger = logger;
        this.data = new Map();
        this.groups = new Set();
        this.blocks = new Map();
        this.p2pGossipLatencyByTopic = {};
    }
    recordSentTx(tx, group) {
        const txHash = tx.getTxHash().toString();
        const priorityFees = tx.getGasSettings().maxPriorityFeesPerGas;
        if (this.data.has(txHash)) {
            this.logger?.debug(`Overwriting tx inclusion data for ${txHash}`, {
                txHash,
                group
            });
        }
        this.data.set(txHash, {
            txHash,
            sentAt: Math.trunc(Date.now() / 1000),
            minedAt: -1,
            attestedAt: -1,
            blocknumber: -1,
            priorityFee: Number(priorityFees.feePerDaGas + priorityFees.feePerL2Gas),
            totalFee: -1,
            positionInBlock: -1,
            group
        });
        this.groups.add(group);
    }
    async recordMinedTx(txReceipt) {
        const { txHash, blockNumber } = txReceipt;
        if (!txReceipt.isMined() || !txReceipt.hasExecutionSucceeded() || !blockNumber) {
            this.logger?.debug('Skipping mined tx record due to receipt status', {
                txHash: txHash.toString(),
                status: txReceipt.status,
                blockNumber
            });
            return;
        }
        if (!this.blocks.has(blockNumber)) {
            this.blocks.set(blockNumber, this.aztecNode.getBlock(blockNumber));
        }
        const block = await this.blocks.get(blockNumber);
        if (!block) {
            this.logger?.warn('Failed to load block for mined tx receipt', {
                txHash: txHash.toString(),
                blockNumber
            });
            return;
        }
        const data = this.data.get(txHash.toString());
        if (!data) {
            const message = `Missing sent tx record for mined tx ${txHash.toString()}`;
            this.logger?.warn(message, {
                txHash: txHash.toString(),
                blockNumber
            });
            throw new Error(message);
        }
        data.blocknumber = blockNumber;
        data.minedAt = Number(block.header.globalVariables.timestamp);
        data.attestedAt = -1;
        data.totalFee = Number(txReceipt.transactionFee ?? 0n);
        data.positionInBlock = block.body.txEffects.findIndex((txEffect)=>txEffect.txHash.equals(txHash));
    }
    inclusionTimeInSeconds(group) {
        const histogram = createHistogram({});
        for (const tx of this.data.values()){
            if (!tx.blocknumber || tx.group !== group || tx.minedAt === -1) {
                continue;
            }
            histogram.record(tx.minedAt - tx.sentAt);
        }
        if (histogram.count === 0) {
            return {
                group,
                count: 0,
                mean: 0,
                max: 0,
                median: 0,
                min: 0,
                p99: 0
            };
        }
        return {
            group,
            count: histogram.count,
            mean: histogram.mean,
            max: histogram.max,
            median: histogram.percentile(50),
            min: histogram.min,
            p99: histogram.percentile(99)
        };
    }
    recordP2PGossipLatency(topicName, p50, p95) {
        this.p2pGossipLatencyByTopic[topicName] = {
            p50,
            p95
        };
    }
    recordAttestationLatency(p50, p95) {
        this.attestationLatency = {
            p50,
            p95
        };
    }
    recordAttestationCounts(success, failedBad, failedNode) {
        this.attestationCounts = {
            success,
            failedBad,
            failedNode
        };
    }
    recordReqRespStats(fraction, delayP50, delayP95) {
        this.reqRespStats = {
            fraction,
            delayP50,
            delayP95
        };
    }
    recordPeerStats(avgCount, connectionDurationP50, connectionDurationP95) {
        this.peerStats = {
            avgCount,
            connectionDurationP50,
            connectionDurationP95
        };
    }
    recordMempoolMinedDelay(txP50, txP95, attestationP50, attestationP95) {
        this.mempoolMinedDelay = {
            txP50,
            txP95,
            attestationP50,
            attestationP95
        };
    }
    toGithubActionBenchmarkJSON() {
        const data = [];
        for (const group of this.groups){
            const stats = this.inclusionTimeInSeconds(group);
            data.push({
                name: `${group}/avg_inclusion`,
                unit: 's',
                value: stats.mean
            }, {
                name: `${group}/median_inclusion`,
                unit: 's',
                value: stats.median
            }, {
                name: `${group}/p99_inclusion`,
                unit: 's',
                value: stats.p99
            });
        }
        for (const [topic, { p50, p95 }] of Object.entries(this.p2pGossipLatencyByTopic)){
            data.push({
                name: `p2p_gossip_latency/${topic}/p50`,
                unit: 'ms',
                value: p50
            });
            data.push({
                name: `p2p_gossip_latency/${topic}/p95`,
                unit: 'ms',
                value: p95
            });
        }
        if (this.attestationLatency) {
            data.push({
                name: 'attestation_latency/p50',
                unit: 'ms',
                value: this.attestationLatency.p50
            }, {
                name: 'attestation_latency/p95',
                unit: 'ms',
                value: this.attestationLatency.p95
            });
        }
        if (this.attestationCounts) {
            const { success, failedBad, failedNode } = this.attestationCounts;
            const total = success + failedBad + failedNode;
            const ratio = total > 0 ? success / total : 0;
            data.push({
                name: 'attestation/success_count',
                unit: 'count',
                value: success
            }, {
                name: 'attestation/failed_bad_proposal_count',
                unit: 'count',
                value: failedBad
            }, {
                name: 'attestation/failed_node_issue_count',
                unit: 'count',
                value: failedNode
            }, {
                name: 'attestation/success_ratio',
                unit: 'ratio',
                value: ratio
            });
        }
        if (this.reqRespStats) {
            data.push({
                name: 'req_resp/txs_requested_fraction',
                unit: 'ratio',
                value: this.reqRespStats.fraction
            }, {
                name: 'req_resp/delay_p50',
                unit: 'ms',
                value: this.reqRespStats.delayP50
            }, {
                name: 'req_resp/delay_p95',
                unit: 'ms',
                value: this.reqRespStats.delayP95
            });
        }
        if (this.peerStats) {
            data.push({
                name: 'peers/avg_count',
                unit: 'peers',
                value: this.peerStats.avgCount
            }, {
                name: 'peers/connection_duration_p50',
                unit: 'ms',
                value: this.peerStats.connectionDurationP50
            }, {
                name: 'peers/connection_duration_p95',
                unit: 'ms',
                value: this.peerStats.connectionDurationP95
            });
        }
        if (this.mempoolMinedDelay) {
            data.push({
                name: 'mempool/tx_mined_delay_p50',
                unit: 'ms',
                value: this.mempoolMinedDelay.txP50
            }, {
                name: 'mempool/tx_mined_delay_p95',
                unit: 'ms',
                value: this.mempoolMinedDelay.txP95
            }, {
                name: 'mempool/attestation_mined_delay_p50',
                unit: 'ms',
                value: this.mempoolMinedDelay.attestationP50
            }, {
                name: 'mempool/attestation_mined_delay_p95',
                unit: 'ms',
                value: this.mempoolMinedDelay.attestationP95
            });
        }
        const scenario = process.env.BENCH_SCENARIO?.trim();
        if (!scenario) {
            return data;
        }
        const scenarioPrefix = `scenario/${scenario}/`;
        return data.map((entry)=>({
                ...entry,
                name: `${scenarioPrefix}${entry.name}`
            }));
    }
}
