Skip to content

Commit a2b267b

Browse files
authored
feat: add new metrics (AztecProtocol#3855)
This PR adds a couple of new metrics to track performance of tree insertions and tx processing.
1 parent a935ca3 commit a2b267b

File tree

11 files changed

+187
-10
lines changed

11 files changed

+187
-10
lines changed

yarn-project/merkle-tree/src/standard_indexed_tree/standard_indexed_tree.ts

+12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { toBigIntBE, toBufferBE } from '@aztec/foundation/bigint-buffer';
22
import { createDebugLogger } from '@aztec/foundation/log';
3+
import { Timer } from '@aztec/foundation/timer';
34
import { IndexedTreeLeaf, IndexedTreeLeafPreimage } from '@aztec/foundation/trees';
45
import { Hasher, SiblingPath } from '@aztec/types';
6+
import { TreeInsertionStats } from '@aztec/types/stats';
57

68
import { LevelUp } from 'levelup';
79

@@ -507,6 +509,7 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree {
507509
leaves: Buffer[],
508510
subtreeHeight: SubtreeHeight,
509511
): Promise<BatchInsertionResult<TreeHeight, SubtreeSiblingPathHeight>> {
512+
const timer = new Timer();
510513
const insertedKeys = new Map<bigint, boolean>();
511514
const emptyLowLeafWitness = getEmptyLowLeafWitness(this.getDepth() as TreeHeight, this.leafPreimageFactory);
512515
// Accumulators
@@ -603,6 +606,15 @@ export class StandardIndexedTree extends TreeBase implements IndexedTree {
603606
// inclusion. See {@link encodeLeaf} for a more through param explanation.
604607
await this.encodeAndAppendLeaves(pendingInsertionSubtree, false);
605608

609+
this.log(`Inserted ${leaves.length} leaves into ${this.getName()} tree`, {
610+
eventName: 'tree-insertion',
611+
duration: timer.ms(),
612+
batchSize: leaves.length,
613+
treeName: this.getName(),
614+
treeDepth: this.getDepth(),
615+
treeType: 'indexed',
616+
} satisfies TreeInsertionStats);
617+
606618
return {
607619
lowLeavesWitnessData: lowLeavesWitnesses,
608620
sortedNewLeaves: sortedDescendingLeafTuples.map(leafTuple => leafTuple.leaf.toBuffer()),

yarn-project/merkle-tree/src/standard_tree/standard_tree.ts

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { Timer } from '@aztec/foundation/timer';
2+
import { TreeInsertionStats } from '@aztec/types/stats';
3+
14
import { AppendOnlySnapshotBuilder, TreeSnapshot } from '../index.js';
25
import { AppendOnlyTree } from '../interfaces/append_only_tree.js';
36
import { TreeBase } from '../tree_base.js';
@@ -14,7 +17,16 @@ export class StandardTree extends TreeBase implements AppendOnlyTree {
1417
* @returns Empty promise.
1518
*/
1619
public async appendLeaves(leaves: Buffer[]): Promise<void> {
20+
const timer = new Timer();
1721
await super.appendLeaves(leaves);
22+
this.log(`Inserted ${leaves.length} leaves into ${this.getName()} tree`, {
23+
eventName: 'tree-insertion',
24+
duration: timer.ms(),
25+
batchSize: leaves.length,
26+
treeName: this.getName(),
27+
treeDepth: this.getDepth(),
28+
treeType: 'append-only',
29+
} satisfies TreeInsertionStats);
1830
}
1931

2032
public snapshot(block: number): Promise<TreeSnapshot> {

yarn-project/merkle-tree/src/tree_base.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { toBigIntLE, toBufferLE } from '@aztec/foundation/bigint-buffer';
2+
import { DebugLogger, createDebugLogger } from '@aztec/foundation/log';
23
import { Hasher, SiblingPath } from '@aztec/types';
34

45
import { LevelUp, LevelUpChain } from 'levelup';
@@ -36,6 +37,7 @@ export abstract class TreeBase implements MerkleTree {
3637
private root!: Buffer;
3738
private zeroHashes: Buffer[] = [];
3839
private cache: { [key: string]: Buffer } = {};
40+
protected log: DebugLogger;
3941

4042
public constructor(
4143
protected db: LevelUp,
@@ -58,6 +60,8 @@ export abstract class TreeBase implements MerkleTree {
5860

5961
this.root = root ? root : current;
6062
this.maxIndex = 2n ** BigInt(depth) - 1n;
63+
64+
this.log = createDebugLogger(`aztec:merkle-tree:${name}`);
6165
}
6266

6367
/**

yarn-project/pxe/src/pxe_service/pxe_service.ts

+8
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { padArrayEnd } from '@aztec/foundation/collection';
2323
import { Fr } from '@aztec/foundation/fields';
2424
import { SerialQueue } from '@aztec/foundation/fifo';
2525
import { DebugLogger, createDebugLogger } from '@aztec/foundation/log';
26+
import { Timer } from '@aztec/foundation/timer';
2627
import { NoirWasmVersion } from '@aztec/noir-compiler/versions';
2728
import {
2829
AuthWitness,
@@ -52,6 +53,7 @@ import {
5253
getNewContractPublicFunctions,
5354
isNoirCallStackUnresolved,
5455
} from '@aztec/types';
56+
import { TxPXEProcessingStats } from '@aztec/types/stats';
5557

5658
import { PXEServiceConfig, getPackageInfo } from '../config/index.js';
5759
import { ContractDataOracle } from '../contract_data_oracle/index.js';
@@ -353,7 +355,13 @@ export class PXEService implements PXE {
353355
const deployedContractAddress = txRequest.txContext.isContractDeploymentTx ? txRequest.origin : undefined;
354356
const newContract = deployedContractAddress ? await this.db.getContract(deployedContractAddress) : undefined;
355357

358+
const timer = new Timer();
356359
const tx = await this.#simulateAndProve(txRequest, newContract);
360+
this.log(`Processed private part of ${tx.data.end.newNullifiers[0]}`, {
361+
eventName: 'tx-pxe-processing',
362+
duration: timer.ms(),
363+
...tx.getStats(),
364+
} satisfies TxPXEProcessingStats);
357365
if (simulatePublic) {
358366
await this.#simulatePublicCalls(tx);
359367
}

yarn-project/scripts/src/benchmarks/aggregate.ts

+29
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ import {
2424
NodeSyncedChainHistoryStats,
2525
NoteProcessorCaughtUpStats,
2626
Stats,
27+
TreeInsertionStats,
2728
TxAddedToPoolStats,
29+
TxPXEProcessingStats,
30+
TxSequencerProcessingStats,
2831
} from '@aztec/types/stats';
2932

3033
import * as fs from 'fs';
@@ -151,6 +154,26 @@ function processTxAddedToPool(entry: TxAddedToPoolStats, results: BenchmarkColle
151154
append(results, 'tx_size_in_bytes', entry.newContractCount, entry.size);
152155
}
153156

157+
/** Process entries for events tx-private-part-processed, grouped by new commitments */
158+
function processTxPXEProcessingStats(entry: TxPXEProcessingStats, results: BenchmarkCollectedResults) {
159+
append(results, 'tx_pxe_processing_time_ms', entry.newCommitmentCount, entry.duration);
160+
}
161+
162+
/** Process entries for events tx-public-part-processed, grouped by public data writes */
163+
function processTxSequencerProcessingStats(entry: TxSequencerProcessingStats, results: BenchmarkCollectedResults) {
164+
append(results, 'tx_sequencer_processing_time_ms', entry.publicDataUpdateRequests, entry.duration);
165+
}
166+
167+
/** Process a tree insertion event and updates results */
168+
function processTreeInsertion(entry: TreeInsertionStats, results: BenchmarkCollectedResults) {
169+
const bucket = entry.batchSize;
170+
if (entry.treeType === 'append-only') {
171+
append(results, 'batch_insert_into_append_only_tree_ms', bucket, entry.duration);
172+
} else if (entry.treeType === 'indexed') {
173+
append(results, 'batch_insert_into_indexed_tree_ms', bucket, entry.duration);
174+
}
175+
}
176+
154177
/** Processes a parsed entry from a log-file and updates results */
155178
function processEntry(entry: Stats, results: BenchmarkCollectedResults) {
156179
switch (entry.eventName) {
@@ -168,6 +191,12 @@ function processEntry(entry: Stats, results: BenchmarkCollectedResults) {
168191
return processNodeSyncedChain(entry, results);
169192
case 'tx-added-to-pool':
170193
return processTxAddedToPool(entry, results);
194+
case 'tx-pxe-processing':
195+
return processTxPXEProcessingStats(entry, results);
196+
case 'tx-sequencer-processing':
197+
return processTxSequencerProcessingStats(entry, results);
198+
case 'tree-insertion':
199+
return processTreeInsertion(entry, results);
171200
default:
172201
return;
173202
}

yarn-project/scripts/src/benchmarks/markdown.ts

+25-5
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,13 @@ function getCell(
9595
row: string,
9696
col: string,
9797
) {
98-
const value = data[row][col];
98+
const value: number | undefined = data[row][col];
9999
const formattedValue = formatValue(value);
100-
const baseValue = base ? (base[row] ?? {})[col] : undefined;
101-
const percentDiff = baseValue ? Math.round(((value - baseValue) / baseValue) * 100) : undefined;
100+
const baseValue: number | undefined = base?.[row]?.[col];
101+
const percentDiff =
102+
typeof baseValue === 'number' && baseValue > 0 && typeof value === 'number'
103+
? Math.round(((value - baseValue) / baseValue) * 100)
104+
: undefined;
102105
if (!percentDiff || Math.abs(percentDiff) < 1) {
103106
return formattedValue;
104107
}
@@ -118,7 +121,11 @@ function withDesc(name: string) {
118121
}
119122

120123
/** Formats a numeric value for display. */
121-
function formatValue(value: number) {
124+
function formatValue(value: number | undefined): string {
125+
if (typeof value === 'undefined') {
126+
return 'N/A';
127+
}
128+
122129
if (value < 100) {
123130
return value.toPrecision(3);
124131
}
@@ -180,6 +187,10 @@ export function getMarkdown() {
180187
const metricsByChainLength = Metrics.filter(m => m.groupBy === 'chain-length').map(m => m.name);
181188
const metricsByCircuitName = Metrics.filter(m => m.groupBy === 'circuit-name').map(m => m.name);
182189
const metricsByContractCount = Metrics.filter(m => m.groupBy === 'contract-count').map(m => m.name);
190+
const metricsByLeafCount = Metrics.filter(m => m.groupBy === 'leaf-count').map(m => m.name);
191+
192+
const metricsTxPxeProcessing = Metrics.filter(m => m.name === 'tx_pxe_processing_time_ms').map(m => m.name);
193+
const metricsTxSeqProcessing = Metrics.filter(m => m.name === 'tx_sequencer_processing_time_ms').map(m => m.name);
183194

184195
const baseHash = process.env.BASE_COMMIT_HASH;
185196
const baseUrl = baseHash && `[\`${baseHash.slice(0, 8)}\`](${S3_URL}/benchmarks-v1/master/${baseHash}.json)`;
@@ -202,7 +213,7 @@ ${getWarningsSummary(benchmark, baseBenchmark)}
202213
203214
<summary>Detailed results</summary>
204215
205-
All benchmarks are run on txs on the \`Benchmarking\` contract on the repository. Each tx consists of a batch call to \`create_note\` and \`increment_balance\`, which guarantees that each tx has a private call, a nested private call, a public call, and a nested public call, as well as an emitted private note, an unencrypted log, and public storage read and write.
216+
All benchmarks are run on txs on the \`Benchmarking\` contract on the repository. Each tx consists of a batch call to \`create_note\` and \`increment_balance\`, which guarantees that each tx has a private call, a nested private call, a public call, and a nested public call, as well as an emitted private note, an unencrypted log, and public storage read and write.
206217
${prSourceDataText}
207218
${baseCommitText}
208219
@@ -221,11 +232,20 @@ ${getTableContent(pick(benchmark, metricsByChainLength), baseBenchmark, 'blocks'
221232
Stats on running time and I/O sizes collected for every circuit run across all benchmarks.
222233
${getTableContent(transpose(pick(benchmark, metricsByCircuitName)), transpose(baseBenchmark), '', 'Circuit')}
223234
235+
### Tree insertion stats
236+
237+
The duration to insert a fixed batch of leaves into each tree type.
238+
${getTableContent(pick(benchmark, metricsByLeafCount), baseBenchmark, 'leaves')}
239+
224240
### Miscellaneous
225241
226242
Transaction sizes based on how many contracts are deployed in the tx.
227243
${getTableContent(pick(benchmark, metricsByContractCount), baseBenchmark, 'deployed contracts')}
228244
245+
Transaction processing duration by data writes.
246+
${getTableContent(pick(benchmark, metricsTxPxeProcessing), baseBenchmark, 'new commitments')}
247+
${getTableContent(pick(benchmark, metricsTxSeqProcessing), baseBenchmark, 'public data writes')}
248+
229249
</details>
230250
${COMMENT_MARK}
231251
`;

yarn-project/sequencer-client/src/sequencer/public_processor.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ import { computeVarArgsHash } from '@aztec/circuits.js/abis';
4444
import { arrayNonEmptyLength, isArrayEmpty, padArrayEnd } from '@aztec/foundation/collection';
4545
import { createDebugLogger } from '@aztec/foundation/log';
4646
import { to2Fields } from '@aztec/foundation/serialize';
47+
import { Timer } from '@aztec/foundation/timer';
4748
import { ContractDataSource, FunctionL2Logs, L1ToL2MessageSource, MerkleTreeId, Tx } from '@aztec/types';
49+
import { TxSequencerProcessingStats } from '@aztec/types/stats';
4850
import { MerkleTreeOperations } from '@aztec/world-state';
4951

5052
import { getVerificationKeys } from '../index.js';
@@ -159,12 +161,23 @@ export class PublicProcessor {
159161

160162
protected async processTx(tx: Tx): Promise<ProcessedTx> {
161163
if (!isArrayEmpty(tx.data.end.publicCallStack, item => item.isEmpty())) {
164+
const timer = new Timer();
165+
162166
const [publicKernelOutput, publicKernelProof, newUnencryptedFunctionLogs] = await this.processEnqueuedPublicCalls(
163167
tx,
164168
);
165169
tx.unencryptedLogs.addFunctionLogs(newUnencryptedFunctionLogs);
166170

167-
return makeProcessedTx(tx, publicKernelOutput, publicKernelProof);
171+
const processedTransaction = await makeProcessedTx(tx, publicKernelOutput, publicKernelProof);
172+
this.log(`Processed public part of ${tx.data.end.newNullifiers[0]}`, {
173+
eventName: 'tx-sequencer-processing',
174+
duration: timer.ms(),
175+
publicDataUpdateRequests:
176+
processedTransaction.data.end.publicDataUpdateRequests.filter(x => !x.leafSlot.isZero()).length ?? 0,
177+
...tx.getStats(),
178+
} satisfies TxSequencerProcessingStats);
179+
180+
return processedTransaction;
168181
} else {
169182
return makeProcessedTx(tx);
170183
}

yarn-project/types/src/logs/log_filter.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AztecAddress, FunctionSelector } from '@aztec/circuits.js';
22

3-
import { TxHash } from '../tx/index.js';
3+
import { TxHash } from '../tx/tx_hash.js';
44
import { LogId } from './log_id.js';
55

66
/**

yarn-project/types/src/stats/metrics.ts

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { StatsEventName } from './stats.js';
22

33
/** How a metric is grouped in benchmarks: by block size, by length of chain processed, or by circuit name. */
4-
export type MetricGroupBy = 'block-size' | 'chain-length' | 'circuit-name' | 'contract-count';
4+
export type MetricGroupBy =
5+
| 'block-size'
6+
| 'chain-length'
7+
| 'circuit-name'
8+
| 'contract-count'
9+
| 'leaf-count'
10+
| 'data-writes';
511

612
/** Definition of a metric to track in benchmarks. */
713
export interface Metric {
@@ -127,6 +133,30 @@ export const Metrics = [
127133
description: 'Size of txs received in the mempool.',
128134
events: ['tx-added-to-pool'],
129135
},
136+
{
137+
name: 'tx_pxe_processing_time_ms',
138+
groupBy: 'data-writes',
139+
description: 'Time to process the private part of a tx.',
140+
events: ['tx-pxe-processing'],
141+
},
142+
{
143+
name: 'tx_sequencer_processing_time_ms',
144+
groupBy: 'data-writes',
145+
description: 'Time to process the public part of a tx.',
146+
events: ['tx-sequencer-processing'],
147+
},
148+
{
149+
name: 'batch_insert_into_append_only_tree_ms',
150+
groupBy: 'leaf-count',
151+
description: 'Time to insert a batch of leaves into an append-only tree',
152+
events: ['tree-insertion'],
153+
},
154+
{
155+
name: 'batch_insert_into_indexed_tree_ms',
156+
groupBy: 'leaf-count',
157+
description: 'Time to insert a batch of leaves into an indexed tree',
158+
events: ['tree-insertion'],
159+
},
130160
] as const satisfies readonly Metric[];
131161

132162
/** Metric definitions to track from benchmarks. */

yarn-project/types/src/stats/stats.ts

+48-1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,50 @@ export type TxStats = {
138138
newContractDataSize: number;
139139
/** Number of new contracts deployed in this tx. */
140140
newContractCount: number;
141+
/** comm */
142+
newCommitmentCount: number;
143+
/** a */
144+
newNullifierCount: number;
145+
};
146+
147+
/**
148+
* Stats for a tx that has been processed by the public processor.
149+
*/
150+
export type TxPXEProcessingStats = {
151+
/** Name of the event. */
152+
eventName: 'tx-pxe-processing';
153+
/** Duration in ms. */
154+
duration: number;
155+
} & TxStats;
156+
157+
/**
158+
* Stats for a tx that has been processed by the public processor.
159+
*/
160+
export type TxSequencerProcessingStats = {
161+
/** Name of the event. */
162+
eventName: 'tx-sequencer-processing';
163+
/** Duration in ms. */
164+
duration: number;
165+
/** Count of how many public writes this tx has made. Acts as a proxy for how 'heavy' this tx */
166+
publicDataUpdateRequests: number;
167+
} & TxStats;
168+
169+
/**
170+
* Stats for tree insertions
171+
*/
172+
export type TreeInsertionStats = {
173+
/** Name of the event. */
174+
eventName: 'tree-insertion';
175+
/** Duration in ms. */
176+
duration: number;
177+
/** The size of the insertion batch */
178+
batchSize: number;
179+
/** The tree name */
180+
treeName: string;
181+
/** The tree depth */
182+
treeDepth: number;
183+
/** Tree type */
184+
treeType: 'append-only' | 'indexed';
141185
};
142186

143187
/** A new tx was added to the tx pool. */
@@ -154,7 +198,10 @@ export type Stats =
154198
| L2BlockBuiltStats
155199
| L2BlockHandledStats
156200
| NoteProcessorCaughtUpStats
157-
| TxAddedToPoolStats;
201+
| TxAddedToPoolStats
202+
| TxPXEProcessingStats
203+
| TxSequencerProcessingStats
204+
| TreeInsertionStats;
158205

159206
/** Set of event names across emitted stats. */
160207
export type StatsEventName = Stats['eventName'];

0 commit comments

Comments
 (0)