Skip to content

fix(core): use monotonic render metrics#292

Merged
RtlZeroMemory merged 2 commits intomainfrom
codex/fix-render-metrics-monotonic-clock
Mar 18, 2026
Merged

fix(core): use monotonic render metrics#292
RtlZeroMemory merged 2 commits intomainfrom
codex/fix-render-metrics-monotonic-clock

Conversation

@RtlZeroMemory
Copy link
Copy Markdown
Owner

@RtlZeroMemory RtlZeroMemory commented Mar 17, 2026

Summary

  • time public internal_onRender metrics with the always-on monotonic clock instead of perf-only timing
  • keep runtime breadcrumb frame.renderTimeMs aligned with the same monotonic render measurement
  • add deterministic regressions for widget and draw mode render timing

Testing

  • npm run build
  • npm run lint
  • npm run typecheck
  • node scripts/run-tests.mjs --filter "runtimeBreadcrumbs"
  • node scripts/run-tests.mjs --filter "updates"
  • node scripts/run-tests.mjs

PTY Evidence

  • worker-mode starship run at deterministic viewport 300x68
  • backend_submitted=735, worker_payload=735, worker_accepted=735, worker_completed=735
  • hash_mismatch_backend_vs_worker=0
  • route summary: bridge=377, engineering=293, crew=53, settings=12, invalidCmdStreams=0
  • sampled hashes: bridge=0xcec610ce, engineering=0xbff64150, crew=0xff318b6f, settings=0x17d371fd

Summary by CodeRabbit

  • Bug Fixes

    • Render timing measurements now use a monotonic clock so timings remain consistent when performance instrumentation is disabled.
  • Tests

    • Added deterministic tests for breadcrumb and render timing behavior with mocked performance values.
    • Introduced median-based benchmarking and tolerance-driven regression checks to stabilize performance tests.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

Render timing instrumentation was changed to use a monotonic clock for render start/elapsed measurements; a test helper to mock performance.now() was added; tests were added/updated to assert monotonic render timings; a benchmark test was switched to median-based measurements and tolerance checks.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Added Unreleased notes: bug fixes for monotonic render timing in internal_onRender and runtime breadcrumbs; noted new deterministic regression tests.
Test Utilities
packages/core/src/app/__tests__/helpers.ts
Added withMockPerformanceNow(values, fn) which temporarily mocks globalThis.performance.now() with a deterministic sequence and restores it after the async callback.
Runtime Breadcrumb Tests
packages/core/src/app/__tests__/runtimeBreadcrumbs.test.ts
Imported withMockPerformanceNow and added a test asserting runtime breadcrumbs preserve monotonic render timings when performance instrumentation is disabled; the test appears duplicated in the diff.
Internal Render Tests
packages/core/src/app/__tests__/updates.test.ts
Imported withMockPerformanceNow and added a draw-mode test verifying internal_onRender reports monotonic render timing using mocked performance timeline.
Render Loop Implementation
packages/core/src/app/createApp/renderLoop.ts
Replaced usages of perfNow() for render-start with monotonicNowMs() (renamed renderStartrenderStartMs) and updated subsequent elapsed-time calculations to use the monotonic clock.
Benchmark Tests
packages/ink-compat/src/__tests__/perf/bottleneck-profile.test.ts
Switched benchmarks to median-based measurement (median, benchMedian), added grid-benchmark config constants and tolerance-based assertions comparing slowdown against regression tolerance.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped in code at break of dawn,
Swapped ticking clocks so time moves on.
Tests hum true, no jumps, no shocks —
Monotonic beats in steady clocks. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'fix(core): use monotonic render metrics' directly and clearly summarizes the main change: switching render timing measurements to use a monotonic clock instead of performance-dependent timing.

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

Important

Merge conflicts detected (Beta)

  • Resolve merge conflict in branch codex/fix-render-metrics-monotonic-clock
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-render-metrics-monotonic-clock
📝 Coding Plan
  • Generate coding plan for human review comments

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

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/ink-compat/src/__tests__/perf/bottleneck-profile.test.ts (2)

325-329: Add an explicit reuse-path correctness assertion.

Current check proves fresh allocation equivalence, but it doesn’t validate the second-call reset/reuse behavior that the benchmark is timing. A quick mutate-then-reuse assertion would lock this down.

Suggested patch
     const expectedGrid = allocateGrid_CURRENT(cols, rows);
     reusableCols = 0;
     const actualGrid = allocateGrid_REUSE(cols, rows);
     assert.deepEqual(actualGrid, expectedGrid);
