Skip to content

feat: dynamic buffer panels, session persistence, counter improvements#5

Merged
zarubaf merged 76 commits intomainfrom
feat/dynamic-buffer-panels
Mar 31, 2026
Merged

feat: dynamic buffer panels, session persistence, counter improvements#5
zarubaf merged 76 commits intomainfrom
feat/dynamic-buffer-panels

Conversation

@zarubaf
Copy link
Copy Markdown
Owner

@zarubaf zarubaf commented Mar 31, 2026

Summary

Major feature branch adding dynamic buffer visualization, session persistence, and counter display improvements.

Dynamic Buffer Panels

  • Buffer panels created dynamically from uscope schema (SF_BUFFER storages)
  • Live per-cycle buffer state queried via Reader::state_at()
  • Table display with column headers (Slot | Stage | Instruction | entity fields)
  • Entity fields from entities storage (rbid, fpb_id, iq_id, etc.) shown per slot
  • Field formatting: inst_bits as hex, ready_time_ps as cycles
  • Flushed instructions visually dimmed
  • Clickable rows to select instructions
  • Column visibility control with session persistence
  • Sparse slot validity check (StorageState.valid[])

Session Persistence

  • Auto-save UI state on quit, tab close, tab switch, layout change
  • Auto-restore on file open: viewport, cursors, counter modes, dock layout
  • Session file stored alongside trace (.filename.session) with config dir fallback
  • Full DockArea layout round-trip via PanelRegistry (dump()/load())
  • Graceful degradation on corrupt/missing session files

Counter Display Fixes

  • f64 accumulation in mipmap downsample (was truncating to 0)
  • Per-cycle counter samples for small traces (≤32K cycles)
  • Sparkline follows display mode (Delta shows per-bucket deltas)
  • Fixed DC offset from pre-first-sample interval
  • Fixed sub-cycle bucket boundary spikes in both rate and delta modes
  • Counter segment extraction during lazy load_segments()
  • Minimap trendline cache invalidation on new counter data

Other

  • uscope format spec updated to v0.2-draft (SF_BUFFER, SECTION_COUNTER_SUMMARY, TSUM wire format)
  • Pipeline indicator as subtle bottom bar in minimap
  • Shared paint_bars() renderer for all counter visualizations
  • Warning cleanup throughout

Test Plan

  • Open short trace (trifid_trace.uscope) — verify buffer panels, counters, session save/restore
  • Open large trace (stress_1M.uscope) — verify fast open, mipmap counters work
  • Quit and reopen — verify session restores viewport, cursors, dock layout
  • Switch counter modes (T/R/Δ) — verify sparkline updates
  • Verify no warnings in release build

zarubaf and others added 30 commits March 31, 2026 19:49
- Detect SF_BUFFER storages from uscope schema during trace parsing
- Create one BufferPanel per detected buffer storage (dynamic count)
- Remove hardcoded QueueKind enum and three fixed queue panel fields
- BufferPanel shows buffer metadata (name, capacity, fields) at cursor
- Empty dock when trace has no buffer declarations
- QueuePanel kept as dead code for reference (no longer imported)

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

- Add counter_count config to GeneratorConfig
- Generate counters with varied rates and burstiness patterns
- 20 named counters (committed_insns, dcache_misses, etc.) + numbered overflow
- Cmd+G now generates 1M instructions with 100 counters for perf testing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New uscope crate: gen-uscope — standalone binary that generates
  .uscope files with configurable instruction count, counter count,
  stages, fetch width, and RNG seed
- Usage: gen-uscope -o out.uscope -n 1000000 --counters 100
- Also adds counter_count to Reflex's GeneratorConfig (used by Cmd+G)
- 1M instructions + 100 counters generates in ~4s (179 MB)

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

- Wrap PipelineTrace in Arc to eliminate per-frame deep clones
  (~8GB of data was being cloned 120x/sec in paint closures)
- Remove Konata parser, TraceSource trait, and TraceRegistry
  (use konata2uscope converter for Konata traces)
- Direct parse_uscope() calls replace registry indirection
- 9 Konata-specific tests removed; 21 remaining tests pass

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

- parse_uscope() now returns (PipelineTrace, Reader, SegmentIndex)
- TraceState stores Option<Reader> and SegmentIndex for future lazy queries
- SegmentIndex maps segments to (start_cycle, end_cycle) ranges with
  segments_in_range() for viewport-to-segment lookups
- Trace still loads eagerly — Reader is preserved for Phase 3+ use

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instructions are no longer parsed upfront. open_uscope() reads metadata
and counters only (fast). Instructions load on demand when the viewport
scrolls to a new region via ensure_segments_loaded().

- open_uscope() returns metadata-only PipelineTrace + Reader + context
- load_segments() parses instructions for specific segment indices
- ensure_segments_loaded() in paint callback loads visible + buffer zone
- SegmentIndex.segments_in_range() finds segments for a cycle range
- PipelineTrace.merge_loaded() appends and re-sorts instructions
- max_cycle_override reports full trace extent before instructions load
- Generator traces use eager path (no Reader)
- No segment eviction yet (accumulates loaded data)
- New test: lazy_load_sample_uscope verifies lazy vs eager parity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ensure_segments_loaded: after merge_loaded re-sorts instructions,
  find the row matching the anchor cycle and restore scroll_row
  (prevents viewport jumping to random row after sort)
