Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Performance & Profiler

Tevm Node ships a built-in profiler that tracks execution time, gas, and other metrics per EVM operation.

Quick Start

Basic Setup

import { createTevmNode } from 'tevm'
 
const node = createTevmNode({
  profiler: {
    enabled: true,
    includeOpcodes: true,
    includePrecompiles: true,
  }
})
 
const vm = await node.getVm()
await vm.runTx({ /* transaction details */ })
 
const logs = vm.evm.getPerformanceLogs()
vm.evm.clearPerformanceLogs()

Complete Example

import { createTevmNode } from 'tevm'
 
const node = createTevmNode({
  profiler: {
    enabled: true,
    includeOpcodes: true,
    includePrecompiles: true,
    includeMemory: true,
    includeStorage: true,
    callStackDepth: 10,
  }
})
 
const vm = await node.getVm()
const deployTx = createTx({ /* deployment transaction */ })
await vm.runTx({ tx: deployTx })
const contractAddress = deployTx.createdAddress
 
vm.evm.clearPerformanceLogs()
 
const callTx = createTx({
  to: contractAddress,
  data: '0xa9059cbb000000000000000000000000123...',
})
await vm.runTx({ tx: callTx })
 
const logs = vm.evm.getPerformanceLogs()
const analysis = analyzePerformance(logs)
 
console.log(`Total execution time: ${analysis.totalTime}ms`)
console.log(`Total gas used: ${analysis.totalGas}`)
console.log(`Hotspots: ${JSON.stringify(analysis.hotspots)}`)

Profiler Configuration

  • Opcode Tracking — execution time and gas per opcode
  • Call Stack Analysis — full call tree
  • Gas Analysis — most gas-hungry operations
  • Memory Usage — allocation and access patterns
  • Storage I/O — SLOAD/SSTORE impact
  • Precompile Usage — built-in and custom precompiles

Log Types

All log entries share a base shape:

interface PerformanceLog {
  type: 'opcode' | 'precompile' | 'call' | 'create'
  startTime: number     // Performance.now() start
  endTime: number       // Performance.now() end
  executionTime: number // ms
  gasUsed?: bigint
}
interface OpcodeLog extends PerformanceLog {
  type: 'opcode'
  opcode: string  // e.g. "ADD", "SSTORE"
  pc: number
  stack?: bigint[]
}
interface CallLog extends PerformanceLog {
  type: 'call'
  from: string
  to: string
  value: bigint
  input: Uint8Array
  depth: number
}
interface PrecompileLog extends PerformanceLog {
  type: 'precompile'
  address: string
  name: string
  input: Uint8Array
}

Performance Analysis

Opcode Analysis

const opcodeStats = logs
  .filter(log => log.type === 'opcode')
  .reduce((acc, log) => {
    const key = log.opcode
    acc[key] = acc[key] || { count: 0, totalTime: 0, totalGas: 0n }
    acc[key].count++
    acc[key].totalTime += log.executionTime
    acc[key].totalGas += log.gasUsed ?? 0n
    return acc
  }, {})
 
const expensiveOpsByTime = Object.entries(opcodeStats)
  .sort(([, a], [, b]) => b.totalTime - a.totalTime)
  .slice(0, 5)
  .map(([opcode, stats]) => ({
    opcode,
    count: stats.count,
    totalTimeMs: stats.totalTime.toFixed(2),
    avgTimeMs: (stats.totalTime / stats.count).toFixed(2)
  }))
 
console.table(expensiveOpsByTime)

Call Tree Analysis

const callTree = logs
  .filter(log => log.type === 'call')
  .map(log => ({
    from: log.from,
    to: log.to,
    executionTime: log.executionTime,
    gasUsed: log.gasUsed,
    depth: log.depth
  }))
 
const contractTimes = callTree.reduce((acc, call) => {
  acc[call.to] = (acc[call.to] || 0) + call.executionTime
  return acc
}, {})
 
const slowestContracts = Object.entries(contractTimes)
  .sort(([, a], [, b]) => b - a)
  .slice(0, 3)
  .map(([address, time]) => `${address}: ${time.toFixed(2)}ms`)

Gas Analysis

const gasTimeline = logs
  .filter(log => log.gasUsed !== undefined)
  .map(log => ({ timestamp: log.startTime, gasUsed: log.gasUsed, type: log.type }))
 