+    actualGrid[0]![0]!.char = "X";
+    actualGrid[0]![0]!.style = { bold: true };
+    const reusedGrid = allocateGrid_REUSE(cols, rows);
+    assert.equal(reusedGrid, actualGrid);
+    assert.deepEqual(reusedGrid, expectedGrid);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ink-compat/src/__tests__/perf/bottleneck-profile.test.ts` around
lines 325 - 329, The test currently only compares a fresh
allocateGrid_REUSE(cols, rows) to allocateGrid_CURRENT(cols, rows) but doesn't
assert that the reuse path actually resets and reinitializes state on a second
call; update the test to mutate the grid returned by the first
allocateGrid_REUSE call (or set reusableCols to a nonzero sentinel), then call
allocateGrid_REUSE(cols, rows) again and assert the result equals
allocateGrid_CURRENT(cols, rows) (use allocateGrid_CURRENT(cols, rows) as the
expected value) to validate the reset/reuse behavior of allocateGrid_REUSE;
reference allocateGrid_REUSE, allocateGrid_CURRENT and reusableCols when making
the change.

227-238: Harden median helpers for edge inputs.

median()/benchMedian() currently assume non-empty samples. If samples is ever set to 0, Line 229 returns undefined (asserted non-null) and downstream math becomes invalid.

Suggested patch
 function median(values: readonly number[]): number {
+  if (values.length === 0) {
+    throw new Error("median() requires at least one value");
+  }
   const sorted = [...values].sort((left, right) => left - right);
-  return sorted[Math.floor(sorted.length / 2)]!;
+  const middle = Math.floor(sorted.length / 2);
+  return sorted.length % 2 === 0
+    ? (sorted[middle - 1]! + sorted[middle]!) / 2
+    : sorted[middle]!;
 }
 
 function benchMedian(fn: () => void, iterations: number, samples: number): number {
+  if (samples <= 0) {
+    throw new Error("benchMedian() requires samples > 0");
+  }
   const runs: number[] = [];
   for (let index = 0; index < samples; index += 1) {
     runs.push(bench(`sample-${String(index)}`, fn, iterations));
   }
   return median(runs);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ink-compat/src/__tests__/perf/bottleneck-profile.test.ts` around
lines 227 - 238, The median() and benchMedian() helpers assume non-empty inputs
and will return undefined when given empty arrays or samples=0; update
median(values) to handle values.length===0 (e.g., return 0 or NaN) instead of
indexing into sorted[], and update benchMedian(fn, iterations, samples) to
early-return the same neutral numeric value when samples<=0 (no runs) to avoid
downstream invalid math; reference the median and benchMedian functions (and the
use of bench within benchMedian) and ensure return types remain number.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/ink-compat/src/__tests__/perf/bottleneck-profile.test.ts`:
- Around line 325-329: The test currently only compares a fresh
allocateGrid_REUSE(cols, rows) to allocateGrid_CURRENT(cols, rows) but doesn't
assert that the reuse path actually resets and reinitializes state on a second
call; update the test to mutate the grid returned by the first
allocateGrid_REUSE call (or set reusableCols to a nonzero sentinel), then call
allocateGrid_REUSE(cols, rows) again and assert the result equals
allocateGrid_CURRENT(cols, rows) (use allocateGrid_CURRENT(cols, rows) as the
expected value) to validate the reset/reuse behavior of allocateGrid_REUSE;
reference allocateGrid_REUSE, allocateGrid_CURRENT and reusableCols when making
the change.
- Around line 227-238: The median() and benchMedian() helpers assume non-empty
inputs and will return undefined when given empty arrays or samples=0; update
median(values) to handle values.length===0 (e.g., return 0 or NaN) instead of
indexing into sorted[], and update benchMedian(fn, iterations, samples) to
early-return the same neutral numeric value when samples<=0 (no runs) to avoid
downstream invalid math; reference the median and benchMedian functions (and the
use of bench within benchMedian) and ensure return types remain number.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 793ca435-ca9e-43b4-8f96-8c7e25540004

📥 Commits

Reviewing files that changed from the base of the PR and between 0061f1d and e33cc94.

📒 Files selected for processing (1)
  • packages/ink-compat/src/__tests__/perf/bottleneck-profile.test.ts

@RtlZeroMemory RtlZeroMemory merged commit f31dcb7 into main Mar 18, 2026
16 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