diff --git a/yarn-project/circuit-types/src/stats/metrics.ts b/yarn-project/circuit-types/src/stats/metrics.ts index 479a3a87eb9d..347732a591b7 100644 --- a/yarn-project/circuit-types/src/stats/metrics.ts +++ b/yarn-project/circuit-types/src/stats/metrics.ts @@ -151,24 +151,72 @@ export const Metrics = [ description: 'Time to insert a batch of leaves into an append-only tree', events: ['tree-insertion'], }, + { + name: 'batch_insert_into_append_only_tree_16_depth_hash_count', + groupBy: 'leaf-count', + description: 'The number of hashes necessary to insert a batch of leaves into', + events: ['tree-insertion'], + }, + { + name: 'batch_insert_into_append_only_tree_16_depth_hash_ms', + groupBy: 'leaf-count', + description: 'Average duration for a hash operation', + events: ['tree-insertion'], + }, { name: 'batch_insert_into_append_only_tree_32_depth_ms', groupBy: 'leaf-count', description: 'Time to insert a batch of leaves into an append-only tree', events: ['tree-insertion'], }, + { + name: 'batch_insert_into_append_only_tree_32_depth_hash_count', + groupBy: 'leaf-count', + description: 'The number of hashes necessary to insert a batch of leaves into', + events: ['tree-insertion'], + }, + { + name: 'batch_insert_into_append_only_tree_32_depth_hash_ms', + groupBy: 'leaf-count', + description: 'Average duration for a hash operation', + events: ['tree-insertion'], + }, { name: 'batch_insert_into_indexed_tree_20_depth_ms', groupBy: 'leaf-count', description: 'Time to insert a batch of leaves into an indexed tree', events: ['tree-insertion'], }, + { + name: 'batch_insert_into_indexed_tree_20_depth_hash_count', + groupBy: 'leaf-count', + description: 'The number of hashes necessary to insert a batch of leaves into', + events: ['tree-insertion'], + }, + { + name: 'batch_insert_into_indexed_tree_20_depth_hash_ms', + groupBy: 'leaf-count', + description: 'Average duration for a hash operation', + events: ['tree-insertion'], + }, { name: 'batch_insert_into_indexed_tree_40_depth_ms', groupBy: 'leaf-count', description: 'Time to insert a batch of leaves into an indexed tree', events: ['tree-insertion'], }, + { + name: 'batch_insert_into_indexed_tree_40_depth_hash_count', + groupBy: 'leaf-count', + description: 'The number of hashes necessary to insert a batch of leaves into', + events: ['tree-insertion'], + }, + { + name: 'batch_insert_into_indexed_tree_40_depth_hash_ms', + groupBy: 'leaf-count', + description: 'Average duration for a hash operation', + events: ['tree-insertion'], + }, ] as const satisfies readonly Metric[]; /** Metric definitions to track from benchmarks. */ diff --git a/yarn-project/circuit-types/src/stats/stats.ts b/yarn-project/circuit-types/src/stats/stats.ts index b78b98ea0bea..74ca04d4005f 100644 --- a/yarn-project/circuit-types/src/stats/stats.ts +++ b/yarn-project/circuit-types/src/stats/stats.ts @@ -184,6 +184,10 @@ export type TreeInsertionStats = { treeDepth: number; /** Tree type */ treeType: 'append-only' | 'indexed'; + /** Number of hashes performed */ + hashCount: number; + /** Average duration of a hash operation */ + hashDuration: number; }; /** A new tx was added to the tx pool. */ diff --git a/yarn-project/merkle-tree/src/hasher_with_stats.ts b/yarn-project/merkle-tree/src/hasher_with_stats.ts new file mode 100644 index 000000000000..34d00d6b41ae --- /dev/null +++ b/yarn-project/merkle-tree/src/hasher_with_stats.ts @@ -0,0 +1,51 @@ +import { Hasher } from '@aztec/types/interfaces'; + +import { createHistogram, performance } from 'perf_hooks'; + +/** + * A helper class to track stats for a Hasher + */ +export class HasherWithStats implements Hasher { + hashCount = 0; + hashInputsCount = 0; + hashHistogram = createHistogram(); + hashInputsHistogram = createHistogram(); + + hash: Hasher['hash']; + hashInputs: Hasher['hashInputs']; + + constructor(hasher: Hasher) { + this.hash = performance.timerify( + (lhs, rhs) => { + this.hashCount++; + return hasher.hash(lhs, rhs); + }, + { histogram: this.hashHistogram }, + ); + this.hashInputs = performance.timerify( + (inputs: Buffer[]) => { + this.hashInputsCount++; + return hasher.hashInputs(inputs); + }, + { histogram: this.hashInputsHistogram }, + ); + } + + stats() { + return { + hashCount: this.hashCount, + // timerify records in ns, convert to ms + hashDuration: this.hashHistogram.mean / 1e6, + hashInputsCount: this.hashInputsCount, + hashInputsDuration: this.hashInputsHistogram.mean / 1e6, + }; + } + + reset() { + this.hashCount = 0; + this.hashHistogram.reset(); + + this.hashInputsCount = 0; + this.hashInputsHistogram.reset(); + } +} diff --git a/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts b/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts index 22297f55da9f..dcd56f31839d 100644 --- a/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts +++ b/yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts @@ -510,6 +510,7 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree { leaves: Buffer[], subtreeHeight: SubtreeHeight, ): Promise> { + this.hasher.reset(); const timer = new Timer(); const insertedKeys = new Map(); const emptyLowLeafWitness = getEmptyLowLeafWitness(this.getDepth() as TreeHeight, this.leafPreimageFactory); @@ -614,6 +615,7 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree { treeName: this.getName(), treeDepth: this.getDepth(), treeType: 'indexed', + ...this.hasher.stats(), } satisfies TreeInsertionStats); return { diff --git a/yarn-project/merkle-tree/src/standard_tree/standard_tree.ts b/yarn-project/merkle-tree/src/standard_tree/standard_tree.ts index cc9af7e28bf4..cc587ee3a3d1 100644 --- a/yarn-project/merkle-tree/src/standard_tree/standard_tree.ts +++ b/yarn-project/merkle-tree/src/standard_tree/standard_tree.ts @@ -17,6 +17,7 @@ export class StandardTree extends TreeBase implements AppendOnlyTree { * @returns Empty promise. */ public async appendLeaves(leaves: Buffer[]): Promise { + this.hasher.reset(); const timer = new Timer(); await super.appendLeaves(leaves); this.log(`Inserted ${leaves.length} leaves into ${this.getName()} tree`, { @@ -26,6 +27,7 @@ export class StandardTree extends TreeBase implements AppendOnlyTree { treeName: this.getName(), treeDepth: this.getDepth(), treeType: 'append-only', + ...this.hasher.stats(), } satisfies TreeInsertionStats); } diff --git a/yarn-project/merkle-tree/src/tree_base.ts b/yarn-project/merkle-tree/src/tree_base.ts index 2d32b032279a..36f9b12b4443 100644 --- a/yarn-project/merkle-tree/src/tree_base.ts +++ b/yarn-project/merkle-tree/src/tree_base.ts @@ -5,6 +5,7 @@ import { SiblingPath } from '@aztec/types/membership'; import { LevelUp, LevelUpChain } from 'levelup'; +import { HasherWithStats } from './hasher_with_stats.js'; import { MerkleTree } from './interfaces/merkle_tree.js'; const MAX_DEPTH = 254; @@ -40,9 +41,11 @@ export abstract class TreeBase implements MerkleTree { private cache: { [key: string]: Buffer } = {}; protected log: DebugLogger; + protected hasher: HasherWithStats; + public constructor( protected db: LevelUp, - protected hasher: Hasher, + hasher: Hasher, private name: string, private depth: number, protected size: bigint = 0n, @@ -52,6 +55,8 @@ export abstract class TreeBase implements MerkleTree { throw Error('Invalid depth'); } + this.hasher = new HasherWithStats(hasher); + // Compute the zero values at each layer. let current = INITIAL_LEAF; for (let i = depth - 1; i >= 0; --i) { diff --git a/yarn-project/scripts/src/benchmarks/aggregate.ts b/yarn-project/scripts/src/benchmarks/aggregate.ts index 04065d63aa3e..1b2235765d9e 100644 --- a/yarn-project/scripts/src/benchmarks/aggregate.ts +++ b/yarn-project/scripts/src/benchmarks/aggregate.ts @@ -171,14 +171,22 @@ function processTreeInsertion(entry: TreeInsertionStats, results: BenchmarkColle if (entry.treeType === 'append-only') { if (depth === 16) { append(results, 'batch_insert_into_append_only_tree_16_depth_ms', bucket, entry.duration); + append(results, 'batch_insert_into_append_only_tree_16_depth_hash_count', bucket, entry.hashCount); + append(results, 'batch_insert_into_append_only_tree_16_depth_hash_ms', bucket, entry.hashDuration); } else if (depth === 32) { append(results, 'batch_insert_into_append_only_tree_32_depth_ms', bucket, entry.duration); + append(results, 'batch_insert_into_append_only_tree_32_depth_hash_count', bucket, entry.hashCount); + append(results, 'batch_insert_into_append_only_tree_32_depth_hash_ms', bucket, entry.hashDuration); } } else if (entry.treeType === 'indexed') { if (depth === 20) { append(results, 'batch_insert_into_indexed_tree_20_depth_ms', bucket, entry.duration); + append(results, 'batch_insert_into_indexed_tree_20_depth_hash_count', bucket, entry.hashCount); + append(results, 'batch_insert_into_indexed_tree_20_depth_hash_ms', bucket, entry.hashDuration); } else if (depth === 40) { append(results, 'batch_insert_into_indexed_tree_40_depth_ms', bucket, entry.duration); + append(results, 'batch_insert_into_indexed_tree_40_depth_hash_count', bucket, entry.hashCount); + append(results, 'batch_insert_into_indexed_tree_40_depth_hash_ms', bucket, entry.hashDuration); } } }