Skip to content

Add debug_traceTransactionProfile endpoint#3267

Merged
Kbhat1 merged 15 commits into
mainfrom
kartik/trace-profile-endpoint
Apr 24, 2026
Merged

Add debug_traceTransactionProfile endpoint#3267
Kbhat1 merged 15 commits into
mainfrom
kartik/trace-profile-endpoint

Conversation

@Kbhat1

@Kbhat1 Kbhat1 commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Describe your changes and provide context

  • Adds debug_traceTransactionProfile, a trace endpoint that returns the normal tx trace plus a granular timing/profile breakdown
  • Captures store-level access data during tracing, including module stats, iterator activity, and low-level read events when supported
  • Cleans up trace-scoped resources after each request to avoid leaking snapshots or iterators.

Testing performed to validate your change

  • Verified end to end on node

Adds a granular tracing layer on top of the existing debug_trace path so
operators can see where time is spent during a historical trace.

New RPC endpoint:
- debug_traceTransactionProfile returns the normal trace result plus a
  profile payload that breaks the request into high-level phases
  (block lookup, historical-state replay, tx prep, execution, tracer
  result), and dumps per-module store access stats, iterator stats, and
  any low-level DB read events collected during the trace.

Store tracer plumbing:
- Introduce a StoreTracer on sdk.Context that records per-module
  accesses, iterator lifecycle events, and low-level ReadTraceEvents.
- Thread the tracer through gaskv, cachekv, and tracekv so every store
  operation is timed; cachekv/tracekv forward the collector to their
  parents so the tracer reaches the state store underneath.
- Wire storev2/state.Store to optionally swap its underlying StateStore
  for a traceable wrapper via SetReadTraceCollector, and add forwarding
  WithReadTraceCollector shims on the composite/cosmos/evm SS-layer
  stores so a collector injected from the top reaches the MVCC layer
  when it implements TraceableStateStore.

Lifecycle:
- TraceTransactionProfile (and TraceStateAccess) defer a cleanup that
  closes any trace-scoped resources (Pebble snapshots, reusable
  iterators) attached during the request.

Reporting CLI:
- Add seidb trace-profile-report which takes an RPC endpoint and a
  block range, calls debug_traceTransactionProfile for every tx,
  persists the raw responses, and emits a summary + interactive HTML
  report (phase breakdown, hot modules, hot low-level ops, per-tx
  table).

The low-level Pebble read events populate when the MVCC layer
implements TraceableStateStore (shipped in the companion perf PR); if
that PR is not merged, the profile still shows phase timings, module
access stats, and iterator stats, but the low-level MVCC breakdown will
be empty.

Made-with: Cursor
@github-actions

github-actions Bot commented Apr 16, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedApr 24, 2026, 4:01 PM

@Kbhat1 Kbhat1 changed the title feat: add debug_traceTransactionProfile endpoint and reporting tool Add debug_traceTransactionProfile endpoint and reporting tool Apr 17, 2026
outputDir is a CLI flag on this offline seidb reporting tool, not a
request-level input; suppress the file-inclusion warning with a
nolint:gosec comment matching conventions elsewhere in sei-db/tools.
@Kbhat1 Kbhat1 changed the title Add debug_traceTransactionProfile endpoint and reporting tool Add debug_traceTransactionProfile endpoint Apr 17, 2026
Audit found the closer plumbing on StoreTracer is dormant: nothing in the
repo calls AddReadTraceCloser, so readClosers is always empty and both
CloseReadTraceResources and the closeStoreTraceResources wrapper in
evmrpc are no-ops. Remove all of it. When low-level (pebbledb) read
tracing gets wired in via TraceableStateStore in a follow-up, the closer
side can return with the add side in one change.

Also add doc comments to the newly-introduced exported surface that the
profile endpoint depends on: StoreTracer and its types
(ModuleTrace/IteratorTrace/Access/OpType), record methods
(Get/Set/Has/Delete/StartIterator/RecordIteratorValue/RecordIteratorNext),
Dump/DerivePrestateToJson/RecordReadTrace, and the seidb-db types
(ReadTraceEvent/ReadTraceCollector/TraceableStateStore/NewReadTraceEvent).
Also annotate the per-tx sample caps with the reason they exist.

No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Apr 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 40.98039% with 301 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.18%. Comparing base (e9d506b) to head (98bd98a).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...tools/cmd/seidb/operations/trace_profile_report.go 0.00% 258 Missing ⚠️
evmrpc/trace_profile.go 69.51% 13 Missing and 12 partials ⚠️
sei-cosmos/types/tracer.go 89.47% 10 Missing and 4 partials ⚠️
evmrpc/block_trace_profiled.go 81.81% 2 Missing ⚠️
sei-db/tools/cmd/seidb/main.go 0.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3267      +/-   ##
==========================================
- Coverage   59.19%   59.18%   -0.02%     
==========================================
  Files        2091     2072      -19     
  Lines      171432   169472    -1960     
==========================================
- Hits       101481   100295    -1186     
+ Misses      61152    60420     -732     
+ Partials     8799     8757      -42     
Flag Coverage Δ
sei-chain-pr 69.43% <40.98%> (?)
sei-db 70.41% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sei-cosmos/store/gaskv/store.go 93.96% <100.00%> (+0.69%) ⬆️
evmrpc/block_trace_profiled.go 15.59% <81.81%> (+14.89%) ⬆️
sei-db/tools/cmd/seidb/main.go 0.00% <0.00%> (ø)
sei-cosmos/types/tracer.go 87.97% <89.47%> (-2.03%) ⬇️
evmrpc/trace_profile.go 69.51% <69.51%> (ø)
...tools/cmd/seidb/operations/trace_profile_report.go 0.00% <0.00%> (ø)

