okengine 0.17.2 → 0.18.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -3
- package/README.md +14 -10
- package/manifest.v1.schema.json +61 -2
- package/package.json +19 -5
- package/site/content/docs/ai/index.mdx +1 -1
- package/site/content/docs/ai/mcp.mdx +10 -1
- package/site/content/docs/ai/skills.mdx +9 -9
- package/site/content/docs/elements/ai.mdx +1 -1
- package/site/content/docs/elements/clock.mdx +1 -1
- package/site/content/docs/elements/flow.mdx +25 -1
- package/site/content/docs/elements/gate.mdx +3 -2
- package/site/content/docs/elements/store.mdx +289 -341
- package/site/content/docs/elements/vault.mdx +5 -5
- package/site/content/docs/get-started/basic-usage.mdx +3 -10
- package/site/content/docs/get-started/index.mdx +1 -1
- package/site/content/docs/get-started/installation.mdx +2 -3
- package/site/content/docs/get-started/introduction.mdx +58 -121
- package/site/content/docs/get-started/meta.json +9 -1
- package/site/content/docs/get-started/project-structure.mdx +4 -11
- package/site/content/docs/get-started/testing.mdx +328 -0
- package/site/content/docs/get-started/why.mdx +93 -71
- package/site/content/docs/index.mdx +44 -11
- package/site/content/docs/meta.json +8 -5
- package/site/content/docs/plugins/apple.mdx +151 -0
- package/site/content/docs/plugins/discord.mdx +139 -0
- package/site/content/docs/plugins/facebook.mdx +134 -0
- package/site/content/docs/plugins/figma.mdx +138 -0
- package/site/content/docs/plugins/github.mdx +138 -0
- package/site/content/docs/plugins/google.mdx +153 -0
- package/site/content/docs/plugins/index.mdx +47 -1
- package/site/content/docs/plugins/meta.json +10 -0
- package/site/content/docs/plugins/microsoft.mdx +151 -0
- package/site/content/docs/plugins/oauth.mdx +188 -0
- package/site/content/docs/plugins/x.mdx +125 -0
- package/site/content/docs/providers/index.mdx +2 -0
- package/site/content/docs/recipes/index.mdx +2 -0
- package/site/content/docs/reference/cli.md +3 -2
- package/site/content/docs/reference/client.mdx +58 -1
- package/site/content/docs/reference/configuration.mdx +2 -4
- package/site/content/docs/reference/fx.mdx +3 -1
- package/site/content/docs/reference/index.mdx +0 -5
- package/site/content/docs/reference/meta.json +2 -2
- package/site/content/docs/reference/okid.mdx +137 -0
- package/src/auth/bindings.ts +1 -1
- package/src/auth/config.ts +9 -0
- package/src/auth/identity-sql.ts +314 -0
- package/src/auth/identity.ts +140 -2
- package/src/auth/index.ts +17 -1
- package/src/auth/method-context.ts +3 -0
- package/src/auth/oauth-as/cimd.ts +132 -0
- package/src/auth/oauth-as/crypto.test.ts +101 -0
- package/src/auth/oauth-as/crypto.ts +393 -0
- package/src/auth/oauth-as/errors.ts +68 -0
- package/src/auth/oauth-as/http.test.ts +419 -0
- package/src/auth/oauth-as/http.ts +842 -0
- package/src/auth/oauth-as/stores.ts +61 -0
- package/src/auth/oauth-as/tables.ts +142 -0
- package/src/auth/tables.ts +0 -11
- package/src/bench/README.md +83 -0
- package/src/bench/REPORT.md +176 -0
- package/src/bench/g01-rls-stamp.bench.ts +194 -0
- package/src/bench/g02-clock-per-tenant.bench.ts +158 -0
- package/src/bench/g03-signal-once.bench.ts +157 -0
- package/src/bench/g03-signal-reconnect.bench.ts +254 -0
- package/src/bench/g03-signal-sse-memory.bench.ts +191 -0
- package/src/bench/g04-auth-vault-hotpath.bench.ts +170 -0
- package/src/bench/g05-sustained-full.bench.ts +265 -0
- package/src/bench/g06-mixed-load.bench.ts +260 -0
- package/src/bench/g07-vault-crypto.bench.ts +100 -0
- package/src/bench/g07-vault-rotate-under-read.bench.ts +285 -0
- package/src/bench/g08-conn-oversubscribe.bench.ts +194 -0
- package/src/bench/g08-store-kv-durable.bench.ts +133 -0
- package/src/bench/g08-store-sql.bench.ts +178 -0
- package/src/bench/g09-journal-sustained.bench.ts +203 -0
- package/src/bench/g10-observability-contention.bench.ts +246 -0
- package/src/bench/g11-cold-start-cycle.bench.ts +164 -0
- package/src/bench/g13-elements.bench.ts +427 -0
- package/src/bench/g14-graceful-shutdown.bench.ts +244 -0
- package/src/bench/g15-postgres-degradation.bench.ts +264 -0
- package/src/bench/g16-live-query-fanout.bench.ts +206 -0
- package/src/bench/lib/event-loop-lag.ts +26 -0
- package/src/bench/lib/infra.ts +60 -0
- package/src/bench/lib/report.ts +52 -0
- package/src/bench/lib/rss-sampler.ts +61 -0
- package/src/bench/lib/signal-pg.ts +88 -0
- package/src/bench/load-app.ts +337 -0
- package/src/bench/load-child.ts +108 -0
- package/src/bench/smoke.bench.ts +43 -0
- package/src/cli/competitor-mention-removal.test.ts +28 -0
- package/src/cli/doctor-fd.ts +117 -0
- package/src/cli/doctor.test.ts +192 -0
- package/src/cli/doctor.ts +129 -1
- package/src/client/create.ts +95 -1
- package/src/client/index.ts +9 -2
- package/src/client/transport.ts +11 -4
- package/src/client/use-live-query.ts +154 -0
- package/src/client-react/index.ts +15 -1
- package/src/client-react/live-resource.ts +246 -0
- package/src/client-react/use-live-query.test.ts +475 -0
- package/src/client-react/use-live-query.ts +530 -0
- package/src/compiler/extract.test.ts +518 -0
- package/src/compiler/extract.ts +386 -19
- package/src/console/server/invoke-user-flow.ts +2 -1
- package/src/console/ui-next/dist/assets/{access-page-DnWbnGzq.js → access-page-De7Lc2JC.js} +1 -1
- package/src/console/ui-next/dist/assets/{flows-page-BiZ4-6yQ.js → flows-page-RGy7VEA_.js} +1 -1
- package/src/console/ui-next/dist/assets/{index-C8NRK2R-.js → index-_rgpdVzo.js} +3 -3
- package/src/console/ui-next/dist/assets/{observability-page-CrB6vd1T.js → observability-page-Ds6pcnh-.js} +1 -1
- package/src/console/ui-next/dist/assets/{store-page-CS5-aETQ.js → store-page-02xOiqIK.js} +3 -3
- package/src/console/ui-next/dist/assets/{units-page-CjtdlW8l.js → units-page-4rHOePuE.js} +1 -1
- package/src/console/ui-next/dist/assets/{vault-page-C6Xxm9SA.js → vault-page-DISPgxLM.js} +1 -1
- package/src/console/ui-next/dist/index.html +1 -1
- package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +36 -2
- package/src/drivers/cdc-outbox.ts +389 -0
- package/src/drivers/memory.ts +20 -0
- package/src/drivers/oauth-apple.ts +156 -0
- package/src/drivers/oauth-discord.ts +79 -0
- package/src/drivers/oauth-facebook.ts +80 -0
- package/src/drivers/oauth-figma.ts +116 -0
- package/src/drivers/oauth-github.ts +92 -0
- package/src/drivers/oauth-google.ts +142 -0
- package/src/drivers/oauth-microsoft.ts +174 -0
- package/src/drivers/oauth-oidc.ts +293 -0
- package/src/drivers/oauth-shared.ts +326 -0
- package/src/drivers/oauth-types.ts +159 -0
- package/src/drivers/oauth-x.ts +77 -0
- package/src/drivers/oauth2-common.ts +95 -0
- package/src/drivers/oauth2-token.ts +61 -0
- package/src/drivers/pg-rls-row-passes.ts +251 -0
- package/src/drivers/pg-rls.ts +2 -0
- package/src/drivers/postgres.ts +45 -2
- package/src/drivers/signal-postgres.ts +2 -1
- package/src/elements/channel/runtime.ts +29 -2
- package/src/elements/channel.test.ts +52 -0
- package/src/elements/gate/boot.ts +29 -2
- package/src/elements/store/emit-drizzle.ts +147 -14
- package/src/elements/store/field-ddl.test.ts +118 -0
- package/src/elements/store/field-types.test.ts +455 -0
- package/src/elements/store/list-query.golden.json +777 -0
- package/src/elements/store/list-query.parity.test.ts +396 -0
- package/src/elements/store/list-query.ts +792 -0
- package/src/elements/store/live-default.test.ts +136 -0
- package/src/elements/store/live-http.test.ts +160 -0
- package/src/elements/store/live-isolation.test.ts +291 -0
- package/src/elements/store/live-query-runtime.test.ts +323 -0
- package/src/elements/store/live-query-runtime.ts +403 -0
- package/src/elements/store/live-query-server.test.ts +377 -0
- package/src/elements/store/live-query-server.ts +102 -0
- package/src/elements/store/live-query.ts +97 -0
- package/src/elements/store/resource.ts +189 -680
- package/src/elements/store/rls-row-passes-policies.parity.test.ts +665 -0
- package/src/elements/store/schema-decl.ts +539 -41
- package/src/elements/store/sql-rls-stamp.test.ts +27 -0
- package/src/elements/store/sql-session.ts +297 -35
- package/src/elements/store/table.ts +102 -21
- package/src/elements/store.test.ts +3 -1
- package/src/elements/store.ts +12 -1
- package/src/elements/vault/chaos-child.ts +74 -1
- package/src/elements/vault/chaos.test.ts +4 -2
- package/src/elements/vault/storage.ts +4 -2
- package/src/index.ts +5 -2
- package/src/kernel/app-auth.ts +1 -0
- package/src/kernel/app.ts +116 -2
- package/src/kernel/auth-sharing.test.ts +196 -0
- package/src/kernel/boot.test.ts +3 -3
- package/src/kernel/errors.ts +8 -0
- package/src/kernel/fx.test.ts +1 -0
- package/src/kernel/fx.ts +14 -2
- package/src/kernel/horizontal-child.ts +2 -1
- package/src/kernel/http-resource.ts +33 -7
- package/src/kernel/identity-host-persist.test.ts +119 -0
- package/src/kernel/instance-id.ts +4 -2
- package/src/kernel/journal.ts +2 -1
- package/src/kernel/mcp-tool.test.ts +95 -0
- package/src/kernel/on.ts +9 -0
- package/src/kernel/realtime-bind.ts +326 -0
- package/src/kernel/resource-live.ts +117 -0
- package/src/kernel/triggers.ts +86 -4
- package/src/manifest/diff.ts +37 -0
- package/src/manifest/types.ts +64 -2
- package/src/okid.bench.test.ts +64 -0
- package/src/okid.test.ts +338 -0
- package/src/okid.ts +245 -0
- package/src/plugins/anonymous.ts +19 -1
- package/src/plugins/auth/shared.ts +15 -0
- package/src/plugins/index.ts +2 -0
- package/src/plugins/magic-link.ts +10 -8
- package/src/plugins/mcp-oauth.ts +208 -0
- package/src/plugins/oauth/flow-store.ts +117 -0
- package/src/plugins/oauth/link.ts +69 -0
- package/src/plugins/oauth/shared.ts +108 -0
- package/src/plugins/oauth/token-vault.ts +100 -0
- package/src/plugins/oauth.security.test.ts +535 -0
- package/src/plugins/oauth.ts +532 -0
- package/src/plugins/otp.ts +48 -6
- package/src/plugins/passkey.ts +20 -1
- package/src/plugins/two-factor.ts +11 -0
- package/src/plugins/username.ts +40 -7
- package/src/release/build-lib.ts +7 -1
- package/src/release/measure.ts +1 -0
- package/src/release/official-plugins.ts +4 -1
- package/src/runs/collect.ts +2 -1
- package/src/runs/drivers/files.ts +2 -1
- package/src/test/create-test-app.ts +114 -5
- package/src/test/export-bundle.test.ts +33 -0
- package/src/test/live-signals.test.ts +83 -0
- package/src/test/tenant-isolation.test.ts +175 -0
- package/src/testing.ts +26 -0
- package/src/upgrade/codemods.ts +1 -1
- package/site/content/docs/reference/migrating-environments.mdx +0 -158
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G8a — store.sql sustained insert/update (live Postgres via pgdog).
|
|
3
|
+
*
|
|
4
|
+
* Drives `createStoreRuntime` → `sharedSqlConn` through the public
|
|
5
|
+
* `runtime.open()` path (same connection the fx layer uses), then hammers:
|
|
6
|
+
* 1. sustained single-row INSERTs,
|
|
7
|
+
* 2. sustained point UPDATEs,
|
|
8
|
+
* 3. mixed insert+update interleave.
|
|
9
|
+
*
|
|
10
|
+
* Run: OKE_BENCH=1 OKE_TEST_POSTGRES=1 DATABASE_URL=… bun test ./src/bench/g08-store-sql.bench.ts --timeout 300000
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
14
|
+
import { postgresDriver } from "../drivers/postgres.ts";
|
|
15
|
+
import { sql } from "../elements/store/declare.ts";
|
|
16
|
+
import { defineTable } from "../elements/store/table.ts";
|
|
17
|
+
import { createSqlStoreHandle, type SqlStoreHandle } from "../elements/store/sql-session.ts";
|
|
18
|
+
import { createStoreRuntime } from "../elements/store/runtime.ts";
|
|
19
|
+
import { okid } from "../okid.ts";
|
|
20
|
+
import { LIVE_PG } from "./lib/infra.ts";
|
|
21
|
+
import { DISCLAIMER, HARDWARE, percentile, writeArtifact } from "./lib/report.ts";
|
|
22
|
+
|
|
23
|
+
const CAL = process.env.OKE_BENCH_CAL === "1";
|
|
24
|
+
/** Ops per phase — sized so each full run is ~30–60 s against pgdog. */
|
|
25
|
+
const OPS = CAL ? 200 : 2_000;
|
|
26
|
+
|
|
27
|
+
const db = sql("g8sql");
|
|
28
|
+
const rows = defineTable("oke_bench_g8_rows", {
|
|
29
|
+
id: true,
|
|
30
|
+
n: true,
|
|
31
|
+
at: true,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
let handle: SqlStoreHandle;
|
|
35
|
+
let runtime: ReturnType<typeof createStoreRuntime>;
|
|
36
|
+
|
|
37
|
+
beforeAll(async () => {
|
|
38
|
+
if (!LIVE_PG) throw new Error("G8 needs live Postgres: set OKE_TEST_POSTGRES=1 + DATABASE_URL");
|
|
39
|
+
runtime = createStoreRuntime({
|
|
40
|
+
drivers: { sql: postgresDriver },
|
|
41
|
+
sql: { g8sql: { name: "g8sql", primary: { url: LIVE_PG } } },
|
|
42
|
+
now: () => Date.now(),
|
|
43
|
+
});
|
|
44
|
+
runtime.register(db);
|
|
45
|
+
const conn = await runtime.primarySql();
|
|
46
|
+
if (!conn) throw new Error("G8: runtime.primarySql() returned undefined");
|
|
47
|
+
await conn.exec(`
|
|
48
|
+
CREATE TABLE IF NOT EXISTS oke_bench_g8_rows (
|
|
49
|
+
id TEXT PRIMARY KEY,
|
|
50
|
+
n BIGINT NOT NULL DEFAULT 0,
|
|
51
|
+
at BIGINT NOT NULL DEFAULT 0
|
|
52
|
+
)
|
|
53
|
+
`);
|
|
54
|
+
await conn.exec(`DELETE FROM oke_bench_g8_rows WHERE id LIKE 'bench-g8-%'`);
|
|
55
|
+
// Same session shape the fx layer builds: shared conn, no PII classes.
|
|
56
|
+
handle = createSqlStoreHandle(db.ref, {
|
|
57
|
+
connection: conn,
|
|
58
|
+
classifications: new Map(),
|
|
59
|
+
routedRole: "primary",
|
|
60
|
+
domainDdl: "off",
|
|
61
|
+
});
|
|
62
|
+
}, 20_000);
|
|
63
|
+
|
|
64
|
+
afterAll(async () => {
|
|
65
|
+
try {
|
|
66
|
+
const conn = await runtime?.primarySql();
|
|
67
|
+
await conn?.exec(`DELETE FROM oke_bench_g8_rows WHERE id LIKE 'bench-g8-%'`);
|
|
68
|
+
} catch {
|
|
69
|
+
/* best-effort cleanup */
|
|
70
|
+
}
|
|
71
|
+
await runtime?.close();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
async function timedPhase(
|
|
75
|
+
name: string,
|
|
76
|
+
op: (i: number) => Promise<unknown>,
|
|
77
|
+
): Promise<{
|
|
78
|
+
latencies: number[];
|
|
79
|
+
}> {
|
|
80
|
+
const latencies: number[] = [];
|
|
81
|
+
for (let i = 0; i < OPS; i++) {
|
|
82
|
+
const t0 = performance.now();
|
|
83
|
+
await op(i);
|
|
84
|
+
latencies.push(performance.now() - t0);
|
|
85
|
+
}
|
|
86
|
+
latencies.sort((a, b) => a - b);
|
|
87
|
+
console.log(
|
|
88
|
+
`[G8a] ${name}: ops=${OPS} p50=${percentile(latencies, 50).toFixed(3)}ms ` +
|
|
89
|
+
`p99=${percentile(latencies, 99).toFixed(3)}ms`,
|
|
90
|
+
);
|
|
91
|
+
return { latencies };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function metricsFor(label: string, latencies: number[], wallS: number): Record<string, number> {
|
|
95
|
+
return {
|
|
96
|
+
[`${label}.ops`]: latencies.length,
|
|
97
|
+
[`${label}.p50Ms`]: Number(percentile(latencies, 50).toFixed(3)),
|
|
98
|
+
[`${label}.p99Ms`]: Number(percentile(latencies, 99).toFixed(3)),
|
|
99
|
+
[`${label}.opsPerSec`]: Number((latencies.length / wallS).toFixed(1)),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
describe.skipIf(!process.env.OKE_BENCH || !LIVE_PG)("G8a — store.sql sustained", () => {
|
|
104
|
+
test(
|
|
105
|
+
"sustained insert / update / mixed against live postgres",
|
|
106
|
+
async () => {
|
|
107
|
+
const ids = Array.from({ length: OPS }, () => `bench-g8-${okid()}`);
|
|
108
|
+
const metrics: Record<string, number> = {};
|
|
109
|
+
|
|
110
|
+
// 1. Sustained inserts.
|
|
111
|
+
let t0 = performance.now();
|
|
112
|
+
const ins = await timedPhase("insert", (i) =>
|
|
113
|
+
handle.insert(rows).values({ id: ids[i]!, n: i, at: Date.now() }).execute(),
|
|
114
|
+
);
|
|
115
|
+
let wallS = (performance.now() - t0) / 1000;
|
|
116
|
+
Object.assign(metrics, metricsFor("insert", ins.latencies, wallS));
|
|
117
|
+
|
|
118
|
+
// 2. Sustained point updates.
|
|
119
|
+
t0 = performance.now();
|
|
120
|
+
const upd = await timedPhase("update", (i) =>
|
|
121
|
+
handle
|
|
122
|
+
.update(rows)
|
|
123
|
+
.set({ n: i + 1 })
|
|
124
|
+
.where({ id: ids[i]! }),
|
|
125
|
+
);
|
|
126
|
+
wallS = (performance.now() - t0) / 1000;
|
|
127
|
+
Object.assign(metrics, metricsFor("update", upd.latencies, wallS));
|
|
128
|
+
|
|
129
|
+
// 3. Mixed interleave — read-modify-write pairs.
|
|
130
|
+
t0 = performance.now();
|
|
131
|
+
const mixLatencies: number[] = [];
|
|
132
|
+
for (let i = 0; i < OPS; i++) {
|
|
133
|
+
const s = performance.now();
|
|
134
|
+
const found = await handle.select().from(rows).where({ id: ids[i]! }).limit(1);
|
|
135
|
+
if (found.length !== 1) throw new Error(`G8a mixed: row ${ids[i]} missing`);
|
|
136
|
+
await handle
|
|
137
|
+
.update(rows)
|
|
138
|
+
.set({ n: i + 2 })
|
|
139
|
+
.where({ id: ids[i]! });
|
|
140
|
+
mixLatencies.push(performance.now() - s);
|
|
141
|
+
}
|
|
142
|
+
wallS = (performance.now() - t0) / 1000;
|
|
143
|
+
Object.assign(metrics, metricsFor("mixed", mixLatencies, wallS));
|
|
144
|
+
Object.assign(metrics, { mixedReadThenWritePairs: OPS });
|
|
145
|
+
|
|
146
|
+
// Sanity: last update landed.
|
|
147
|
+
const check = await handle
|
|
148
|
+
.select()
|
|
149
|
+
.from(rows)
|
|
150
|
+
.where({ id: ids[OPS - 1]! })
|
|
151
|
+
.limit(1);
|
|
152
|
+
expect(Number(check[0]?.n)).toBe(OPS + 1);
|
|
153
|
+
|
|
154
|
+
console.log("[G8a] metrics:", JSON.stringify(metrics));
|
|
155
|
+
const issues: string[] = [];
|
|
156
|
+
if (metrics["update.opsPerSec"]! < metrics["insert.opsPerSec"]! * 0.3) {
|
|
157
|
+
issues.push(
|
|
158
|
+
`update throughput collapsed vs insert: ${metrics["update.opsPerSec"]} < 30% of ${metrics["insert.opsPerSec"]}`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const path = await writeArtifact({
|
|
163
|
+
group: "G8a",
|
|
164
|
+
hardware: HARDWARE,
|
|
165
|
+
disclaimer: DISCLAIMER,
|
|
166
|
+
command:
|
|
167
|
+
"OKE_BENCH=1 OKE_TEST_POSTGRES=1 DATABASE_URL=$DATABASE_URL bun test ./src/bench/g08-store-sql.bench.ts --timeout 300000",
|
|
168
|
+
metrics,
|
|
169
|
+
issues,
|
|
170
|
+
fixes: [],
|
|
171
|
+
remeasured: null,
|
|
172
|
+
});
|
|
173
|
+
console.log(`[G8a] artifact: ${path}`);
|
|
174
|
+
expect(issues.length).toBe(0);
|
|
175
|
+
},
|
|
176
|
+
CAL ? 90_000 : 300_000,
|
|
177
|
+
);
|
|
178
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G9 — durable journal / fx.step sustained (live Postgres via pgdog).
|
|
3
|
+
*
|
|
4
|
+
* Boots a real `oke()` app whose journal runtime is `createPostgresJournalStore`
|
|
5
|
+
* (the SKIP LOCKED claim/lease path), then drives parallel durable flow
|
|
6
|
+
* executions closed-loop for `OKE_BENCH_DURATION_S` seconds (default 300).
|
|
7
|
+
* Every run records 3 journaled steps; `store.put` is wrapped with a counter
|
|
8
|
+
* so journal write rate is exact. A background sampler tracks backlog depth
|
|
9
|
+
* (`running`/`sleeping` runs) every 5 s.
|
|
10
|
+
*
|
|
11
|
+
* Run: OKE_BENCH=1 OKE_TEST_POSTGRES=1 DATABASE_URL=… bun test ./src/bench/g09-journal-sustained.bench.ts --timeout 420000
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
15
|
+
import { createPostgresJournalStore } from "../drivers/journal-postgres.ts";
|
|
16
|
+
import { oke, type OkeApp } from "../kernel/app.ts";
|
|
17
|
+
import type { JournalRuntime } from "../kernel/boot-bind/journal.ts";
|
|
18
|
+
import { flow, type AnyFlowDef } from "../kernel/flow.ts";
|
|
19
|
+
import type { Binding } from "../kernel/on.ts";
|
|
20
|
+
import { http } from "../kernel/triggers.ts";
|
|
21
|
+
import type { JournalRun, JournalStore } from "../kernel/journal.ts";
|
|
22
|
+
import { LIVE_PG } from "./lib/infra.ts";
|
|
23
|
+
import { DISCLAIMER, HARDWARE, percentile, writeArtifact } from "./lib/report.ts";
|
|
24
|
+
|
|
25
|
+
const CAL = process.env.OKE_BENCH_CAL === "1";
|
|
26
|
+
const DURATION_S = CAL ? 15 : Math.max(10, Number(process.env.OKE_BENCH_DURATION_S ?? "300"));
|
|
27
|
+
/** Closed-loop workers — each awaits run completion before firing again. */
|
|
28
|
+
const CONCURRENCY = CAL ? 4 : 16;
|
|
29
|
+
const FLOW_PREFIX = "g9sustained";
|
|
30
|
+
|
|
31
|
+
let app: OkeApp;
|
|
32
|
+
let journal: Awaited<ReturnType<typeof createPostgresJournalStore>>;
|
|
33
|
+
let putsCount = 0;
|
|
34
|
+
let stepRuns = 0;
|
|
35
|
+
let failedRuns = 0;
|
|
36
|
+
|
|
37
|
+
beforeAll(async () => {
|
|
38
|
+
if (!LIVE_PG) throw new Error("G9 needs live Postgres: set OKE_TEST_POSTGRES=1 + DATABASE_URL");
|
|
39
|
+
journal = await createPostgresJournalStore({ url: LIVE_PG });
|
|
40
|
+
await journal.sql.exec(`DELETE FROM oke_journal_runs WHERE flow LIKE '${FLOW_PREFIX}%'`);
|
|
41
|
+
|
|
42
|
+
// Exact journal write accounting: wrap put().
|
|
43
|
+
const counted = {
|
|
44
|
+
...journal,
|
|
45
|
+
put: async (run: JournalRun) => {
|
|
46
|
+
putsCount += 1;
|
|
47
|
+
await journal.put(run);
|
|
48
|
+
},
|
|
49
|
+
} as JournalStore & NonNullable<JournalRuntime["store"]>;
|
|
50
|
+
const jrt: JournalRuntime = {
|
|
51
|
+
store: counted,
|
|
52
|
+
instanceId: "g9-bench",
|
|
53
|
+
leaseMs: 30_000,
|
|
54
|
+
driverId: "postgres",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Three lightweight fx.steps per run — replay never re-runs them.
|
|
58
|
+
const work = flow(`${FLOW_PREFIX}.work`, {
|
|
59
|
+
durable: true,
|
|
60
|
+
do: async (input, fx) => {
|
|
61
|
+
const n = Number((input as { n?: number }).n ?? 0);
|
|
62
|
+
const a = await fx.step("step-a", () => n + 1);
|
|
63
|
+
const b = await fx.step("step-b", async () => a * 2);
|
|
64
|
+
await fx.step("step-c", () => ({ b, at: Date.now() }));
|
|
65
|
+
stepRuns += 3;
|
|
66
|
+
return { ok: true as const, b };
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
const bindings: Binding[] = [
|
|
70
|
+
{ trigger: http.post("/g9/work").public(), flow: work as AnyFlowDef },
|
|
71
|
+
];
|
|
72
|
+
app = oke({
|
|
73
|
+
name: "g9-journal-sustained",
|
|
74
|
+
env: "test",
|
|
75
|
+
startScheduler: false,
|
|
76
|
+
gate: { unguardedHttp: "allow" },
|
|
77
|
+
bindings,
|
|
78
|
+
elements: { journal: jrt },
|
|
79
|
+
});
|
|
80
|
+
await app.boot();
|
|
81
|
+
}, 30_000);
|
|
82
|
+
|
|
83
|
+
afterAll(async () => {
|
|
84
|
+
try {
|
|
85
|
+
await journal?.sql.exec(`DELETE FROM oke_journal_runs WHERE flow LIKE '${FLOW_PREFIX}%'`);
|
|
86
|
+
await journal?.close();
|
|
87
|
+
} catch {
|
|
88
|
+
/* best-effort cleanup */
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
interface BacklogSample {
|
|
93
|
+
tS: number;
|
|
94
|
+
backlog: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
describe.skipIf(!process.env.OKE_BENCH || !LIVE_PG)("G9 — journal sustained", () => {
|
|
98
|
+
test(
|
|
99
|
+
`${DURATION_S}s of parallel durable flows — steps/sec, write rate, backlog depth`,
|
|
100
|
+
async () => {
|
|
101
|
+
const samples: BacklogSample[] = [];
|
|
102
|
+
let sampling = true;
|
|
103
|
+
const t0 = performance.now();
|
|
104
|
+
const sampler = (async () => {
|
|
105
|
+
while (sampling) {
|
|
106
|
+
try {
|
|
107
|
+
const rows = await journal.sql.query(
|
|
108
|
+
`SELECT COUNT(*) AS c FROM oke_journal_runs
|
|
109
|
+
WHERE flow LIKE ? AND status IN ('running','sleeping','compensating')`,
|
|
110
|
+
[`${FLOW_PREFIX}%`],
|
|
111
|
+
);
|
|
112
|
+
samples.push({
|
|
113
|
+
tS: Number(((performance.now() - t0) / 1000).toFixed(1)),
|
|
114
|
+
backlog: Number((rows[0] as { c: number }).c),
|
|
115
|
+
});
|
|
116
|
+
} catch {
|
|
117
|
+
/* sampler is best-effort */
|
|
118
|
+
}
|
|
119
|
+
await Bun.sleep(5_000);
|
|
120
|
+
}
|
|
121
|
+
})();
|
|
122
|
+
|
|
123
|
+
const latencies: number[] = [];
|
|
124
|
+
let done = 0;
|
|
125
|
+
const deadlineMs = DURATION_S * 1000;
|
|
126
|
+
|
|
127
|
+
const worker = async (): Promise<void> => {
|
|
128
|
+
for (;;) {
|
|
129
|
+
if (performance.now() - t0 >= deadlineMs) return;
|
|
130
|
+
const s = performance.now();
|
|
131
|
+
try {
|
|
132
|
+
const res = await app.fetch(
|
|
133
|
+
new Request("http://localhost/g9/work", {
|
|
134
|
+
method: "POST",
|
|
135
|
+
body: JSON.stringify({ n: done }),
|
|
136
|
+
headers: { "content-type": "application/json" },
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
if (!res.ok) throw new Error(`g9 work HTTP ${res.status}`);
|
|
140
|
+
} catch {
|
|
141
|
+
failedRuns += 1;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
latencies.push(performance.now() - s);
|
|
145
|
+
done += 1;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
await Promise.all(Array.from({ length: CONCURRENCY }, worker));
|
|
150
|
+
const wallS = (performance.now() - t0) / 1000;
|
|
151
|
+
sampling = false;
|
|
152
|
+
await sampler;
|
|
153
|
+
|
|
154
|
+
const metrics: Record<string, number> = {
|
|
155
|
+
durationS: Number(wallS.toFixed(1)),
|
|
156
|
+
concurrency: CONCURRENCY,
|
|
157
|
+
runsCompleted: done,
|
|
158
|
+
runsPerSec: Number((done / wallS).toFixed(2)),
|
|
159
|
+
stepsExecuted: stepRuns,
|
|
160
|
+
stepsPerSec: Number((stepRuns / wallS).toFixed(2)),
|
|
161
|
+
journalPuts: putsCount,
|
|
162
|
+
journalWritesPerSec: Number((putsCount / wallS).toFixed(2)),
|
|
163
|
+
runP50Ms: Number(percentile(latencies, 50).toFixed(3)),
|
|
164
|
+
runP99Ms: Number(percentile(latencies, 99).toFixed(3)),
|
|
165
|
+
runMaxMs: latencies.length ? Number(Math.max(...latencies).toFixed(3)) : 0,
|
|
166
|
+
failedRuns,
|
|
167
|
+
backlogMax: samples.length ? Math.max(...samples.map((x) => x.backlog)) : -1,
|
|
168
|
+
backlogAvg:
|
|
169
|
+
samples.length > 0
|
|
170
|
+
? Number((samples.reduce((a, x) => a + x.backlog, 0) / samples.length).toFixed(2))
|
|
171
|
+
: -1,
|
|
172
|
+
backlogFinal: samples.length ? samples[samples.length - 1]!.backlog : -1,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
console.log("[G9] metrics:", JSON.stringify(metrics));
|
|
176
|
+
const issues: string[] = [];
|
|
177
|
+
if (failedRuns > 0) issues.push(`failed durable runs under sustained load: ${failedRuns}`);
|
|
178
|
+
if (metrics.backlogFinal! > CONCURRENCY * 2) {
|
|
179
|
+
issues.push(
|
|
180
|
+
`backlog did not drain: final=${metrics.backlogFinal} vs concurrency=${CONCURRENCY}`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const path = await writeArtifact({
|
|
185
|
+
group: "G9",
|
|
186
|
+
hardware: HARDWARE,
|
|
187
|
+
disclaimer: DISCLAIMER,
|
|
188
|
+
command:
|
|
189
|
+
"OKE_BENCH=1 OKE_TEST_POSTGRES=1 DATABASE_URL=$DATABASE_URL bun test ./src/bench/g09-journal-sustained.bench.ts --timeout 420000",
|
|
190
|
+
metrics,
|
|
191
|
+
issues,
|
|
192
|
+
fixes: [],
|
|
193
|
+
remeasured: null,
|
|
194
|
+
});
|
|
195
|
+
console.log(`[G9] artifact: ${path}`);
|
|
196
|
+
|
|
197
|
+
expect(failedRuns).toBe(0);
|
|
198
|
+
expect(done).toBeGreaterThan(0);
|
|
199
|
+
expect(stepRuns).toBe(done * 3);
|
|
200
|
+
},
|
|
201
|
+
CAL ? 120_000 : Math.min(600_000, DURATION_S * 1000 + 120_000),
|
|
202
|
+
);
|
|
203
|
+
});
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G10 — observability contention: same load twice, app alone vs app +
|
|
3
|
+
* Console signals-panel queries (`projectSignalsList`,
|
|
4
|
+
* `src/console/server/signals.ts`) hammering the same Postgres pool.
|
|
5
|
+
*
|
|
6
|
+
* Each arm boots a fresh `load-child serve` and runs an identical G5-shaped
|
|
7
|
+
* closed-loop (30× /ping + 10× durable journal flow + 10× store insert) for
|
|
8
|
+
* `OKE_BENCH_G10_ARM_S` seconds. Arm B adds 3 concurrent
|
|
9
|
+
* `projectSignalsList()` loops whose `bus.inspect()` COUNT queries run on the
|
|
10
|
+
* same DATABASE_URL pool. Metric: req/sec delta between arms.
|
|
11
|
+
*
|
|
12
|
+
* Run: OKE_BENCH=1 OKE_TEST_POSTGRES=1 DATABASE_URL=… OKE_TEST_REDIS_URL=… bun test ./src/bench/g10-observability-contention.bench.ts --timeout 600000
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, expect, test } from "bun:test";
|
|
16
|
+
import { openPostgresSignal } from "../drivers/signal-postgres.ts";
|
|
17
|
+
import type { SignalBus } from "../drivers/signal-types.ts";
|
|
18
|
+
import { createMemorySignalConfigStore } from "../elements/signal/reconcile.ts";
|
|
19
|
+
import { signal } from "../elements/signal/declare.ts";
|
|
20
|
+
import { projectSignalsList } from "../console/server/signals.ts";
|
|
21
|
+
import { LIVE_PG } from "./lib/infra.ts";
|
|
22
|
+
import { createBunSignalSql } from "./lib/signal-pg.ts";
|
|
23
|
+
import { DISCLAIMER, HARDWARE, percentile, writeArtifact } from "./lib/report.ts";
|
|
24
|
+
|
|
25
|
+
const CAL = process.env.OKE_BENCH_CAL === "1";
|
|
26
|
+
const ARM_S = CAL ? 15 : Math.max(30, Number(process.env.OKE_BENCH_G10_ARM_S ?? "120"));
|
|
27
|
+
const PING_WORKERS = 30;
|
|
28
|
+
const DURABLE_WORKERS = 10;
|
|
29
|
+
const STORE_WORKERS = 10;
|
|
30
|
+
const OBSERVER_LOOPS = 3;
|
|
31
|
+
|
|
32
|
+
type Server = Bun.Subprocess<"ignore", "pipe", "ignore">;
|
|
33
|
+
|
|
34
|
+
function spawnServe(port: number): Server {
|
|
35
|
+
return Bun.spawn(["bun", "run", "src/bench/load-child.ts", "serve", String(port)], {
|
|
36
|
+
stdout: "pipe",
|
|
37
|
+
stderr: "ignore",
|
|
38
|
+
env: process.env,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function readReadyPid(proc: Server): Promise<number> {
|
|
43
|
+
const reader = proc.stdout.getReader();
|
|
44
|
+
const dec = new TextDecoder();
|
|
45
|
+
let buf = "";
|
|
46
|
+
for (;;) {
|
|
47
|
+
const { done, value } = await reader.read();
|
|
48
|
+
if (done) throw new Error("server died before ready");
|
|
49
|
+
buf += dec.decode(value, { stream: true });
|
|
50
|
+
const nl = buf.indexOf("\n");
|
|
51
|
+
if (nl >= 0) {
|
|
52
|
+
const line = buf.slice(0, nl).trim();
|
|
53
|
+
if (line.startsWith("{")) {
|
|
54
|
+
void (async () => {
|
|
55
|
+
try {
|
|
56
|
+
for (;;) {
|
|
57
|
+
const r = await reader.read();
|
|
58
|
+
if (r.done) break;
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
/* closed */
|
|
62
|
+
}
|
|
63
|
+
})();
|
|
64
|
+
return (JSON.parse(line) as { pid: number }).pid;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** One arm: identical closed-loop shape; optional concurrent console queries. */
|
|
71
|
+
async function runArm(
|
|
72
|
+
port: number,
|
|
73
|
+
observers: boolean,
|
|
74
|
+
sql: ReturnType<typeof createBunSignalSql> | null,
|
|
75
|
+
): Promise<{
|
|
76
|
+
pingRps: number;
|
|
77
|
+
durableRps: number;
|
|
78
|
+
storeRps: number;
|
|
79
|
+
totalRps: number;
|
|
80
|
+
errors: number;
|
|
81
|
+
obsRps: number;
|
|
82
|
+
obsP50Ms: number;
|
|
83
|
+
obsP99Ms: number;
|
|
84
|
+
}> {
|
|
85
|
+
const server = spawnServe(port);
|
|
86
|
+
try {
|
|
87
|
+
await readReadyPid(server);
|
|
88
|
+
const base = `http://127.0.0.1:${port}`;
|
|
89
|
+
await Bun.sleep(CAL ? 2_000 : 4_000); // warm-up
|
|
90
|
+
|
|
91
|
+
let stop = false;
|
|
92
|
+
const c = { ping: 0, durable: 0, store: 0, errors: 0 };
|
|
93
|
+
|
|
94
|
+
const loop = async (
|
|
95
|
+
path: string,
|
|
96
|
+
body: Record<string, unknown>,
|
|
97
|
+
counter: keyof typeof c,
|
|
98
|
+
): Promise<void> => {
|
|
99
|
+
while (!stop) {
|
|
100
|
+
try {
|
|
101
|
+
const res = await fetch(`${base}${path}`, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
headers: { "content-type": "application/json" },
|
|
104
|
+
body: JSON.stringify(body),
|
|
105
|
+
});
|
|
106
|
+
await res.arrayBuffer();
|
|
107
|
+
if (!res.ok) c.errors++;
|
|
108
|
+
} catch {
|
|
109
|
+
c.errors++;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
c[counter]++;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
// /ping is GET — separate worker shape.
|
|
116
|
+
const pingLoop = async (): Promise<void> => {
|
|
117
|
+
while (!stop) {
|
|
118
|
+
try {
|
|
119
|
+
const res = await fetch(`${base}/ping`);
|
|
120
|
+
await res.arrayBuffer();
|
|
121
|
+
if (!res.ok) c.errors++;
|
|
122
|
+
} catch {
|
|
123
|
+
c.errors++;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
c.ping++;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const workers = [
|
|
131
|
+
...Array.from({ length: PING_WORKERS }, pingLoop),
|
|
132
|
+
...Array.from({ length: DURABLE_WORKERS }, () => loop("/_/bench/durable", {}, "durable")),
|
|
133
|
+
...Array.from({ length: STORE_WORKERS }, () => loop("/_/bench/rls", { n: 1 }, "store")),
|
|
134
|
+
];
|
|
135
|
+
const running = Promise.all(workers);
|
|
136
|
+
|
|
137
|
+
const obsLats: number[] = [];
|
|
138
|
+
let obsQueries = 0;
|
|
139
|
+
if (observers && sql) {
|
|
140
|
+
const decl = signal("g10-watch", { delivery: "once", optional: true });
|
|
141
|
+
const consoleBus: SignalBus = await openPostgresSignal({
|
|
142
|
+
signals: new Map([[decl.name, decl]]),
|
|
143
|
+
sql,
|
|
144
|
+
leaseMs: 5_000,
|
|
145
|
+
});
|
|
146
|
+
const config = createMemorySignalConfigStore();
|
|
147
|
+
const observerLoop = async (): Promise<void> => {
|
|
148
|
+
while (!stop) {
|
|
149
|
+
const s = performance.now();
|
|
150
|
+
try {
|
|
151
|
+
await projectSignalsList({ manifest: null, config, bus: consoleBus });
|
|
152
|
+
} catch {
|
|
153
|
+
/* observability panel is read-only; never fail the load arm */
|
|
154
|
+
}
|
|
155
|
+
obsLats.push(performance.now() - s);
|
|
156
|
+
obsQueries++;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
for (let i = 0; i < OBSERVER_LOOPS; i++) void observerLoop();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await Bun.sleep(ARM_S * 1000);
|
|
163
|
+
stop = true;
|
|
164
|
+
await running;
|
|
165
|
+
|
|
166
|
+
const wallS = ARM_S;
|
|
167
|
+
return {
|
|
168
|
+
pingRps: Number((c.ping / wallS).toFixed(1)),
|
|
169
|
+
durableRps: Number((c.durable / wallS).toFixed(1)),
|
|
170
|
+
storeRps: Number((c.store / wallS).toFixed(1)),
|
|
171
|
+
totalRps: Number(((c.ping + c.durable + c.store) / wallS).toFixed(1)),
|
|
172
|
+
errors: c.errors,
|
|
173
|
+
obsRps: Number((obsQueries / wallS).toFixed(2)),
|
|
174
|
+
obsP50Ms: Number(percentile(obsLats, 50).toFixed(1)),
|
|
175
|
+
obsP99Ms: Number(percentile(obsLats, 99).toFixed(1)),
|
|
176
|
+
};
|
|
177
|
+
} finally {
|
|
178
|
+
server.kill();
|
|
179
|
+
await server.exited.catch(() => {});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
describe.skipIf(!process.env.OKE_BENCH || !LIVE_PG)("G10 — observability contention", () => {
|
|
184
|
+
test(
|
|
185
|
+
`app alone vs app + console signals queries × ${ARM_S}s/arm — req/sec delta`,
|
|
186
|
+
async () => {
|
|
187
|
+
// Arm A: load only.
|
|
188
|
+
const a = await runArm(6677, false, null);
|
|
189
|
+
// Arm B: identical load + observability panel queries.
|
|
190
|
+
const sql = createBunSignalSql(LIVE_PG!);
|
|
191
|
+
const b = await runArm(6678, true, sql);
|
|
192
|
+
await sql.close().catch(() => {});
|
|
193
|
+
|
|
194
|
+
const deltaPct = (x: number, y: number): number =>
|
|
195
|
+
x > 0 ? Number((((y - x) / x) * 100).toFixed(1)) : -1;
|
|
196
|
+
|
|
197
|
+
const metrics: Record<string, number> = {
|
|
198
|
+
armSeconds: ARM_S,
|
|
199
|
+
armATotalRps: a.totalRps,
|
|
200
|
+
armAPingRps: a.pingRps,
|
|
201
|
+
armADurableRps: a.durableRps,
|
|
202
|
+
armAStoreRps: a.storeRps,
|
|
203
|
+
armBTotalRps: b.totalRps,
|
|
204
|
+
armBPingRps: b.pingRps,
|
|
205
|
+
armBDurableRps: b.durableRps,
|
|
206
|
+
armBStoreRps: b.storeRps,
|
|
207
|
+
totalDeltaPct: deltaPct(a.totalRps, b.totalRps),
|
|
208
|
+
pingDeltaPct: deltaPct(a.pingRps, b.pingRps),
|
|
209
|
+
durableDeltaPct: deltaPct(a.durableRps, b.durableRps),
|
|
210
|
+
storeDeltaPct: deltaPct(a.storeRps, b.storeRps),
|
|
211
|
+
consoleQueriesPerSec: b.obsRps,
|
|
212
|
+
consoleQueryP50Ms: b.obsP50Ms,
|
|
213
|
+
consoleQueryP99Ms: b.obsP99Ms,
|
|
214
|
+
errorsA: a.errors,
|
|
215
|
+
errorsB: b.errors,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const issues: string[] = [];
|
|
219
|
+
if (a.errors + b.errors > 0) issues.push(`errors: armA=${a.errors} armB=${b.errors}`);
|
|
220
|
+
if (metrics.totalDeltaPct! < -25) {
|
|
221
|
+
issues.push(
|
|
222
|
+
`observability panel costs ${Math.abs(metrics.totalDeltaPct!)}% of throughput on the shared pool`,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log("[G10] metrics:", JSON.stringify(metrics));
|
|
227
|
+
const path = await writeArtifact({
|
|
228
|
+
group: "G10-observability-contention",
|
|
229
|
+
hardware: HARDWARE,
|
|
230
|
+
disclaimer: DISCLAIMER,
|
|
231
|
+
command:
|
|
232
|
+
"OKE_BENCH=1 OKE_TEST_POSTGRES=1 DATABASE_URL=$DATABASE_URL OKE_TEST_REDIS_URL=$OKE_TEST_REDIS_URL bun test ./src/bench/g10-observability-contention.bench.ts --timeout 600000",
|
|
233
|
+
metrics,
|
|
234
|
+
issues,
|
|
235
|
+
fixes: [],
|
|
236
|
+
remeasured: null,
|
|
237
|
+
});
|
|
238
|
+
console.log(`[G10] artifact: ${path}`);
|
|
239
|
+
|
|
240
|
+
expect(a.errors).toBeLessThan(10);
|
|
241
|
+
expect(b.errors).toBeLessThan(10);
|
|
242
|
+
expect(b.totalRps).toBeGreaterThan(0);
|
|
243
|
+
},
|
|
244
|
+
CAL ? 120_000 : 500_000,
|
|
245
|
+
);
|
|
246
|
+
});
|