
























@@ -46,6 +46,7 @@ import {
4646__testing,
4747clearPluginLoaderCache,
4848loadOpenClawPlugins,
49+type PluginLoadOptions,
4950PluginLoadReentryError,
5051resolveRuntimePluginRegistry,
5152} from "./loader.js";
@@ -96,6 +97,10 @@ let cachedBundledTelegramDir = "";
9697let cachedBundledMemoryDir = "";
97989899type GlobalHookRunner = NonNullable<ReturnType<typeof getGlobalHookRunner>>;
100+type PluginStartupTraceDetail = {
101+name: string;
102+metrics: ReadonlyArray<readonly [string, number | string]>;
103+};
99104100105function countMatching<T>(items: readonly T[], predicate: (item: T) => boolean): number {
101106let count = 0;
@@ -913,6 +918,36 @@ function expectEscapingEntryRejected(params: {
913918return registry;
914919}
915920921+function createStartupTraceRecorder(): {
922+details: PluginStartupTraceDetail[];
923+startupTrace: NonNullable<PluginLoadOptions["startupTrace"]>;
924+} {
925+const details: PluginStartupTraceDetail[] = [];
926+return {
927+ details,
928+startupTrace: {
929+detail: (name, metrics) => {
930+details.push({ name, metrics });
931+},
932+},
933+};
934+}
935+936+function collectStartupTraceMetrics(
937+details: readonly PluginStartupTraceDetail[],
938+name: string,
939+): Record<string, number | string> {
940+const matched = details.filter((entry) => entry.name === name);
941+expect(matched.length).toBeGreaterThan(0);
942+const metrics: Record<string, number | string> = {};
943+for (const entry of matched) {
944+for (const [key, value] of entry.metrics) {
945+metrics[key] = value;
946+}
947+}
948+return metrics;
949+}
950+916951afterEach(() => {
917952clearRuntimeConfigSnapshot();
918953runtimeRegistryLoaderTesting.resetPluginRegistryLoadedForTests();
@@ -926,6 +961,76 @@ afterAll(() => {
926961});
927962928963describe("loadOpenClawPlugins", () => {
964+it("emits loader startup trace timings for normal plugin load and register", () => {
965+useNoBundledPlugins();
966+const plugin = writePlugin({
967+id: "trace-plugin",
968+filename: "trace-plugin.cjs",
969+body: `module.exports = { id: "trace-plugin", register() {} };`,
970+});
971+const { details, startupTrace } = createStartupTraceRecorder();
972+973+loadRegistryFromSinglePlugin({
974+ plugin,
975+pluginConfig: {
976+allow: ["trace-plugin"],
977+},
978+options: {
979+ startupTrace,
980+},
981+});
982+983+const metrics = collectStartupTraceMetrics(details, "plugins.gateway-load.plugin.trace-plugin");
984+expect(metrics.loadMs).toEqual(expect.any(Number));
985+expect(metrics.loadFailedCount).toBe(0);
986+expect(metrics.registerMs).toEqual(expect.any(Number));
987+expect(metrics.registerFailedCount).toBe(0);
988+expect(metrics.loadAndRegisterMs).toEqual(expect.any(Number));
989+});
990+991+it("emits loader startup trace failure counts for load and register failures", () => {
992+useNoBundledPlugins();
993+const loadFailPlugin = writePlugin({
994+id: "trace-load-fail",
995+filename: "trace-load-fail.cjs",
996+body: `throw new Error("load boom");`,
997+});
998+const registerFailPlugin = writePlugin({
999+id: "trace-register-fail",
1000+filename: "trace-register-fail.cjs",
1001+body: `module.exports = { id: "trace-register-fail", register() { throw new Error("register boom"); } };`,
1002+});
1003+const { details, startupTrace } = createStartupTraceRecorder();
1004+1005+loadOpenClawPlugins({
1006+cache: false,
1007+config: {
1008+plugins: {
1009+load: { paths: [loadFailPlugin.file, registerFailPlugin.file] },
1010+allow: ["trace-load-fail", "trace-register-fail"],
1011+},
1012+},
1013+ startupTrace,
1014+});
1015+1016+const loadFailMetrics = collectStartupTraceMetrics(
1017+details,
1018+"plugins.gateway-load.plugin.trace-load-fail",
1019+);
1020+expect(loadFailMetrics.loadMs).toEqual(expect.any(Number));
1021+expect(loadFailMetrics.loadFailedCount).toBe(1);
1022+expect(loadFailMetrics.registerMs).toBeUndefined();
1023+1024+const registerFailMetrics = collectStartupTraceMetrics(
1025+details,
1026+"plugins.gateway-load.plugin.trace-register-fail",
1027+);
1028+expect(registerFailMetrics.loadFailedCount).toBe(0);
1029+expect(registerFailMetrics.registerMs).toEqual(expect.any(Number));
1030+expect(registerFailMetrics.registerFailedCount).toBe(1);
1031+expect(registerFailMetrics.loadAndRegisterMs).toEqual(expect.any(Number));
1032+});
1033+9291034it("can load scoped plugins from a supplied manifest registry without rereading manifests", () => {
9301035useNoBundledPlugins();
9311036const plugin = writePlugin({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。