Commit Graph
12 Commits
Author SHA1 Message Date
Thomas Camlong 79ea987761 perf: bound V8's young generation and unbundle server-only deps
Two further wins, both measured on a restored 28-widget board behind a real
demo session (scripts/benchmarks/stress-restore.mts).

--max-semi-space-size=4 in run.sh. --max-old-space-size only bounds the old
generation; V8 also sizes the young generation generously (16 MB per
semi-space) and that slack is pure resident memory. 4 MB cut idle anonymous
memory 120.0 -> 97.2 MiB and peak 710.6 -> 560.2 MiB. It costs CPU, as any
GC-space reduction does — 15.8 -> 17.1 CPU-seconds over the run, ~8% — with
no board-load latency regression (median 594 -> 570 ms). Overridable via
HOMARR_MAX_SEMI_SPACE_SIZE.

serverExternalPackages for the remaining server-only dependencies. Bundling
a package inlines its transitive graph into a chunk that is compiled at boot
even when the code path never runs; leaving it external makes it a lazy
require. Adding winston, drizzle-orm, ical.js, jszip, ldapts, node-unifi,
@kubernetes/client-node and linkedom took idle to 90.3 MiB and board-loaded
209.6 -> 164.6 MiB, with slightly lower CPU (16.3s) and latency (549 ms).
The trade-off is image size: 388 -> 401 MB, because those packages now ship
in node_modules instead of being tree-shaken into chunks.

Cumulative vs the merge-base: idle 141.7 -> 90.3 MiB (-36%), board loaded
257.8 -> 164.6 MiB (-36%), under load 392.7 -> 302.5 MiB (-23%).

The harness now proves the session rather than inferring it: it asserts an
anonymous request to the private board is denied, requires the Auth.js
session cookie after sign-in, and reports whether it had to modify the
restored data at all (now "none"). It also records cgroup CPU seconds and
board-load latency, so a memory win bought with CPU cannot hide.

pnpm test 144/144 files, 1795 tests; typecheck and lint clean.
2026-08-09 01:43:06 +02:00
Thomas Camlong fb80b3882d test(benchmarks): compare on anon memory, retry pre-hydration login clicks
Two fixes found by running the harness against a real pair of images:

Login submitted before the form hydrated, so the click was a no-op that
looked identical to bad credentials. Load the page at networkidle and
retry the submit, surfacing any page alert if all attempts fail — the
same race already handled for the onboarding restore toggle.

memory.current counts reclaimable page cache, which moved the "same"
baseline between 192 and 261 MiB across runs. Anonymous memory is now
the headline metric (idle was 144 MiB anon vs 77 MiB of page cache), so
an A/B is not decided by unrelated disk I/O.
2026-08-09 00:44:50 +02:00
Thomas Camlong 6363d96a1b test(benchmarks): fix post-login wait, allow V8 flag injection
Sign-in redirects client-side, so page.waitForURL never reached its
implicit "load" state and every run failed after the restore step even
though authentication had succeeded. Poll window.location instead.

STRESS_NODE_OPTIONS injects NODE_OPTIONS directly, which is what makes
the V8 heap cap attributable: the merge-base run.sh does not read
HOMARR_MAX_OLD_SPACE_SIZE, so the cap can only be A/B'd against that
image by passing the flag in.
2026-08-09 00:44:50 +02:00
Thomas Camlong 0d19968131 test(benchmarks): add populated-dashboard memory stress benchmark
Container memory was only ever measured on an empty install, which hides
the cost that actually matters: a real board with real widgets and
integrations loaded.

stress-restore.mts boots a fresh container, restores a supplied backup
through the onboarding restore UI (including the "I understand"
confirmation), signs in, opens the busiest board, waits for every widget
to mount, then hammers navigation and soaks idle while sampling cgroup
v2 memory at each stage.

The backup is passed by path via STRESS_BACKUP_ZIP and never enters the
repo — it carries real integration secrets and an encryption key.
.gitignore now also refuses homarr-backup*.zip and loose *.sqlite.

Baseline reading (merge-base image, 28-item board, all widgets ready):
223 MiB idle -> 448 MiB board loaded -> 688 MiB peak.
2026-08-09 00:44:50 +02:00
Thomas Camlong abec79abe8 fix: address CodeRabbit review — typed mocks, robust benchmark script
- Replace `as any` casts in integration-test-connection.spec.ts with
  Pick<Integration, "testConnectionAsync"> + mockResolvedValue
- build-compare.sh: split local declarations (SC2318), clean .next
  between builds with --force, add EXIT trap for safe stash/cleanup
2026-08-08 13:00:42 +02:00
Thomas Camlong e036cd86d8 perf: move dependencies to build-time static JSON, add build benchmark
- Move getDependenciesAsync filesystem scan to scripts/dump-dependencies.mts
  (runs at build time, outputs static-data/dependencies.json)
- About page now imports dependencies.json like other static data
  (eliminates runtime glob of all package.json files)
- Delete dead getDependenciesAsync + parseDependencyCatalogsAsync from
  package-reader.ts (only getPackageVersion remains)
- Add scripts/benchmarks/build-compare.sh for dev vs branch comparison
2026-08-08 08:58:54 +02:00
Thomas Camlong 6692acbd33 chore: strip formatting noise, delete dead routes, fix CodeRabbit issues
- Remove 179 format-only file changes (docs, migrations, plex, patchmon, CSS)
- Delete .cursor/goal files from git tracking
- Delete 4 dead /api/about routes (unused, data loaded from static JSON)
- Fix resolveAlias to use absolute paths (CodeRabbit)
- Fix Tailwind classes in Mantine app layout fallback (CodeRabbit)
- Return stale data during cache revalidation (CodeRabbit)
2026-08-08 08:45:16 +02:00
Thomas Camlong 018f98e318 perf: implement Phase 1 homepage diet via Next.js Cache Components
P1-0: V8 heap cap (HOMARR_MAX_OLD_SPACE_SIZE, default 512MB)
P1-1: Module graph cuts
  - Dynamic integration imports (55 static → lazy loaders)
  - Conditional DB driver loading (only configured driver loaded)
  - Replace jsdom with linkedom for SVG sanitization
  - Delete eager widget barrel (55 import * as → 0)
  - Extract createRetryableLoader to @homarr/common
P1-2: Enable cacheComponents + restructure root layout
  - Suspense boundaries around session-dependent providers
  - ColorSchemeScript replaces server-side cookie reads
  - Board/manage/auth layouts wrapped in Suspense
P1-3: Cache read paths
  - Cache tag registry + invalidation helpers
  - Cache invalidation wired into all mutation routers
  - Translation deep-merge memoized per locale
P1-4: Prerendered shells + loading.tsx files
P1-5: Shared integration data cache (60s TTL, stale-while-revalidate)
P1-6: Redis-backed Next.js cache handler (@homarr/next-cache-handler)
P1-7: Redis optional (memory channel fallback), TTL on pubSub:last:*,
      Redis clients consolidated from 6 to 2
2026-08-07 18:59:47 +02:00
Thomas Camlong 3f9fb215c6 fix: avoid ambiguous benchmark regex 2026-08-03 14:27:21 +02:00
Thomas Camlong de801f3f00 perf: optimize dashboard loading and builds 2026-08-02 15:58:16 +02:00
ajnart 0b9c4d6375 fix: address performance review feedback 2026-07-29 06:50:13 +02:00
ajnart 440626ba00 test(performance): add authenticated local benchmarks 2026-07-29 06:47:07 +02:00