docs(bench): record the boot profile and the runtime results

The profile section in docs/contributing/benchmarks.md says how to run
scripts/bench/boot-profile.ts, what the profile of the v1.44 runtime
found (the worker graph as one 753 ms block, a live heap of about
165 MB of which the query compiler holds 50 and the catalogs 33) and what
changed. scripts/bench/results/v1.45.0-*.md hold the before and after
numbers per area, from back-to-back runs on the same machine. The size
baselines take the lowered numbers.
This commit is contained in:
Marc-André Bombeck committed 2026-10-22 09:10:14 +02:00
1 parent 83661319d2
commit a451a114a2
16 files changed
+6636 -10

No files matched your search

+36
View File
@@ -68,6 +68,42 @@ The output is `<label>.json` (every number, plus the commit, Node and Postgres v
Run on a quiet machine; a parallel build or test run moves latency by tens of percent and leaves the statement counts alone. Compare statement counts across machines, latency only across runs on the same machine. A local number is only valid under the production heap ceiling, which the script sets.
## Boot and idle-memory profile
`scripts/bench/boot-profile.ts` profiles a production start the way production runs it: `next start` on the build, every role (web and worker), the production heap ceiling (`--max-old-space-size=792`, the heap Node derives in the 1.5 GB container), against the seeded benchmark database.
```sh
pnpm dlx tsx scripts/bench/boot-profile.ts --idle-seconds 300 --out scripts/bench/results/<label>-boot
```
It records:
- the time to the first answer from `/api/health` and to the worker's `startup` line;
- every `event_loop_stall` the process logs, with the uptime, startup phase, boot step and work in flight it names;
- every `runtime.heap.sampled` line, and every full collection from `--trace-gc` (used heap before, live heap after);
- a CPU profile of the start: the main thread's busy stretches over 50 ms without an idle sample, each with its self time per source module, resolved through the chunk source maps (`profile-analyze.ts`);
- one heap snapshot after the idle wait, taken with `--heapsnapshot-signal` (after a full collection, so it is the live heap), summarised by node type, constructor, largest strings and the largest retained subtrees with a retaining path, and the number of parsed locale catalogs.
The worker runs for the whole idle wait, and its boot passes fold and consolidate the seeded account (old dense readings into their `stats:` rows), roughly halving its row count. Reseed before the next request benchmark, or its numbers describe a different account.
The raw `.cpuprofile` and `.heapsnapshot` go to a temporary directory and are deleted after the summary unless `--keep` is given. Only run it against the benchmark database: a snapshot holds whatever the process decrypted. `--no-snapshot` and `--no-cpu-prof` skip either part. The profiler itself costs memory, so read RSS from this run as an upper bound.
### What the profile found (v1.44 runtime, October 2026)
On the seeded account, idle, with the worker running and no requests (`results/v1.45.0-before-boot.md`):
- **The boot stall is module evaluation.** The longest busy stretch of the main thread was 753 ms, 0.7 s into the start: the instrumentation hook importing the worker, whose five registrars pulled practically all of `src/lib` into one synchronous evaluation (`register-maintenance.ts` alone 280 ms inclusive), followed by the first `/api/health` request compiling its route. The event-loop monitor started after the key and time-zone checks, and a 250 ms tick measures a block by how late the next tick fires, so it reported less than the block or nothing. The beta's 718 ms and 671 ms stalls are this block on a slower machine.
- **The live heap is about 165 MB, not 400.** Full collections brought the heap back to 162 to 172 MB every 15 to 20 s while idle; the used heap between them reached 190 MB. What a heap line shows above the live heap is garbage awaiting the next full collection, plus, on a server with users, the server caches (13 caches of up to 2,000 entries with stale windows of up to an hour). The beta's 400 MB plateau is therefore not a baseline the process cannot shed; it is what the heap grows to under a large limit before a collection, plus caches.
- **What the live heap holds.** Of 172 MB: the query compiler about 50 MB (the engine object 34 MB, its WebAssembly base64 source twice at 4.4 MB, its chunk source twice at 5.6 MB); the locale catalogs about 33 MB (32 parsed catalog objects, eight locales in each of four server bundles, 14 MB, plus the eight-locale chunk source twice at 9.7 MB); module source strings over 50 KB about 44 MB in total (the catalog chunks included); compiled code 19 MB.
### What changed (v1.45.0)
- The event-loop monitor starts first in `register()`, and a stall line names the boot step, startup phase and work in flight (`src/lib/boot/boot-steps.ts`, one `boot.steps` line per boot with each step's duration).
- The worker's registrars are imported one at a time after `boss.start()`, with a turn of the event loop between them.
- The server reads a locale catalog from `messages/<locale>.json` on first use and keeps one parsed copy per process (`src/lib/i18n/server-catalogs.ts`); the seven matchers that index every locale build their index on first use.
Result on the same machine (`results/v1.45.0-boot.md`): the worker graph loads in stretches of at most 246 ms instead of one of 753 ms (the longest application stretch left is the query compiler's start, 336 ms, unchanged); the live heap after five idle minutes went from 162 to 172 MB to 142 to 153 MB; two parsed catalogs (1.6 MB) instead of 32 (14.1 MB), and no catalog chunk sources.
## Statement budgets in the integration suite
`tests/integration/query-budget-hot-routes.test.ts` seeds a small account (four months of the main types with overlapping sources, sleep, glucose, a medication, workouts and mood), and counts the statements of one request per hot route on a cache miss: `/api/auth/me`, the dashboard summary and snapshot, the pulse series over 30, 365 and 3650 days, the day view, insights health status, correlations, and a 500-row batch ingest.
+7 -1
View File
@@ -25,6 +25,9 @@
* The raw `.cpuprofile` and `.heapsnapshot` are written to a temporary
* directory and deleted after the summary unless `--keep` is given.
*
* The worker's boot passes fold the seeded account while it idles; reseed
* before running `run.ts` again.
*
* Output: `<out>.json` and `<out>.md`.
*/
import { execSync, spawn } from "node:child_process";
@@ -285,8 +288,11 @@ async function main(): Promise<void> {
stallMaxMs: Math.max(0, ...stalls.map((s) => Number(s.lag_ms ?? 0))),
};
mkdirSync(dirname(OUT), { recursive: true });
// Paths in frame and retainer names are made relative to the checkout.
// Paths in frame and retainer names are made relative to the checkout,
// before anything shortens them.
const relative = (text: string) => text.split(`${process.cwd()}/`).join("");
if (heap) heap = JSON.parse(relative(JSON.stringify(heap))) as typeof heap;
tasks = JSON.parse(relative(JSON.stringify(tasks))) as typeof tasks;
writeFileSync(
`${OUT}.json`,
relative(
+254
View File
@@ -0,0 +1,254 @@
{
"meta": {
"label": "v1.45.0-before",
"commit": "1915f9152",
"date": "2026-10-22T05:51:47.626Z",
"node": "v22.23.2",
"postgres": "16.15",
"cpus": 14,
"loadAverage": [
29.9,
21.9,
18.9
],
"measurements": 1265561,
"iterations": 20,
"warmup": 3,
"heapMb": 792,
"idleStatementsPerSecond": 0,
"batch5k": {
"totalMs": 1730,
"totalStatements": 534
}
},
"results": [
{
"name": "/api/auth/me",
"mode": "cold",
"request": "GET /api/auth/me",
"n": 20,
"p50Ms": 9.2,
"p95Ms": 11.6,
"maxMs": 13.3,
"statementsMedian": 19,
"statementsMax": 19,
"failures": 0
},
{
"name": "/api/auth/me",
"mode": "warm",
"request": "GET /api/auth/me",
"n": 20,
"p50Ms": 8.6,
"p95Ms": 10.5,
"maxMs": 13,
"statementsMedian": 19,
"statementsMax": 19,
"failures": 0
},
{
"name": "dashboard summary",
"mode": "cold",
"request": "GET /api/dashboard/summary",
"n": 20,
"p50Ms": 170.2,
"p95Ms": 220.5,
"maxMs": 228.3,
"statementsMedian": 31,
"statementsMax": 34,
"failures": 0
},
{
"name": "dashboard summary",
"mode": "warm",
"request": "GET /api/dashboard/summary",
"n": 20,
"p50Ms": 6,
"p95Ms": 6.7,
"maxMs": 6.8,
"statementsMedian": 7,
"statementsMax": 7,
"failures": 0
},
{
"name": "dashboard snapshot",
"mode": "cold",
"request": "GET /api/dashboard/snapshot",
"n": 20,
"p50Ms": 1481.8,
"p95Ms": 1526.2,
"maxMs": 1530,
"statementsMedian": 158,
"statementsMax": 161,
"failures": 0
},
{
"name": "dashboard snapshot",
"mode": "warm",
"request": "GET /api/dashboard/snapshot",
"n": 20,
"p50Ms": 7,
"p95Ms": 7.9,
"maxMs": 10.2,
"statementsMedian": 13,
"statementsMax": 13,
"failures": 0
},
{
"name": "series pulse 30 d",
"mode": "cold",
"request": "GET /api/measurements/series?kind=pulse&days=30",
"n": 20,
"p50Ms": 24.4,
"p95Ms": 30.8,
"maxMs": 31.1,
"statementsMedian": 8,
"statementsMax": 8,
"failures": 0
},
{
"name": "series pulse 30 d",
"mode": "warm",
"request": "GET /api/measurements/series?kind=pulse&days=30",
"n": 20,
"p50Ms": 20.5,
"p95Ms": 29.4,
"maxMs": 30.2,
"statementsMedian": 8,
"statementsMax": 9,
"failures": 0
},
{
"name": "series pulse 365 d",
"mode": "cold",
"request": "GET /api/measurements/series?kind=pulse&days=365",
"n": 20,
"p50Ms": 184.1,
"p95Ms": 192.6,
"maxMs": 201.1,
"statementsMedian": 8,
"statementsMax": 8,
"failures": 0
},
{
"name": "series pulse 365 d",
"mode": "warm",
"request": "GET /api/measurements/series?kind=pulse&days=365",
"n": 20,
"p50Ms": 178.1,
"p95Ms": 186.5,
"maxMs": 187.6,
"statementsMedian": 8,
"statementsMax": 8,
"failures": 0
},
{
"name": "series pulse 3650 d",
"mode": "cold",
"request": "GET /api/measurements/series?kind=pulse&days=3650",
"n": 20,
"p50Ms": 1627,
"p95Ms": 1664.5,
"maxMs": 1675.7,
"statementsMedian": 8,
"statementsMax": 9,
"failures": 0
},
{
"name": "series pulse 3650 d",
"mode": "warm",
"request": "GET /api/measurements/series?kind=pulse&days=3650",
"n": 20,
"p50Ms": 2090.9,
"p95Ms": 2435.4,
"maxMs": 2529.7,
"statementsMedian": 8,
"statementsMax": 9,
"failures": 0
},
{
"name": "day view",
"mode": "cold",
"request": "GET /api/day/2026-10-21",
"n": 20,
"p50Ms": 76.7,
"p95Ms": 93.6,
"maxMs": 116.2,
"statementsMedian": 49,
"statementsMax": 49,
"failures": 0
},
{
"name": "day view",
"mode": "warm",
"request": "GET /api/day/2026-10-21",
"n": 20,
"p50Ms": 62.8,
"p95Ms": 72,
"maxMs": 73.5,
"statementsMedian": 48,
"statementsMax": 48,
"failures": 0
},
{
"name": "insights health status",
"mode": "cold",
"request": "GET /api/insights/health-status",
"n": 20,
"p50Ms": 244.7,
"p95Ms": 401.2,
"maxMs": 404.1,
"statementsMedian": 68,
"statementsMax": 69,
"failures": 0
},
{
"name": "insights health status",
"mode": "warm",
"request": "GET /api/insights/health-status",
"n": 20,
"p50Ms": 238.9,
"p95Ms": 399.1,
"maxMs": 476.6,
"statementsMedian": 68,
"statementsMax": 68,
"failures": 0
},
{
"name": "insights correlations",
"mode": "cold",
"request": "GET /api/insights/correlations",
"n": 20,
"p50Ms": 105.6,
"p95Ms": 326.9,
"maxMs": 349.7,
"statementsMedian": 49,
"statementsMax": 52,
"failures": 0
},
{
"name": "insights correlations",
"mode": "warm",
"request": "GET /api/insights/correlations",
"n": 20,
"p50Ms": 11.3,
"p95Ms": 16.6,
"maxMs": 20.3,
"statementsMedian": 8,
"statementsMax": 8,
"failures": 0
},
{
"name": "batch ingest 5k (per 500)",
"mode": "write",
"request": "POST /api/measurements/batch ×10",
"n": 10,
"p50Ms": 166.1,
"p95Ms": 220.3,
"maxMs": 220.3,
"statementsMedian": 53,
"statementsMax": 56,
"failures": 0
}
]
}
+25
View File
@@ -0,0 +1,25 @@
# Benchmark v1.45.0-before
Commit 1915f9152 (the v1.44 runtime this branch starts from), 2026-10-22T05:51:47.626Z. 1265561 measurement rows on the bench account, 20 measured requests per row after 3 warm-up requests, one at a time. Server: production build, web process, heap ceiling 792 MB. Postgres 16.15, 14 cores, load average 29.9,21.9,18.9 at the end. Idle background: 0 statements/s.
| Request | Mode | n | p50 ms | p95 ms | max ms | statements (median) | statements (max) |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |
| /api/auth/me | cold | 20 | 9.2 | 11.6 | 13.3 | 19 | 19 |
| /api/auth/me | warm | 20 | 8.6 | 10.5 | 13 | 19 | 19 |
| dashboard summary | cold | 20 | 170.2 | 220.5 | 228.3 | 31 | 34 |
| dashboard summary | warm | 20 | 6 | 6.7 | 6.8 | 7 | 7 |
| dashboard snapshot | cold | 20 | 1481.8 | 1526.2 | 1530 | 158 | 161 |
| dashboard snapshot | warm | 20 | 7 | 7.9 | 10.2 | 13 | 13 |
| series pulse 30 d | cold | 20 | 24.4 | 30.8 | 31.1 | 8 | 8 |
| series pulse 30 d | warm | 20 | 20.5 | 29.4 | 30.2 | 8 | 9 |
| series pulse 365 d | cold | 20 | 184.1 | 192.6 | 201.1 | 8 | 8 |
| series pulse 365 d | warm | 20 | 178.1 | 186.5 | 187.6 | 8 | 8 |
| series pulse 3650 d | cold | 20 | 1627 | 1664.5 | 1675.7 | 8 | 9 |
| series pulse 3650 d | warm | 20 | 2090.9 | 2435.4 | 2529.7 | 8 | 9 |
| day view | cold | 20 | 76.7 | 93.6 | 116.2 | 49 | 49 |
| day view | warm | 20 | 62.8 | 72 | 73.5 | 48 | 48 |
| insights health status | cold | 20 | 244.7 | 401.2 | 404.1 | 68 | 69 |
| insights health status | warm | 20 | 238.9 | 399.1 | 476.6 | 68 | 68 |
| insights correlations | cold | 20 | 105.6 | 326.9 | 349.7 | 49 | 52 |
| insights correlations | warm | 20 | 11.3 | 16.6 | 20.3 | 8 | 8 |
| batch ingest 5k (per 500) | write | 10 | 166.1 | 220.3 | 220.3 | 53 | 56 |
File diff suppressed because it is too large. Load diff
+804
View File
@@ -0,0 +1,804 @@
# Boot profile v1.45.0-boot-final
Commit a57fa8335, 2026-10-22T06:58:52.591Z. Production build, every role, heap ceiling 792 MB, 300 s idle. 14 cores, load average 5.6,6.4,11.1 at the end.
- first answer from /api/health: 1325 ms; background role ready: 3217 ms
- event-loop stalls logged: 0, max 0 ms
## Stalls
## Heap samples
| at s | used MB | old | large obj | code | external | array buffers | rss |
| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| 6 | 225 | 158 | 54 | 4 | 11 | 0 | 500 |
| 306 | 158 | 96 | 53 | 5 | 11 | 0 | 788 |
## Full collections (mark-compact)
98 mark-compacts during the run. Used heap before the collection, and what survived it (the live heap):
| at s | before MB | after MB |
| ---: | ---: | ---: |
| 0 | 21.5 | 17.5 |
| 8 | 203.8 | 146.5 |
| 24 | 236.5 | 142.5 |
| 41 | 223.8 | 144.9 |
| 60 | 225.8 | 147.5 |
| 85 | 224.4 | 147.7 |
| 124 | 225.4 | 151.2 |
| 167 | 176.7 | 151 |
| 209 | 230.1 | 150.4 |
| 242 | 197.9 | 151.5 |
| 273 | 224.8 | 152.2 |
| 302 | 177.9 | 152.5 |
| 312 | 170.5 | 152.9 |
| 327 | 170.6 | 152 |
| 338 | 169.7 | 151.8 |
## Main-thread busy stretches over 50 ms without an idle sample, first two minutes
### at 5 ms, 396 ms
| self time by module | ms |
| --- | ---: |
| (native) | 230.2 |
| node:internal/modules/cjs/loader | 57.8 |
| npm:next | 52.3 |
| node:internal/bootstrap/realm | 29.3 |
| node:internal/deps/undici/undici | 8.9 |
| node:internal/modules/esm/loader | 4.5 |
| node:fs | 3 |
| node:internal/net | 2.5 |
| node:internal/bootstrap/switches/is_main_thread | 2 |
| node:os | 1.8 |
| inclusive, application modules | ms |
| --- | ---: |
### at 472 ms, 336 ms
| self time by module | ms |
| --- | ---: |
| (native) | 121.3 |
| wasm://wasm/00d1cb12 | 43 |
| node:internal/modules/cjs/loader | 40.5 |
| npm:next | 31.7 |
| npm:@prisma/client | 23.8 |
| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 23.4 |
| node:internal/modules/esm/utils | 17.3 |
| npm:zod | 5.9 |
| turbopack:///[turbopack]/shared/runtime/runtime-utils.ts | 3 |
| node:vm | 1.7 |
| inclusive, application modules | ms |
| --- | ---: |
| src/instrumentation.ts | 95.8 |
| src/lib/db-session.ts | 47.3 |
| src/lib/process-type.ts | 27.1 |
| src/lib/boot/startup-state.ts | 25.5 |
| src/lib/boot/boot-steps.ts | 25.5 |
| src/lib/logging/background.ts | 25.5 |
| src/lib/logging/redact.ts | 13 |
| src/lib/logging/context.ts | 11 |
| src/lib/validations/measurement.ts | 10 |
| src/generated/prisma/internal/class.ts | 4.7 |
### at 811 ms, 225 ms
| self time by module | ms |
| --- | ---: |
| node:internal/modules/cjs/loader | 52.3 |
| (native) | 48.2 |
| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 37 |
| npm:next | 22.2 |
| npm:zod | 12.5 |
| node:internal/modules/esm/utils | 8.7 |
| turbopack:///[turbopack]/shared/runtime/async-module.ts | 5.6 |
| node:fs | 4.5 |
| node:internal/async_hooks | 3 |
| node:internal/modules/esm/translators | 2.6 |
| inclusive, application modules | ms |
| --- | ---: |
| src/app/api/health/route.ts | 62 |
| src/lib/api-errors.ts | 25.9 |
| src/lib/auth/secure-cookie.ts | 14.9 |
| src/lib/logging/background.ts | 13.4 |
| src/lib/sharing/acting-session.ts | 12.8 |
| src/lib/validations/measurement.ts | 11.9 |
| src/lib/db-session.ts | 11.3 |
| src/lib/export/environment-backup.ts | 11.3 |
| src/lib/export/full-backup-payload.ts | 11.3 |
| src/lib/jobs/job-failures.ts | 11.3 |
### at 1063 ms, 246 ms
| self time by module | ms |
| --- | ---: |
| (native) | 64.7 |
| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 47.2 |
| node:internal/modules/cjs/loader | 31.6 |
| npm:luxon | 18 |
| turbopack:///[turbopack]/shared/runtime/async-module.ts | 15.3 |
| npm:yaml | 15 |
| npm:zod | 10.2 |
| src/lib/i18n/server-catalogs.ts | 8.2 |
| turbopack:///[turbopack]/shared/runtime/runtime-utils.ts | 4.5 |
| npm:cron-parser | 4 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/jobs/reminder-worker.ts | 137.6 |
| src/lib/jobs/reminder/google-health-sync.ts | 78 |
| src/lib/whoop/sync.ts | 65.9 |
| src/instrumentation.ts | 53.8 |
| src/lib/insights/bucket-series.ts | 47.1 |
| src/lib/whoop/oauth-descriptor.ts | 47.1 |
| src/lib/whoop/sync-core.ts | 47.1 |
| src/lib/whoop/sync-body.ts | 47.1 |
| src/lib/import/unzip-export-xml.ts | 35.3 |
| src/lib/jobs/integration-status-null-probe.ts | 35.3 |
### at 1363 ms, 68 ms
| self time by module | ms |
| --- | ---: |
| node:internal/modules/cjs/loader | 29.2 |
| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 6.7 |
| (native) | 5.9 |
| turbopack:///[turbopack]/shared/runtime/runtime-utils.ts | 4.5 |
| src/lib/daily/today-overview.ts | 4.1 |
| node:fs | 3 |
| turbopack:///[turbopack]/shared/runtime/async-module.ts | 3 |
| node:internal/streams/writable | 1.5 |
| src/lib/jobs/reminder/insights-handlers.ts | 1.5 |
| npm:next | 1.5 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/jobs/reminder-worker.ts | 62.5 |
| src/lib/jobs/reminder/insights-handlers.ts | 24.4 |
| src/lib/ai/prompts/arrival-reaction.ts | 12.8 |
| src/lib/daily/score-steady.ts | 11.3 |
| src/lib/dashboard/bands.ts | 7.1 |
| src/lib/dashboard/snapshot-read.ts | 5.8 |
| src/lib/jobs/data-arrival.ts | 4.5 |
| src/lib/daily/today-overview.ts | 4.1 |
| src/lib/daily/priority-item-key.ts | 4.1 |
| src/lib/ai/prompts/status-batch.ts | 3 |
### at 1545 ms, 57 ms
| self time by module | ms |
| --- | ---: |
| node:internal/modules/cjs/loader | 25.3 |
| (native) | 12.9 |
| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 7.2 |
| node:fs | 4.2 |
| node:buffer | 1.5 |
| npm:zod | 1.5 |
| src/lib/jobs/pr-detection.ts | 1.5 |
| turbopack:///[turbopack]/shared/runtime/async-module.ts | 1.5 |
| src/lib/jobs/measurement-maintenance.ts | 1.3 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/jobs/reminder-worker.ts | 55.3 |
| src/lib/jobs/reminder/ops-handlers.ts | 13 |
| src/lib/export/restore-backup.ts | 3 |
| src/lib/medications/ingredients/label-extract.ts | 3 |
| src/lib/environment/service.ts | 1.5 |
| src/lib/export/backup-blob.ts | 1.5 |
| src/lib/jobs/pr-detection.ts | 1.5 |
| src/lib/jobs/document-content-index-backfill.ts | 1.5 |
| src/lib/labs/ocr-extract.ts | 1.5 |
| src/lib/jobs/reminder/backup-handlers.ts | 1.5 |
### at 1820 ms, 114 ms
| self time by module | ms |
| --- | ---: |
| src/lib/tz/zone-offset.ts | 36 |
| npm:@prisma/client | 18.2 |
| src/lib/validations/source-priority.ts | 18.1 |
| (native) | 13.1 |
| src/lib/personal-records/pr-detection-worker.ts | 7.2 |
| node:buffer | 4.1 |
| src/lib/analytics/source-priority.ts | 3.7 |
| npm:zod | 2.3 |
| npm:pg | 2 |
| src/lib/tz/format.ts | 1.9 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 73.3 |
| src/lib/tz/format.ts | 40.7 |
| src/lib/measurements/consolidation-tz.ts | 40.7 |
| src/lib/tz/zone-offset.ts | 38.8 |
| src/lib/measurements/consolidation-base.ts | 38.1 |
| src/lib/analytics/source-priority.ts | 28.1 |
| src/lib/validations/source-priority.ts | 20.4 |
| src/lib/tz/intl-cache.ts | 1.5 |
### at 2407 ms, 50 ms
| self time by module | ms |
| --- | ---: |
| (native) | 18.9 |
| src/lib/validations/source-priority.ts | 14.1 |
| src/lib/analytics/source-priority.ts | 3.7 |
| src/lib/personal-records/pr-detection-worker.ts | 3 |
| npm:next | 1.8 |
| npm:%40prisma | 1.5 |
| src/lib/tz/format.ts | 1.5 |
| node:internal/async_local_storage/async_hooks | 1.5 |
| npm:pg | 1.5 |
| npm:@prisma/client | 1.3 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 26.6 |
| src/lib/analytics/source-priority.ts | 19.3 |
| src/lib/validations/source-priority.ts | 14.1 |
| src/lib/tz/format.ts | 3 |
| src/lib/measurements/consolidation-tz.ts | 3 |
| src/lib/measurements/consolidation-base.ts | 2.8 |
| src/lib/tz/zone-offset.ts | 1.5 |
### at 18192 ms, 59 ms
| self time by module | ms |
| --- | ---: |
| (native) | 15.5 |
| npm:@prisma/client | 13.9 |
| src/lib/validations/source-priority.ts | 11.8 |
| npm:pg | 3.6 |
| node:events | 3 |
| node:buffer | 2.5 |
| src/lib/measurements/consolidation-base.ts | 1.5 |
| src/lib/personal-records/pr-detection-worker.ts | 1.5 |
| node:internal/async_hooks | 1.5 |
| npm:pg-types | 1.4 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 18.9 |
| src/lib/analytics/source-priority.ts | 12.9 |
| src/lib/validations/source-priority.ts | 11.8 |
| src/lib/measurements/consolidation-base.ts | 4.5 |
| src/lib/tz/zone-offset.ts | 2.6 |
| src/lib/tz/format.ts | 2.6 |
| src/lib/measurements/consolidation-tz.ts | 2.6 |
### at 19914 ms, 65 ms
| self time by module | ms |
| --- | ---: |
| src/lib/validations/source-priority.ts | 18.1 |
| (native) | 12.3 |
| npm:@prisma/client | 11.6 |
| npm:next | 5.9 |
| src/lib/personal-records/pr-detection-worker.ts | 3 |
| src/lib/tz/format.ts | 3 |
| npm:pg | 2.6 |
| npm:pg-protocol | 2.5 |
| node:internal/async_hooks | 1.5 |
| src/lib/analytics/source-priority.ts | 1.5 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 29.8 |
| src/lib/analytics/source-priority.ts | 22.6 |
| src/lib/validations/source-priority.ts | 18.1 |
| src/lib/tz/format.ts | 5.8 |
| src/lib/measurements/consolidation-tz.ts | 5.8 |
| src/lib/tz/zone-offset.ts | 2.8 |
| src/lib/measurements/consolidation-base.ts | 2.8 |
### at 20296 ms, 55 ms
| self time by module | ms |
| --- | ---: |
| (native) | 22.4 |
| npm:@prisma/client | 14.8 |
| node:buffer | 3 |
| npm:zod | 3 |
| src/lib/measurements/consolidation-base.ts | 3 |
| src/lib/tz/format.ts | 1.5 |
| node:internal/stream_base_commons | 1.5 |
| src/lib/analytics/source-priority.ts | 1.5 |
| src/lib/personal-records/pr-detection-worker.ts | 1.5 |
| npm:pg-protocol | 1.3 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 10.5 |
| src/lib/analytics/source-priority.ts | 6 |
| src/lib/validations/source-priority.ts | 3 |
| src/lib/measurements/consolidation-base.ts | 3 |
| src/lib/tz/format.ts | 1.5 |
| src/lib/measurements/consolidation-tz.ts | 1.5 |
### at 30289 ms, 69 ms
| self time by module | ms |
| --- | ---: |
| src/lib/validations/source-priority.ts | 18.3 |
| (native) | 12.9 |
| npm:@prisma/client | 8.8 |
| node:buffer | 6.8 |
| src/lib/personal-records/pr-detection-worker.ts | 4.5 |
| src/lib/tz/format.ts | 4 |
| npm:pg-protocol | 3 |
| npm:zod | 3 |
| node:timers | 1.5 |
| node:internal/async_hooks | 1.5 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 33 |
| src/lib/analytics/source-priority.ts | 24.3 |
| src/lib/validations/source-priority.ts | 21.3 |
| src/lib/tz/format.ts | 5.6 |
| src/lib/measurements/consolidation-tz.ts | 5.6 |
| src/lib/measurements/consolidation-base.ts | 2.6 |
| src/lib/tz/zone-offset.ts | 1.5 |
### at 31004 ms, 68 ms
| self time by module | ms |
| --- | ---: |
| (native) | 17.8 |
| src/lib/validations/source-priority.ts | 14.9 |
| npm:@prisma/client | 7.6 |
| npm:next | 4.5 |
| node:internal/async_hooks | 4.5 |
| npm:pg-protocol | 4.1 |
| npm:pg | 3 |
| src/lib/tz/zone-offset.ts | 3 |
| node:buffer | 2.7 |
| src/lib/analytics/source-priority.ts | 1.5 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 26.4 |
| src/lib/analytics/source-priority.ts | 19 |
| src/lib/validations/source-priority.ts | 16.2 |
| src/lib/measurements/consolidation-base.ts | 6.1 |
| src/lib/tz/format.ts | 5.8 |
| src/lib/measurements/consolidation-tz.ts | 5.8 |
| src/lib/tz/zone-offset.ts | 4.5 |
### at 35350 ms, 65 ms
| self time by module | ms |
| --- | ---: |
| src/lib/validations/source-priority.ts | 17 |
| npm:@prisma/client | 11.8 |
| (native) | 8.6 |
| node:buffer | 6.5 |
| src/lib/tz/format.ts | 4.5 |
| npm:pg-protocol | 3 |
| src/lib/personal-records/pr-detection-worker.ts | 3 |
| npm:%40prisma | 1.6 |
| npm:pg | 1.5 |
| src/lib/analytics/source-priority.ts | 1.5 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 26 |
| src/lib/analytics/source-priority.ts | 20 |
| src/lib/validations/source-priority.ts | 17 |
| src/lib/tz/format.ts | 4.5 |
| src/lib/measurements/consolidation-tz.ts | 4.5 |
| src/lib/measurements/consolidation-base.ts | 3 |
### at 36452 ms, 57 ms
| self time by module | ms |
| --- | ---: |
| (native) | 20 |
| src/lib/validations/source-priority.ts | 16 |
| npm:@prisma/client | 7.3 |
| node:internal/per_context/primordials | 2.8 |
| npm:%40prisma | 1.5 |
| npm:next | 1.5 |
| src/lib/tz/zone-offset.ts | 1.5 |
| node:internal/async_hooks | 1.5 |
| node:internal/streams/readable | 1.5 |
| npm:pg-protocol | 1.2 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 21.2 |
| src/lib/validations/source-priority.ts | 16 |
| src/lib/analytics/source-priority.ts | 16 |
| src/lib/tz/format.ts | 2.5 |
| src/lib/measurements/consolidation-tz.ts | 2.5 |
| src/lib/measurements/consolidation-base.ts | 2.5 |
| src/lib/tz/zone-offset.ts | 1.5 |
### at 38744 ms, 56 ms
| self time by module | ms |
| --- | ---: |
| src/lib/validations/source-priority.ts | 13.3 |
| (native) | 9.9 |
| npm:@prisma/client | 9.2 |
| node:buffer | 4.7 |
| npm:pg-protocol | 4.3 |
| node:events | 1.9 |
| npm:pg | 1.6 |
| npm:%40prisma | 1.5 |
| npm:next | 1.5 |
| src/lib/tz/format.ts | 1.5 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 21.8 |
| src/lib/analytics/source-priority.ts | 16 |
| src/lib/validations/source-priority.ts | 13.3 |
| src/lib/measurements/consolidation-base.ts | 4.3 |
| src/lib/tz/format.ts | 3 |
| src/lib/measurements/consolidation-tz.ts | 3 |
| src/lib/tz/zone-offset.ts | 1.4 |
### at 42458 ms, 50 ms
| self time by module | ms |
| --- | ---: |
| npm:@prisma/client | 13.5 |
| src/lib/validations/source-priority.ts | 10.1 |
| (native) | 7.6 |
| npm:pg-protocol | 3.2 |
| node:events | 3 |
| src/lib/personal-records/pr-detection-worker.ts | 3 |
| src/lib/tz/format.ts | 3 |
| npm:pg | 2.8 |
| node:internal/async_hooks | 1.5 |
| npm:%40prisma | 1.4 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 17.3 |
| src/lib/analytics/source-priority.ts | 12.7 |
| src/lib/validations/source-priority.ts | 10.1 |
| src/lib/tz/format.ts | 3 |
| src/lib/measurements/consolidation-tz.ts | 3 |
| src/lib/measurements/consolidation-base.ts | 1.5 |
| src/lib/jobs/job-lock.ts | 1.3 |
| src/lib/jobs/long-pass.ts | 1.3 |
| src/lib/jobs/run-job.ts | 1.3 |
| src/lib/jobs/job-observer.ts | 1.3 |
### at 45451 ms, 63 ms
| self time by module | ms |
| --- | ---: |
| src/lib/validations/source-priority.ts | 15.8 |
| npm:@prisma/client | 10.5 |
| (native) | 8.4 |
| src/lib/tz/format.ts | 5.3 |
| node:internal/async_local_storage/async_hooks | 4.4 |
| node:buffer | 3 |
| npm:pg | 3 |
| src/lib/personal-records/pr-detection-worker.ts | 3 |
| npm:zod | 3 |
| npm:%40prisma | 1.5 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 31.7 |
| src/lib/analytics/source-priority.ts | 21.8 |
| src/lib/validations/source-priority.ts | 18.8 |
| src/lib/measurements/consolidation-base.ts | 5.3 |
| src/lib/tz/format.ts | 5.3 |
| src/lib/measurements/consolidation-tz.ts | 5.3 |
### at 56854 ms, 66 ms
| self time by module | ms |
| --- | ---: |
| src/lib/validations/source-priority.ts | 15 |
| npm:@prisma/client | 13.7 |
| (native) | 6.6 |
| node:buffer | 6.6 |
| npm:next | 6 |
| npm:pg | 4.5 |
| src/lib/personal-records/pr-detection-worker.ts | 4.1 |
| src/lib/tz/format.ts | 3.1 |
| node:internal/process/task_queues | 1.5 |
| node:internal/async_hooks | 1.5 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 25.3 |
| src/lib/analytics/source-priority.ts | 18.1 |
| src/lib/validations/source-priority.ts | 16.5 |
| src/lib/tz/format.ts | 3.1 |
| src/lib/measurements/consolidation-tz.ts | 3.1 |
| src/lib/measurements/consolidation-base.ts | 3.1 |
### at 56923 ms, 52 ms
| self time by module | ms |
| --- | ---: |
| (native) | 20 |
| npm:@prisma/client | 10.1 |
| node:buffer | 3.7 |
| npm:pg | 3 |
| src/lib/tz/format.ts | 2.6 |
| npm:pg-protocol | 2.6 |
| src/lib/analytics/source-priority.ts | 2.6 |
| src/lib/personal-records/pr-detection-worker.ts | 1.5 |
| src/lib/tz/zone-offset.ts | 1.5 |
| npm:pg-types | 1.5 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 9.3 |
| src/lib/analytics/source-priority.ts | 5.3 |
| src/lib/tz/format.ts | 4.1 |
| src/lib/measurements/consolidation-tz.ts | 4.1 |
| src/lib/measurements/consolidation-base.ts | 2.6 |
| src/lib/tz/zone-offset.ts | 1.5 |
| src/lib/validations/source-priority.ts | 1.1 |
### at 59508 ms, 56 ms
| self time by module | ms |
| --- | ---: |
| npm:@prisma/client | 15.7 |
| src/lib/validations/source-priority.ts | 9.7 |
| node:buffer | 7 |
| (native) | 6.1 |
| npm:pg-protocol | 4.5 |
| npm:next | 4.1 |
| src/lib/tz/format.ts | 3 |
| src/lib/analytics/source-priority.ts | 3 |
| node:internal/async_hooks | 1.5 |
| src/lib/measurements/consolidation-base.ts | 1.5 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 21.7 |
| src/lib/analytics/source-priority.ts | 15.8 |
| src/lib/validations/source-priority.ts | 9.7 |
| src/lib/tz/format.ts | 5.9 |
| src/lib/measurements/consolidation-tz.ts | 5.9 |
| src/lib/measurements/consolidation-base.ts | 5.9 |
| src/lib/tz/zone-offset.ts | 2.9 |
### at 61315 ms, 62 ms
| self time by module | ms |
| --- | ---: |
| src/lib/validations/source-priority.ts | 20.1 |
| npm:@prisma/client | 14.1 |
| (native) | 8.2 |
| npm:pg | 4.5 |
| node:buffer | 3.1 |
| src/lib/personal-records/pr-detection-worker.ts | 3 |
| src/lib/measurements/consolidation-base.ts | 3 |
| npm:pg-protocol | 1.5 |
| npm:next | 1.5 |
| src/lib/analytics/source-priority.ts | 1.4 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 30.3 |
| src/lib/analytics/source-priority.ts | 22.8 |
| src/lib/validations/source-priority.ts | 21.4 |
| src/lib/measurements/consolidation-base.ts | 4.5 |
| src/lib/tz/zone-offset.ts | 1.5 |
| src/lib/tz/format.ts | 1.5 |
| src/lib/measurements/consolidation-tz.ts | 1.5 |
### at 72986 ms, 57 ms
| self time by module | ms |
| --- | ---: |
| src/lib/validations/source-priority.ts | 19.1 |
| npm:@prisma/client | 10.2 |
| (native) | 6.6 |
| npm:pg-protocol | 5.5 |
| npm:next | 3 |
| src/lib/analytics/source-priority.ts | 3 |
| npm:pg | 2.5 |
| src/lib/personal-records/pr-detection-worker.ts | 1.5 |
| node:internal/async_local_storage/async_hooks | 1.5 |
| src/lib/tz/format.ts | 1.5 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 29.6 |
| src/lib/analytics/source-priority.ts | 23.5 |
| src/lib/validations/source-priority.ts | 19.1 |
| src/lib/measurements/consolidation-base.ts | 4.6 |
| src/lib/tz/format.ts | 4.5 |
| src/lib/measurements/consolidation-tz.ts | 4.5 |
| src/lib/tz/zone-offset.ts | 3 |
### at 73523 ms, 51 ms
| self time by module | ms |
| --- | ---: |
| src/lib/validations/source-priority.ts | 12 |
| npm:next | 8.9 |
| npm:@prisma/client | 6 |
| (native) | 4.5 |
| src/lib/analytics/source-priority.ts | 4.2 |
| node:buffer | 4 |
| npm:pg | 2.1 |
| npm:pg-protocol | 1.5 |
| src/lib/personal-records/pr-detection-worker.ts | 1.5 |
| src/lib/tz/zone-offset.ts | 1.5 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 27.8 |
| src/lib/analytics/source-priority.ts | 17.7 |
| src/lib/validations/source-priority.ts | 13.5 |
| src/lib/measurements/consolidation-base.ts | 8.6 |
| src/lib/tz/format.ts | 7.1 |
| src/lib/measurements/consolidation-tz.ts | 7.1 |
| src/lib/tz/zone-offset.ts | 6 |
### at 75393 ms, 100 ms
| self time by module | ms |
| --- | ---: |
| (native) | 32.6 |
| npm:@prisma/client | 17.2 |
| src/lib/validations/source-priority.ts | 14.6 |
| npm:next | 7.5 |
| src/lib/personal-records/pr-detection-worker.ts | 6.1 |
| src/lib/tz/zone-offset.ts | 5.6 |
| npm:pg-protocol | 4.1 |
| npm:pg | 2.5 |
| src/lib/tz/format.ts | 2.3 |
| node:internal/async_local_storage/async_hooks | 1.5 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 34.6 |
| src/lib/analytics/source-priority.ts | 24.3 |
| src/lib/validations/source-priority.ts | 16.1 |
| src/lib/tz/format.ts | 10.9 |
| src/lib/measurements/consolidation-tz.ts | 10.9 |
| src/lib/tz/zone-offset.ts | 8.6 |
| src/lib/measurements/consolidation-base.ts | 5.7 |
### at 78085 ms, 54 ms
| self time by module | ms |
| --- | ---: |
| src/lib/tz/zone-offset.ts | 26.6 |
| src/lib/validations/source-priority.ts | 13.5 |
| npm:@prisma/client | 4.2 |
| (native) | 4.1 |
| node:internal/async_local_storage/async_hooks | 1.5 |
| src/lib/tz/format.ts | 1.5 |
| src/lib/tz/intl-cache.ts | 1.5 |
| npm:pg | 1.1 |
| inclusive, application modules | ms |
| --- | ---: |
| src/lib/personal-records/pr-detection-worker.ts | 44.7 |
| src/lib/tz/format.ts | 29.6 |
| src/lib/measurements/consolidation-tz.ts | 29.6 |
| src/lib/measurements/consolidation-base.ts | 29.6 |
| src/lib/tz/zone-offset.ts | 28.1 |
| src/lib/analytics/source-priority.ts | 15 |
| src/lib/validations/source-priority.ts | 13.5 |
| src/lib/tz/intl-cache.ts | 1.5 |
## Self time by module until the worker is ready
| module | ms |
| --- | ---: |
| (native) | 853 |
| npm:@prisma/client | 295.9 |
| node:internal/modules/cjs/loader | 248.7 |
| src/lib/validations/source-priority.ts | 210.3 |
| npm:next | 188.4 |
| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 121.5 |
| src/lib/tz/zone-offset.ts | 83.2 |
| wasm://wasm/00d1cb12 | 81.4 |
| npm:pg | 78.5 |
| node:buffer | 59.1 |
| src/lib/tz/format.ts | 55 |
| npm:zod | 50.7 |
| src/lib/personal-records/pr-detection-worker.ts | 48 |
| npm:pg-protocol | 37 |
| node:internal/bootstrap/realm | 32 |
| src/lib/analytics/source-priority.ts | 31.7 |
| turbopack:///[turbopack]/shared/runtime/async-module.ts | 27.7 |
| node:internal/modules/esm/utils | 26 |
| npm:luxon | 22.1 |
| node:internal/async_hooks | 21.7 |
| node:internal/async_local_storage/async_hooks | 21.2 |
| node:fs | 18.8 |
| npm:pg-boss | 18.3 |
| npm:pg-pool | 18.1 |
| npm:yaml | 15 |
## Heap snapshot after 300 s idle (live heap after a full GC)
159.5 MB in 1302659 nodes; module source strings over 50 KB: 39.8 MB.
- parsed locale catalogs: 2 objects, 1.6 MB retained
| node type | MB |
| --- | ---: |
| string | 67.5 |
| array | 26.6 |
| code | 19.2 |
| object | 15.9 |
| hidden | 9.2 |
| native | 8 |
| object shape | 5.9 |
| closure | 5.2 |
| concatenated string | 1.3 |
| number | 0.5 |
| sliced string | 0.2 |
| regexp | 0.2 |
| synthetic | 0 |
| symbol | 0 |
| bigint | 0 |
| constructor | MB | count |
| --- | ---: | ---: |
| array:(object elements) | 20.8 | 25651 |
| object:Object | 11.4 | 293893 |
| native:system / JSArrayBufferData | 7.5 | 170 |
| array:(object properties) | 3.3 | 6517 |
| array: | 2.5 | 10169 |
| closure: | 1.6 | 29121 |
| object:system / Context | 1.6 | 28886 |
| closure:native_bind | 1 | 20804 |
| object:Array | 0.6 | 20938 |
| object:Date1 | 0.5 | 5332 |
| object:a | 0.4 | 4438 |
| native:Node / histogram | 0.4 | 1 |
| object:d | 0.3 | 4425 |
| closure:get | 0.3 | 4732 |
| object:Set | 0.2 | 5670 |
| object:Module | 0.2 | 1963 |
| closure:set | 0.2 | 3428 |
| closure:n | 0.1 | 1572 |
| closure:i | 0.1 | 1578 |
| object:Promise | 0.1 | 1341 |
| largest retained subtrees | MB | retaining path |
| --- | ---: | --- |
| object:yr | 34.6 | [1] > [10] > 22 > extension > global > prisma > [1] > _engine |
| object:system / Context | 7.4 | Node / PrincipalRealm > base_object_list > Node / BaseObjectList > [25] > Node / ModuleWrap > native_to_javascript > ModuleWrap > 2 > [4] > context |
| object:Promise | 5.9 | Object > executor > e > #r > gr > transactions > Map > table > > 6 > closing |
| object:Module | 5.6 | [1] > [10] > 99 > process > getBuiltinModule > getBuiltinModule > context > system / Context > $Module > Module > _cache > |
| code:.next/server/chu | 5.6 | Module > _cache > Object > .next/server/chunks/_1kwfnjg._.js > Module > exports > Array > [1] > > shared > script |
| code:.next/server/chu | 5.6 | global > prisma > [1] > t > _engineConfig > Object > compilerWasm > Object > getRuntime > getRuntime > shared > script |
| object:Generator | 4.4 | [6] > principal_realm > Node / PrincipalRealm > base_object_list > Node / BaseObjectList > [26] > Node / ModuleWrap > native_to_javascript > ModuleWrap > 2 > [4 |
| string:AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/ | 4.4 | [1] > [21] > 60025 |
| object:Object | 3.4 | system / Context > $Module > Module > _cache > Object > .next/server/chunks/[turbopack]_runtime.js > Module > export |
| object:global | 2.6 | [1] > [10] > 22 > extension |
| largest strings | MB |
| --- | ---: |
| module.exports=[234494,A=>{"use strict";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=> | 5.6 |
| module.exports=[17856,A=>{"use strict";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=>i | 5.6 |
| module.exports=[17856,A=>{"use strict";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=>i | 5.6 |
| const wasm = "AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/fwBgBX9/f39/AGAB | 4.4 |
| AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/fwBgBX9/f39/AGABfwF/YAZ/f39/f3 | 4.4 |
| lliADOAUngIEAACgKwAgBgAAoSsAIAcAAKErACAIAACiKwAgCQAAoysAIAoAAKQrACALAAClKwAgDAAApisAIA0AAK | 1.9 |
| (()=>{var e,t,r={"./dist/build/webpack/alias/react-dom-server.js"(e,t,r){"use strict";var | 1.3 |
| module.exports=[842865,(e,t,r)=>{let i=e.r(522734);t.exports=function(e){return e?/-----BE | 1.2 |
| module.exports=[842865,(e,t,r)=>{let i=e.r(522734);t.exports=function(e){return e?/-----BE | 1.2 |
| module.exports=[799092,(e,t,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value: | 1.1 |
| // v1.4.34 web-freeze. Schema is locked additive-only until iOS launch // + v1.5.0 marker. | 1.1 |
| module.exports=[971743,(A,e,t)=>{"use strict";let r=A.r(57328),{addAbortListener:s}=A.r(68 | 1.1 |
| module.exports=[835717,(A,e,t)=>{"use strict";let r=A.r(57328),{addAbortListener:s}=A.r(68 | 1 |
| module.exports=[767189,e=>e.a(async(t,a)=>{try{var n=e.i(64105),r=e.i(563616),i=t([n]);[n] | 0.8 |
| module.exports=[594108,806176,674750,98746,t=>{"use strict";var i;let r;function n(t){retu | 0.7 |
+28
View File
@@ -0,0 +1,28 @@
# v1.45.0: boot and idle memory
`scripts/bench/boot-profile.ts`, production build, every role, heap ceiling
792 MB, against the seeded account, 300 s idle. `v1.45.0-before-boot` is the
v1.44 runtime, `v1.45.0-boot-after` the branch after the boot changes and
before the lazy match indexes, `v1.45.0-boot-final` the final tree.
| | before | final |
| --- | ---: | ---: |
| longest main-thread busy stretch, application code | 753 ms (worker graph, one block) | 336 ms (query compiler start, 324 ms before as well) |
| longest stretch loading the worker graph | 753 ms | 246 ms (one registrar) |
| live heap after full collections, idle | 162 to 172 MB | 142 to 153 MB |
| heap snapshot after 300 s idle | 172.3 MB | 159.5 MB |
| parsed locale catalogs in the heap | 32 objects, 14.1 MB | 2 objects, 1.6 MB |
| catalog chunk sources in the heap | 2 x 9.7 MB | none |
| module source strings over 50 KB | 44.2 MB | 39.8 MB |
| event-loop stalls logged (500 ms threshold) | 0 (monitor started late) | 0 (monitor first) |
Time to the first health answer and to a ready worker moved with the host's
load (1.6 s / 5.4 s before at load 9, 1.3 s / 3.2 s after at load 6) and is
not claimed as a result.
The rest of the 150 MB is the runtime itself: the query compiler (about
50 MB: its engine object, its WebAssembly source and chunk, twice), compiled
code (19 MB) and the remaining module sources. The used heap between full
collections stayed under 240 MB in both runs; the beta's 400 MB plateau is
garbage awaiting a collection under a larger limit plus the server caches of
real users, not a baseline the process cannot shed.
+22
View File
@@ -0,0 +1,22 @@
# v1.45.0: request fan-out
Same runs as `v1.45.0-read-path.md`.
| request | statements before | statements after | p50 ms before / after |
| --- | ---: | ---: | ---: |
| /api/auth/me | 19 | 14 | 9 / 8 |
| day view, cold | 49 | 48 | 77 / 68 |
- `/api/auth/me` read the AppSettings singleton six times (MFA policy,
Assistant switches, module availability, provider presence, AI usage
bounds, reasoning controls); one memoised read per request now serves all
six (`src/lib/settings/operator-row.ts`). On the small budget fixture it
went from 18 to 13 statements. The remaining reads are the session, the
grant, cycle profile, onboarding, consent, coach usage and five user reads
with different column sets.
- The day view's record sections run six at a time instead of about
twenty-five at once, so one day never holds more than a third of the pool.
The statement count is the same; what changes is the peak of connections.
- Keyset pagination for `/api/measurements` was not done: a cursor needs a
new request parameter and a response field, a contract change for the
native client.
+254
View File
@@ -0,0 +1,254 @@
{
"meta": {
"label": "v1.45.0-final",
"commit": "a57fa8335",
"date": "2026-10-22T06:52:09.539Z",
"node": "v22.23.2",
"postgres": "16.15",
"cpus": 14,
"loadAverage": [
6.8,
8.9,
14.5
],
"measurements": 1265561,
"iterations": 20,
"warmup": 3,
"heapMb": 792,
"idleStatementsPerSecond": 0,
"batch5k": {
"totalMs": 4448,
"totalStatements": 504
}
},
"results": [
{
"name": "/api/auth/me",
"mode": "cold",
"request": "GET /api/auth/me",
"n": 20,
"p50Ms": 8.3,
"p95Ms": 27.2,
"maxMs": 221,
"statementsMedian": 14,
"statementsMax": 14,
"failures": 0
},
{
"name": "/api/auth/me",
"mode": "warm",
"request": "GET /api/auth/me",
"n": 20,
"p50Ms": 7.9,
"p95Ms": 10.1,
"maxMs": 17.1,
"statementsMedian": 14,
"statementsMax": 17,
"failures": 0
},
{
"name": "dashboard summary",
"mode": "cold",
"request": "GET /api/dashboard/summary",
"n": 20,
"p50Ms": 86.6,
"p95Ms": 100.8,
"maxMs": 126.8,
"statementsMedian": 30,
"statementsMax": 31,
"failures": 0
},
{
"name": "dashboard summary",
"mode": "warm",
"request": "GET /api/dashboard/summary",
"n": 20,
"p50Ms": 9.2,
"p95Ms": 17.1,
"maxMs": 18.4,
"statementsMedian": 6,
"statementsMax": 6,
"failures": 0
},
{
"name": "dashboard snapshot",
"mode": "cold",
"request": "GET /api/dashboard/snapshot",
"n": 20,
"p50Ms": 545.3,
"p95Ms": 592.5,
"maxMs": 606.3,
"statementsMedian": 153,
"statementsMax": 156,
"failures": 0
},
{
"name": "dashboard snapshot",
"mode": "warm",
"request": "GET /api/dashboard/snapshot",
"n": 20,
"p50Ms": 8.1,
"p95Ms": 17.9,
"maxMs": 21.9,
"statementsMedian": 10,
"statementsMax": 10,
"failures": 0
},
{
"name": "series pulse 30 d",
"mode": "cold",
"request": "GET /api/measurements/series?kind=pulse&days=30",
"n": 20,
"p50Ms": 18.7,
"p95Ms": 27.2,
"maxMs": 36.1,
"statementsMedian": 8,
"statementsMax": 8,
"failures": 0
},
{
"name": "series pulse 30 d",
"mode": "warm",
"request": "GET /api/measurements/series?kind=pulse&days=30",
"n": 20,
"p50Ms": 15.9,
"p95Ms": 21.8,
"maxMs": 26,
"statementsMedian": 8,
"statementsMax": 8,
"failures": 0
},
{
"name": "series pulse 365 d",
"mode": "cold",
"request": "GET /api/measurements/series?kind=pulse&days=365",
"n": 20,
"p50Ms": 40.7,
"p95Ms": 55.6,
"maxMs": 56.2,
"statementsMedian": 7,
"statementsMax": 8,
"failures": 0
},
{
"name": "series pulse 365 d",
"mode": "warm",
"request": "GET /api/measurements/series?kind=pulse&days=365",
"n": 20,
"p50Ms": 38.3,
"p95Ms": 59.6,
"maxMs": 88.6,
"statementsMedian": 7,
"statementsMax": 7,
"failures": 0
},
{
"name": "series pulse 3650 d",
"mode": "cold",
"request": "GET /api/measurements/series?kind=pulse&days=3650",
"n": 20,
"p50Ms": 334.9,
"p95Ms": 348.5,
"maxMs": 351.2,
"statementsMedian": 7,
"statementsMax": 7,
"failures": 0
},
{
"name": "series pulse 3650 d",
"mode": "warm",
"request": "GET /api/measurements/series?kind=pulse&days=3650",
"n": 20,
"p50Ms": 328.1,
"p95Ms": 346,
"maxMs": 348.9,
"statementsMedian": 7,
"statementsMax": 8,
"failures": 0
},
{
"name": "day view",
"mode": "cold",
"request": "GET /api/day/2026-10-21",
"n": 20,
"p50Ms": 68.4,
"p95Ms": 102.6,
"maxMs": 104.7,
"statementsMedian": 48,
"statementsMax": 49,
"failures": 0
},
{
"name": "day view",
"mode": "warm",
"request": "GET /api/day/2026-10-21",
"n": 20,
"p50Ms": 46.4,
"p95Ms": 66.1,
"maxMs": 72.6,
"statementsMedian": 47,
"statementsMax": 47,
"failures": 0
},
{
"name": "insights health status",
"mode": "cold",
"request": "GET /api/insights/health-status",
"n": 20,
"p50Ms": 156,
"p95Ms": 190,
"maxMs": 197.7,
"statementsMedian": 67,
"statementsMax": 67,
"failures": 0
},
{
"name": "insights health status",
"mode": "warm",
"request": "GET /api/insights/health-status",
"n": 20,
"p50Ms": 173.6,
"p95Ms": 1163.4,
"maxMs": 1419.4,
"statementsMedian": 67,
"statementsMax": 67,
"failures": 0
},
{
"name": "insights correlations",
"mode": "cold",
"request": "GET /api/insights/correlations",
"n": 20,
"p50Ms": 106.1,
"p95Ms": 229.1,
"maxMs": 383.3,
"statementsMedian": 48,
"statementsMax": 49,
"failures": 0
},
{
"name": "insights correlations",
"mode": "warm",
"request": "GET /api/insights/correlations",
"n": 20,
"p50Ms": 18.7,
"p95Ms": 626.6,
"maxMs": 1136.5,
"statementsMedian": 7,
"statementsMax": 7,
"failures": 0
},
{
"name": "batch ingest 5k (per 500)",
"mode": "write",
"request": "POST /api/measurements/batch ×10",
"n": 10,
"p50Ms": 185.7,
"p95Ms": 1269.2,
"maxMs": 1269.2,
"statementsMedian": 50,
"statementsMax": 53,
"failures": 0
}
]
}
+25
View File
@@ -0,0 +1,25 @@
# Benchmark v1.45.0-final
Commit a57fa8335 with the working tree of the final commits, 2026-10-22T06:52:09.539Z. 1265561 measurement rows on the bench account, 20 measured requests per row after 3 warm-up requests, one at a time. Server: production build, web process, heap ceiling 792 MB. Postgres 16.15, 14 cores, load average 6.8,8.9,14.5 at the end. Idle background: 0 statements/s.
| Request | Mode | n | p50 ms | p95 ms | max ms | statements (median) | statements (max) |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |
| /api/auth/me | cold | 20 | 8.3 | 27.2 | 221 | 14 | 14 |
| /api/auth/me | warm | 20 | 7.9 | 10.1 | 17.1 | 14 | 17 |
| dashboard summary | cold | 20 | 86.6 | 100.8 | 126.8 | 30 | 31 |
| dashboard summary | warm | 20 | 9.2 | 17.1 | 18.4 | 6 | 6 |
| dashboard snapshot | cold | 20 | 545.3 | 592.5 | 606.3 | 153 | 156 |
| dashboard snapshot | warm | 20 | 8.1 | 17.9 | 21.9 | 10 | 10 |
| series pulse 30 d | cold | 20 | 18.7 | 27.2 | 36.1 | 8 | 8 |
| series pulse 30 d | warm | 20 | 15.9 | 21.8 | 26 | 8 | 8 |
| series pulse 365 d | cold | 20 | 40.7 | 55.6 | 56.2 | 7 | 8 |
| series pulse 365 d | warm | 20 | 38.3 | 59.6 | 88.6 | 7 | 7 |
| series pulse 3650 d | cold | 20 | 334.9 | 348.5 | 351.2 | 7 | 7 |
| series pulse 3650 d | warm | 20 | 328.1 | 346 | 348.9 | 7 | 8 |
| day view | cold | 20 | 68.4 | 102.6 | 104.7 | 48 | 49 |
| day view | warm | 20 | 46.4 | 66.1 | 72.6 | 47 | 47 |
| insights health status | cold | 20 | 156 | 190 | 197.7 | 67 | 67 |
| insights health status | warm | 20 | 173.6 | 1163.4 | 1419.4 | 67 | 67 |
| insights correlations | cold | 20 | 106.1 | 229.1 | 383.3 | 48 | 49 |
| insights correlations | warm | 20 | 18.7 | 626.6 | 1136.5 | 7 | 7 |
| batch ingest 5k (per 500) | write | 10 | 185.7 | 1269.2 | 1269.2 | 50 | 53 |
@@ -0,0 +1,45 @@
# v1.45.0: read path
Request benchmark on the seeded account (1,265,561 rows), production build,
web process, heap ceiling 792 MB, 20 requests per row, one at a time.
`v1.45.0-before` is the v1.44 runtime (1915f9152), `v1.45.0-final` this
branch; both on the same machine within the hour, load average 7 to 30
(other work on the host), so latency compares within a factor of noise and
statements exactly. Cold means every request follows a one-row manual sync.
| request | before p50 / p95 ms | after p50 / p95 ms | statements before / after |
| --- | ---: | ---: | ---: |
| dashboard snapshot, cold | 1482 / 1526 | 545 / 593 | 158 / 153 |
| dashboard summary, cold | 170 / 221 | 87 / 101 | 31 / 30 |
| series pulse 3650 d, cold | 1627 / 1665 | 335 / 349 | 8 / 7 |
| series pulse 3650 d, warm | 2091 / 2435 | 328 / 346 | 8 / 7 |
| series pulse 365 d, cold | 184 / 193 | 41 / 56 | 8 / 7 |
| series pulse 30 d, cold | 24 / 31 | 19 / 27 | 8 / 8 |
| insights health status, cold | 245 / 401 | 156 / 190 | 68 / 67 |
What moved them:
- **JIT off for app sessions.** Postgres compiled every fold whose estimated
cost passed `jit_above_cost`; on the ten-year pulse pick that was 100 to
140 ms of a 220 ms statement (`EXPLAIN ANALYZE`: 395 ms with JIT, 220 ms
without).
- **The canonical-source pick groups first.** `DISTINCT ON` now sorts the
distinct `(type, day, source)` triples instead of every raw reading (the
ten-year pulse pick sorted 260,000 rows and spilled to disk); 220 ms to
175 ms on its own. Parity: `tests/integration/canonical-source-pick-parity.test.ts`
runs the old statement beside the new one over five reader shapes and two
ladders on a fixture with overlapping and off-ladder sources, tombstones,
empty days and readings around midnight; identical rows.
- **The dense series reads its rows once.** Buckets and strip figures come
from one statement over a materialised window instead of two statements
that each picked the canonical source and scanned the readings. Parity:
`tests/integration/dense-series-read-parity.test.ts`, the two old
statements as reference, pulse (two ranked sources), glucose and oxygen
saturation, day and hour grains, UTC, Europe/Berlin, Asia/Kolkata and
America/New_York; identical buckets and figures (relative 1e-9).
- **Closed blocks survive a sync.** The slice's pre-fold remainder (every
reading older than five years, the snapshot's largest statement at 357 ms)
and the day view's past history blocks are cached under a key that names
the instant their readings end before; a sync of newer readings no longer
drops them (`src/lib/cache/closed-block.ts`). The values are the same
statement's; only how long they are kept changed.
@@ -0,0 +1,28 @@
# v1.45.0: workout duplicate window
A correctness change, not a speed one; the request benchmark does not read
workouts, so there is no latency row for it.
Before: two copies of one session were collapsed only when both starts fell
in the same fixed five-minute clock slot (`floor(start / 5 min)`). A pair 30 s
apart that straddled a slot boundary stayed two workouts in the list, the
batch write, the calendar, strain and the Coach.
After: per user and sport, sorted by start, a workout joins the open cluster
while it starts less than five minutes after the cluster's first start.
Proof (`src/lib/measurements/__tests__/pick-canonical-workout-rows.property.test.ts`,
against the previous picker kept as `pick-canonical-workout-rows.legacy.ts`):
| property | inputs |
| --- | --- |
| same rows as the fixed slots wherever no pair within five minutes straddles a slot boundary, three ladders | 4,000 generated inputs, over 1,000 comparable |
| same strain impulses as before on those inputs | 2,000 generated inputs, over 500 comparable |
| two sources' copies collapse at any offset under five minutes (the old picker missed a share) | 3,000 random offsets |
| five minutes or more apart never collapse | 2,000 |
| a uniform shift of every start keeps the picked set | 1,500 |
| a row of workouts four minutes apart does not chain | fixed case |
One existing case changed its expectation: three runs at 08:04, 08:08 and
08:12 used to stay three because they sat in three slots; 08:08 is now the
08:04 run from a lower-ranked source (four minutes apart), and 08:12 stays.
@@ -0,0 +1,26 @@
# v1.45.0: write path
Same runs as `v1.45.0-read-path.md`.
| request | before p50 ms | after p50 ms | statements before / after |
| --- | ---: | ---: | ---: |
| batch ingest, 500 fresh rows per request | 166 | 186 | 53 / 50 |
Fresh rows were already set-based (v1.44); the benchmark's ingest is all
fresh rows, so it shows no gain, as expected. The changes are for re-posts:
- **Unchanged `stats:` re-posts** write nothing and start nothing. One read
over the batch's `stats:` ids replaces a reconciler transaction (about ten
round trips) per row, and an all-unchanged batch no longer refolds, enqueues
rollups or PR detection, or marks caches stale. The per-entry status stays
`updated` and the summary's `updated` count is unchanged.
`tests/integration/routes/measurements-batch-unchanged-stats.test.ts`: four
unchanged re-posts take at most 12 statements in total (before: about 40)
with zero rollup, invalidation and PR calls; a changed value, a changed
device or a tombstoned row still goes through the reconciler.
- **Apple Health re-import** skips the UPDATE of a row stored with exactly the
values it would write; one read per chunk instead of one UPDATE per row.
`tests/integration/apple-health-reimport-unchanged.test.ts`: a re-import of
20 stored rows reports the same per-type counts and sends no UPDATE.
- **Narrower invalidation.** The batch route tells the cache how far back it
wrote (`touchedFrom`), so closed blocks of older readings stay.
@@ -1,14 +1,13 @@
{
"$comment": "Production files over 800 lines and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.",
"count": 129,
"total": 156380,
"count": 128,
"total": 155494,
"entries": {
"app/api/dashboard/summary/route.ts": 1032,
"app/api/documents/inbound/route.ts": 811,
"app/api/insights/generate/route.ts": 1209,
"app/api/measurements/batch/route.ts": 1569,
"app/api/measurements/route.ts": 938,
"app/api/measurements/series/route.ts": 879,
"app/api/medications/[id]/route.ts": 1281,
"app/api/medications/intake/bulk/route.ts": 1044,
"app/api/workouts/batch/route.ts": 971,
@@ -113,7 +112,7 @@
"lib/mcp/rich-reads.ts": 1467,
"lib/mcp/tools.ts": 2654,
"lib/measurements/dense-intraday-retention.ts": 1057,
"lib/measurements/import-apple-health-export.ts": 1729,
"lib/measurements/import-apple-health-export.ts": 1722,
"lib/medications/scheduling/cadence.ts": 978,
"lib/medications/scheduling/recurrence.ts": 1108,
"lib/medications/scheduling/slot-upsert.ts": 926,
@@ -1,7 +1,7 @@
{
"$comment": "Production functions over 200 lines, keyed \"file :: name\", and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.",
"count": 415,
"total": 154564,
"total": 154432,
"entries": {
"app/achievements/page.tsx :: AchievementsPage": 206,
"app/api/admin/backups/upload/route.ts :: apiHandler(arg)": 314,
@@ -41,7 +41,7 @@
"app/api/measurements/batch/route.ts :: postBatch": 1262,
"app/api/measurements/route.ts :: apiHandler(arg)": 286,
"app/api/measurements/route.ts :: postMeasurement": 377,
"app/api/measurements/series/route.ts :: apiHandler(arg)": 670,
"app/api/measurements/series/route.ts :: apiHandler(arg)": 549,
"app/api/medications/[id]/dose-history/route.ts :: apiHandler(arg)": 256,
"app/api/medications/[id]/intake/[eventId]/route.ts :: apiHandler(arg)": 390,
"app/api/medications/[id]/intake/route.ts :: postIntake": 607,
@@ -394,7 +394,7 @@
"lib/measurements/dense-intraday-retention.ts :: runDenseIntradayRetention > walkSource > writeDay > foldOnce": 212,
"lib/measurements/drain-per-sample-cumulative.ts :: drainPerSampleCumulative": 344,
"lib/measurements/drain-per-sample-cumulative.ts :: drainPerSampleCumulative > writeDay": 205,
"lib/measurements/import-apple-health-export.ts :: streamParseExportXml": 1256,
"lib/measurements/import-apple-health-export.ts :: streamParseExportXml": 1245,
"lib/measurements/import-apple-health-export.ts :: streamParseExportXml > (anonymous) #2": 323,
"lib/medications/efficacy/build-efficacy.ts :: buildMedicationEfficacy": 268,
"lib/medications/intake-slot-dedup.ts :: dedupeUserIntakeSlots": 321,
+3 -2
View File
@@ -64,8 +64,9 @@ export async function register() {
// Without the mirror the process keeps the default method.
}
// Every Node process watches its heap as it watches its event loop: a trend line, and one report naming the work in
// flight before the heap runs out (a heap-limit crash logs nothing else).
// Every Node process watches its heap as it watches its event loop: a
// trend line, and one report naming the work in flight before the heap
// runs out (a heap-limit crash logs nothing else).
beginBootStep("monitors");
try {
const [{ startHttpInFlightTracking }, { startHeapMonitor }] =