Skip to content

libhive,hiveview: Track per-test client log ranges for better reporting#1402

Merged
fjl merged 6 commits into
ethereum:masterfrom
danceratopz:add-enginex
Mar 17, 2026
Merged

libhive,hiveview: Track per-test client log ranges for better reporting#1402
fjl merged 6 commits into
ethereum:masterfrom
danceratopz:add-enginex

Conversation

@danceratopz

@danceratopz danceratopz commented Mar 11, 2026

Copy link
Copy Markdown
Member

Summary

Edit: The simulator Dockerfile has been removed from this PR and added in #1403.

This PR adds the eels/consume-enginex simulator and implements per-test client log isolation for multi-test client reuse. It adds:

And, to enable client log file handling for this simulator/"multi-test" clients, it implements:

  • Backend tracking of log byte ranges per test.
  • Hiveview client log highlighting of the relevant section corresponding to each test.

These last changes are required because although simulators can already reuse a client across multiple tests, the results lack per-test client log isolation. All tests point to the same full client log file with no way to see which portion is relevant.

Backend (internal/libhive)

  • Add registerMultiTestNode API endpoint (POST /testsuite/{suite}/test/{test}/node/{node}/register/{target}) that registers a target test for use with an existing client, capturing the current log file offset as the new test's starting byte position.
  • Track per-test log byte ranges via TestLogOffsets on ClientInfo; capture end offset in EndTest.
  • Mark source (lifecycle) tests as MultiTestContext and filter them from result summaries and test counts. Without this, the client lifecycle test case inflates the test count by one. (edit: filtering removed).
  • Add testmanager_test.go covering registration, lifecycle ownership, and log offset correctness.

Frontend (cmd/hiveview)

  • When a client has log byte offsets, highlight the corresponding line range and scroll to it with context.

Testing

To test, cherry-pick the commit that adds the enginex simulator in #1403 and use experiments/enginex-fix-test-reporting from ethereum/execution-specs#2476 that uses the new register endpoint:

./hive --sim ethereum/eels/consume-enginex \
    --client go-ethereum \
    --sim.buildarg "branch=experiments/enginex-fix-test-reporting" \
    --sim.buildarg "fixtures=https://github.com/danceratopz/execution-specs/releases/download/v0.1.0a2/fixtures.tar.gz" \
    --client-file client.yaml  \
    --sim.loglevel=5
    --docker.nocache enginex 

where client.yaml, is, for example:

- client: go-ethereum
  nametag: default
  build_args:
    baseimage: docker.ethquokkaops.io/dh/ethpandaops/geth
    tag: master

and the specified "release" contains an example set of 222 benchmark tests split across 4 pre-alloc groups:

Allocation Statistics Summary
Total groups: 4
Total tests: 222
Total accounts: 375

Tests and Accounts per Group
┏━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━━━┓
┃ Group Hash  ┃ Fork  ┃ Tests ┃ Accounts ┃
┡━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━━━┩
│ 0xa9149b... │ Osaka │   218 │      101 │
│ 0x5f42b5... │ Osaka │     2 │        9 │
│ 0x269141... │ Osaka │     1 │      195 │
│ 0x7763b1... │ Osaka │     1 │       70 │
└─────────────┴───────┴───────┴──────────┘

Screengrab

hiveview-multi-client-log-reporting-demo-small.mp4

