Docs
Testing

Gas profiling and snapshots

Profile gas hot paths, inspect flamegraphs in Test UI, and compare gas snapshots in CI

Gas profiling records gas and fee metrics for message opcodes and transaction chains. Performance regressions show up in the --snapshot and --baseline-snapshot outputs. For source-level hot paths, Acton can also write gas-weighted execution profiles with --gas-profile and render them in Test UI.

What gets profiled

Snapshot profiling tracks aggregate gas and fee metrics:

  • Opcode gas usage table: min, max, and avg gas per opcode or message opcode name.
  • Per test gas and fees summary.
  • Per trace gas and fees details inside each test.

For chain-level metrics, Acton tracks: transaction count, gas used, gas fee, forward fee, and the total fee.

Source-level execution profiles track sampled VM execution stacks and weight each sample by gas delta. They answer questions such as "which function inside this contract consumed gas?" instead of only "which trace consumed gas?"

Profiling artifacts are collected after the selected tests pass. If any selected test fails, Acton skips snapshot writing, comparison tables, and gas profile files for that run.

Quick start

Create a baseline snapshot

acton test --snapshot gas-baseline.json

This runs tests and writes a JSON snapshot of opcode and trace-chain statistics:

terminal
 GAS USAGE

───────────────────────────────────────────────
 Opcode            Min Gas   Max Gas   Avg Gas
───────────────────────────────────────────────
 IncreaseCounter      1536      1536      1536
 ResetCounter         1349      1349      1349
 0x00000999            680       680       680

 CHAIN GAS & FEES SUMMARY

──────────────────────────────────────────────────────────────────────────────────────────────────
 Test                    Traces   Tx   Gas Used   Gas Fee         Forward Fee       Total Fee
──────────────────────────────────────────────────────────────────────────────────────────────────
 test increase counter        4    4       2789   0.0011156 GRAM   0.002000284 GRAM   0.0011156 GRAM
 test reset counter           5    5       4138   0.0016552 GRAM   0.002266953 GRAM   0.0016552 GRAM
 test unknown message         5    5       2568   0.0010272 GRAM   0.002266953 GRAM   0.0010272 GRAM

Compare current run with baseline

acton test --baseline-snapshot gas-baseline.json

This prints comparison tables with diffs for opcode gas and chain metrics.

terminal
 GAS USAGE COMPARISON

Baseline: gas-baseline.json (3 opcodes, captured 2026-02-28 14:49:39 UTC)

────────────────────────────────────────────────────────
 Opcode            Baseline   Current   Diff   % Change
────────────────────────────────────────────────────────
 IncreaseCounter       1536      1398   -138      -9.0%
 ResetCounter          1349      1349     +0      +0.0%
 0x00000999             680       680     +0      +0.0%

 CHAIN GAS & FEES SUMMARY COMPARISON

Baseline: gas-baseline.json (14 traces, captured 2026-02-28 14:49:39 UTC)

──────────────────────────────────────────────────────────────────────────────────
 Test                    Traces   Tx   Gas Used   Gas Fee          Total Fee
──────────────────────────────────────────────────────────────────────────────────
 test increase counter        4    4       2789    0.0011156 GRAM    0.0011156 GRAM
                              4    4       2651    0.0010604 GRAM    0.0010604 GRAM
                                           -138   -0.0000552 GRAM   -0.0000552 GRAM
 test reset counter           5    5       4138    0.0016552 GRAM    0.0016552 GRAM
                              5    5       4000       0.0016 GRAM       0.0016 GRAM
                                           -138   -0.0000552 GRAM   -0.0000552 GRAM
 test unknown message         5    5       2568    0.0010272 GRAM    0.0010272 GRAM
terminal
 CHAIN GAS & FEES · test increase counter

────────────────────────────────────────────────────────────────────────────────────────────
 Trace             Tx Count   Gas Used   Gas Fee          Fwd Fee            Total Fee
────────────────────────────────────────────────────────────────────────────────────────────
 Trace 1                  1        309    0.0001236 GRAM    0.000539205 GRAM    0.0001236 GRAM
 Trace 2                  1        309    0.0001236 GRAM    0.000547738 GRAM    0.0001236 GRAM
 Trace 3                  1        635     0.000254 GRAM    0.000646672 GRAM     0.000254 GRAM
                          1        635     0.000254 GRAM    0.000629605 GRAM     0.000254 GRAM
                                                          -0.000017067 GRAM
 increase counter         1       1536    0.0006144 GRAM    0.000266669 GRAM    0.0006144 GRAM
                          1       1398    0.0005592 GRAM    0.000266669 GRAM    0.0005592 GRAM
                                  -138   -0.0000552 GRAM                      -0.0000552 GRAM

How to read row groups

In comparison tables, changed chains are shown as baseline row, current row, and diff row.

Write a source-level execution profile

acton test --gas-profile build/gas.cpuprofile

