diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index ab59cca54207..049e87733226 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -294,6 +294,7 @@ export class Archiver implements ArchiveSource, Traceable { `to ${provenBlockNumber} due to predicted reorg at L1 block ${currentL1BlockNumber}. ` + `Updated L2 latest block is ${await this.getBlockNumber()}.`, ); + this.instrumentation.processPrune(); // TODO(palla/reorg): Do we need to set the block synched L1 block number here? // Seems like the next iteration should handle this. // await this.store.setBlockSynchedL1BlockNumber(currentL1BlockNumber); diff --git a/yarn-project/archiver/src/archiver/instrumentation.ts b/yarn-project/archiver/src/archiver/instrumentation.ts index 716f2948d0a1..f9b921df5809 100644 --- a/yarn-project/archiver/src/archiver/instrumentation.ts +++ b/yarn-project/archiver/src/archiver/instrumentation.ts @@ -23,6 +23,7 @@ export class ArchiverInstrumentation { private proofsSubmittedDelay: Histogram; private proofsSubmittedCount: UpDownCounter; private dbMetrics: LmdbMetrics; + private pruneCount: UpDownCounter; private log = createLogger('archiver:instrumentation'); @@ -68,6 +69,11 @@ export class ArchiverInstrumentation { }, lmdbStats, ); + + this.pruneCount = meter.createUpDownCounter(Metrics.ARCHIVER_PRUNE_COUNT, { + description: 'Number of prunes detected', + valueType: ValueType.INT, + }); } public static async new(telemetry: TelemetryClient, lmdbStats?: LmdbStatsCallback) { @@ -93,6 +99,10 @@ export class ArchiverInstrumentation { } } + public processPrune() { + this.pruneCount.add(1); + } + public updateLastProvenBlock(blockNumber: number) { this.blockHeight.record(blockNumber, { [Attributes.STATUS]: 'proven' }); } diff --git a/yarn-project/aztec.js/src/contract/get_gas_limits.test.ts b/yarn-project/aztec.js/src/contract/get_gas_limits.test.ts index f42e45a06551..a4f64ba896da 100644 --- a/yarn-project/aztec.js/src/contract/get_gas_limits.test.ts +++ b/yarn-project/aztec.js/src/contract/get_gas_limits.test.ts @@ -16,6 +16,7 @@ describe('getGasLimits', () => { txSimulationResult.publicOutput!.gasUsed = { totalGas: Gas.from({ daGas: 140, l2Gas: 280 }), teardownGas: Gas.from({ daGas: 10, l2Gas: 20 }), + publicGas: Gas.from({ daGas: 50, l2Gas: 200 }), }; }); diff --git a/yarn-project/aztec.js/src/contract/get_gas_limits.ts b/yarn-project/aztec.js/src/contract/get_gas_limits.ts index a6e0e7196fbd..37561490c682 100644 --- a/yarn-project/aztec.js/src/contract/get_gas_limits.ts +++ b/yarn-project/aztec.js/src/contract/get_gas_limits.ts @@ -1,11 +1,24 @@ -import { type GasUsed, type TxSimulationResult } from '@aztec/circuit-types'; +import { type TxSimulationResult } from '@aztec/circuit-types'; +import { type Gas } from '@aztec/circuits.js'; /** * Returns suggested total and teardown gas limits for a simulated tx. * Note that public gas usage is only accounted for if the publicOutput is present. * @param pad - Percentage to pad the suggested gas limits by, (as decimal, e.g., 0.10 for 10%). */ -export function getGasLimits(simulationResult: TxSimulationResult, pad = 0.1): GasUsed { +export function getGasLimits( + simulationResult: TxSimulationResult, + pad = 0.1, +): { + /** + * Total gas used across private and public + */ + totalGas: Gas; + /** + * Teardown gas used + */ + teardownGas: Gas; +} { return { totalGas: simulationResult.gasUsed.totalGas.mul(1 + pad), teardownGas: simulationResult.gasUsed.teardownGas.mul(1 + pad), diff --git a/yarn-project/circuit-types/src/mocks.ts b/yarn-project/circuit-types/src/mocks.ts index 4d38c4bc9868..9909b1e179ed 100644 --- a/yarn-project/circuit-types/src/mocks.ts +++ b/yarn-project/circuit-types/src/mocks.ts @@ -166,6 +166,7 @@ export const mockSimulatedTx = (seed = 1) => { { totalGas: makeGas(), teardownGas: makeGas(), + publicGas: makeGas(), }, ); return new TxSimulationResult(privateExecutionResult, tx.data, output); diff --git a/yarn-project/circuit-types/src/stats/stats.ts b/yarn-project/circuit-types/src/stats/stats.ts index 279e70899b26..545a7531e623 100644 --- a/yarn-project/circuit-types/src/stats/stats.ts +++ b/yarn-project/circuit-types/src/stats/stats.ts @@ -36,6 +36,10 @@ export type L1PublishStats = { calldataGas: number; /** Size in bytes of the calldata. */ calldataSize: number; + /** Gas cost of the blob data */ + blobDataGas: bigint; + /** Amount of blob gas used. */ + blobGasUsed: bigint; }; /** Stats logged for each L1 rollup publish tx.*/ diff --git a/yarn-project/circuit-types/src/test/factories.ts b/yarn-project/circuit-types/src/test/factories.ts index 9eb5b7ab46db..487bd5a86364 100644 --- a/yarn-project/circuit-types/src/test/factories.ts +++ b/yarn-project/circuit-types/src/test/factories.ts @@ -26,6 +26,7 @@ import { type MerkleTreeReadOperations } from '../interfaces/merkle_tree_operati import { ProvingRequestType } from '../interfaces/proving-job.js'; import { makeHeader } from '../l2_block_code_to_purge.js'; import { mockTx } from '../mocks.js'; +import { type GasUsed } from '../tx/gas_used.js'; import { makeProcessedTxFromPrivateOnlyTx, makeProcessedTxFromTxWithPublicCalls } from '../tx/processed_tx.js'; /** Makes a bloated processed tx for testing purposes. */ @@ -125,7 +126,8 @@ export function makeBloatedProcessedTx({ const gasUsed = { totalGas: Gas.empty(), teardownGas: Gas.empty(), - }; + publicGas: Gas.empty(), + } satisfies GasUsed; return makeProcessedTxFromTxWithPublicCalls( tx, diff --git a/yarn-project/circuit-types/src/tx/gas_used.ts b/yarn-project/circuit-types/src/tx/gas_used.ts index 2522b74b8635..ceb145467fc1 100644 --- a/yarn-project/circuit-types/src/tx/gas_used.ts +++ b/yarn-project/circuit-types/src/tx/gas_used.ts @@ -7,6 +7,10 @@ export interface GasUsed { * `GasSettings`, rather than actual teardown gas. */ totalGas: Gas; + + /** Total gas used during public execution, including teardown gas */ + publicGas: Gas; + /** * The actual gas used in the teardown phase. */ diff --git a/yarn-project/circuit-types/src/tx/processed_tx.ts b/yarn-project/circuit-types/src/tx/processed_tx.ts index 7006ee42e160..ddbc926647c5 100644 --- a/yarn-project/circuit-types/src/tx/processed_tx.ts +++ b/yarn-project/circuit-types/src/tx/processed_tx.ts @@ -112,6 +112,7 @@ export function makeEmptyProcessedTx( gasUsed: { totalGas: Gas.empty(), teardownGas: Gas.empty(), + publicGas: Gas.empty(), }, revertReason: undefined, isEmpty: true, @@ -148,7 +149,8 @@ export function makeProcessedTxFromPrivateOnlyTx( const gasUsed = { totalGas: tx.data.gasUsed, teardownGas: Gas.empty(), - }; + publicGas: Gas.empty(), + } satisfies GasUsed; return { hash: tx.getTxHash(), diff --git a/yarn-project/circuit-types/src/tx/public_simulation_output.ts b/yarn-project/circuit-types/src/tx/public_simulation_output.ts index 98ca5e8fc604..03b2fb9910d8 100644 --- a/yarn-project/circuit-types/src/tx/public_simulation_output.ts +++ b/yarn-project/circuit-types/src/tx/public_simulation_output.ts @@ -61,7 +61,7 @@ export class PublicSimulationOutput { constants: CombinedConstantData.schema, txEffect: TxEffect.schema, publicReturnValues: z.array(NestedProcessReturnValues.schema), - gasUsed: z.object({ totalGas: Gas.schema, teardownGas: Gas.schema }), + gasUsed: z.object({ totalGas: Gas.schema, teardownGas: Gas.schema, publicGas: Gas.schema }), }) .transform( fields => @@ -81,7 +81,7 @@ export class PublicSimulationOutput { CombinedConstantData.empty(), TxEffect.empty(), times(2, NestedProcessReturnValues.random), - { teardownGas: Gas.random(), totalGas: Gas.random() }, + { teardownGas: Gas.random(), totalGas: Gas.random(), publicGas: Gas.random() }, ); } } diff --git a/yarn-project/circuit-types/src/tx/simulated_tx.ts b/yarn-project/circuit-types/src/tx/simulated_tx.ts index df143b6e092f..5a80dd2244b2 100644 --- a/yarn-project/circuit-types/src/tx/simulated_tx.ts +++ b/yarn-project/circuit-types/src/tx/simulated_tx.ts @@ -61,6 +61,7 @@ export class TxSimulationResult extends PrivateSimulationResult { this.publicOutput?.gasUsed ?? { totalGas: this.publicInputs.gasUsed, teardownGas: Gas.empty(), + publicGas: Gas.empty(), } ); } diff --git a/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts b/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts index f03231342b9d..cd69d1d353f0 100644 --- a/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts +++ b/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts @@ -169,12 +169,7 @@ describe('L1Publisher integration', () => { worldStateDbMapSizeKb: 10 * 1024 * 1024, worldStateBlockHistory: 0, }; - worldStateSynchronizer = new ServerWorldStateSynchronizer( - builderDb, - blockSource, - worldStateConfig, - new NoopTelemetryClient(), - ); + worldStateSynchronizer = new ServerWorldStateSynchronizer(builderDb, blockSource, worldStateConfig); await worldStateSynchronizer.start(); publisher = new L1Publisher( diff --git a/yarn-project/prover-node/src/job/epoch-proving-job.ts b/yarn-project/prover-node/src/job/epoch-proving-job.ts index e1999365c460..06e5d26fa82b 100644 --- a/yarn-project/prover-node/src/job/epoch-proving-job.ts +++ b/yarn-project/prover-node/src/job/epoch-proving-job.ts @@ -71,12 +71,13 @@ export class EpochProvingJob implements Traceable { }) public async run() { const epochNumber = Number(this.epochNumber); - const epochSize = this.blocks.length; + const epochSizeBlocks = this.blocks.length; + const epochSizeTxs = this.blocks.reduce((total, current) => total + current.body.numberOfTxsIncludingPadded, 0); const [fromBlock, toBlock] = [this.blocks[0].number, this.blocks.at(-1)!.number]; this.log.info(`Starting epoch ${epochNumber} proving job with blocks ${fromBlock} to ${toBlock}`, { fromBlock, toBlock, - epochSize, + epochSizeBlocks, epochNumber, uuid: this.uuid, }); @@ -87,7 +88,7 @@ export class EpochProvingJob implements Traceable { this.runPromise = promise; try { - this.prover.startNewEpoch(epochNumber, fromBlock, epochSize); + this.prover.startNewEpoch(epochNumber, fromBlock, epochSizeBlocks); await asyncPool(this.config.parallelBlockLimit, this.blocks, async block => { const globalVariables = block.header.globalVariables; @@ -136,7 +137,7 @@ export class EpochProvingJob implements Traceable { this.log.info(`Submitted proof for epoch`, { epochNumber, uuid: this.uuid }); this.state = 'completed'; - this.metrics.recordProvingJob(timer); + this.metrics.recordProvingJob(timer, epochSizeBlocks, epochSizeTxs); } catch (err) { this.log.error(`Error running epoch ${epochNumber} prover job`, err, { uuid: this.uuid, epochNumber }); this.state = 'failed'; diff --git a/yarn-project/prover-node/src/metrics.ts b/yarn-project/prover-node/src/metrics.ts index f37e12ef2995..98a6cf36608d 100644 --- a/yarn-project/prover-node/src/metrics.ts +++ b/yarn-project/prover-node/src/metrics.ts @@ -3,6 +3,8 @@ import { type Histogram, Metrics, type TelemetryClient, ValueType } from '@aztec export class ProverNodeMetrics { provingJobDuration: Histogram; + provingJobBlocks: Histogram; + provingJobTransactions: Histogram; constructor(public readonly client: TelemetryClient, name = 'ProverNode') { const meter = client.getMeter(name); @@ -11,10 +13,20 @@ export class ProverNodeMetrics { unit: 'ms', valueType: ValueType.INT, }); + this.provingJobBlocks = meter.createHistogram(Metrics.PROVER_NODE_JOB_BLOCKS, { + description: 'Number of blocks in a proven epoch', + valueType: ValueType.INT, + }); + this.provingJobTransactions = meter.createHistogram(Metrics.PROVER_NODE_JOB_TRANSACTIONS, { + description: 'Number of transactions in a proven epoch', + valueType: ValueType.INT, + }); } - public recordProvingJob(timerOrMs: Timer | number) { + public recordProvingJob(timerOrMs: Timer | number, numBlocks: number, numTxs: number) { const ms = Math.ceil(typeof timerOrMs === 'number' ? timerOrMs : timerOrMs.ms()); this.provingJobDuration.record(ms); + this.provingJobBlocks.record(Math.floor(numBlocks)); + this.provingJobTransactions.record(Math.floor(numTxs)); } } diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher-metrics.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher-metrics.ts index f23ac261169b..787b570e0785 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher-metrics.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher-metrics.ts @@ -20,6 +20,8 @@ export class L1PublisherMetrics { private txGas: Histogram; private txCalldataSize: Histogram; private txCalldataGas: Histogram; + private txBlobDataGasUsed: Histogram; + private txBlobDataGasCost: Histogram; constructor(client: TelemetryClient, name = 'L1Publisher') { const meter = client.getMeter(name); @@ -57,6 +59,18 @@ export class L1PublisherMetrics { unit: 'gas', valueType: ValueType.INT, }); + + this.txBlobDataGasUsed = meter.createHistogram(Metrics.L1_PUBLISHER_TX_BLOBDATA_GAS_USED, { + description: 'The amount of blob gas used in transactions', + unit: 'gas', + valueType: ValueType.INT, + }); + + this.txBlobDataGasCost = meter.createHistogram(Metrics.L1_PUBLISHER_TX_BLOBDATA_GAS_COST, { + description: 'The gas cost of blobs in transactions', + unit: 'gwei', + valueType: ValueType.INT, + }); } recordFailedTx(txType: L1TxType) { @@ -98,6 +112,9 @@ export class L1PublisherMetrics { this.txCalldataGas.record(stats.calldataGas, attributes); this.txCalldataSize.record(stats.calldataSize, attributes); + this.txBlobDataGasCost.record(Number(stats.blobDataGas), attributes); + this.txBlobDataGasUsed.record(Number(stats.blobGasUsed), attributes); + try { this.gasPrice.record(parseInt(formatEther(stats.gasPrice, 'gwei'), 10)); } catch (e) { diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts index a89969ede840..29ee97c8568b 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts @@ -580,6 +580,8 @@ export class L1Publisher { const stats: L1PublishBlockStats = { gasPrice: receipt.effectiveGasPrice, gasUsed: receipt.gasUsed, + blobGasUsed: receipt.blobGasUsed ?? 0n, + blobDataGas: receipt.blobGasPrice ?? 0n, transactionHash: receipt.transactionHash, ...pick(tx!, 'calldataGas', 'calldataSize', 'sender'), ...block.getStats(), @@ -642,6 +644,8 @@ export class L1Publisher { gasPrice: receipt.effectiveGasPrice, gasUsed: receipt.gasUsed, transactionHash: receipt.transactionHash, + blobDataGas: 0n, + blobGasUsed: 0n, ...pick(tx!, 'calldataGas', 'calldataSize', 'sender'), }; this.log.verbose(`Submitted claim epoch proof right to L1 rollup contract`, { @@ -758,6 +762,8 @@ export class L1Publisher { const stats: L1PublishProofStats = { ...pick(receipt, 'gasPrice', 'gasUsed', 'transactionHash'), ...pick(tx!, 'calldataGas', 'calldataSize', 'sender'), + blobDataGas: 0n, + blobGasUsed: 0n, eventName: 'proof-published-to-l1', }; this.log.info(`Published epoch proof to L1 rollup contract`, { ...stats, ...ctx }); diff --git a/yarn-project/sequencer-client/src/sequencer/metrics.ts b/yarn-project/sequencer-client/src/sequencer/metrics.ts index 55cd5b1b145a..806d35534cca 100644 --- a/yarn-project/sequencer-client/src/sequencer/metrics.ts +++ b/yarn-project/sequencer-client/src/sequencer/metrics.ts @@ -19,6 +19,7 @@ export class SequencerMetrics { private stateTransitionBufferDuration: Histogram; private currentBlockNumber: Gauge; private currentBlockSize: Gauge; + private blockBuilderInsertions: Histogram; private timeToCollectAttestations: Gauge; @@ -49,14 +50,23 @@ export class SequencerMetrics { this.currentBlockNumber = meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_NUMBER, { description: 'Current block number', + valueType: ValueType.INT, }); this.currentBlockSize = meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_SIZE, { - description: 'Current block number', + description: 'Current block size', + valueType: ValueType.INT, }); this.timeToCollectAttestations = meter.createGauge(Metrics.SEQUENCER_TIME_TO_COLLECT_ATTESTATIONS, { description: 'The time spent collecting attestations from committee members', + valueType: ValueType.INT, + }); + + this.blockBuilderInsertions = meter.createHistogram(Metrics.SEQUENCER_BLOCK_BUILD_INSERTION_TIME, { + description: 'Timer for tree insertions performed by the block builder', + unit: 'us', + valueType: ValueType.INT, }); this.setCurrentBlock(0, 0); @@ -75,6 +85,10 @@ export class SequencerMetrics { this.timeToCollectAttestations.record(time); } + recordBlockBuilderTreeInsertions(timeUs: number) { + this.blockBuilderInsertions.record(Math.ceil(timeUs)); + } + recordCancelledBlock() { this.blockCounter.add(1, { [Attributes.STATUS]: 'cancelled', diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index bc5457666cb1..e373b26efa17 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -519,7 +519,12 @@ export class Sequencer { this.log.verbose(`Dropping failed txs ${Tx.getHashes(failedTxData).join(', ')}`); await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData)); } + + const start = process.hrtime.bigint(); await blockBuilder.addTxs(processedTxs); + const end = process.hrtime.bigint(); + const duration = Number(end - start) / 1_000; + this.metrics.recordBlockBuilderTreeInsertions(duration); await interrupt?.(processedTxs); diff --git a/yarn-project/simulator/src/public/executor_metrics.ts b/yarn-project/simulator/src/public/executor_metrics.ts index 621a3c094981..7fe14a34e34a 100644 --- a/yarn-project/simulator/src/public/executor_metrics.ts +++ b/yarn-project/simulator/src/public/executor_metrics.ts @@ -13,6 +13,7 @@ export class ExecutorMetrics { private fnCount: UpDownCounter; private fnDuration: Histogram; private manaPerSecond: Histogram; + private privateEffectsInsertions: Histogram; constructor(client: TelemetryClient, name = 'PublicExecutor') { this.tracer = client.getTracer(name); @@ -33,6 +34,12 @@ export class ExecutorMetrics { unit: 'mana/s', valueType: ValueType.INT, }); + + this.privateEffectsInsertions = meter.createHistogram(Metrics.PUBLIC_EXECUTION_PRIVATE_EFFECTS_INSERTION, { + description: 'Private effects insertion time', + unit: 'us', + valueType: ValueType.INT, + }); } recordFunctionSimulation(durationMs: number, manaUsed: number, fnName: string) { @@ -55,4 +62,10 @@ export class ExecutorMetrics { [Attributes.OK]: false, }); } + + recordPrivateEffectsInsertion(durationUs: number, type: 'revertible' | 'non-revertible') { + this.privateEffectsInsertions.record(Math.ceil(durationUs), { + [Attributes.REVERTIBILITY]: type, + }); + } } diff --git a/yarn-project/simulator/src/public/public_processor.test.ts b/yarn-project/simulator/src/public/public_processor.test.ts index 51a816e46cf4..e9570c3368f5 100644 --- a/yarn-project/simulator/src/public/public_processor.test.ts +++ b/yarn-project/simulator/src/public/public_processor.test.ts @@ -63,6 +63,7 @@ describe('public_processor', () => { gasUsed: { totalGas: Gas.empty(), teardownGas: Gas.empty(), + publicGas: Gas.empty(), }, revertCode: RevertCode.OK, processedPhases: [], diff --git a/yarn-project/simulator/src/public/public_processor.ts b/yarn-project/simulator/src/public/public_processor.ts index e0f3f328ba33..aeea00c00b6d 100644 --- a/yarn-project/simulator/src/public/public_processor.ts +++ b/yarn-project/simulator/src/public/public_processor.ts @@ -15,6 +15,7 @@ import { type BlockHeader, type ContractDataSource, Fr, + Gas, type GlobalVariables, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, @@ -113,6 +114,8 @@ export class PublicProcessor implements Traceable { const result: ProcessedTx[] = []; const failed: FailedTx[] = []; let returns: NestedProcessReturnValues[] = []; + let totalGas = new Gas(0, 0); + const timer = new Timer(); for (const tx of txs) { // only process up to the limit of the block @@ -123,6 +126,7 @@ export class PublicProcessor implements Traceable { const [processedTx, returnValues] = await this.processTx(tx, txValidator); result.push(processedTx); returns = returns.concat(returnValues); + totalGas = totalGas.add(processedTx.gasUsed.publicGas); } catch (err: any) { const errorMessage = err instanceof Error ? err.message : 'Unknown error'; this.log.warn(`Failed to process tx ${tx.getTxHash()}: ${errorMessage} ${err?.stack}`); @@ -135,6 +139,10 @@ export class PublicProcessor implements Traceable { } } + const duration = timer.s(); + const rate = duration > 0 ? totalGas.l2Gas / duration : 0; + this.metrics.recordAllTxs(totalGas, rate); + return [result, failed, returns]; } @@ -187,6 +195,7 @@ export class PublicProcessor implements Traceable { // b) always had a txHandler with the same db passed to it as this.db, which updated the db in buildBaseRollupHints in this loop // To see how this ^ happens, move back to one shared db in test_context and run orchestrator_multi_public_functions.test.ts // The below is taken from buildBaseRollupHints: + const treeInsertionStart = process.hrtime.bigint(); await this.db.appendLeaves( MerkleTreeId.NOTE_HASH_TREE, padArrayEnd(processedTx.txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX), @@ -211,6 +220,8 @@ export class PublicProcessor implements Traceable { MerkleTreeId.PUBLIC_DATA_TREE, processedTx.txEffect.publicDataWrites.map(x => x.toBuffer()), ); + const treeInsertionEnd = process.hrtime.bigint(); + this.metrics.recordTreeInsertions(Number(treeInsertionEnd - treeInsertionStart) / 1_000); return [processedTx, returnValues ?? []]; } @@ -302,7 +313,7 @@ export class PublicProcessor implements Traceable { const phaseCount = processedPhases.length; const durationMs = timer.ms(); - this.metrics.recordTx(phaseCount, durationMs); + this.metrics.recordTx(phaseCount, durationMs, gasUsed.publicGas); const processedTx = makeProcessedTxFromTxWithPublicCalls(tx, avmProvingRequest, gasUsed, revertCode, revertReason); diff --git a/yarn-project/simulator/src/public/public_processor_metrics.ts b/yarn-project/simulator/src/public/public_processor_metrics.ts index 84d9b52cbded..2f07408a1aa1 100644 --- a/yarn-project/simulator/src/public/public_processor_metrics.ts +++ b/yarn-project/simulator/src/public/public_processor_metrics.ts @@ -1,7 +1,9 @@ import { type TxExecutionPhase } from '@aztec/circuit-types'; +import { type Gas } from '@aztec/circuits.js'; import { type ContractClassRegisteredEvent } from '@aztec/protocol-contracts/class-registerer'; import { Attributes, + type Gauge, type Histogram, Metrics, type TelemetryClient, @@ -21,6 +23,12 @@ export class PublicProcessorMetrics { private phaseCount: UpDownCounter; private bytecodeDeployed: Histogram; + private totalGas: Gauge; + private totalGasHistogram: Histogram; + private gasRate: Histogram; + private txGas: Histogram; + + private treeInsertionDuration: Histogram; constructor(client: TelemetryClient, name = 'PublicProcessor') { this.tracer = client.getTracer(name); @@ -54,6 +62,32 @@ export class PublicProcessorMetrics { description: 'Size of deployed bytecode', unit: 'By', }); + + this.totalGas = meter.createGauge(Metrics.PUBLIC_PROCESSOR_TOTAL_GAS, { + description: 'Total gas used in block', + unit: 'gas', + }); + + this.totalGasHistogram = meter.createHistogram(Metrics.PUBLIC_PROCESSOR_TOTAL_GAS_HISTOGRAM, { + description: 'Total gas used in block as histogram', + unit: 'gas/block', + }); + + this.txGas = meter.createHistogram(Metrics.PUBLIC_PROCESSOR_TX_GAS, { + description: 'Gas used in transaction', + unit: 'gas/tx', + }); + + this.gasRate = meter.createHistogram(Metrics.PUBLIC_PROCESSOR_GAS_RATE, { + description: 'L2 gas per second for complete block', + unit: 'gas/s', + }); + + this.treeInsertionDuration = meter.createHistogram(Metrics.PUBLIC_PROCESSOR_TREE_INSERTION, { + description: 'How long it takes for tree insertion', + unit: 'us', + valueType: ValueType.INT, + }); } recordPhaseDuration(phaseName: TxExecutionPhase, durationMs: number) { @@ -61,12 +95,36 @@ export class PublicProcessorMetrics { this.phaseDuration.record(Math.ceil(durationMs), { [Attributes.TX_PHASE_NAME]: phaseName }); } - recordTx(phaseCount: number, durationMs: number) { + recordTx(phaseCount: number, durationMs: number, gasUsed: Gas) { this.txPhaseCount.add(phaseCount); this.txDuration.record(Math.ceil(durationMs)); this.txCount.add(1, { [Attributes.OK]: true, }); + this.txGas.record(gasUsed.daGas, { + [Attributes.GAS_DIMENSION]: 'DA', + }); + this.txGas.record(gasUsed.l2Gas, { + [Attributes.GAS_DIMENSION]: 'L2', + }); + } + + recordAllTxs(totalGas: Gas, gasRate: number) { + this.totalGas.record(totalGas.daGas, { + [Attributes.GAS_DIMENSION]: 'DA', + }); + this.totalGas.record(totalGas.l2Gas, { + [Attributes.GAS_DIMENSION]: 'L2', + }); + this.gasRate.record(gasRate, { + [Attributes.GAS_DIMENSION]: 'L2', + }); + this.totalGasHistogram.record(totalGas.daGas, { + [Attributes.GAS_DIMENSION]: 'DA', + }); + this.totalGasHistogram.record(totalGas.l2Gas, { + [Attributes.GAS_DIMENSION]: 'L2', + }); } recordFailedTx() { @@ -89,4 +147,8 @@ export class PublicProcessorMetrics { this.bytecodeDeployed.record(totalBytecode); } } + + recordTreeInsertions(durationUs: number) { + this.treeInsertionDuration.record(Math.ceil(durationUs)); + } } diff --git a/yarn-project/simulator/src/public/public_tx_context.ts b/yarn-project/simulator/src/public/public_tx_context.ts index ec7aaa93316e..62dab19d16ec 100644 --- a/yarn-project/simulator/src/public/public_tx_context.ts +++ b/yarn-project/simulator/src/public/public_tx_context.ts @@ -282,6 +282,15 @@ export class PublicTxContext { return this.getTotalGasUsed().sub(teardownGasLimits).add(this.teardownGasUsed); } + /** + * Compute the public gas used using the actual gas used during teardown instead + * of the teardown gas limit. + */ + getActualPublicGasUsed(): Gas { + assert(this.halted, 'Can only compute actual gas used after tx execution ends'); + return this.gasUsedByPublic.add(this.teardownGasUsed); + } + /** * Get the transaction fee as is available to the specified phase. * Only teardown should have access to the actual transaction fee. diff --git a/yarn-project/simulator/src/public/public_tx_simulator.test.ts b/yarn-project/simulator/src/public/public_tx_simulator.test.ts index fdc11c42ae61..3029c1b1ee28 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator.test.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator.test.ts @@ -295,6 +295,7 @@ describe('public_tx_simulator', () => { expect(txResult.gasUsed).toEqual({ totalGas: expectedTotalGas, teardownGas: Gas.empty(), + publicGas: expectedPublicGasUsed, }); const availableGasForFirstSetup = gasLimits.sub(privateGasUsed); @@ -330,6 +331,7 @@ describe('public_tx_simulator', () => { expect(txResult.gasUsed).toEqual({ totalGas: expectedTotalGas, teardownGas: Gas.empty(), + publicGas: expectedPublicGasUsed, }); const availableGasForFirstAppLogic = gasLimits.sub(privateGasUsed); @@ -365,6 +367,7 @@ describe('public_tx_simulator', () => { expect(txResult.gasUsed).toEqual({ totalGas: expectedTotalGas, teardownGas: expectedTeardownGasUsed, + publicGas: expectedTeardownGasUsed, }); expectAvailableGasForCalls([teardownGasLimits]); @@ -404,6 +407,7 @@ describe('public_tx_simulator', () => { expect(txResult.gasUsed).toEqual({ totalGas: expectedTotalGas, teardownGas: expectedTeardownGasUsed, + publicGas: expectedPublicGasUsed.add(expectedTeardownGasUsed), }); // Check that each enqueued call is allocated the correct amount of gas. @@ -553,6 +557,7 @@ describe('public_tx_simulator', () => { expect(txResult.gasUsed).toEqual({ totalGas: expectedTotalGas, teardownGas: expectedTeardownGasUsed, + publicGas: expectedTotalGas.sub(privateGasUsed), }); const availableGasForSetup = gasLimits.sub(teardownGasLimits).sub(privateGasUsed); @@ -634,6 +639,7 @@ describe('public_tx_simulator', () => { expect(txResult.gasUsed).toEqual({ totalGas: expectedTotalGas, teardownGas: expectedTeardownGasUsed, + publicGas: expectedTotalGas.sub(privateGasUsed), }); const availableGasForSetup = gasLimits.sub(teardownGasLimits).sub(privateGasUsed); @@ -717,6 +723,7 @@ describe('public_tx_simulator', () => { expect(txResult.gasUsed).toEqual({ totalGas: expectedTotalGas, teardownGas: expectedTeardownGasUsed, + publicGas: expectedTotalGas.sub(privateGasUsed), }); const availableGasForSetup = gasLimits.sub(teardownGasLimits).sub(privateGasUsed); @@ -804,6 +811,7 @@ describe('public_tx_simulator', () => { expect(txResult.gasUsed).toEqual({ totalGas: expectedTotalGas, teardownGas: expectedTeardownGasUsed, + publicGas: expectedPublicGasUsed.add(expectedTeardownGasUsed), }); const output = txResult.avmProvingRequest!.inputs.output; diff --git a/yarn-project/simulator/src/public/public_tx_simulator.ts b/yarn-project/simulator/src/public/public_tx_simulator.ts index dc8c3198df4f..b4c91484e275 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator.ts @@ -91,14 +91,20 @@ export class PublicTxSimulator { // FIXME: we shouldn't need to directly modify worldStateDb here! await this.worldStateDB.addNewContracts(tx); + const nonRevertStart = process.hrtime.bigint(); await this.insertNonRevertiblesFromPrivate(context); + const nonRevertEnd = process.hrtime.bigint(); + this.metrics.recordPrivateEffectsInsertion(Number(nonRevertEnd - nonRevertStart) / 1_000, 'non-revertible'); const processedPhases: ProcessedPhase[] = []; if (context.hasPhase(TxExecutionPhase.SETUP)) { const setupResult: ProcessedPhase = await this.simulateSetupPhase(context); processedPhases.push(setupResult); } + const revertStart = process.hrtime.bigint(); await this.insertRevertiblesFromPrivate(context); + const revertEnd = process.hrtime.bigint(); + this.metrics.recordPrivateEffectsInsertion(Number(revertEnd - revertStart) / 1_000, 'revertible'); if (context.hasPhase(TxExecutionPhase.APP_LOGIC)) { const appLogicResult: ProcessedPhase = await this.simulateAppLogicPhase(context); processedPhases.push(appLogicResult); @@ -135,7 +141,11 @@ export class PublicTxSimulator { return { avmProvingRequest, - gasUsed: { totalGas: context.getActualGasUsed(), teardownGas: context.teardownGasUsed }, + gasUsed: { + totalGas: context.getActualGasUsed(), + teardownGas: context.teardownGasUsed, + publicGas: context.getActualPublicGasUsed(), + }, revertCode, revertReason: context.revertReason, processedPhases: processedPhases, @@ -345,7 +355,7 @@ export class PublicTxSimulator { const avmCallResult = await simulator.execute(); const result = avmCallResult.finalize(); - this.log.debug( + this.log.verbose( result.reverted ? `Simulation of enqueued public call ${fnName} reverted with reason ${result.revertReason}.` : `Simulation of enqueued public call ${fnName} completed successfully.`, diff --git a/yarn-project/telemetry-client/src/attributes.ts b/yarn-project/telemetry-client/src/attributes.ts index 364b96d8b24e..6fa7d7c32677 100644 --- a/yarn-project/telemetry-client/src/attributes.ts +++ b/yarn-project/telemetry-client/src/attributes.ts @@ -99,3 +99,9 @@ export const WS_DB_DATA_TYPE = 'aztec.world_state.db_type'; /** Identifier for component database (e.g. archiver, tx pool) */ export const DB_DATA_TYPE = 'aztec.db_type'; + +export const REVERTIBILITY = 'aztec.revertibility'; + +export const GAS_DIMENSION = 'aztec.gas_dimension'; + +export const WORLD_STATE_REQUEST_TYPE = 'aztec.world_state_request'; diff --git a/yarn-project/telemetry-client/src/metrics.ts b/yarn-project/telemetry-client/src/metrics.ts index 84fe768da50c..5e2d33befe8d 100644 --- a/yarn-project/telemetry-client/src/metrics.ts +++ b/yarn-project/telemetry-client/src/metrics.ts @@ -41,6 +41,7 @@ export const ARCHIVER_BLOCK_HEIGHT = 'aztec.archiver.block_height'; export const ARCHIVER_BLOCK_SIZE = 'aztec.archiver.block_size'; export const ARCHIVER_ROLLUP_PROOF_DELAY = 'aztec.archiver.rollup_proof_delay'; export const ARCHIVER_ROLLUP_PROOF_COUNT = 'aztec.archiver.rollup_proof_count'; +export const ARCHIVER_PRUNE_COUNT = 'aztec.archiver.prune_count'; export const NODE_RECEIVE_TX_DURATION = 'aztec.node.receive_tx.duration'; export const NODE_RECEIVE_TX_COUNT = 'aztec.node.receive_tx.count'; @@ -52,6 +53,7 @@ export const SEQUENCER_CURRENT_STATE = 'aztec.sequencer.current.state'; export const SEQUENCER_CURRENT_BLOCK_NUMBER = 'aztec.sequencer.current.block_number'; export const SEQUENCER_CURRENT_BLOCK_SIZE = 'aztec.sequencer.current.block_size'; export const SEQUENCER_TIME_TO_COLLECT_ATTESTATIONS = 'aztec.sequencer.time_to_collect_attestations'; +export const SEQUENCER_BLOCK_BUILD_INSERTION_TIME = 'aztec.sequencer.block_builder_tree_insertion_duration'; export const L1_PUBLISHER_GAS_PRICE = 'aztec.l1_publisher.gas_price'; export const L1_PUBLISHER_TX_COUNT = 'aztec.l1_publisher.tx_count'; @@ -59,18 +61,26 @@ export const L1_PUBLISHER_TX_DURATION = 'aztec.l1_publisher.tx_duration'; export const L1_PUBLISHER_TX_GAS = 'aztec.l1_publisher.tx_gas'; export const L1_PUBLISHER_TX_CALLDATA_SIZE = 'aztec.l1_publisher.tx_calldata_size'; export const L1_PUBLISHER_TX_CALLDATA_GAS = 'aztec.l1_publisher.tx_calldata_gas'; +export const L1_PUBLISHER_TX_BLOBDATA_GAS_USED = 'aztec.l1_publisher.tx_blobdata_gas_used'; +export const L1_PUBLISHER_TX_BLOBDATA_GAS_COST = 'aztec.l1_publisher.tx_blobdata_gas_cost'; export const PUBLIC_PROCESSOR_TX_DURATION = 'aztec.public_processor.tx_duration'; export const PUBLIC_PROCESSOR_TX_COUNT = 'aztec.public_processor.tx_count'; export const PUBLIC_PROCESSOR_TX_PHASE_COUNT = 'aztec.public_processor.tx_phase_count'; +export const PUBLIC_PROCESSOR_TX_GAS = 'aztec.public_processor.tx_gas'; export const PUBLIC_PROCESSOR_PHASE_DURATION = 'aztec.public_processor.phase_duration'; export const PUBLIC_PROCESSOR_PHASE_COUNT = 'aztec.public_processor.phase_count'; export const PUBLIC_PROCESSOR_DEPLOY_BYTECODE_SIZE = 'aztec.public_processor.deploy_bytecode_size'; +export const PUBLIC_PROCESSOR_TOTAL_GAS = 'aztec.public_processor.total_gas'; +export const PUBLIC_PROCESSOR_TOTAL_GAS_HISTOGRAM = 'aztec.public_processor.total_gas_histogram'; +export const PUBLIC_PROCESSOR_GAS_RATE = 'aztec.public_processor.gas_rate'; +export const PUBLIC_PROCESSOR_TREE_INSERTION = 'aztec.public_processor.tree_insertion'; export const PUBLIC_EXECUTOR_SIMULATION_COUNT = 'aztec.public_executor.simulation_count'; export const PUBLIC_EXECUTOR_SIMULATION_DURATION = 'aztec.public_executor.simulation_duration'; export const PUBLIC_EXECUTOR_SIMULATION_MANA_PER_SECOND = 'aztec.public_executor.simulation_mana_per_second'; export const PUBLIC_EXECUTION_SIMULATION_BYTECODE_SIZE = 'aztec.public_executor.simulation_bytecode_size'; +export const PUBLIC_EXECUTION_PRIVATE_EFFECTS_INSERTION = 'aztec.public_executor.private_effects_insertion'; export const PROVING_ORCHESTRATOR_BASE_ROLLUP_INPUTS_DURATION = 'aztec.proving_orchestrator.base_rollup.inputs_duration'; @@ -91,6 +101,8 @@ export const PROVING_QUEUE_DB_USED_SIZE = 'aztec.proving_queue.db.used_size'; export const PROVING_AGENT_IDLE = 'aztec.proving_queue.agent.idle'; export const PROVER_NODE_JOB_DURATION = 'aztec.prover_node.job_duration'; +export const PROVER_NODE_JOB_BLOCKS = 'aztec.prover_node.job_blocks'; +export const PROVER_NODE_JOB_TRANSACTIONS = 'aztec.prover_node.job_transactions'; export const WORLD_STATE_FORK_DURATION = 'aztec.world_state.fork.duration'; export const WORLD_STATE_SYNC_DURATION = 'aztec.world_state.sync.duration'; @@ -103,6 +115,7 @@ export const WORLD_STATE_FINALISED_HEIGHT = 'aztec.world_state.finalised_height' export const WORLD_STATE_OLDEST_BLOCK = 'aztec.world_state.oldest_block'; export const WORLD_STATE_DB_USED_SIZE = 'aztec.world_state.db_used_size'; export const WORLD_STATE_DB_NUM_ITEMS = 'aztec.world_state.db_num_items'; +export const WORLD_STATE_REQUEST_TIME = 'aztec.world_state.request_time'; export const PROOF_VERIFIER_COUNT = 'aztec.proof_verifier.count'; diff --git a/yarn-project/telemetry-client/src/otel.ts b/yarn-project/telemetry-client/src/otel.ts index 14d28f9fe6fe..e8b6d767b489 100644 --- a/yarn-project/telemetry-client/src/otel.ts +++ b/yarn-project/telemetry-client/src/otel.ts @@ -168,6 +168,21 @@ export class OpenTelemetryClient implements TelemetryClient { true, ), }), + new View({ + instrumentType: InstrumentType.HISTOGRAM, + instrumentUnit: 'us', + aggregation: new ExplicitBucketHistogramAggregation( + [ + ...linearBuckets(5, 100, 20), // 20 buckets between 5 and 100us + ...linearBuckets(100, 1_000, 20).slice(1), // another 20 buckets between 100us and 1ms. slice(1) to remove duplicate 100 + ...linearBuckets(1_000, 10_000, 90).slice(1), // 90 buckets between 1ms and 10ms + ...linearBuckets(10_000, 100_000, 20).slice(1), // 20 buckets between 10ms and 100ms + ...linearBuckets(100_000, 1_000_000, 20).slice(1), // 20 buckets between 100ms and 1s + ...linearBuckets(1_000_000, 60_000_000, 20).slice(1), // 20 buckets between 1s and 1m + ], + true, + ), + }), new View({ instrumentType: InstrumentType.HISTOGRAM, instrumentUnit: 'By', @@ -183,6 +198,38 @@ export class OpenTelemetryClient implements TelemetryClient { true, ), }), + new View({ + instrumentType: InstrumentType.HISTOGRAM, + instrumentUnit: 'gas/s', + aggregation: new ExplicitBucketHistogramAggregation( + [...linearBuckets(100_000, 10_000_000, 100), ...linearBuckets(10_000_000, 100_000_000, 100).slice(1)], + true, + ), + }), + new View({ + instrumentType: InstrumentType.HISTOGRAM, + instrumentUnit: 'mana/s', + aggregation: new ExplicitBucketHistogramAggregation( + [...linearBuckets(100_000, 10_000_000, 100), ...linearBuckets(10_000_000, 100_000_000, 100).slice(1)], + true, + ), + }), + new View({ + instrumentType: InstrumentType.HISTOGRAM, + instrumentUnit: 'gas/block', + aggregation: new ExplicitBucketHistogramAggregation( + [...linearBuckets(100_000, 10_000_000, 100), ...linearBuckets(10_000_000, 50_000_000, 50).slice(1)], + true, + ), + }), + new View({ + instrumentType: InstrumentType.HISTOGRAM, + instrumentUnit: 'gas/tx', + aggregation: new ExplicitBucketHistogramAggregation( + [...linearBuckets(50_000, 1_000_000, 20), ...linearBuckets(1_000_000, 10_000_000, 100).slice(1)], + true, + ), + }), ], }); diff --git a/yarn-project/world-state/src/synchronizer/instrumentation.ts b/yarn-project/world-state/src/instrumentation/instrumentation.ts similarity index 75% rename from yarn-project/world-state/src/synchronizer/instrumentation.ts rename to yarn-project/world-state/src/instrumentation/instrumentation.ts index a57ae79fe86d..fa7c7c7d57fb 100644 --- a/yarn-project/world-state/src/synchronizer/instrumentation.ts +++ b/yarn-project/world-state/src/instrumentation/instrumentation.ts @@ -1,8 +1,21 @@ import { MerkleTreeId } from '@aztec/circuit-types'; import { createLogger } from '@aztec/foundation/log'; -import { Attributes, type Gauge, type TelemetryClient, ValueType } from '@aztec/telemetry-client'; - -import { type DBStats, type TreeDBStats, type TreeMeta, type WorldStateStatusFull } from '../native/message.js'; +import { + Attributes, + type Gauge, + type Histogram, + Metrics, + type TelemetryClient, + ValueType, +} from '@aztec/telemetry-client'; + +import { + type DBStats, + type TreeDBStats, + type TreeMeta, + WorldStateMessageType, + type WorldStateStatusFull, +} from '../native/message.js'; type DBTypeString = 'leaf_preimage' | 'leaf_indices' | 'nodes' | 'blocks' | 'block_indices'; @@ -14,43 +27,50 @@ export class WorldStateInstrumentation { private oldestBlock: Gauge; private dbNumItems: Gauge; private dbUsedSize: Gauge; + private requestHistogram: Histogram; constructor(public readonly telemetry: TelemetryClient, private log = createLogger('world-state:instrumentation')) { const meter = telemetry.getMeter('World State'); - this.dbMapSize = meter.createGauge(`aztec.world_state.db_map_size`, { + this.dbMapSize = meter.createGauge(Metrics.WORLD_STATE_DB_MAP_SIZE, { description: `The current configured map size for each merkle tree`, valueType: ValueType.INT, }); - this.treeSize = meter.createGauge(`aztec.world_state.tree_size`, { + this.treeSize = meter.createGauge(Metrics.WORLD_STATE_TREE_SIZE, { description: `The current number of leaves in each merkle tree`, valueType: ValueType.INT, }); - this.unfinalisedHeight = meter.createGauge(`aztec.world_state.unfinalised_height`, { + this.unfinalisedHeight = meter.createGauge(Metrics.WORLD_STATE_UNFINALISED_HEIGHT, { description: `The unfinalised block height of each merkle tree`, valueType: ValueType.INT, }); - this.finalisedHeight = meter.createGauge(`aztec.world_state.finalised_height`, { + this.finalisedHeight = meter.createGauge(Metrics.WORLD_STATE_FINALISED_HEIGHT, { description: `The finalised block height of each merkle tree`, valueType: ValueType.INT, }); - this.oldestBlock = meter.createGauge(`aztec.world_state.oldest_block`, { + this.oldestBlock = meter.createGauge(Metrics.WORLD_STATE_OLDEST_BLOCK, { description: `The oldest historical block of each merkle tree`, valueType: ValueType.INT, }); - this.dbUsedSize = meter.createGauge(`aztec.world_state.db_used_size`, { + this.dbUsedSize = meter.createGauge(Metrics.WORLD_STATE_DB_USED_SIZE, { description: `The current used database size for each db of each merkle tree`, valueType: ValueType.INT, }); - this.dbNumItems = meter.createGauge(`aztec.world_state.db_num_items`, { + this.dbNumItems = meter.createGauge(Metrics.WORLD_STATE_DB_NUM_ITEMS, { description: `The current number of items in each database of each merkle tree`, valueType: ValueType.INT, }); + + this.requestHistogram = meter.createHistogram(Metrics.WORLD_STATE_REQUEST_TIME, { + description: 'The round trip time of world state requests', + unit: 'us', + valueType: ValueType.INT, + }); } private updateTreeStats(treeDbStats: TreeDBStats, treeMeta: TreeMeta, tree: MerkleTreeId) { @@ -119,4 +139,10 @@ export class WorldStateInstrumentation { MerkleTreeId.PUBLIC_DATA_TREE, ); } + + public recordRoundTrip(timeUs: number, request: WorldStateMessageType) { + this.requestHistogram.record(Math.ceil(timeUs), { + [Attributes.WORLD_STATE_REQUEST_TYPE]: WorldStateMessageType[request], + }); + } } diff --git a/yarn-project/world-state/src/native/native_world_state.ts b/yarn-project/world-state/src/native/native_world_state.ts index 332a22008bea..84e25ea60245 100644 --- a/yarn-project/world-state/src/native/native_world_state.ts +++ b/yarn-project/world-state/src/native/native_world_state.ts @@ -21,12 +21,14 @@ import { } from '@aztec/circuits.js'; import { padArrayEnd } from '@aztec/foundation/collection'; import { createLogger } from '@aztec/foundation/log'; +import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import assert from 'assert/strict'; import { mkdir, mkdtemp, rm } from 'fs/promises'; import { tmpdir } from 'os'; import { join } from 'path'; +import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js'; import { type MerkleTreeAdminDatabase as MerkleTreeDatabase } from '../world-state-db/merkle_tree_db.js'; import { MerkleTreesFacade, MerkleTreesForkFacade, serializeLeaf } from './merkle_trees_facade.js'; import { @@ -58,6 +60,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase { protected constructor( protected readonly instance: NativeWorldState, + protected readonly worldStateInstrumentation: WorldStateInstrumentation, protected readonly log = createLogger('world-state:database'), private readonly cleanup = () => Promise.resolve(), ) {} @@ -66,6 +69,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase { rollupAddress: EthAddress, dataDir: string, dbMapSizeKb: number, + instrumentation = new WorldStateInstrumentation(new NoopTelemetryClient()), log = createLogger('world-state:database'), cleanup = () => Promise.resolve(), ): Promise { @@ -89,8 +93,8 @@ export class NativeWorldStateService implements MerkleTreeDatabase { await mkdir(worldStateDirectory, { recursive: true }); await newWorldStateVersion.writeVersionFile(versionFile); - const instance = new NativeWorldState(worldStateDirectory, dbMapSizeKb); - const worldState = new this(instance, log, cleanup); + const instance = new NativeWorldState(worldStateDirectory, dbMapSizeKb, instrumentation); + const worldState = new this(instance, instrumentation, log, cleanup); try { await worldState.init(); } catch (e) { @@ -101,7 +105,11 @@ export class NativeWorldStateService implements MerkleTreeDatabase { return worldState; } - static async tmp(rollupAddress = EthAddress.ZERO, cleanupTmpDir = true): Promise { + static async tmp( + rollupAddress = EthAddress.ZERO, + cleanupTmpDir = true, + instrumentation = new WorldStateInstrumentation(new NoopTelemetryClient()), + ): Promise { const log = createLogger('world-state:database'); const dataDir = await mkdtemp(join(tmpdir(), 'aztec-world-state-')); const dbMapSizeKb = 10 * 1024 * 1024; @@ -117,7 +125,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase { } }; - return this.new(rollupAddress, dataDir, dbMapSizeKb, log, cleanup); + return this.new(rollupAddress, dataDir, dbMapSizeKb, instrumentation, log, cleanup); } protected async init() { diff --git a/yarn-project/world-state/src/native/native_world_state_instance.ts b/yarn-project/world-state/src/native/native_world_state_instance.ts index 590a1ac916be..25cee92f60d1 100644 --- a/yarn-project/world-state/src/native/native_world_state_instance.ts +++ b/yarn-project/world-state/src/native/native_world_state_instance.ts @@ -12,7 +12,6 @@ import { } from '@aztec/circuits.js'; import { createLogger } from '@aztec/foundation/log'; import { SerialQueue } from '@aztec/foundation/queue'; -import { Timer } from '@aztec/foundation/timer'; import assert from 'assert'; import bindings from 'bindings'; @@ -20,6 +19,7 @@ import { Decoder, Encoder, addExtension } from 'msgpackr'; import { cpus } from 'os'; import { isAnyArrayBuffer } from 'util/types'; +import { type WorldStateInstrumentation } from '../instrumentation/instrumentation.js'; import { MessageHeader, TypedMessage, @@ -82,7 +82,12 @@ export class NativeWorldState implements NativeWorldStateInstance { private queue = new SerialQueue(); /** Creates a new native WorldState instance */ - constructor(dataDir: string, dbMapSizeKb: number, private log = createLogger('world-state:database')) { + constructor( + dataDir: string, + dbMapSizeKb: number, + private instrumentation: WorldStateInstrumentation, + private log = createLogger('world-state:database'), + ) { const threads = Math.min(cpus().length, MAX_WORLD_STATE_THREADS); log.info( `Creating world state data store at directory ${dataDir} with map size ${dbMapSizeKb} KB and ${threads} threads.`, @@ -200,11 +205,12 @@ export class NativeWorldState implements NativeWorldStateInstance { this.log.trace(`Calling messageId=${messageId} ${WorldStateMessageType[messageType]}`); } - const timer = new Timer(); + const start = process.hrtime.bigint(); const request = new TypedMessage(messageType, new MessageHeader({ messageId }), body); const encodedRequest = this.encoder.encode(request); - const encodingDuration = timer.ms(); + const encodingEnd = process.hrtime.bigint(); + const encodingDuration = Number(encodingEnd - start) / 1_000_000; let encodedResponse: any; try { @@ -214,7 +220,9 @@ export class NativeWorldState implements NativeWorldStateInstance { throw error; } - const callDuration = timer.ms() - encodingDuration; + const callEnd = process.hrtime.bigint(); + + const callDuration = Number(callEnd - encodingEnd) / 1_000_000; const buf = Buffer.isBuffer(encodedResponse) ? encodedResponse @@ -238,8 +246,9 @@ export class NativeWorldState implements NativeWorldStateInstance { } const response = TypedMessage.fromMessagePack(decodedResponse); - const decodingDuration = timer.ms() - callDuration; - const totalDuration = timer.ms(); + const decodingEnd = process.hrtime.bigint(); + const decodingDuration = Number(decodingEnd - callEnd) / 1_000_000; + const totalDuration = Number(decodingEnd - start) / 1_000_000; this.log.trace(`Call messageId=${messageId} ${WorldStateMessageType[messageType]} took (ms)`, { totalDuration, encodingDuration, @@ -257,6 +266,9 @@ export class NativeWorldState implements NativeWorldStateInstance { throw new Error('Invalid response message type: ' + response.msgType + ' != ' + messageType); } + const callDurationUs = Number(callEnd - encodingEnd) / 1000; + this.instrumentation.recordRoundTrip(callDurationUs, messageType); + return response.value; } } diff --git a/yarn-project/world-state/src/synchronizer/factory.ts b/yarn-project/world-state/src/synchronizer/factory.ts index f694d0d40724..690fcd03ce2d 100644 --- a/yarn-project/world-state/src/synchronizer/factory.ts +++ b/yarn-project/world-state/src/synchronizer/factory.ts @@ -1,12 +1,9 @@ import { type L1ToL2MessageSource, type L2BlockSource } from '@aztec/circuit-types'; -import { createLogger } from '@aztec/foundation/log'; import { type DataStoreConfig } from '@aztec/kv-store/config'; -import { createStore } from '@aztec/kv-store/lmdb'; import { type TelemetryClient } from '@aztec/telemetry-client'; -import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; +import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js'; import { NativeWorldStateService } from '../native/native_world_state.js'; -import { MerkleTrees } from '../world-state-db/merkle_trees.js'; import { type WorldStateConfig } from './config.js'; import { ServerWorldStateSynchronizer } from './server_world_state_synchronizer.js'; @@ -15,13 +12,14 @@ export async function createWorldStateSynchronizer( l2BlockSource: L2BlockSource & L1ToL2MessageSource, client: TelemetryClient, ) { - const merkleTrees = await createWorldState(config, client); - return new ServerWorldStateSynchronizer(merkleTrees, l2BlockSource, config, client); + const instrumentation = new WorldStateInstrumentation(client); + const merkleTrees = await createWorldState(config, instrumentation); + return new ServerWorldStateSynchronizer(merkleTrees, l2BlockSource, config, instrumentation); } export async function createWorldState( config: WorldStateConfig & DataStoreConfig, - client: TelemetryClient = new NoopTelemetryClient(), + instrumentation: WorldStateInstrumentation, ) { const newConfig = { dataDirectory: config.worldStateDataDirectory ?? config.dataDirectory, @@ -33,13 +31,12 @@ export async function createWorldState( } // If a data directory is provided in config, then create a persistent store. - const merkleTrees = ['true', '1'].includes(process.env.USE_LEGACY_WORLD_STATE ?? '') - ? await MerkleTrees.new(await createStore('world-state', newConfig, createLogger('world-state:lmdb')), client) - : newConfig.dataDirectory + const merkleTrees = newConfig.dataDirectory ? await NativeWorldStateService.new( config.l1Contracts.rollupAddress, newConfig.dataDirectory, newConfig.dataStoreMapSizeKB, + instrumentation, ) : await NativeWorldStateService.tmp( config.l1Contracts.rollupAddress, diff --git a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts index af06954bc9aa..c58ddf9ab05a 100644 --- a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts +++ b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts @@ -12,7 +12,6 @@ import { times } from '@aztec/foundation/collection'; import { randomInt } from '@aztec/foundation/crypto'; import { type Logger, createLogger } from '@aztec/foundation/log'; import { SHA256Trunc } from '@aztec/merkle-tree'; -import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; @@ -213,7 +212,7 @@ class TestWorldStateSynchronizer extends ServerWorldStateSynchronizer { worldStateConfig: WorldStateConfig, private mockBlockStream: L2BlockStream, ) { - super(merkleTrees, blockAndMessagesSource, worldStateConfig, new NoopTelemetryClient()); + super(merkleTrees, blockAndMessagesSource, worldStateConfig); } protected override createBlockStream(): L2BlockStream { diff --git a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts index c43ce6e8bde2..b0d590c3b2a7 100644 --- a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts +++ b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts @@ -23,12 +23,13 @@ import { createLogger } from '@aztec/foundation/log'; import { promiseWithResolvers } from '@aztec/foundation/promise'; import { elapsed } from '@aztec/foundation/timer'; import { SHA256Trunc } from '@aztec/merkle-tree'; -import { type TelemetryClient, TraceableL2BlockStream } from '@aztec/telemetry-client'; +import { TraceableL2BlockStream } from '@aztec/telemetry-client'; +import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; +import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js'; import { type WorldStateStatusFull } from '../native/message.js'; import { type MerkleTreeAdminDatabase } from '../world-state-db/merkle_tree_db.js'; import { type WorldStateConfig } from './config.js'; -import { WorldStateInstrumentation } from './instrumentation.js'; /** * Synchronizes the world state with the L2 blocks from a L2BlockSource via a block stream. @@ -47,16 +48,14 @@ export class ServerWorldStateSynchronizer private syncPromise = promiseWithResolvers(); protected blockStream: L2BlockStream | undefined; - private instrumentation: WorldStateInstrumentation; constructor( private readonly merkleTreeDb: MerkleTreeAdminDatabase, private readonly l2BlockSource: L2BlockSource & L1ToL2MessageSource, private readonly config: WorldStateConfig, - telemetry: TelemetryClient, + private instrumentation = new WorldStateInstrumentation(new NoopTelemetryClient()), private readonly log = createLogger('world_state'), ) { - this.instrumentation = new WorldStateInstrumentation(telemetry); this.merkleTreeCommitted = this.merkleTreeDb.getCommitted(); this.historyToKeep = config.worldStateBlockHistory < 1 ? undefined : config.worldStateBlockHistory; this.log.info( diff --git a/yarn-project/world-state/src/test/integration.test.ts b/yarn-project/world-state/src/test/integration.test.ts index 49bc37d0e3e1..daa105e78516 100644 --- a/yarn-project/world-state/src/test/integration.test.ts +++ b/yarn-project/world-state/src/test/integration.test.ts @@ -8,6 +8,7 @@ import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { jest } from '@jest/globals'; +import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js'; import { NativeWorldStateService } from '../native/native_world_state.js'; import { type WorldStateConfig } from '../synchronizer/config.js'; import { createWorldState } from '../synchronizer/factory.js'; @@ -52,8 +53,16 @@ describe('world-state integration', () => { archiver = new MockPrefilledArchiver(blocks, messages); - db = (await createWorldState(config)) as NativeWorldStateService; - synchronizer = new TestWorldStateSynchronizer(db, archiver, config, new NoopTelemetryClient()); + db = (await createWorldState( + config, + new WorldStateInstrumentation(new NoopTelemetryClient()), + )) as NativeWorldStateService; + synchronizer = new TestWorldStateSynchronizer( + db, + archiver, + config, + new WorldStateInstrumentation(new NoopTelemetryClient()), + ); log.info(`Created synchronizer`); }, 30_000); @@ -146,7 +155,12 @@ describe('world-state integration', () => { await expectSynchedToBlock(5); await synchronizer.stopBlockStream(); - synchronizer = new TestWorldStateSynchronizer(db, archiver, config, new NoopTelemetryClient()); + synchronizer = new TestWorldStateSynchronizer( + db, + archiver, + config, + new WorldStateInstrumentation(new NoopTelemetryClient()), + ); archiver.createBlocks(3); await synchronizer.start(); @@ -167,7 +181,7 @@ describe('world-state integration', () => { db, archiver, { ...config, worldStateProvenBlocksOnly: true }, - new NoopTelemetryClient(), + new WorldStateInstrumentation(new NoopTelemetryClient()), ); archiver.createBlocks(5); @@ -206,7 +220,7 @@ describe('world-state integration', () => { db, archiver, { ...config, worldStateBlockCheckIntervalMS: 1000 }, - new NoopTelemetryClient(), + new WorldStateInstrumentation(new NoopTelemetryClient()), ); });