const gasEfficiencyByType = gasTimeline.reduce((acc, log) => {
  acc[log.type] = acc[log.type] || { totalGas: 0n, totalTime: 0, count: 0 }
  acc[log.type].totalGas += log.gasUsed
  acc[log.type].totalTime += log.executionTime
  acc[log.type].count++
  return acc
}, {})
 
Object.entries(gasEfficiencyByType).forEach(([type, stats]) => {
  const gasPerMs = Number(stats.totalGas) / stats.totalTime
  console.log(`${type}: ${gasPerMs.toFixed(2)} gas/ms (${stats.count} operations)`)
})

Use Cases

Smart Contract Optimization

const vm = await node.getVm()
const deployTx = createTx({ data: contractBytecode })
await vm.runTx({ tx: deployTx })
const contractAddress = deployTx.createdAddress
 
vm.evm.clearPerformanceLogs()
 
const functionCallTx = createTx({
  to: contractAddress,
  data: encodeFunctionData({ abi, functionName: 'expensiveFunction', args: [param1, param2] })
})
 
await vm.runTx({ tx: functionCallTx })
const logs = vm.evm.getPerformanceLogs()
const hotspots = identifyHotspots(logs)
console.log('Optimization targets:', hotspots)

Compare Different Implementations

async function compareImplementations(implementations) {
  const results = []
  const vm = await node.getVm()
 
  for (const impl of implementations) {
    vm.evm.clearPerformanceLogs()
    const deployTx = createTx({ data: impl.bytecode })
    await vm.runTx({ tx: deployTx })
 
    const callTx = createTx({ to: deployTx.createdAddress, data: impl.encodedFunctionCall })
    await vm.runTx({ tx: callTx })
    const logs = vm.evm.getPerformanceLogs()
    results.push(analyzePerformance(logs, impl.name))
  }
 
  return compareResults(results)
}
 
// Example output:
// Implementation A: 1.2ms, 45,000 gas
// Implementation B: 0.8ms, 32,000 gas (29% improvement)

Gas Optimization

const contractGasUsage = logs
  .filter(log => log.type === 'opcode' && log.gasUsed)
  .reduce((acc, log) => {
    const pcKey = log.pc.toString().padStart(4, '0')
    acc[pcKey] = acc[pcKey] || { opcode: log.opcode, count: 0, totalGas: 0n }
    acc[pcKey].count++
    acc[pcKey].totalGas += log.gasUsed
    return acc
  }, {})
 
const gasHotspots = Object.entries(contractGasUsage)
  .sort(([, a], [, b]) => Number(b.totalGas - a.totalGas))
  .slice(0, 10)

Best Practices

Targeted Profiling

Profile specific operations in isolation for accurate results.

vm.evm.clearPerformanceLogs()
await vm.runTx({ tx: specificOperationTx })
 
const logs = vm.evm.getPerformanceLogs()
const analysis = analyzePerformance(logs)
 
vm.evm.clearPerformanceLogs()

Memory Management

async function profileWithMemoryManagement(operations) {
  const results = []
 
  for (const op of operations) {
    vm.evm.clearPerformanceLogs()
    await vm.runTx({ tx: op.tx })
 
    const logs = vm.evm.getPerformanceLogs()
    const summary = summarizeLogs(logs)
 
    results.push({ operation: op.name, summary })
    vm.evm.clearPerformanceLogs()
  }
 
  return results
}

Comparative Analysis

Always measure before/after optimization.

async function measureOptimizationImpact(originalCode, optimizedCode) {
  vm.evm.clearPerformanceLogs()
  await runImplementation(originalCode)
  const beforeMetrics = analyzePerformance(vm.evm.getPerformanceLogs())
 
  vm.evm.clearPerformanceLogs()
  await runImplementation(optimizedCode)
  const afterMetrics = analyzePerformance(vm.evm.getPerformanceLogs())
 
  return {
    timeImprovement: (1 - afterMetrics.totalTime / beforeMetrics.totalTime) * 100,
    gasImprovement: (1 - Number(afterMetrics.totalGas) / Number(beforeMetrics.totalGas)) * 100,
    details: compareMetrics(beforeMetrics, afterMetrics)
  }
}

Production Use

const node = createTevmNode({
  profiler: {
    enabled: process.env.ENABLE_PROFILING === 'true',
    includeOpcodes: false,
    samplingRate: 0.01,         // Profile 1% of operations
    includeMemory: false,
    includeStorage: false,
    includeHighLevelCalls: true
  }
})

Related Resources