This writes a Chrome DevTools-compatible .cpuprofile where sample weights are gas deltas rather than elapsed time. For flamegraph-style tooling, use folded stacks:

acton test --gas-profile build/gas.collapsed --gas-profile-format collapsed

Execution profiles record contract message transactions by default. Add --gas-profile-include-tests if you also need .test.tolk unit-test get-method execution in the profile. This is useful when the expensive work is called directly from a unit test:

get fun `test heavy job`() {
    var x = X.create();
    x.heavyJob();
}

Inspect profiles in Test UI

Run the UI together with a gas profile:

acton test --gas-profile build/gas.cpuprofile --ui

Test UI adds a top-level Profile tab when a gas profile is available. The profile is grouped by contract, and each contract view shows a flamegraph plus details for the selected frame: source location, total gas, self gas, and share inside the contract.

When --gas-profile-include-tests is enabled, tests with recorded unit execution also get a Profile tab next to Info, Transactions, and Logs. That per-test view hides Acton runtime frames from @acton/.acton paths so the test hot path stays focused on user code. Exported cpuprofile and collapsed files remain complete and unfiltered.

Fail CI on drift

Use strict mode to return non-zero exit code when baseline and current profile differ:

acton test --baseline-snapshot gas-baseline.json --fail-on-diff

To enable it by default, set the fail-on-diff to true in the Acton.toml:

Acton.toml
[test]
fail-on-diff = true

Update the baseline on purpose

If changes are expected, recreate baseline:

acton test --snapshot gas-baseline.json

Compare mode does not overwrite snapshot

With both --snapshot and --baseline-snapshot, Acton runs in compare mode and does not rewrite the snapshot file.

Name trace chains for readable reports

Chain reports are much easier to read when traces have explicit names.

Use SendResultList.giveName(...) after net.send(...):

import "@acton/emulation/network"

get fun `test transfer flow`() {
    // ...
    val txs = net.send(sender.address, msg);
    txs.giveName("transfer-wallet-to-wallet");
    expect(txs).toHaveAllSuccessfulTxs();
}

Without custom names, traces are shown as Trace 1, Trace 2, and so on.

How baseline matching works

Baseline comparison is key-based rather than fuzzy. Opcode rows are keyed by the resolved opcode label.

If ABI metadata resolves the opcode, the key is the message name such as IncreaseCounter. Otherwise, the key falls back to the raw hex opcode such as 0x00000999.

Trace-chain rows are keyed by a stable internal snapshot key:

<test-name>::trace#<1-based trace index>

That key comes from the test name plus the 1-based position of the trace in that test's execution order. trace_name is stored only for display. Renaming a trace with giveName(...) makes reports easier to read, but it does not change how baseline matching works.

Read comparison output

In baseline comparison mode:

  • New opcodes and traces are shown as NEW
  • Higher usage is highlighted as regression
  • Lower usage is highlighted as improvement
  • Unchanged rows are dimmed

Stabilize test data first

For meaningful diffs, keep tests deterministic: use fixed inputs, stable send order, and no random or time-dependent behavior unless controlled explicitly.

Snapshot file format

Gas snapshot is stored as JSON with this high-level structure:

{
  "timestamp": 1772290179,
  "opcodes": {
    "IncreaseCounter": {
      "min_gas": 1536,
      "max_gas": 1536,
      "avg_gas": 1536,
      "samples": 2,
      "all_values": [1536, 1536]
    }
  },
  "trace_chains": {
    "test reset counter::trace#3": {
      "test_name": "test reset counter",
      "trace_name": "Trace 3",
      "tx_count": 1,
      "total_gas_used": 635,
      "total_gas_fees": 254000,
      "total_forward_fees": 646672,
      "total_fees": 254000
    }
  }
}

Flags that pair well with profiling

# Profile only selected tests
acton test --filter "transfer.*" --snapshot gas-baseline.json

# Profile one or more suites/files
acton test tests/wallet.test.tolk tests/jetton --baseline-snapshot gas-baseline.json

Troubleshooting

Snapshot file was not created

Snapshot is written only when:

  • --snapshot is set;
  • --baseline-snapshot is not set;
  • selected tests passed;
  • profiling collected at least one opcode or trace-chain metric.

Trace names changed but diff mapping is still stable

Trace comparison is matched by stable snapshot key, while trace_name is used for display: <test-name>::trace#<index>.

--fail-on-diff is stricter than the tables

The comparison tables mainly show aggregated numbers such as averages and fee totals. Strict drift detection compares the full snapshot content except for the top-level timestamp.

That means --fail-on-diff treats these as real drift:

  • opcode sample-count changes
  • min_gas, max_gas, or avg_gas changes
  • differences in per-opcode all_values
  • per-trace tx_count or fee changes
  • changed trace_name for the same snapshot key

For stable CI baselines, keep both the execution order and the profiled test data deterministic.

Last updated on

On this page