- Minimap click: call ensure_segments_loaded for the clicked region
  BEFORE searching for instructions there (fixes jump-to-row-0 when
  clicking on an unloaded region)
- Both fixes ensure clamp() is called after max_row update

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous fix forcefully reset scroll_row every time new segments
loaded (via ensure_segments_loaded), which fought with the user's
scroll input and caused "stuck" scrolling. Now scroll_row is only
repositioned on the first load (0 → N rows transition). Normal
segment loading preserves the user's current scroll position.

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

CounterSeries now stores sparse (cycle, cumulative_value) pairs — one
per segment boundary — instead of dense per-cycle Vec<u64>. This
reduces counter memory from ~8GB (100 counters × 10M cycles) to ~240KB.

Query methods (counter_value_at, counter_rate_at, counter_delta_at,
counter_downsample_minmax) updated to work with sparse data via
binary search and interpolation.

This is a stepping stone — the uscope format will be extended with
proper counter mipmaps next.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Compute counter mipmaps via uscope::summary::compute_counter_summary()
  during trace open (replays segments once, builds multi-level min/max pyramids)
- Add TraceState::counter_downsample() that picks the right mipmap level
  based on requested resolution, falls back to sparse samples
- Counter panel sparklines, heatmap, minimap trendline, and timeline
  overlay all use mipmap queries instead of O(cycles) scans
- Counter overview rendering is now O(bucket_count) regardless of trace size

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- gen-uscope calls embed_counter_summary() after close() to write
  mipmaps as SECTION_COUNTER_SUMMARY inside the .uscope file
- Reader auto-loads mipmaps during open() from the embedded section
- Reflex uses embedded mipmaps if present, falls back to compute on load
- Removed sidecar file approach entirely

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

- Minimap: render ALL cursors, not just the active one
- Minimap: pipeline viewport indicator is now a 4px rounded yellow bar
  at the bottom with better visibility
- Scroll loading: when scrolling past loaded instructions, extrapolate
  the cycle range from instruction density to load more segments

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

- auto_follow: snap directly when stages are >2x viewport width away
  from current scroll position (fixes stages not being visible after
  vertical scroll through lazily-loaded data)
- auto_follow: considers both min and max cycle of visible rows' stages
- Pipeline indicator on minimap: increased min width to 8px and height
  to 5px for better visibility on large traces

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

- Count total instructions during open_uscope() segment replay pass
  (counts DA_SLOT_SET on entities storage — zero extra I/O)
- Store as total_instruction_count on PipelineTrace
- Status bar shows "12'328/1'000'000 instrs" when lazily loaded
- Add fmt_num() for apostrophe thousand separators (e.g., 1'000'000)
- Applied to instrs, cycles, rows, cursor position in status bar

TODO: Make total_instruction_count a first-class field in the uscope
CPU protocol header instead of counting during replay.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- load_trace_lazy: set max_row from total_instruction_count (not 0)
  so the scrollbar represents the full 1M-instruction trace
- ensure_segments_loaded: don't reduce max_row to loaded count
- Scroll extrapolation: use global instruction-to-cycle ratio
  (row / total_instrs * max_cycle) for better segment loading
  when scrolling past loaded regions
- Fixes: viewport appearing "at the end" with only 24K loaded

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Build a lightweight (first_cycle, entity_id) index for every instruction
during open_uscope() — 12 bytes × 1M instructions = 12MB. Sorted by
first_cycle, this provides exact row-to-cycle mapping for lazy loading.

- Row N now corresponds to the Nth instruction in the trace globally
- Scrolling to row 500K loads segments at the correct cycle
- max_row = total_instruction_count (full trace coverage)
- No more estimation/extrapolation — instruction_index[row] gives the
  exact target cycle for segment loading

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename CounterSummary → TraceSummary with instruction density mipmap
- open_uscope() no longer replays segments — reads metadata + summary only
- Remove instruction_index from PipelineTrace (was 12MB for 1M instructions)
- Row-to-cycle mapping via TraceSummary.row_to_cycle() density prefix-sum
- total_instruction_count from TraceSummary.total_instructions
- Counter data from TraceSummary mipmaps (no sparse samples needed)
- Build SegmentIndex from file segment table without replay
- Handles missing summary gracefully (live/old traces)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The row-to-cycle mapping was always applied, even when scroll_row=0.
This caused load_start=min(clicked_cycle, row_to_cycle(0))=0, loading
ALL segments from the start to the clicked position on the first click.

Now only uses row-to-cycle mapping when actually scrolled past loaded
rows (row_end > loaded row_count). Normal minimap clicks load only the
segments around the clicked cycle — 1-2 segments, not the entire trace.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
zarubaf and others added 21 commits March 31, 2026 19:50
- Trendline uses uniform ACCENT at alpha 0.35 (was bright/dim split
  that created visible two-tone background)