Comment thread cmd/hiveview/listing.go Outdated
}
for _, test := range s.TestCases {
if test.MultiTestContext {
continue

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this thing should be reverted.

@danceratopz danceratopz Mar 11, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I've removed the filtering of test cases with this field in hiveview.

The backend additions for the field remain:

  • The MultiTestContext field,
  • SetMultiTestContext method, and,
  • the SetMultiTestContext call in registerMultiTestNode.

I can easily remove these if need be by dropping 319ebe5.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Btw, the motivation behind keeping the field is to potentially enable special handling of these "client lifecycle" tests in https://github.com/ethpandaops/hive-ui.

Comment thread cmd/hiveview/assets/lib/app-suite.js Outdated
for (var k in data.testCases) {
let tc = data.testCases[k];
if (tc.multiTestContext) {
continue;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This also

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done, as above.

@danceratopz

Copy link
Copy Markdown
Member Author

From the description:

Frontend (cmd/hiveview)

  • When a client has log byte offsets, highlight the corresponding line range and scroll to it with context

@fjl, this understates the change in hiveview behavior. All clients get log byte offsets, even clients whose register endpoint is never called (i.e, single-test clients). So this behavior effects all tests and upon opening the client log it will always jump to the log portion corresponding to the test, not line 0 as before. This is because logFileSize(logFilePath) is called after StartContainer returns, so logBegin is the file size after startup. I prefer this behavior, but wanted to highlight the change.

@danceratopz danceratopz requested a review from fjl March 11, 2026 21:29
@danceratopz danceratopz changed the title simulators,libhive: Add enginex and enable log reporting for multi-test clients libhive,hiveview: Add enginex and enable log reporting for multi-test clients Mar 11, 2026
@danceratopz danceratopz changed the title libhive,hiveview: Add enginex and enable log reporting for multi-test clients libhive,hiveview: Track per-test client log ranges for better reporting Mar 12, 2026
Comment thread internal/libhive/testmanager_test.go Outdated
)

// noopBackend is a minimal ContainerBackend for testing.
type noopBackend struct{}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We don't need to define this backend here, there is already a mock backend in package internal/fakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The test accessed libhive internals, so couldn't use fakes as-is. I re-wrote it to test via the public API; now it's declared as package libhive_test and uses fakes, see ec9d4cb

Add LogOffsets field to ClientInfo to track byte offsets into the
client log file for the portion relevant to each test case. This
enables log filtering when a single client container serves multiple
tests (e.g., in the enginex simulator with many-test-to-single-client
mapping).

- Add LogOffsets *TestLogOffsets to ClientInfo struct
- Add LogFileSize helper function to get current log size
- Set LogOffsets.Begin when client container starts (api.go)
- Set LogOffsets.End when test ends (testmanager.go)
- `StartTest` and `EndTest` write to `suite.TestCases` under `testCaseMutex`.
- `IsTestInSuite` read from the same map without holding `testCaseMutex`.
- Acquire `testCaseMutex.RLock()` before the map read to fix the race.
Use `package libhive_test` with `fakes.NewContainerBackend` to avoid the
import cycle that `noopBackend` worked around. Client start and multi-test
node registration now go through the HTTP API via `httptest.NewServer` so
the `wait` callback is set by real code paths. Container lifecycle is
verified through a `DeleteContainer` hook and log offsets through
`tm.Results()`.

`TestLogFileSize` moves to its own internal-package file since it tests
an unexported function and has no external dependencies.
@danceratopz

Copy link
Copy Markdown
Member Author

Heavier testing with a larger fixture release and higher --sim.parallelism revealed a race in IsTestInSuite on the suite.TestCases map read. This was addressed in aa244cc.

@danceratopz danceratopz requested a review from fjl March 17, 2026 16:41
for _, v := range testCase.ClientInfo {
if v.LogOffsets != nil && manager.config.LogDir != "" {
logFilePath := filepath.Join(manager.config.LogDir, filepath.FromSlash(v.LogFile))
v.LogOffsets.End = logFileSize(logFilePath)

@fjl fjl Mar 17, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I wouldn't have another method handy, but just want to note that this way of getting the offsets might be unreliable. The way logs are handled is that hive attaches to the docker output stream and writes the output to the file in a background goroutine. This process is not at all synchronized with the running of tests. So depending on machine load, docker latency, etc., the collected offsets can be way behind. Basically what's happening here is, we are taking the current size of the output file, as reported by the OS, and recording that.

We could improve this by adding more API surface in the docker backend to allow reading the current output size of the container. This would remove the OS filesystem from the equation at least.
Nothing further can be done though, since we fundamentally rely on docker to provide us with the output from the client container.

There is also the fundamental issue that client logs may not always be emitted in time with the tests.

@fjl fjl merged commit 1fc72ff into ethereum:master Mar 17, 2026
6 checks passed
skylenet pushed a commit to ethpandaops/hive-ui that referenced this pull request Jul 6, 2026
* feat: display per-test client log sections via TestLogOffsets

Hive can now reuse client containers across many tests (ethereum/hive#1402)
and reports the byte range of the client log belonging to each test in
clientInfo[].logOffsets. Surface those ranges in the UI:

- types: TestLogOffsets, clientInfo[].logOffsets, multiTestContext,
  optional summaryResult fields
- hide multi-test context pseudo-tests from the results table and stats
- per-test Logs links anchor the viewer on the test's byte range
- expanded rows show a client-log excerpt (Range request, capped at
  128 KB) next to the existing test-details excerpt
- LogViewer: ?begin&end now loads the full file, maps byte offsets to
  lines (byte-accurate), highlights the section and scrolls to it
- byteRange utils defend against servers that ignore Range headers

* fix: clamp byte offsets to buffer length in line mapping

* feat: windowed log viewer for byte-range anchored views

When anchored on a test's byte range (?begin&end), fetch only the range
plus 64 KB of context via HTTP Range requests instead of the whole file,
so shared client logs of any size render instantly:

- expand-up/expand-down buttons load more context in 64 KB steps,
  preserving the scroll position when prepending
- absolute line numbers resolve in the background by streaming the
  window prefix and counting newlines (window-relative until then)
- 'Load full file' escape hatch switches to the full view
- servers without Range support fall back to the full view for free

* feat: log-to-test navigation for shared client logs

Adds the inverse mapping of per-test log offsets — from a shared client
log back to the tests it contains:

- LogViewer sidebar listing every test section of the current log in
  log order, with pass/fail dots, a failures filter, and per-segment
  links to the test details; clicking a segment re-anchors the viewer
- suite page 'Shared Client Sessions' panel summarising the client
  instances owned by multi-test context entries (tests served, log
  size, lifecycle status), replacing the hidden pseudo test case

* fix: use instant scroll for programmatic jumps in virtualized log

tanstack-virtual smooth scrolling does not reliably complete over long
distances on a freshly mounted virtualizer, leaving anchored views at
the top of the window instead of on the highlighted section.

* feat: show absolute line numbers in log excerpt headers

Counted directly when the server returned the whole file, otherwise by
streaming the prefix in the background; skipped for excerpts starting
beyond 8 MB so expanding a row never triggers huge downloads.

* refactor: apply review cleanups to log-offset code

- share one isRealTestCase predicate (5 inline multiTestContext filters)
  and one logViewerUrl builder (3 inline route constructions)
- move window chunk helpers (EOF detection, partial-line trims) into
  byteRange.ts, shared by initial load and both expand directions
- replace per-request prefix streaming with checkpoint-cached, bounded
  line-number resolution (resolveLineNumber); navigating between tests
  in the same log now only streams the gap since the last count
- drop the HEAD pre-flight (Content-Range already carries the size),
  LogViewer's duplicate byte formatter, derivable lineCount/truncated
  state, dead resets, and the stale-result ref machinery
- VirtualizedLogContent takes prependedLines (its own units) instead of
  a pixel scroll adjustment; line-height knowledge stays internal
- suite JSON query marked immutable (staleTime: Infinity); themed CSS
  variables replace hardcoded border/text colors in the new components

* fix: handle empty log ranges and stale scroll timeouts

Production results record logOffsets with begin == end when a client
produced no output during a test; requesting that range can even yield
416 at EOF. fetchByteRange now short-circuits empty ranges and expanded
rows say so instead of rendering an empty excerpt.

Scroll-to-index timeouts in the virtualized views are cleared on re-run
so a pending scroll never fires against an outdated index, and sidebar
rows guard against virtual items that momentarily outlive a shrinking
filtered list.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants