Public: anyone can read this; the crew can change it.

An app's Server-Timing tells the forge's own wait apart from the app's #10842

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

Relayed app answers now carry a `forge-lag;dur=<ms>` entry right after `forge-ttfb`. Its value is the forge's worst event-loop lag over the last 10 s, from `currentLoopLag()` in `src/perf/loop_worst.ts`. `forge-ttfb` is measured on the forge's clock, so when the forge's loop was blocked by something else, that wait lands in it even while the app was fast. When `forge-lag` is more than half of `for…

shipped production is serving it deploy befa93c

opened this

Relayed app answers now carry a forge-lag;dur=<ms> entry right after forge-ttfb. Its value is the forge's worst event-loop lag over the last 10 s, from currentLoopLag() in src/perf/loop_worst.ts. forge-ttfb is measured on the forge's clock, so when the forge's loop was blocked by something else, that wait lands in it even while the app was fast. When forge-lag is more than half of forge-ttfb, the header now shows the wait was the forge's, not the app's.

What changed

  • src/apps/proxy.ts: only timing lines changed:

- "lag" added to the RelayPhase names. - relayUpstream writes forge-lag between forge-ttfb and forge-upstream. - The perf/loop_worst.ts import. - A comment saying how to read forge-lag.

The HTML rewrite path is untouched.

  • test/apps-proxy-timing.test.ts: two new tests.

- The first checks that forge-lag appears exactly once, right after forge-ttfb, as a one-decimal duration. - The second blocks the forge's loop for 250 ms and checks that forge-lag then reads at least 150 ms.

Both fail without the change.

What I chose not to do The second done item ("the app's page on the forge does not say the app was slow") needed no code. No forge page judges an app slow from forge-ttfb or forge-upstream. The app's settings page shows only beacon p75 figures (TTFB, load, LCP, INP, CLS), with no verdict. With nothing making that claim, I added no rule here to suppress it. If a slowness verdict is added later, it should use the forge-lag > ½ forge-ttfb rule described in the new comment on relayUpstream.

I could not keep a plan on the issue: the keep_plan tool was not available in this run.

Checks run

  • bun scripts/gate.ts and bun x tsc --noEmit both pass.
  • scripts/fast.ts named 1879 test files for this diff, too many to run here. I ran the suites that cover it instead: all test/apps-proxy-*.test.ts, server-timing-sweep, loop-worst and perf-loop-lag-attribution. All 42 tests pass.

Try it Request any app through its forge host and read Server-Timing, for example curl -sI https://lab--10838--forge-trevato-apps.open-platform.sh/ | grep -i server-timing. You should see forge-ttfb;dur=…, forge-lag;dur=…, forge-upstream;dur=….

🤖 Generated with Claude Code

Closes #10838

commented

Relayed app answers now carry a forge-lag;dur=<ms> entry right after forge-ttfb. Its value is the forge's worst event-loop lag over the last 10 s, from currentLoopLag() in src/perf/loop_worst.ts. forge-ttfb is measured on the forge's clock, so when the forge's loop was blocked by something else, that wait lands in it even while the app was fast. When forge-lag is more than half of forge-ttfb, the header now shows the wait was the forge's, not the app's.

What changed

  • src/apps/proxy.ts: only timing lines changed:

- "lag" added to the RelayPhase names. - relayUpstream writes forge-lag between forge-ttfb and forge-upstream. - The perf/loop_worst.ts import. - A comment saying how to read forge-lag.

The HTML rewrite path is untouched.

  • test/apps-proxy-timing.test.ts: two new tests.

- The first checks that forge-lag appears exactly once, right after forge-ttfb, as a one-decimal duration. - The second blocks the forge's loop for 250 ms and checks that forge-lag then reads at least 150 ms.

Both fail without the change.

What I chose not to do The second done item ("the app's page on the forge does not say the app was slow") needed no code. No forge page judges an app slow from forge-ttfb or forge-upstream. The app's settings page shows only beacon p75 figures (TTFB, load, LCP, INP, CLS), with no verdict. With nothing making that claim, I added no rule here to suppress it. If a slowness verdict is added later, it should use the forge-lag > ½ forge-ttfb rule described in the new comment on relayUpstream.

I could not keep a plan on the issue: the keep_plan tool was not available in this run.

Checks run

  • bun scripts/gate.ts and bun x tsc --noEmit both pass.
  • scripts/fast.ts named 1879 test files for this diff, too many to run here. I ran the suites that cover it instead: all test/apps-proxy-*.test.ts, server-timing-sweep, loop-worst and perf-loop-lag-attribution. All 42 tests pass.

Try it Request any app through its forge host and read Server-Timing, for example curl -sI https://lab--10838--forge-trevato-apps.open-platform.sh/ | grep -i server-timing. You should see forge-ttfb;dur=…, forge-lag;dur=…, forge-upstream;dur=….

🤖 Generated with Claude Code

Changes requested — before the latest push

The code is small and correct, and both new tests assert the right thing. Three things need fixing in one round.

  1. Docs. docs/APPS.md (around line 267-271) is where forge-ttfb/forge-upstream are explained and how to read them. forge-lag is not there. Add it, with the rule: when forge-lag is more than half of forge-ttfb, the wait was the forge's, not the app's. Also check docs/REFERENCE.md line 22, which lists the forge's phases.
  1. Box 2 is unmet. Saying nothing slow is called an app's fault today does not make the page behave. Put the rule in a small named function (for example forgeWaited(lagMs, ttfbMs), plain data in and out) with a test, so the "more than half" rule exists once in code and not only in a comment. If an app page shows nothing that judges slowness, say so on the pull and keep the box open. Alternatively, wire the rule into wherever the forge describes a slow wait.
  1. The preview does not show the feature. curl -I on / and /healthz at the preview returns forge-ttfb;dur=…, forge-upstream;dur=… with no forge-lag. The relay that answers there is not this branch's code, so please say on the pull how a person can see the header at this head. The Try it URL points at a #10838 host, not this preview.

Smaller: the 250 ms busy-wait test depends on the sampler having started in that suite. Start it explicitly, or assert via a fake clock, so the test cannot flake.

commented

Relayed app answers carry forge-lag;dur=<ms> right after forge-ttfb. It is the forge's worst event-loop lag over the last 10 s (currentLoopLag() in src/perf/loop_worst.ts). forge-ttfb is measured on the forge's clock, so when the forge's loop was busy with something else, that wait counts in forge-ttfb even when the app answered fast. The rule that tells the two apart now exists once in code: forgeWaited(lagMs, ttfbMs) in src/perf/loop_worst.ts is true when forge-lag is more than half of forge-ttfb.

What changed

  • src/apps/proxy.ts: only the timing lines changed: relayUpstream writes forge-lag between forge-ttfb and forge-upstream. The HTML rewrite path is untouched.
  • src/perf/loop_worst.ts: adds forgeWaited. A reading that is not finite, a negative lag, or a zero wait never blames the forge.
  • Docs:

- docs/APPS.md ("Reading a slow app's Server-Timing") explains forge-lag and the more-than-half rule. - docs/REFERENCE.md lists every phase name with the same rule.

  • test/apps-proxy-timing.test.ts:

- One test checks that forge-lag appears exactly once, right after forge-ttfb, with one decimal. - One test starts its own lag sampler, so it does not depend on which test ran first. It blocks the forge's loop for 250 ms, then checks that forge-lag is at least 200 ms and that forgeWaited says the wait was the forge's. - Unit tests for forgeWaited use the issue's own readings (1490.7 ms forge-ttfb) and check the edges: exactly half is not more than half, and readings that are not durations never blame the forge.

Box 2 stays open. No forge page about an app judges it slow from these timings today. The app's settings page shows only the beacon's p75 figures, with no verdict. So forgeWaited is called only from the tests for now. A future slowness verdict should go through it.

Seeing it at this head The preview does not show the header yet. At https://lab--10842--forge-trevato-apps.open-platform.sh/healthz, Server-Timing currently reads … forge-ttfb;dur=1.6, forge-upstream;dur=1.6 with no forge-lag. That is because the relay in front of the preview is production's forge, which runs main, not this branch. At this head, bun test test/apps-proxy-timing.test.ts runs this branch's relay in front of a test app and checks the header. After merge and the production rollout, curl -sI https://lab--forge-trevato-apps.open-platform.sh/healthz | grep -i server-timing shows forge-lag. The earlier "Try it" pointed at a #10838 host that doesn't exist; this replaces it.

Checks run

  • bun scripts/gate.ts and bun x tsc --noEmit both pass.
  • test/apps-proxy-timing.test.ts and test/loop-worst.test.ts pass (21 tests).
  • scripts/fast.ts names 1879 files for a change to proxy.ts, too many to run here. In the first round I also ran the other apps-proxy-*, server-timing-sweep and perf-loop-lag-attribution suites; this round leaves the relay code as it was, so I did not rerun them.

🤖 Generated with Claude Code

Approved

The relay now writes forge-lag right after forge-ttfb. The new tests cover its presence, its format and a blocked loop. The docs in APPS.md and REFERENCE.md name the phase and the more-than-half rule, and README is untouched. The preview's relay is production's forge on main, so the header is not visible there. The test runs this branch's relay and checks it.

Non-blocking: forgeWaited is called only from tests, so it is unused in production until a page judges an app slow. I'd do two things next: (1) when a slowness verdict is added, route it through forgeWaited; (2) show forge-lag on the app's timing view so a person can see whose wait it was.

commented

Left #10838 open: 1 unticked item remain (When forge lag explains more than half of forge-ttfb, the app's page on the forge does not say the app was slow.).

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs