Skip to content

Add a metric to count the EVM block processing time during event ingestion - #944

Merged
m-Peter merged 3 commits into
mainfrom
mpeter/add-block-processing-time-metric
Jan 13, 2026
Merged

Add a metric to count the EVM block processing time during event ingestion#944
m-Peter merged 3 commits into
mainfrom
mpeter/add-block-processing-time-metric

Conversation

@m-Peter

@m-Peter m-Peter commented Dec 22, 2025

Copy link
Copy Markdown
Collaborator

Closes: #938

Description

This will give us some insights on how fast the block processing logic is, and if it is able to keep up with the 0.8s block production rate.


For contributor use:

  • Targeted PR against master branch
  • Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
  • Code follows the standards mentioned here.
  • Updated relevant documentation
  • Re-reviewed Files changed in the Github PR explorer
  • Added appropriate labels

Summary by CodeRabbit

  • New Features

    • Adds a block processing time metric to report how long block handling (including replay and validation) takes.
  • Chores

    • Hooks timing into event processing so block processing durations are recorded and exposed for monitoring.
    • Ensures the metric is included in public metrics and supported by the no-op metrics implementation.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 22, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a Prometheus Summary metric evm_gateway_block_process_time_seconds, exposes it via a new BlockProcessTime(start time.Time) method on the Collector interface, implements the method in default and noop collectors, initializes and registers the summary, updates help text for blockIngestionTime, and records the metric in the ingestion engine around block processing.

Changes

Cohort / File(s) Summary
Metrics core
metrics/collector.go
Added blockProcessTime Prometheus Summary (evm_gateway_block_process_time_seconds); extended Collector interface with BlockProcessTime(start time.Time); added field and implementation on DefaultCollector; initialized and registered the summary in NewCollector; updated blockIngestionTime help text; included blockProcessTime in public metrics slice.
No-op collector
metrics/nop.go
Added no-op BlockProcessTime(start time.Time) method on nopCollector to satisfy updated Collector interface.
Ingestion instrumentation
services/ingestion/engine.go
Capture start := time.Now() at processEvents start and call e.collector.BlockProcessTime(start) after successful batch indexing to observe elapsed processing time.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Engine as Ingestion Engine
  participant Collector as Metrics Collector
  participant Prom as Prometheus

  Engine->>Engine: start := time.Now()\nprocessEvents(batch)
  alt processing succeeds
    Engine->>Collector: BlockProcessTime(start)
    Collector->>Prom: Observe(elapsedSeconds) on summary
  else processing fails
    Engine-->>Collector: (no call)
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • zhangchiqing
  • peterargue
  • janezpodhostnik

Poem

🐰
I hopped with a stopwatch on my paws so neat,
Timing each block with a tiny beat,
Seconds poured into a summary stream,
Metrics now hum like a bright spring dream,
Hop — I logged the time and munched a treat.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main change: adding a metric to measure EVM block processing time during event ingestion.
Linked Issues check ✅ Passed The pull request successfully implements all coding requirements from issue #938: adds a metric (blockProcessTime) to track block processing duration, uses prometheus.Summary for distribution analysis, and integrates it into the ingestion engine.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the blockProcessTime metric: interface and struct updates in collector.go, nop implementation in nop.go, and timing integration in engine.go. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
metrics/collector.go (2)

68-73: Improved metric documentation and configuration.

The updated help text and custom buckets make this metric more useful for monitoring ingestion latency. The buckets align well with the block production rate target.

Consider slightly refining the help text to emphasize this measures latency (wall-clock time from block proposal to indexing completion) rather than processing duration:

🔎 Optional help text refinement
-	Help:    "Time taken to fully ingest an EVM block in the local state index since block proposal",
+	Help:    "Latency from EVM block proposal time to indexing completion (wall-clock duration)",

75-81: Refine histogram buckets for better granularity around the 0.8s target.

Per the PR objectives, this metric aims to evaluate whether processing keeps up with the 0.8s block production rate. The current buckets jump from 0.5s to 1.0s, lacking granularity around the critical 0.8s threshold needed for proper p50/p95/p99 analysis.

🔎 Recommended bucket configuration
 var blockProcessTime = prometheus.NewHistogram(prometheus.HistogramOpts{
 	Name:    prefixedName("block_process_time_seconds"),
 	Help:    "Processing time to fully index an EVM block in the local state index",
-	Buckets: []float64{.5, 1, 2.5, 5, 10, 15, 20, 30, 45},
+	Buckets: []float64{0.1, 0.2, 0.4, 0.6, 0.8, 1, 2, 5, 10, 20},
 })

This provides:

  • Finer granularity around 0.8s (0.6, 0.8, 1.0) to detect when processing approaches or exceeds the block rate
  • Lower buckets (0.1, 0.2, 0.4) to measure fast-path performance
  • Reasonable upper bounds for detecting slowdowns
services/ingestion/engine.go (1)

190-190: LGTM! Timing instrumentation correctly captures block processing duration.

The timing captures the complete block processing workflow including transaction replay, state validation, storage writes, and batch commit. The placement after successful batch commit ensures only completed processing is measured, which aligns with the PR objective to evaluate throughput.

Consider whether failed processing attempts should also be measured to gain insight into performance during errors. This could help identify if errors are caused by slow processing:

🔎 Optional: Measure all processing attempts
 	start := time.Now()
 	err := e.withBatch(
 		func(batch *pebbleDB.Batch) error {
 			return e.indexEvents(events, batch)
 		},
 	)
+	e.collector.BlockProcessTime(start)
+
 	if err != nil {
 		return fmt.Errorf("failed to index events for cadence block %d: %w", events.CadenceHeight(), err)
 	}
-	e.collector.BlockProcessTime(start)