- Remove dim overlays and separate bright/dim trendline painting
- Skip compute_trace_summary for large files (>100 segments) to avoid
  blocking on open
- Remove unused counter_downsample_overview method

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

- Small traces (≤32K cycles): extract per-cycle counter samples at file
  open via segment replay for precise sparklines and minimap
- Large traces: use mipmap (no upfront replay cost)
- Fix f64 truncation in mipmap downsample (was accumulating as u64,
  truncating fractional sums to 0)
- Segment loading also extracts counter samples incrementally
- Minimap cache invalidates when new counter samples arrive

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Save viewport position, cursor positions, counter display modes, dock
layout preset, and active tab on quit (Cmd+Q). Restore on next open.

Session file stored alongside trace as .filename.session (JSON), with
fallback to ~/.config/reflex/sessions/ for read-only directories.
Graceful degradation: corrupt/missing sessions silently ignored.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ensures session is persisted even if the window is closed without
Cmd+Q (e.g., clicking the red X button).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds dock_open field to session file so buffer panel visibility
persists across restarts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Register all panels (Pipeline, Counter, Buffer, Log) with
  gpui-component's PanelRegistry for DockArea::load() support
- BufferPanel::dump() now includes buffer_idx in PanelInfo
- Session file includes full dock_layout (DockAreaState as JSON)
- On restore, DockArea::load() reconstructs exact panel arrangement
- Falls back to default layout if dock_layout is missing/corrupt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Check StorageState.valid[] before reading slot data in
query_buffer_state(). Sparse storages (buffers) track per-slot
validity via DA_SLOT_SET/DA_SLOT_CLEAR. Without this check, invalid
slots with leftover data from previous allocations appeared occupied.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Show all non-special fields after disasm in buffer slot rows. Skips
entity_id (shown as disasm), Bool fields (shown as ready dot), and
zero-valued fields to reduce clutter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Look up entity fields from the entities storage for each occupied
buffer slot. Shows all non-zero entity fields (skipping entity_id
and pc which are already shown as disasm) after the instruction text.

This makes structured entity annotations like rbid, fpb_id, iq_id,
dq_id, ready_time_ps visible in the buffer panel rows.

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

- Column headers: Slot | Stage | Instruction | entity fields
- Fixed-width columns with consistent alignment
- Horizontal scroll (no text wrapping)
- Field formatting: inst_bits as hex, ready_time_ps as cycles
- Default-hidden fields (inst_bits)
- Hidden columns persisted in session file per buffer name
- Entity field names discovered dynamically from uscope schema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instructions with RetireStatus::Flushed are dimmed in all buffer
panel columns (slot, stage, disasm, entity fields) to visually
distinguish them from active in-flight instructions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
counter_downsample_minmax used integer division for per-cycle rates,
causing intervals with rate < 1/cycle (e.g., 1 event over 6 cycles)
to display as 0. Now uses f64 rates throughout and scales to integer
range at the end. Fixes sparklines not showing early counter activity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The interval from cycle 0 to the first counter sample was assigned
rate = first_value / first_cycle, spreading the first event's value
across the inactive pre-reset period. Now correctly uses rate 0 for
this interval since no counter events fire before the first sample.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When counter display mode is Delta (Δ), the sparkline now shows
per-bucket absolute change instead of rate. Total and Rate modes
continue to show the rate-based downsample. Click a counter header
to cycle through T → R → Δ and see the sparkline update.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cap delta mode bucket count to cycle range so each bucket covers at
least 1 cycle. Prevents thin spikes where only the bucket straddling
a cycle boundary gets the delta while others show zero.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cap rate/total mode bucket count to cycle range, same as delta mode.
With more buckets than cycles, integer truncation caused most buckets
to have start == end, matching no intervals and showing as gaps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@zarubaf zarubaf force-pushed the feat/dynamic-buffer-panels branch from 209fc81 to 15a5387 Compare March 31, 2026 17:51
@zarubaf zarubaf changed the title Feat/dynamic buffer panels feat: dynamic buffer panels, session persistence, counter improvements Mar 31, 2026
zarubaf and others added 6 commits March 31, 2026 20:01
- Extract BufferSlot type alias for complex tuple type
- Use .rev().find() instead of .filter().last() on DoubleEndedIterator

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test expected raw integer rates but counter_downsample_minmax now
returns f64-scaled values. Updated to check relative ratios instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DockArea::load() can hang when session files reference panels from a
different trace (mismatched buffer count). Disable full layout restore
for now — placement + open state are still restored. The dock rebuilds
with correct panels for the current trace.

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

read_segment_table seeks to the segments section offset, leaving the
file cursor at an unknown position. The section table loop then
re-reads the same entries forever. Fix: save cursor position before
the call and re-seek to the next entry after. Also add a max
iteration guard as defensive coding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@zarubaf zarubaf merged commit d2e1206 into main Mar 31, 2026
5 checks passed
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.

1 participant