Skip to content

Commit 72898c8

Browse files
authored
Add full-surface benchmark artifact refs (#1251)
1 parent de93bb7 commit 72898c8

6 files changed

Lines changed: 658 additions & 13 deletions

File tree

docs/benchmark-contract.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,18 @@ The command contract is intentionally broad enough for future workload types:
4646
- **WP-CLI:** configured workload steps that execute in the same sandbox.
4747
- **Ability:** future ability-backed workload steps should still return generic numeric metrics and metadata.
4848
- **REST:** configured `rest-request` steps call `rest_do_request()` in-process. A configured workload may declare `route_matrix` as a compact list of REST routes; WP Codebox expands each route into the existing `rest-request` step type.
49+
- **Database inventory:** configured `db-inventory` steps collect table, row, column, index, and byte-count inventory through the benchmark artifact path.
4950
- **Browser:** `wordpress.browser-probe` captures generic browser performance and memory artifacts. When a recipe runs browser probes before `wordpress.bench`, selected numeric `browser_*` metrics are promoted into each benchmark scenario while raw browser artifacts remain in the bundle.
5051

52+
WP Codebox does not currently expose a REST DB query profiler workload step. Full
53+
REST query profiling needs an upstream runtime hook that brackets each
54+
`rest_do_request()` call, captures `$wpdb->queries` or an equivalent query log for
55+
that request only, and emits a bounded artifact with query count, total query
56+
time, repeated-query summaries, and redacted SQL fingerprints. Until that hook
57+
exists, caller rigs should use real `rest_request_cases` and `db-inventory`
58+
workloads separately and leave `rest-db-query-profiler` as a documented missing
59+
primitive rather than emitting synthetic query-profile artifacts.
60+
5161
## REST Route Matrices
5262

5363
Route-matrix workloads are for API profiling suites that want one benchmark

packages/cli/src/commands/recipe-run-benchmark-artifacts.ts

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,27 @@ interface RestRequestCaseSummaryArtifact {
3939
cases: RestRequestCaseStepSummary[]
4040
}
4141

42+
interface DbInventoryBenchmarkArtifact {
43+
schema: "wp-codebox/benchmark-db-inventory/v1"
44+
componentId: string
45+
scenarioId: string
46+
inventory: unknown
47+
}
48+
49+
interface ExternalHttpGuardrailBenchmarkArtifact {
50+
schema: "wp-codebox/benchmark-external-http-guardrail/v1"
51+
componentId: string
52+
scenarioId: string
53+
guardrail: unknown
54+
}
55+
56+
interface RestDbQueryProfileBenchmarkArtifact {
57+
schema: "wp-codebox/benchmark-rest-db-query-profile/v1"
58+
componentId: string
59+
scenarioId: string
60+
profile: unknown
61+
}
62+
4263
interface RouteMatrixStepSummary {
4364
index?: number
4465
id?: string
@@ -157,7 +178,7 @@ function enrichBenchScenarioArtifactRefs(scenario: BenchResults["scenarios"][num
157178
...scenarioArtifactRefs(scenario.artifacts, manifestFiles, "scenario-artifact"),
158179
...sampleArtifactRefs((scenario as BenchScenarioWithArtifactRefs).samples, manifestFiles),
159180
...metricArtifactRefs(scenario.metrics, manifestFiles),
160-
...browserArtifactRefs(scenario.metrics, manifestFiles),
181+
...browserArtifactRefs(manifestFiles),
161182
]
162183
const existingRefs = Array.isArray((scenario as BenchScenarioWithArtifactRefs).artifactRefs) ? (scenario as BenchScenarioWithArtifactRefs).artifactRefs ?? [] : []
163184
const dedupedRefs = dedupeBenchmarkArtifactRefs([...existingRefs, ...artifactRefs])
@@ -169,8 +190,13 @@ function enrichBenchScenarioArtifactRefs(scenario: BenchResults["scenarios"][num
169190
}
170191

171192
export async function writeBenchmarkArtifactEvidence(artifacts: ArtifactBundle, benchResultsList: BenchResults[]): Promise<void> {
172-
const materializedBenchResultsList = await materializeRouteMatrixSummaryArtifacts(artifacts, benchResultsList)
173-
const scenarios = materializedBenchResultsList.flatMap((result) => result.scenarios.map((scenario) => ({
193+
const materializedBenchResultsList = await materializeBenchmarkSummaryArtifacts(artifacts, benchResultsList)
194+
const manifestFiles = await artifactManifestFilesByPath(artifacts)
195+
const enrichedBenchResultsList = materializedBenchResultsList.map((result) => ({
196+
...result,
197+
scenarios: result.scenarios.map((scenario) => enrichBenchScenarioArtifactRefs(scenario, manifestFiles)),
198+
}))
199+
const scenarios = enrichedBenchResultsList.flatMap((result) => result.scenarios.map((scenario) => ({
174200
componentId: result.component_id,
175201
scenarioId: String(scenario.id ?? ""),
176202
source: typeof scenario.source === "string" ? scenario.source : undefined,
@@ -183,7 +209,7 @@ export async function writeBenchmarkArtifactEvidence(artifacts: ArtifactBundle,
183209
directory: artifacts.directory,
184210
contentDigest: artifacts.contentDigest,
185211
},
186-
results: materializedBenchResultsList,
212+
results: enrichedBenchResultsList,
187213
scenarios,
188214
}
189215
const relativePath = "files/bench-results.json"
@@ -194,13 +220,67 @@ export async function writeBenchmarkArtifactEvidence(artifacts: ArtifactBundle,
194220
await writeFile(artifacts.manifestPath, `${JSON.stringify(manifest, null, 2)}\n`)
195221
}
196222

197-
async function materializeRouteMatrixSummaryArtifacts(artifacts: ArtifactBundle, benchResultsList: BenchResults[]): Promise<BenchResults[]> {
223+
async function materializeBenchmarkSummaryArtifacts(artifacts: ArtifactBundle, benchResultsList: BenchResults[]): Promise<BenchResults[]> {
198224
const manifest = JSON.parse(await readFile(artifacts.manifestPath, "utf8")) as ArtifactManifest
199-
const writes: Array<{ resultIndex: number; scenarioIndex: number; path: string; artifactName: string; kind: string; operation: string; summary: RouteMatrixSummaryArtifact | RestRequestCaseSummaryArtifact }> = []
225+
const writes: Array<{ resultIndex: number; scenarioIndex: number; path: string; artifactName: string; kind: string; operation: string; summary: RouteMatrixSummaryArtifact | RestRequestCaseSummaryArtifact | DbInventoryBenchmarkArtifact | ExternalHttpGuardrailBenchmarkArtifact | RestDbQueryProfileBenchmarkArtifact }> = []
200226

201227
benchResultsList.forEach((result, resultIndex) => {
202228
const routeMatrixScenarioIds = routeMatrixWorkloadScenarioIds(result)
203229
result.scenarios.forEach((scenario, scenarioIndex) => {
230+
const dbInventory = isRecord(scenario.artifacts) && isRecord(scenario.artifacts["db-inventory"]) && scenario.artifacts["db-inventory"].schema === "wp-codebox/wordpress-db-inventory/v1" ? scenario.artifacts["db-inventory"] : undefined
231+
if (dbInventory) {
232+
const scenarioId = String(scenario.id ?? "")
233+
writes.push({
234+
resultIndex,
235+
scenarioIndex,
236+
path: `files/bench/${safeArtifactSegment(result.component_id)}/${safeArtifactSegment(scenarioId)}-db-inventory.json`,
237+
artifactName: "db-inventory",
238+
kind: "benchmark-db-inventory",
239+
operation: "materialize-db-inventory",
240+
summary: {
241+
schema: "wp-codebox/benchmark-db-inventory/v1",
242+
componentId: result.component_id,
243+
scenarioId,
244+
inventory: dbInventory,
245+
},
246+
})
247+
}
248+
const externalHttpGuardrail = isRecord(scenario.artifacts) && isRecord(scenario.artifacts["external-http-guardrail"]) && scenario.artifacts["external-http-guardrail"].schema === "wp-codebox/wordpress-external-http-guardrail/v1" ? scenario.artifacts["external-http-guardrail"] : undefined
249+
if (externalHttpGuardrail) {
250+
const scenarioId = String(scenario.id ?? "")
251+
writes.push({
252+
resultIndex,
253+
scenarioIndex,
254+
path: `files/bench/${safeArtifactSegment(result.component_id)}/${safeArtifactSegment(scenarioId)}-external-http-guardrail.json`,
255+
artifactName: "external-http-guardrail",
256+
kind: "benchmark-external-http-guardrail",
257+
operation: "materialize-external-http-guardrail",
258+
summary: {
259+
schema: "wp-codebox/benchmark-external-http-guardrail/v1",
260+
componentId: result.component_id,
261+
scenarioId,
262+
guardrail: externalHttpGuardrail,
263+
},
264+
})
265+
}
266+
const restDbQueryProfile = isRecord(scenario.artifacts) && isRecord(scenario.artifacts["rest-db-query-profile"]) && scenario.artifacts["rest-db-query-profile"].schema === "wp-codebox/wordpress-rest-db-query-profile/v1" ? scenario.artifacts["rest-db-query-profile"] : undefined
267+
if (restDbQueryProfile) {
268+
const scenarioId = String(scenario.id ?? "")
269+
writes.push({
270+
resultIndex,
271+
scenarioIndex,
272+
path: `files/bench/${safeArtifactSegment(result.component_id)}/${safeArtifactSegment(scenarioId)}-rest-db-query-profile.json`,
273+
artifactName: "rest-db-query-profile",
274+
kind: "benchmark-rest-db-query-profile",
275+
operation: "materialize-rest-db-query-profile",
276+
summary: {
277+
schema: "wp-codebox/benchmark-rest-db-query-profile/v1",
278+
componentId: result.component_id,
279+
scenarioId,
280+
profile: restDbQueryProfile,
281+
},
282+
})
283+
}
204284
if (!routeMatrixScenarioIds.has(String(scenario.id ?? ""))) {
205285
return
206286
}
@@ -286,12 +366,13 @@ async function materializeRouteMatrixSummaryArtifacts(artifacts: ArtifactBundle,
286366
}
287367
const refs = scenarioWrites.map((write) => benchmarkArtifactRef(write.path, { source: "scenario-artifact", name: write.artifactName, kind: write.kind, contentType: "application/json" }, manifestFiles))
288368
const existingRefs = Array.isArray((scenario as BenchScenarioWithArtifactRefs).artifactRefs) ? (scenario as BenchScenarioWithArtifactRefs).artifactRefs ?? [] : []
369+
const materializedArtifacts = Object.fromEntries(scenarioWrites.map((write, index) => [write.artifactName, refs[index]]))
289370
return stripUndefined({
290371
...scenario,
291372
steps: redactRestStepResponses((scenario as BenchScenarioWithArtifactRefs).steps),
292373
artifacts: {
293374
...(scenario.artifacts ?? {}),
294-
...Object.fromEntries(scenarioWrites.map((write, index) => [write.artifactName, refs[index]])),
375+
...materializedArtifacts,
295376
},
296377
artifactRefs: dedupeBenchmarkArtifactRefs([...existingRefs, ...refs]),
297378
}) as BenchResults["scenarios"][number]
@@ -459,11 +540,7 @@ function metricArtifactRefs(metrics: BenchResults["scenarios"][number]["metrics"
459540
return Object.keys(metrics).sort().map((metric) => benchmarkArtifactRef("files/bench-results.json", { source: "metric-source", metric, kind: "benchmark-results", contentType: "application/json" }, manifestFiles))
460541
}
461542

462-
function browserArtifactRefs(metrics: BenchResults["scenarios"][number]["metrics"], manifestFiles: Map<string, ArtifactManifestFile>): BenchmarkArtifactRef[] {
463-
if (!metrics || !Object.keys(metrics).some((metric) => metric.startsWith("browser_"))) {
464-
return []
465-
}
466-
543+
function browserArtifactRefs(manifestFiles: Map<string, ArtifactManifestFile>): BenchmarkArtifactRef[] {
467544
return [...manifestFiles.values()]
468545
.filter((file) => file.path.startsWith("files/browser/"))
469546
.map((file) => benchmarkArtifactRef(file.path, { source: "browser-artifact", kind: file.kind, contentType: file.contentType }, manifestFiles))

packages/runtime-core/src/benchmark-contracts.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,17 @@ export interface BenchResults {
101101

102102
export interface BenchmarkDefinitionWorkloadStep {
103103
type: "php" | "wp-cli" | "rest-request" | "ability" | (string & {})
104+
action?: "install" | "collect" | "reset" | (string & {})
104105
code?: string
105106
file?: string
106107
command?: string
107108
parse?: "json" | (string & {})
109+
allowlistDomains?: string[]
110+
blockNetwork?: boolean
111+
redactUrls?: boolean
112+
blockResponse?: { code?: number; message?: string; body?: string }
113+
sampleLimit?: number
114+
queryLengthLimit?: number
108115
metadata?: Record<string, unknown>
109116
[key: string]: unknown
110117
}
@@ -398,6 +405,21 @@ function benchmarkSchemaDefs(): Record<string, unknown> {
398405
file: { type: "string" },
399406
command: { type: "string" },
400407
parse: { type: "string" },
408+
action: { type: "string" },
409+
allowlistDomains: { type: "array", items: { type: "string", minLength: 1 } },
410+
blockNetwork: { type: "boolean" },
411+
redactUrls: { type: "boolean" },
412+
blockResponse: {
413+
type: "object",
414+
additionalProperties: false,
415+
properties: {
416+
code: { type: "integer", minimum: 100, maximum: 599 },
417+
message: { type: "string" },
418+
body: { type: "string" },
419+
},
420+
},
421+
sampleLimit: { type: "integer", minimum: 0 },
422+
queryLengthLimit: { type: "integer", minimum: 80 },
401423
metadata: { type: "object", additionalProperties: true },
402424
},
403425
},

0 commit comments

Comments
 (0)