This would record timing for both successful and failed attempts. However, the current approach (measuring only successful processing) is also reasonable and aligns with the issue description.

Also applies to: 199-199

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e9c15c and 19f7d5e.

📒 Files selected for processing (3)
  • metrics/collector.go
  • metrics/nop.go
  • services/ingestion/engine.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test
🔇 Additional comments (3)
metrics/collector.go (2)

111-111: LGTM! Proper metric registration and interface integration.

The metric is correctly added to the registration slice, the interface method is properly declared, the field is added to the struct, and initialization follows the established pattern.

Also applies to: 129-129, 151-151, 176-176


246-248: LGTM! Implementation follows established patterns.

The method correctly calculates and observes the elapsed processing time, consistent with other duration metrics in the collector.

metrics/nop.go (1)

24-24: LGTM! No-op implementation correctly satisfies the interface.

The no-op method properly implements the Collector interface without behavioral changes, consistent with the other methods in this collector.

@m-Peter
m-Peter force-pushed the mpeter/add-block-processing-time-metric branch from 19f7d5e to 90070a0 Compare December 22, 2025 15:54
Comment thread metrics/collector.go Outdated

// EVM block processing time during event ingestion, including transaction replay
// and state validation
var blockProcessTime = prometheus.NewHistogram(prometheus.HistogramOpts{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps check if a summary is more appropriate than a histogram: https://prometheus.io/docs/practices/histograms/

Two rules of thumb:
1. If you need to aggregate, choose histograms.
2. Otherwise, choose a histogram if you have an idea of the range and distribution of values that will be observed. Choose a summary if you need an accurate quantile, no matter what the range and distribution of the values is.

In the past I have found that if the metric goes out of the range of the buckets you loose information (which is usually right when you need the most information)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point indeed. Given that we have no idea about the time information for this metric, summary makes a lot more sense than a histogram with specific buckets.
Updated in bc60701 .

@m-Peter
m-Peter force-pushed the mpeter/add-block-processing-time-metric branch from 90070a0 to bc60701 Compare January 13, 2026 14:51

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @metrics/collector.go:
- Around line 76-81: The metric blockProcessTime is created with
prometheus.NewSummary while the DefaultCollector field expects a
prometheus.Histogram and we should use a Histogram for aggregatable percentiles
around the 0.8s target; change creation to
prometheus.NewHistogram(prometheus.HistogramOpts{ Name:
prefixedName("block_process_time_seconds"), Help: "...", Buckets:
[]float64{0.1,0.2,0.4,0.8,1.6,3.2} }) and ensure the variable/blockProcessTime
and the DefaultCollector use the prometheus.Histogram type consistently (update
any var declarations or field types referencing blockProcessTime or
DefaultCollector to prometheus.Histogram).
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 90070a0 and bc60701.

📒 Files selected for processing (3)
  • metrics/collector.go
  • metrics/nop.go
  • services/ingestion/engine.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • services/ingestion/engine.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test
🔇 Additional comments (2)
metrics/nop.go (1)

25-25: LGTM!

The no-op implementation correctly follows the existing pattern and satisfies the updated Collector interface.

metrics/collector.go (1)

258-260: LGTM!

The implementation correctly follows the established pattern (e.g., BlockIngestionTime) for computing elapsed time and recording observations.

Comment thread metrics/collector.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
metrics/collector.go (1)

76-81: Consider using Histogram for consistency and aggregation.

The new metric uses Summary while the similar blockIngestionTime metric uses Histogram. Given the PR objective to compare processing time against the 0.8s block production rate, a Histogram with appropriate buckets would allow:

  1. Alerting on the percentage of blocks exceeding the 0.8s threshold via bucket counts
  2. Aggregation across multiple gateway instances (Summaries cannot be aggregated)
  3. Consistency with the existing blockIngestionTime metric pattern

If Summary is intentional (e.g., for accurate client-side quantiles on a single instance), consider adding explicit Objectives to compute quantiles like p50, p90, p99.

♻️ Proposed alternative using Histogram
-// EVM block processing time during event ingestion, including transaction replay
-// and state validation
-var blockProcessTime = prometheus.NewSummary(prometheus.SummaryOpts{
-	Name: prefixedName("block_process_time_seconds"),
-	Help: "Processing time to fully index an EVM block in the local state index",
-})
+// EVM block processing time during event ingestion, including transaction replay
+// and state validation
+var blockProcessTime = prometheus.NewHistogram(prometheus.HistogramOpts{
+	Name:    prefixedName("block_process_time_seconds"),
+	Help:    "Processing time to fully index an EVM block in the local state index",
+	Buckets: []float64{.1, .2, .4, .6, .8, 1, 1.5, 2, 3, 5},
+})

Note: If using Histogram, also update the struct field type at line 160 from prometheus.Summary to prometheus.Histogram.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc60701 and e134ee5.

📒 Files selected for processing (1)
  • metrics/collector.go
🔇 Additional comments (6)
metrics/collector.go (6)

69-74: LGTM!

The updated help text provides clearer documentation for the metric's purpose.


116-116: LGTM!

Metric correctly added to the registration slice.


135-135: LGTM!

Interface method follows the established pattern used by MeasureRequestDuration.


160-160: LGTM!

Struct field correctly typed and positioned.


187-187: LGTM!

Constructor correctly initializes the new metric field.


258-261: LGTM!

Implementation follows the established pattern used by other duration-recording methods in the collector.

@m-Peter
m-Peter merged commit f84de88 into main Jan 13, 2026
2 checks passed
@m-Peter
m-Peter deleted the mpeter/add-block-processing-time-metric branch January 13, 2026 15:17
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.

Add metric to track the duration of indexing/replaying EVM blocks

2 participants