... and 127 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The CLI already emits raw_profiles.jsonl and summary.json; the third
artifact (report.html) was a 335-line embedded template with picked-
from-a-hat styling, no tests, and no durable callers. Anyone who wants
a visual from summary.json can render it with any tool. Dropping the
template shrinks the CLI file from 855 to 507 lines and removes the
html/template dependency.

- Remove writeTraceHTMLReport + its template block
- Remove traceReportData wrapper type (unused after)
- Remove the report.html write + fprintln in runTraceProfileReport
- Tighten --output-dir help to name the two remaining artifacts

No API or endpoint change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Kbhat1 Kbhat1 requested a review from yzang2019 April 17, 2026 18:28
Kbhat1 and others added 2 commits April 17, 2026 14:36
Nothing on this branch emits ReadTraceEvents: no state store backend
implements TraceableStateStore, so every WithReadTraceCollector
forwarder hits a type-assertion that fails at the leaf, RecordReadTrace
has zero callers, and LowLevelStats/LowLevelSamples on the response are
always empty. The test already gates its assertions with
`if foundAnyLowLevelStats` and passes vacuously.

Same pattern as the earlier AddReadTraceCloser cut: ship the plumbing
with the emitter, not ahead of it. A follow-up PR (on top of perf-mvcc
once its MVCC layer grows a TraceableStateStore implementation) can
restore the interfaces, the forwarders, the RecordReadTrace handler,
the LowLevelStats wire fields, and the CLI aggregation in one change
that's testable end-to-end.

Removed:
- ReadTraceEvent / ReadTraceCollector / TraceableStateStore /
  NewReadTraceEvent from sei-db/db_engine/types/types.go
- WithReadTraceCollector on composite/cosmos/evm SS stores
- SetReadTraceCollector passthroughs on cachekv/tracekv/storev2
- injectReadTraceCollector + its call sites in context.go KVStore /
  TransientStore
- activeStore / readTraceCollector fields on storev2 state.Store
- LowLevelReads field on ModuleTrace, LowLevelStats / LowLevelSamples
  fields on ModuleTraceDump, ReadTraceEventDump type,
  maxLowLevelReadSamples const, RecordReadTrace method and the
  low-level branch in StoreTracer.dumpLocked
- LowLevelStats / LowLevelTotals aggregation in seidb
  trace-profile-report CLI
- low-level assertions in evmrpc TestTraceTransactionProfile

The KVStore-level tracer (Get/Has/Set/Delete + iterator tracking) that
powers Stats and iterators on the profile response is untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The one-line comments I added on Get/Set/Has/Delete/OpType.String
just restated the function names without adding information. The
non-trivial docstrings (StartIterator's iteratorID contract,
RecordIteratorValue's Truncated semantics, Dump's prestate-exclusion
rule, DerivePrestateToJson's consumer, the cap constants) stay.

No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread sei-cosmos/store/gaskv/store.go
Comment thread sei-cosmos/store/gaskv/store.go Outdated
Comment thread sei-cosmos/store/gaskv/store.go Outdated
Kbhat1 and others added 2 commits April 17, 2026 16:23
gs.parent.Has(key) does the same underlying lookup whether the key
exists or not. The tracer's whole point is to surface where time was
spent, so filtering out the miss case hides exactly the pattern we
most want to see in a profile ("tx spent X ms on 500 Has() calls
that all missed"). Drop the && res guard so Has is traced on every
call, matching Get/Set/Delete.

Spotted by @yzang2019 in review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Kbhat1 and others added 2 commits April 17, 2026 16:58
Every Get/Set/Has/Delete/Iterator/Next call was unconditionally
calling time.Now() to stage a duration for the tracer, even when no
tracer is attached (the common case in production). Guard each
time.Now() on gs.tracer != nil so the non-traced hot path is
unaffected by the tracing machinery.

Spotted by @yzang2019 in review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…guards

The 'var start time.Time; if tracer != nil { start = time.Now() }' dance
was repeated at every Get/Set/Has/Delete/iterator/Next call site. Fold
it into a tiny helper so each call site becomes 'start := traceStart(
tracer)'. Behavior is identical: time.Now() only fires when a tracer is
attached, same as before; every tracer call site still nil-checks
before invoking. Addresses @yzang2019's review comment consistently
across the 6 sites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Kbhat1 Kbhat1 enabled auto-merge April 22, 2026 21:38
Kbhat1 and others added 3 commits April 23, 2026 16:06
The old assertion checked that iterated keys showed up in bankDump.Has,
which worked because gasIterator.Key() previously logged every key as a
tracer.Has call. This PR gives iterators their own ops (IteratorOpen /
IteratorValue / IteratorNext) and drops the Has shortcut, so iterator
keys now surface on bankDump.Iterators[0].Keys instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Kbhat1 Kbhat1 added this pull request to the merge queue Apr 24, 2026
Merged via the queue into main with commit 49418bc Apr 24, 2026
38 checks passed
@Kbhat1 Kbhat1 deleted the kartik/trace-profile-endpoint branch April 24, 2026 16:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants