test(performance): count startup phases and SQL statements for the J1 launch journey

Implements plan item A2. With IPTVNATOR_PERF_CAPTURE=1 the main process
keeps named counters and registers a main-only performance:read-counters
IPC handler; without the flag nothing is counted and the handler does not
exist.

- debug-trace.ts owns the registry; traceStartupPhase replaces the
  trace('startup', ...) sites and counts main.startupPhases.
- The database worker counts executed statements through better-sqlite3's
  Statement prototype (the verbose callback expands every statement and
  made bulk inserts 2-4x slower) and posts the count over its message
  port, flushed before every other worker message. The main-thread shared
  connection is counted through a new connection observer in the shared
  database library.
- The first main window freezes main.modulesRegisteredBeforeWindow at
  creation and main.sqlStatementsBeforeReadyToShow at ready-to-show.
- The journey gate drops the ready-to-show that Electron emits for the
  about:blank detour, so the app sees the real document's first paint,
  and taps the counters handler; the J1 record reads both counters after
  the renderer probe completes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-09-27 18:48:29 +02:00
1 parent 9d02f90dfe
commit 37ac72b951
38 files changed
+1817 -73

No files matched your search

+3
View File
@@ -28,6 +28,9 @@ import { content, categories, playlists, type Content } from '@iptvnator/shared/
- `closeDatabase()` - Close connection
- `getDatabasePath()` - Get database file path
### Connection observer (`connection-observer.ts`)
- `setDatabaseConnectionObserver(observer | null)` - Called by `initDatabase` with each connection it opens, before any statement runs on it. The Electron main process registers one only with `IPTVNATOR_PERF_CAPTURE=1`, to count main-thread SQL statements. The module has no runtime dependencies and is also importable as `@iptvnator/shared/database/connection-observer`.
## Database Location
The SQLite database is stored at: `~/.iptvnator/databases/iptvnator.db`
+1
View File
@@ -7,3 +7,4 @@
export * from './lib/schema';
export * from './lib/connection';
export * from './lib/path-utils';
export * from './lib/connection-observer';
@@ -0,0 +1,30 @@
import type Database from 'better-sqlite3';
import {
notifyDatabaseConnectionOpened,
setDatabaseConnectionObserver,
} from './connection-observer';
describe('database connection observer', () => {
afterEach(() => {
setDatabaseConnectionObserver(null);
});
it('does nothing when no observer is registered', () => {
expect(() =>
notifyDatabaseConnectionOpened({} as Database.Database)
).not.toThrow();
});
it('passes each opened connection to the registered observer until removed', () => {
const observer = jest.fn();
const first = { name: 'first' } as unknown as Database.Database;
const second = { name: 'second' } as unknown as Database.Database;
setDatabaseConnectionObserver(observer);
notifyDatabaseConnectionOpened(first);
setDatabaseConnectionObserver(null);
notifyDatabaseConnectionOpened(second);
expect(observer.mock.calls).toEqual([[first]]);
});
});
@@ -0,0 +1,26 @@
import type Database from 'better-sqlite3';
export type DatabaseConnectionObserver = (
connection: Database.Database
) => void;
let observer: DatabaseConnectionObserver | null = null;
/**
* Registers a callback that `initDatabase` calls with each connection it
* opens, before any statement runs on it; `null` removes it. The Electron
* main process uses it with IPTVNATOR_PERF_CAPTURE=1 to count main-thread
* SQL statements. This module has no runtime dependencies, so registering
* the observer does not load better-sqlite3.
*/
export function setDatabaseConnectionObserver(
next: DatabaseConnectionObserver | null
): void {
observer = next;
}
export function notifyDatabaseConnectionOpened(
connection: Database.Database
): void {
observer?.(connection);
}
@@ -23,6 +23,7 @@ import {
} from '@iptvnator/shared/logging';
import * as schema from './schema';
import { getIptvnatorDatabasePath } from './path-utils';
import { notifyDatabaseConnectionOpened } from './connection-observer';
export type DatabaseInstance = BetterSQLite3Database<typeof schema>;
@@ -1228,6 +1229,7 @@ export async function initDatabase(
? (message?: unknown) => traceSqlStatement(message)
: undefined,
});
notifyDatabaseConnectionOpened(sqlite);
if (isSqlTraceEnabled()) {
traceSql('sql-main', 'open', {