Performance Profiler
Tevm provides two inspection levels:
createTrace: truereturns a serializable per-opcode trace for one call.profiler: trueenables the low-level EVM performance log collector.
Use traces for application tooling and profiler logs when investigating the EVM implementation itself.
Profile a Call with a Trace
import { createMemoryClient, PREFUNDED_ACCOUNTS } from 'tevm'
const client = createMemoryClient()
const contract = '0x4444444444444444444444444444444444444444'
await client.tevmSetAccount({
address: contract,
deployedBytecode: '0x6001600055',
})
const result = await client.tevmCall({
from: PREFUNDED_ACCOUNTS[0].address,
to: contract,
createTrace: true,
})
const gasByOpcode = new Map<string, bigint>()
for (const step of result.trace?.structLogs ?? []) {
gasByOpcode.set(
step.op,
(gasByOpcode.get(step.op) ?? 0n) + step.gasCost,
)
}
console.log(result.executionGasUsed, gasByOpcode)executionGasUsed covers EVM execution. totalGasSpent also includes intrinsic transaction costs when available.
Stream Live Steps
import { createMemoryClient } from 'tevm'
const client = createMemoryClient()
let stepCount = 0
await client.tevmCall({
deployedBytecode: '0x6001600055',
onStep(step, next) {
stepCount += 1
console.log(step.pc, step.opcode.name, step.gasLeft)
next?.()
},
})
console.log(stepCount)Live callbacks avoid retaining a full trace but add work to every opcode. Keep handlers small and always call next?.().
Low-Level Profiler Logs
import { createMemoryClient } from 'tevm'
const client = createMemoryClient({ profiler: true })
await client.tevmReady()
await client.tevmCall({
deployedBytecode: '0x6001600055',
})
const vm = await client.transport.tevm.getVm()
const logs = vm.evm.getPerformanceLogs()
console.log(logs)Profiler mode has runtime and memory overhead. Enable it only around measurements, warm up initialization before timing, and compare the same bytecode and chain configuration.

