perf(gateway): trim startup imports and sentinel checks

This commit is contained in:
Peter Steinberger
2026-05-03 16:42:55 +01:00
parent 9dc0f10f8a
commit fa866d562e
7 changed files with 292 additions and 33 deletions

View File

@@ -580,7 +580,10 @@ function parseStartupTraceMetrics(raw: string): Array<{ key: string; value: numb
const value = Number(metricMatch[2]);
if (
!Number.isFinite(value) ||
(key !== "eventLoopMax" && !key.endsWith("Ms") && !key.endsWith("Mb"))
(key !== "eventLoopMax" &&
!key.endsWith("Ms") &&
!key.endsWith("Mb") &&
!key.endsWith("Count"))
) {
continue;
}

View File

@@ -6,6 +6,7 @@ import type { OpenClawConfig } from "../config/types.openclaw.js";
import { normalizePluginsConfig } from "../plugins/config-state.js";
import { clearActivatedPluginRuntimeState, loadOpenClawPlugins } from "../plugins/loader.js";
import { loadPluginLookUpTable, type PluginLookUpTable } from "../plugins/plugin-lookup-table.js";
import { getPluginModuleLoaderStats } from "../plugins/plugin-module-loader-cache.js";
import { createEmptyPluginRegistry } from "../plugins/registry-empty.js";
import { setActivePluginRegistry } from "../plugins/runtime.js";
import { getPluginRuntimeGatewayRequestScope } from "../plugins/runtime/gateway-request-scope.js";
@@ -599,6 +600,7 @@ export function loadGatewayPlugins(params: {
};
}
const beforeLoad = performance.now();
const loaderStatsBefore = getPluginModuleLoaderStats();
const pluginRegistry = loadOpenClawPlugins({
config: resolvedConfig,
activationSourceConfig: params.activationSourceConfig ?? params.cfg,
@@ -624,6 +626,7 @@ export function loadGatewayPlugins(params: {
: {}),
});
const loadMs = performance.now() - beforeLoad;
const loaderStatsAfter = getPluginModuleLoaderStats();
const pluginMethods = Object.keys(pluginRegistry.gatewayHandlers);
const gatewayMethods = Array.from(new Set([...params.baseMethods, ...pluginMethods]));
params.startupTrace?.detail("plugins.gateway-load", [
@@ -633,6 +636,24 @@ export function loadGatewayPlugins(params: {
["loadMs", loadMs],
["pluginIds", String(pluginIds.length)],
["gatewayHandlers", String(pluginMethods.length)],
["loaderCallsCount", loaderStatsAfter.calls - loaderStatsBefore.calls],
["loaderNativeHitsCount", loaderStatsAfter.nativeHits - loaderStatsBefore.nativeHits],
["loaderNativeMissesCount", loaderStatsAfter.nativeMisses - loaderStatsBefore.nativeMisses],
[
"loaderSourceTransformForcedCount",
loaderStatsAfter.sourceTransformForced - loaderStatsBefore.sourceTransformForced,
],
[
"loaderSourceTransformFallbacksCount",
loaderStatsAfter.sourceTransformFallbacks - loaderStatsBefore.sourceTransformFallbacks,
],
[
"loaderTopSourceTransformTargets",
loaderStatsAfter.topSourceTransformTargets
.slice(0, 3)
.map((entry) => `${entry.count}:${entry.target}`)
.join(","),
],
]);
return { pluginRegistry, gatewayMethods };
}

View File

@@ -1,3 +1,6 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type {
PluginHookGatewayContext,
@@ -21,7 +24,9 @@ const hoisted = vi.hoisted(() => {
const scheduleSubagentOrphanRecovery = vi.fn();
const shouldWakeFromRestartSentinel = vi.fn(() => false);
const scheduleRestartSentinelWake = vi.fn();
const refreshLatestUpdateRestartSentinel = vi.fn(async () => null);
const refreshLatestUpdateRestartSentinel = vi.fn<
typeof import("./server-restart-sentinel.js").refreshLatestUpdateRestartSentinel
>(async () => null);
const getAcpRuntimeBackend = vi.fn<(id?: string) => unknown>(() => null);
const reconcilePendingSessionIdentities = vi.fn(async () => ({
checked: 0,
@@ -281,6 +286,61 @@ describe("startGatewayPostAttachRuntime", () => {
expect(events).toEqual(["sidecars", "returned", "sentinel"]);
});
it("skips heavy restart sentinel refresh when no sentinel file exists", async () => {
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-no-sentinel-"));
vi.stubEnv("OPENCLAW_STATE_DIR", stateDir);
const result = await __testing.refreshLatestUpdateRestartSentinelIfPresent();
expect(result).toBeNull();
expect(hoisted.refreshLatestUpdateRestartSentinel).not.toHaveBeenCalled();
fs.rmSync(stateDir, { recursive: true, force: true });
});
it("refreshes the restart sentinel when the sentinel file exists", async () => {
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sentinel-"));
fs.writeFileSync(path.join(stateDir, "restart-sentinel.json"), "{}\n");
vi.stubEnv("OPENCLAW_STATE_DIR", stateDir);
const sentinel = { kind: "update", status: "ok", ts: 1 } as const;
hoisted.refreshLatestUpdateRestartSentinel.mockResolvedValue(sentinel);
const result = await __testing.refreshLatestUpdateRestartSentinelIfPresent();
expect(result).toBe(sentinel);
expect(hoisted.refreshLatestUpdateRestartSentinel).toHaveBeenCalledOnce();
fs.rmSync(stateDir, { recursive: true, force: true });
});
it("expands tilde-based restart sentinel state paths", () => {
const osHome = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-home-"));
try {
const openclawHome = path.join(osHome, "openclaw-home");
const stateDirFromHome = path.join(openclawHome, ".openclaw");
fs.mkdirSync(stateDirFromHome, { recursive: true });
fs.writeFileSync(path.join(stateDirFromHome, "restart-sentinel.json"), "{}\n");
expect(
__testing.hasRestartSentinelFileFast({
HOME: osHome,
OPENCLAW_HOME: "~/openclaw-home",
} as NodeJS.ProcessEnv),
).toBe(true);
const backslashStateDir = path.resolve(`${osHome}\\openclaw-state`);
fs.mkdirSync(backslashStateDir, { recursive: true });
fs.writeFileSync(path.join(backslashStateDir, "restart-sentinel.json"), "{}\n");
expect(
__testing.hasRestartSentinelFileFast({
HOME: osHome,
OPENCLAW_STATE_DIR: "~\\openclaw-state",
} as NodeJS.ProcessEnv),
).toBe(true);
} finally {
fs.rmSync(osHome, { recursive: true, force: true });
}
});
it("loads deferred startup plugins before channel sidecars", async () => {
const events: string[] = [];
const loadedPluginRegistry = {

View File

@@ -1,3 +1,6 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { setTimeout as sleep } from "node:timers/promises";
import type { CliDeps } from "../cli/deps.types.js";
import type { GatewayTailscaleMode } from "../config/types.gateway.js";
@@ -8,6 +11,7 @@ import type { scheduleGatewayUpdateCheck } from "../infra/update-startup.js";
import type { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
import type { PluginHookGatewayCronService } from "../plugins/hook-types.js";
import type { loadOpenClawPlugins } from "../plugins/loader.js";
import { getPluginModuleLoaderStats } from "../plugins/plugin-module-loader-cache.js";
import type { PluginRegistry } from "../plugins/registry.js";
import type { PluginServicesHandle } from "../plugins/services.js";
import {
@@ -26,10 +30,12 @@ const PRIMARY_MODEL_PREWARM_TIMEOUT_MS = 5_000;
const STARTUP_PROVIDER_DISCOVERY_TIMEOUT_MS = 5_000;
const SKIP_STARTUP_MODEL_PREWARM_ENV = "OPENCLAW_SKIP_STARTUP_MODEL_PREWARM";
const QMD_STARTUP_IDLE_DELAY_MS = 120_000;
const RESTART_SENTINEL_FILENAME = "restart-sentinel.json";
type Awaitable<T> = T | Promise<T>;
type GatewayStartupTrace = {
detail: (name: string, metrics: ReadonlyArray<readonly [string, number | string]>) => void;
mark: (name: string) => void;
measure: <T>(name: string, run: () => Awaitable<T>) => Promise<T>;
};
@@ -124,6 +130,61 @@ function schedulePostAttachUpdateSentinelRefresh(params: {
handle.unref?.();
}
function resolveRestartSentinelPathFast(env: NodeJS.ProcessEnv = process.env): string {
const normalizePathEnv = (value: string | undefined) => {
const trimmed = value?.trim();
return trimmed && trimmed !== "undefined" && trimmed !== "null" ? trimmed : undefined;
};
const resolveRawOsHome = () => normalizePathEnv(env.HOME) ?? normalizePathEnv(env.USERPROFILE);
const expandHomePrefix = (input: string, home: string) => input.replace(/^~(?=$|[\\/])/, home);
const resolveHome = () => {
const explicitHome = normalizePathEnv(env.OPENCLAW_HOME);
if (explicitHome) {
const osHome = resolveRawOsHome() ?? os.homedir();
return path.resolve(expandHomePrefix(explicitHome, osHome));
}
return path.resolve(resolveRawOsHome() ?? os.homedir());
};
const resolveUserPath = (input: string) => {
const trimmed = input.trim();
if (trimmed.startsWith("~")) {
return path.resolve(expandHomePrefix(trimmed, resolveHome()));
}
return path.resolve(trimmed);
};
const override = normalizePathEnv(env.OPENCLAW_STATE_DIR);
if (override) {
return path.join(resolveUserPath(override), RESTART_SENTINEL_FILENAME);
}
const home = resolveHome();
const newStateDir = path.join(home, ".openclaw");
if (env.OPENCLAW_TEST_FAST === "1" || fs.existsSync(newStateDir)) {
return path.join(newStateDir, RESTART_SENTINEL_FILENAME);
}
const legacyStateDir = path.join(home, ".clawdbot");
if (fs.existsSync(legacyStateDir)) {
return path.join(legacyStateDir, RESTART_SENTINEL_FILENAME);
}
return path.join(newStateDir, RESTART_SENTINEL_FILENAME);
}
function hasRestartSentinelFileFast(env: NodeJS.ProcessEnv = process.env): boolean {
try {
return fs.existsSync(resolveRestartSentinelPathFast(env));
} catch {
return false;
}
}
async function refreshLatestUpdateRestartSentinelIfPresent(): Promise<Awaited<
ReturnType<typeof refreshLatestUpdateRestartSentinel>
> | null> {
if (!hasRestartSentinelFileFast()) {
return null;
}
return await (await import("./server-restart-sentinel.js")).refreshLatestUpdateRestartSentinel();
}
function hasGatewayStartHooks(pluginRegistry: ReturnType<typeof loadOpenClawPlugins>): boolean {
return pluginRegistry.typedHooks.some((hook) => hook.hookName === "gateway_start");
}
@@ -507,8 +568,7 @@ export async function startGatewaySidecars(params: {
if (!shouldCheckRestartSentinel()) {
return;
}
const { hasRestartSentinel } = await import("../infra/restart-sentinel.js");
if (!(await hasRestartSentinel())) {
if (!hasRestartSentinelFileFast()) {
return;
}
setTimeout(() => {
@@ -556,8 +616,7 @@ const defaultGatewayPostAttachRuntimeDeps: GatewayPostAttachRuntimeDeps = {
(await import("../plugins/hook-runner-global.js")).getGlobalHookRunner(),
logGatewayStartup: async (params) =>
(await import("./server-startup-log.js")).logGatewayStartup(params),
refreshLatestUpdateRestartSentinel: async () =>
(await import("./server-restart-sentinel.js")).refreshLatestUpdateRestartSentinel(),
refreshLatestUpdateRestartSentinel: refreshLatestUpdateRestartSentinelIfPresent,
scheduleGatewayUpdateCheck: async (...args) =>
(await import("../infra/update-startup.js")).scheduleGatewayUpdateCheck(...args),
startGatewaySidecars,
@@ -676,6 +735,7 @@ export async function startGatewayPostAttachRuntime(
? Promise.resolve({ pluginServices: null, pluginRegistry })
: new Promise<void>((resolve) => setImmediate(resolve)).then(async () => {
params.log.info("starting channels and sidecars...");
const loaderStatsBefore = getPluginModuleLoaderStats();
const result = await measureStartup(params.startupTrace, "sidecars.total", () =>
runtimeDeps.startGatewaySidecars({
cfg: params.gatewayPluginConfigAtStart,
@@ -689,6 +749,20 @@ export async function startGatewayPostAttachRuntime(
startupTrace: params.startupTrace,
}),
);
const loaderStatsAfter = getPluginModuleLoaderStats();
params.startupTrace?.detail("sidecars.plugin-loader", [
["callsCount", loaderStatsAfter.calls - loaderStatsBefore.calls],
["nativeHitsCount", loaderStatsAfter.nativeHits - loaderStatsBefore.nativeHits],
["nativeMissesCount", loaderStatsAfter.nativeMisses - loaderStatsBefore.nativeMisses],
[
"sourceTransformForcedCount",
loaderStatsAfter.sourceTransformForced - loaderStatsBefore.sourceTransformForced,
],
[
"sourceTransformFallbacksCount",
loaderStatsAfter.sourceTransformFallbacks - loaderStatsBefore.sourceTransformFallbacks,
],
]);
for (const method of STARTUP_UNAVAILABLE_GATEWAY_METHODS) {
params.unavailableGatewayMethods.delete(method);
}
@@ -758,8 +832,10 @@ export async function startGatewayPostAttachRuntime(
}
export const __testing = {
hasRestartSentinelFileFast,
prewarmConfiguredPrimaryModel,
prewarmConfiguredPrimaryModelWithTimeout,
refreshLatestUpdateRestartSentinelIfPresent,
resolveGatewayMemoryStartupPolicy,
schedulePrimaryModelPrewarm,
shouldSkipStartupModelPrewarm,

View File

@@ -60,28 +60,15 @@ import {
listChannelPluginConfigTargetIds,
pluginConfigTargetsChanged,
} from "./plugin-channel-reload-targets.js";
import { createGatewayAuxHandlers } from "./server-aux-handlers.js";
import { createChannelManager } from "./server-channels.js";
import { resolveGatewayControlUiRootState } from "./server-control-ui-root.js";
import { createLazyGatewayCronState } from "./server-cron-lazy.js";
import { applyGatewayLaneConcurrency } from "./server-lanes.js";
import { createGatewayServerLiveState, type GatewayServerLiveState } from "./server-live-state.js";
import { GATEWAY_EVENTS } from "./server-methods-list.js";
import type { GatewayRequestHandlers } from "./server-methods/types.js";
import { loadGatewayModelCatalog } from "./server-model-catalog.js";
import { bootstrapGatewayNetworkRuntime } from "./server-network-runtime.js";
import { createGatewayNodeSessionRuntime } from "./server-node-session-runtime.js";
import { setFallbackGatewayContextResolver } from "./server-plugins.js";
import type { GatewayPluginReloadResult } from "./server-reload-handlers.js";
import { createGatewayRequestContext } from "./server-request-context.js";
import { resolveGatewayRuntimeConfig } from "./server-runtime-config.js";
import {
activateGatewayScheduledServices,
startGatewayCronWithLogging,
startGatewayRuntimeServices,
} from "./server-runtime-services.js";
import { createGatewayRuntimeState } from "./server-runtime-state.js";
import { startGatewayEventSubscriptions } from "./server-runtime-subscriptions.js";
import { resolveSessionKeyForRun } from "./server-session-key.js";
import {
enforceSharedGatewaySessionGenerationForConfigWrite,
@@ -104,7 +91,6 @@ import {
startGatewayPostAttachRuntime,
} from "./server-startup.js";
import { createWizardSessionTracker } from "./server-wizard-sessions.js";
import { attachGatewayWsHandlers } from "./server-ws-runtime.js";
import { createGatewayEventLoopHealthMonitor } from "./server/event-loop-health.js";
import {
getHealthCache,
@@ -172,6 +158,17 @@ function loadGatewayCloseModule(): Promise<typeof import("./server-close.js")> {
return gatewayCloseModulePromise;
}
type LoadGatewayModelCatalog = typeof import("./server-model-catalog.js").loadGatewayModelCatalog;
let gatewayModelCatalogModulePromise: Promise<typeof import("./server-model-catalog.js")> | null =
null;
const loadGatewayModelCatalog: LoadGatewayModelCatalog = async (...args) => {
gatewayModelCatalogModulePromise ??= import("./server-model-catalog.js");
const mod = await gatewayModelCatalogModulePromise;
return mod.loadGatewayModelCatalog(...args);
};
const logHealth = log.child("health");
const logCron = log.child("cron");
const logReload = log.child("reload");
@@ -490,6 +487,7 @@ export async function startGatewayServer(
port = 18789,
opts: GatewayServerOptions = {},
): Promise<GatewayServer> {
const { bootstrapGatewayNetworkRuntime } = await import("./server-network-runtime.js");
bootstrapGatewayNetworkRuntime();
const minimalTestGateway =
@@ -660,8 +658,9 @@ export async function startGatewayServer(
...listChannelPlugins().flatMap((plugin) => plugin.gatewayMethods ?? []),
]),
);
const runtimeConfig = await startupTrace.measure("runtime.config", () =>
resolveGatewayRuntimeConfig({
const runtimeConfig = await startupTrace.measure("runtime.config", async () => {
const { resolveGatewayRuntimeConfig } = await import("./server-runtime-config.js");
return resolveGatewayRuntimeConfig({
cfg: cfgAtStart,
port,
bind: opts.bind,
@@ -671,8 +670,8 @@ export async function startGatewayServer(
openResponsesEnabled: opts.openResponsesEnabled,
auth: opts.auth,
tailscale: opts.tailscale,
}),
);
});
});
const {
bindHost,
controlUiEnabled,
@@ -755,6 +754,7 @@ export async function startGatewayServer(
const readinessEventLoopHealth = createGatewayEventLoopHealthMonitor();
let startupSidecarsReady = minimalTestGateway;
let startupPendingReason = "startup-sidecars";
const { createChannelManager } = await import("./server-channels.js");
const channelManager = createChannelManager({
getRuntimeConfig: () =>
applyPluginAutoEnable({
@@ -832,6 +832,7 @@ export async function startGatewayServer(
getReadiness,
}),
);
const { createGatewayNodeSessionRuntime } = await import("./server-node-session-runtime.js");
const {
nodeRegistry,
nodePresenceTimers,
@@ -980,6 +981,10 @@ export async function startGatewayServer(
getActiveTaskCount = earlyRuntime.getActiveTaskCount;
runtimeState.skillsChangeUnsub = earlyRuntime.skillsChangeUnsub;
const [{ startGatewayEventSubscriptions }, gatewayRuntimeServices] = await Promise.all([
import("./server-runtime-subscriptions.js"),
import("./server-runtime-services.js"),
]);
Object.assign(
runtimeState,
startGatewayEventSubscriptions({
@@ -999,7 +1004,7 @@ export async function startGatewayServer(
Object.assign(
runtimeState,
startGatewayRuntimeServices({
gatewayRuntimeServices.startGatewayRuntimeServices({
minimalTestGateway,
cfgAtStart,
channelManager,
@@ -1007,6 +1012,7 @@ export async function startGatewayServer(
}),
);
const { createGatewayAuxHandlers } = await import("./server-aux-handlers.js");
const { execApprovalManager, pluginApprovalManager, extraHandlers } = createGatewayAuxHandlers({
log,
activateRuntimeSecrets,
@@ -1179,6 +1185,7 @@ export async function startGatewayServer(
const unavailableGatewayMethods = new Set<string>(
minimalTestGateway ? [] : STARTUP_UNAVAILABLE_GATEWAY_METHODS,
);
const { createGatewayRequestContext } = await import("./server-request-context.js");
const gatewayRequestContext = createGatewayRequestContext({
deps,
runtimeState,
@@ -1272,6 +1279,7 @@ export async function startGatewayServer(
}
}
const { attachGatewayWsHandlers } = await import("./server-ws-runtime.js");
attachGatewayWsHandlers({
wss,
clients,
@@ -1311,7 +1319,7 @@ export async function startGatewayServer(
) {
return;
}
const activated = activateGatewayScheduledServices({
const activated = gatewayRuntimeServices.activateGatewayScheduledServices({
minimalTestGateway,
cfgAtStart,
deps,
@@ -1445,7 +1453,7 @@ export async function startGatewayServer(
runtimeState.dedupeCleanup = maintenance.dedupeCleanup;
runtimeState.mediaCleanup = maintenance.mediaCleanup;
}
startGatewayCronWithLogging({
gatewayRuntimeServices.startGatewayCronWithLogging({
cron: runtimeState.cronState.cron,
logCron,
});

View File

@@ -371,7 +371,7 @@ describe("getCachedPluginModuleLoader", () => {
p.endsWith(".js") || p.endsWith(".mjs") || p.endsWith(".cjs"),
tryNativeRequireJavaScriptModule: nativeStub,
}));
const { getCachedPluginModuleLoader } = await importFreshModule<
const { getCachedPluginModuleLoader, getPluginModuleLoaderStats } = await importFreshModule<
typeof import("./plugin-module-loader-cache.js")
>(import.meta.url, "./plugin-module-loader-cache.js?scope=native-require-fastpath");
@@ -393,6 +393,13 @@ describe("getCachedPluginModuleLoader", () => {
expect(nativeStub).toHaveBeenCalledWith("/repo/dist/extensions/demo/api.js", {
allowWindows: true,
});
expect(getPluginModuleLoaderStats()).toMatchObject({
calls: 1,
nativeHits: 1,
nativeMisses: 0,
sourceTransformFallbacks: 0,
sourceTransformForced: 0,
});
});
it("falls back to source transform when the native-require helper declines", async () => {
@@ -403,7 +410,7 @@ describe("getCachedPluginModuleLoader", () => {
isJavaScriptModulePath: () => true,
tryNativeRequireJavaScriptModule: () => ({ ok: false }),
}));
const { getCachedPluginModuleLoader } = await importFreshModule<
const { getCachedPluginModuleLoader, getPluginModuleLoaderStats } = await importFreshModule<
typeof import("./plugin-module-loader-cache.js")
>(import.meta.url, "./plugin-module-loader-cache.js?scope=native-require-fallback");
@@ -418,6 +425,14 @@ describe("getCachedPluginModuleLoader", () => {
const result = loader("/repo/dist/extensions/demo/api.js") as { fromSourceTransform: boolean };
expect(result.fromSourceTransform).toBe(true);
expect(fromSourceTransformer).toHaveBeenCalledWith("/repo/dist/extensions/demo/api.js");
expect(getPluginModuleLoaderStats()).toMatchObject({
calls: 1,
nativeHits: 0,
nativeMisses: 1,
sourceTransformFallbacks: 1,
sourceTransformForced: 0,
topSourceTransformTargets: [{ target: "/repo/dist/extensions/demo/api.js", count: 1 }],
});
});
it("normalizes Windows absolute paths before creating and calling the source transformer", async () => {
@@ -462,7 +477,7 @@ describe("getCachedPluginModuleLoader", () => {
isJavaScriptModulePath: () => true,
tryNativeRequireJavaScriptModule: nativeStub,
}));
const { getCachedPluginModuleLoader } = await importFreshModule<
const { getCachedPluginModuleLoader, getPluginModuleLoaderStats } = await importFreshModule<
typeof import("./plugin-module-loader-cache.js")
>(import.meta.url, "./plugin-module-loader-cache.js?scope=native-require-opt-out");
@@ -482,6 +497,14 @@ describe("getCachedPluginModuleLoader", () => {
// so its alias rewrites still apply; native require must not be consulted.
expect(nativeStub).not.toHaveBeenCalled();
expect(fromSourceTransformer).toHaveBeenCalledWith("/repo/dist/extensions/demo/api.js");
expect(getPluginModuleLoaderStats()).toMatchObject({
calls: 1,
nativeHits: 0,
nativeMisses: 0,
sourceTransformFallbacks: 0,
sourceTransformForced: 1,
topSourceTransformTargets: [{ target: "/repo/dist/extensions/demo/api.js", count: 1 }],
});
});
it("normalizes Windows absolute paths when native loading is disabled", async () => {

View File

@@ -34,8 +34,67 @@ export type PluginModuleLoaderCacheEntry = {
cacheKey: string;
scopedCacheKey: string;
};
export type PluginModuleLoaderStatsSnapshot = {
calls: number;
nativeHits: number;
nativeMisses: number;
sourceTransformForced: number;
sourceTransformFallbacks: number;
topSourceTransformTargets: Array<{ target: string; count: number }>;
};
const DEFAULT_PLUGIN_MODULE_LOADER_CACHE_ENTRIES = 128;
const MAX_TRACKED_SOURCE_TRANSFORM_TARGETS = 24;
const pluginModuleLoaderStats = {
calls: 0,
nativeHits: 0,
nativeMisses: 0,
sourceTransformForced: 0,
sourceTransformFallbacks: 0,
sourceTransformTargets: new Map<string, number>(),
};
function recordSourceTransformTarget(target: string): void {
const current = pluginModuleLoaderStats.sourceTransformTargets.get(target) ?? 0;
pluginModuleLoaderStats.sourceTransformTargets.set(target, current + 1);
if (pluginModuleLoaderStats.sourceTransformTargets.size <= MAX_TRACKED_SOURCE_TRANSFORM_TARGETS) {
return;
}
let leastUsedTarget: string | undefined;
let leastUsedCount = Number.POSITIVE_INFINITY;
for (const [candidate, count] of pluginModuleLoaderStats.sourceTransformTargets) {
if (count < leastUsedCount) {
leastUsedTarget = candidate;
leastUsedCount = count;
}
}
if (leastUsedTarget) {
pluginModuleLoaderStats.sourceTransformTargets.delete(leastUsedTarget);
}
}
export function getPluginModuleLoaderStats(): PluginModuleLoaderStatsSnapshot {
return {
calls: pluginModuleLoaderStats.calls,
nativeHits: pluginModuleLoaderStats.nativeHits,
nativeMisses: pluginModuleLoaderStats.nativeMisses,
sourceTransformForced: pluginModuleLoaderStats.sourceTransformForced,
sourceTransformFallbacks: pluginModuleLoaderStats.sourceTransformFallbacks,
topSourceTransformTargets: [...pluginModuleLoaderStats.sourceTransformTargets]
.toSorted((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))
.slice(0, 8)
.map(([target, count]) => ({ target, count })),
};
}
export function resetPluginModuleLoaderStatsForTest(): void {
pluginModuleLoaderStats.calls = 0;
pluginModuleLoaderStats.nativeHits = 0;
pluginModuleLoaderStats.nativeMisses = 0;
pluginModuleLoaderStats.sourceTransformForced = 0;
pluginModuleLoaderStats.sourceTransformFallbacks = 0;
pluginModuleLoaderStats.sourceTransformTargets.clear();
}
export function createPluginModuleLoaderCache(
maxEntries = DEFAULT_PLUGIN_MODULE_LOADER_CACHE_ENTRIES,
@@ -139,11 +198,15 @@ function createPluginModuleLoader(params: {
// jiti's alias rewriting to surface a narrow SDK slice), route every
// target through jiti so those alias rewrites still apply.
if (!params.tryNative) {
return ((target: string, ...rest: unknown[]) =>
(getLoadWithSourceTransform() as (t: string, ...a: unknown[]) => unknown)(
return ((target: string, ...rest: unknown[]) => {
pluginModuleLoaderStats.calls += 1;
pluginModuleLoaderStats.sourceTransformForced += 1;
recordSourceTransformTarget(target);
return (getLoadWithSourceTransform() as (t: string, ...a: unknown[]) => unknown)(
target,
...rest,
)) as PluginModuleLoader;
);
}) as PluginModuleLoader;
}
// Otherwise prefer native require() for already-compiled JS artifacts
// (the bundled plugin public surfaces shipped in dist/). jiti's transform
@@ -153,10 +216,15 @@ function createPluginModuleLoader(params: {
// async-module fallbacks `tryNativeRequireJavaScriptModule` declines to
// handle.
return ((target: string, ...rest: unknown[]) => {
pluginModuleLoaderStats.calls += 1;
const native = tryNativeRequireJavaScriptModule(target, { allowWindows: true });
if (native.ok) {
pluginModuleLoaderStats.nativeHits += 1;
return native.moduleExport;
}
pluginModuleLoaderStats.nativeMisses += 1;
pluginModuleLoaderStats.sourceTransformFallbacks += 1;
recordSourceTransformTarget(target);
return (getLoadWithSourceTransform() as (t: string, ...a: unknown[]) => unknown)(
target,
...rest,