@openmixer/plugin-qualify 0.10.1 → 0.11.0
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/README.md +46 -10
- package/bin/plugin-qualify.mjs +61 -28
- package/dist/clap-loader.d.ts +1 -1
- package/dist/clap-loader.js +3 -3
- package/dist/clap-loader.js.map +1 -1
- package/dist/clap-qualify.d.ts +0 -47
- package/dist/clap-qualify.js +2 -88
- package/dist/clap-qualify.js.map +1 -1
- package/dist/cost-fields.d.ts +26 -0
- package/dist/cost-fields.js +30 -0
- package/dist/cost-fields.js.map +1 -1
- package/dist/hosting-codes.d.ts +1 -0
- package/dist/hosting-codes.js +1 -0
- package/dist/hosting-codes.js.map +1 -1
- package/dist/hosting-suitability.d.ts +11 -1
- package/dist/hosting-suitability.js +11 -2
- package/dist/hosting-suitability.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/judge-cli.d.ts +41 -0
- package/dist/judge-cli.js +114 -0
- package/dist/judge-cli.js.map +1 -0
- package/dist/measurement/probe-json.js +1 -0
- package/dist/measurement/probe-json.js.map +1 -1
- package/dist/measurement/vocabulary.js +6 -0
- package/dist/measurement/vocabulary.js.map +1 -1
- package/dist/probe-judge.d.ts +60 -0
- package/dist/probe-judge.js +213 -0
- package/dist/probe-judge.js.map +1 -0
- package/dist/probe-output-fixtures.d.ts +25 -0
- package/dist/probe-output-fixtures.js +134 -0
- package/dist/probe-output-fixtures.js.map +1 -0
- package/dist/probe-output.d.ts +150 -0
- package/dist/probe-output.js +369 -0
- package/dist/probe-output.js.map +1 -0
- package/dist/probe-runner.d.ts +79 -0
- package/dist/probe-runner.js +191 -0
- package/dist/probe-runner.js.map +1 -0
- package/dist/qualification-record.d.ts +80 -0
- package/dist/qualification-record.js +85 -0
- package/dist/qualification-record.js.map +1 -0
- package/dist/qualify-cli.js +1 -1
- package/dist/qualify-cli.js.map +1 -1
- package/dist/qualify-declarations.d.ts +11 -2
- package/dist/qualify-declarations.js +14 -4
- package/dist/qualify-declarations.js.map +1 -1
- package/fixtures/clap-scan/fake.json +30 -6
- package/fixtures/probe-output/memory-limit-refused.json +190 -0
- package/fixtures/probe-output/plugin-crash.json +236 -0
- package/fixtures/probe-output/success-msi.json +363 -0
- package/package.json +5 -1
- package/src/clap-loader.ts +3 -2
- package/src/clap-qualify.ts +2 -133
- package/src/cost-fields.ts +39 -0
- package/src/hosting-codes.ts +1 -0
- package/src/hosting-suitability.ts +21 -3
- package/src/index.ts +5 -0
- package/src/judge-cli.ts +133 -0
- package/src/measurement/probe-json.ts +1 -0
- package/src/measurement/vocabulary.ts +7 -0
- package/src/probe-judge.ts +264 -0
- package/src/probe-output-fixtures.ts +146 -0
- package/src/probe-output.ts +505 -0
- package/src/probe-runner.ts +244 -0
- package/src/qualification-record.ts +142 -0
- package/src/qualify-cli.ts +1 -1
- package/src/qualify-declarations.ts +16 -4
- package/tools/benchmark.py +12 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": "plugin-hostd.probe-output",
|
|
3
|
+
"formatVersion": "1.0",
|
|
4
|
+
"run": {
|
|
5
|
+
"id": "msi-20261017T221500Z-3569027",
|
|
6
|
+
"startedAt": "2026-10-17T22:15:00+02:00",
|
|
7
|
+
"elapsedMs": 184220,
|
|
8
|
+
"complete": true,
|
|
9
|
+
"outcome": "probe.ok",
|
|
10
|
+
"reason": "reference",
|
|
11
|
+
"protocol": {
|
|
12
|
+
"name": "plugin-hostd.probe-protocol",
|
|
13
|
+
"version": 1
|
|
14
|
+
},
|
|
15
|
+
"tools": {
|
|
16
|
+
"runner": "plugin-hostd-probe 0.1.0",
|
|
17
|
+
"child": "plugin-hostd-probe-clap 0.1.0",
|
|
18
|
+
"clapCore": "0.9.0",
|
|
19
|
+
"interposer": "rt-interpose 1"
|
|
20
|
+
},
|
|
21
|
+
"via": "runner"
|
|
22
|
+
},
|
|
23
|
+
"plugin": {
|
|
24
|
+
"format": "clap",
|
|
25
|
+
"id": "org.example.plate",
|
|
26
|
+
"name": "Plate",
|
|
27
|
+
"vendor": "Example",
|
|
28
|
+
"version": "1.4.2",
|
|
29
|
+
"ownerPackage": "example-plate-1.4.2-1.fc44.x86_64"
|
|
30
|
+
},
|
|
31
|
+
"binary": {
|
|
32
|
+
"path": "/usr/lib64/clap/example-plate.clap",
|
|
33
|
+
"sha256": "356902794a4eec2122989e072271ef995968131145db918d9a3084ce582f244b",
|
|
34
|
+
"files": [
|
|
35
|
+
{
|
|
36
|
+
"path": "example-plate.clap",
|
|
37
|
+
"sha256": "356902794a4eec2122989e072271ef995968131145db918d9a3084ce582f244b"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"layout": {
|
|
41
|
+
"scheme": "omx-layout/1",
|
|
42
|
+
"sha256": "0a3e01399262321514e05af0575ebb0b207a35567ac262a060fde655ee069805"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"descriptor": {
|
|
46
|
+
"path": "descriptors/2/clap/org.example.plate@1.4.2-356902794a4e.json",
|
|
47
|
+
"sha256": "cc312bdd5a9aa62796ebcb5a648c354c5ee45a6a99892425495ccb58a31bc44e",
|
|
48
|
+
"bytes": 9412
|
|
49
|
+
},
|
|
50
|
+
"machine": {
|
|
51
|
+
"class": {
|
|
52
|
+
"arch": "x86_64",
|
|
53
|
+
"family": "intel",
|
|
54
|
+
"coreBand": "17-32",
|
|
55
|
+
"clockBand": "4.5GHz+",
|
|
56
|
+
"preemptRt": false,
|
|
57
|
+
"cstatesLimited": true
|
|
58
|
+
},
|
|
59
|
+
"profile": {
|
|
60
|
+
"governor": "performance",
|
|
61
|
+
"turbo": true,
|
|
62
|
+
"smt": true,
|
|
63
|
+
"preempt": "PREEMPT_DYNAMIC",
|
|
64
|
+
"cpuidle": [
|
|
65
|
+
{
|
|
66
|
+
"cpu": 22,
|
|
67
|
+
"state": "POLL",
|
|
68
|
+
"latencyUs": 0,
|
|
69
|
+
"disabled": false,
|
|
70
|
+
"usageDelta": 118
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"cpu": 22,
|
|
74
|
+
"state": "C1_ACPI",
|
|
75
|
+
"latencyUs": 1,
|
|
76
|
+
"disabled": false,
|
|
77
|
+
"usageDelta": 40210
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"cpu": 22,
|
|
81
|
+
"state": "C2_ACPI",
|
|
82
|
+
"latencyUs": 127,
|
|
83
|
+
"disabled": true,
|
|
84
|
+
"usageDelta": 0
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"cpu": 22,
|
|
88
|
+
"state": "C3_ACPI",
|
|
89
|
+
"latencyUs": 1048,
|
|
90
|
+
"disabled": true,
|
|
91
|
+
"usageDelta": 0
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"meetsProtocol": true,
|
|
95
|
+
"declaredName": "omx-live-rig"
|
|
96
|
+
},
|
|
97
|
+
"pinnedCpus": "22",
|
|
98
|
+
"reference": {
|
|
99
|
+
"rig": "msi",
|
|
100
|
+
"image": "omx-ref-2026-10"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"offer": {
|
|
104
|
+
"id": "omx-clap-host",
|
|
105
|
+
"clapExtensions": [
|
|
106
|
+
"clap.audio-ports",
|
|
107
|
+
"clap.latency",
|
|
108
|
+
"clap.log",
|
|
109
|
+
"clap.params",
|
|
110
|
+
"clap.state",
|
|
111
|
+
"clap.thread-check",
|
|
112
|
+
"clap.preset-load/2",
|
|
113
|
+
"clap.track-info/1",
|
|
114
|
+
"clap.remote-controls/2"
|
|
115
|
+
],
|
|
116
|
+
"realtime": "none"
|
|
117
|
+
},
|
|
118
|
+
"limits": {
|
|
119
|
+
"set": "default",
|
|
120
|
+
"applied": [
|
|
121
|
+
{
|
|
122
|
+
"limit": "wall-clock",
|
|
123
|
+
"value": 600,
|
|
124
|
+
"unit": "s",
|
|
125
|
+
"by": "supervisor"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"limit": "cpu-time",
|
|
129
|
+
"value": 540,
|
|
130
|
+
"unit": "s",
|
|
131
|
+
"by": "rlimit"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"limit": "memory",
|
|
135
|
+
"value": 2147483648,
|
|
136
|
+
"unit": "bytes",
|
|
137
|
+
"by": "cgroup"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"limit": "tasks",
|
|
141
|
+
"value": 64,
|
|
142
|
+
"unit": "count",
|
|
143
|
+
"by": "cgroup"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"limit": "files",
|
|
147
|
+
"value": 256,
|
|
148
|
+
"unit": "count",
|
|
149
|
+
"by": "rlimit"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"limit": "file-size",
|
|
153
|
+
"value": 67108864,
|
|
154
|
+
"unit": "bytes",
|
|
155
|
+
"by": "rlimit"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"limit": "core-dump",
|
|
159
|
+
"value": 0,
|
|
160
|
+
"unit": "bytes",
|
|
161
|
+
"by": "rlimit"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"limit": "rt-priority",
|
|
165
|
+
"value": 0,
|
|
166
|
+
"by": "rlimit"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"limit": "cpus",
|
|
170
|
+
"value": "22",
|
|
171
|
+
"by": "affinity"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"limit": "no-new-privs",
|
|
175
|
+
"by": "prctl"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"limit": "network",
|
|
179
|
+
"value": "none",
|
|
180
|
+
"by": "netns"
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
"unavailable": []
|
|
184
|
+
},
|
|
185
|
+
"steps": [
|
|
186
|
+
{
|
|
187
|
+
"step": "hash",
|
|
188
|
+
"outcome": "probe.ok",
|
|
189
|
+
"elapsedMs": 4
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"step": "instantiate",
|
|
193
|
+
"outcome": "probe.ok",
|
|
194
|
+
"elapsedMs": 31
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"step": "lifecycle",
|
|
198
|
+
"outcome": "probe.ok",
|
|
199
|
+
"elapsedMs": 2210
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"step": "latency",
|
|
203
|
+
"rate": 48000,
|
|
204
|
+
"quantum": 128,
|
|
205
|
+
"outcome": "probe.ok",
|
|
206
|
+
"elapsedMs": 310
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"step": "cost",
|
|
210
|
+
"rate": 48000,
|
|
211
|
+
"quantum": 128,
|
|
212
|
+
"outcome": "probe.ok",
|
|
213
|
+
"elapsedMs": 30020
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"step": "rt-safety",
|
|
217
|
+
"rate": 48000,
|
|
218
|
+
"quantum": 128,
|
|
219
|
+
"outcome": "probe.ok",
|
|
220
|
+
"elapsedMs": 4100
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"step": "latency",
|
|
224
|
+
"rate": 96000,
|
|
225
|
+
"quantum": 128,
|
|
226
|
+
"outcome": "probe.ok",
|
|
227
|
+
"elapsedMs": 305
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"step": "cost",
|
|
231
|
+
"rate": 96000,
|
|
232
|
+
"quantum": 128,
|
|
233
|
+
"outcome": "probe.ok",
|
|
234
|
+
"elapsedMs": 30011
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"step": "rt-safety",
|
|
238
|
+
"rate": 96000,
|
|
239
|
+
"quantum": 128,
|
|
240
|
+
"outcome": "probe.ok",
|
|
241
|
+
"elapsedMs": 4080
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"step": "note-sweep",
|
|
245
|
+
"outcome": "probe.not-attempted",
|
|
246
|
+
"elapsedMs": 0,
|
|
247
|
+
"detail": "no note input"
|
|
248
|
+
}
|
|
249
|
+
],
|
|
250
|
+
"grid": [
|
|
251
|
+
{
|
|
252
|
+
"rate": 48000,
|
|
253
|
+
"quantum": 128,
|
|
254
|
+
"outcome": "probe.ok",
|
|
255
|
+
"latency": {
|
|
256
|
+
"declaredFrames": 0,
|
|
257
|
+
"measuredFrames": 0,
|
|
258
|
+
"method": "impulse-onset"
|
|
259
|
+
},
|
|
260
|
+
"cost": {
|
|
261
|
+
"unit": "ns-per-sample",
|
|
262
|
+
"median": 41.2,
|
|
263
|
+
"percentile": {
|
|
264
|
+
"p": 95,
|
|
265
|
+
"value": 47.9
|
|
266
|
+
},
|
|
267
|
+
"min": 39.8,
|
|
268
|
+
"max": 112.4,
|
|
269
|
+
"iqr": 3.1,
|
|
270
|
+
"blocks": 11250,
|
|
271
|
+
"warmupBlocks": 64,
|
|
272
|
+
"warmupMedian": 58.0,
|
|
273
|
+
"timingFloorNsPerBlock": 180
|
|
274
|
+
},
|
|
275
|
+
"rtSafety": {
|
|
276
|
+
"interposer": true,
|
|
277
|
+
"swept": true,
|
|
278
|
+
"sweepApplicable": true,
|
|
279
|
+
"blocks": 512,
|
|
280
|
+
"allocations": 0,
|
|
281
|
+
"locks": 0,
|
|
282
|
+
"syscalls": 0,
|
|
283
|
+
"threadViolations": 0,
|
|
284
|
+
"processErrors": 0,
|
|
285
|
+
"events": []
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
"rate": 96000,
|
|
290
|
+
"quantum": 128,
|
|
291
|
+
"outcome": "probe.ok",
|
|
292
|
+
"latency": {
|
|
293
|
+
"declaredFrames": 0,
|
|
294
|
+
"measuredFrames": 0,
|
|
295
|
+
"method": "impulse-onset"
|
|
296
|
+
},
|
|
297
|
+
"cost": {
|
|
298
|
+
"unit": "ns-per-sample",
|
|
299
|
+
"median": 40.7,
|
|
300
|
+
"percentile": {
|
|
301
|
+
"p": 95,
|
|
302
|
+
"value": 46.3
|
|
303
|
+
},
|
|
304
|
+
"min": 39.1,
|
|
305
|
+
"max": 98.0,
|
|
306
|
+
"iqr": 2.8,
|
|
307
|
+
"blocks": 22500,
|
|
308
|
+
"warmupBlocks": 64,
|
|
309
|
+
"timingFloorNsPerBlock": 180,
|
|
310
|
+
"samples": {
|
|
311
|
+
"name": "cost-96000-128",
|
|
312
|
+
"path": "probes/org.example.plate@1.4.2-356902794a4e.cost-96000-128.bin",
|
|
313
|
+
"sha256": "5f649130dafd22f1edc70441f9455415d0856f0f7d26cf7274463b82ac2c5813",
|
|
314
|
+
"bytes": 180000
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
"rtSafety": {
|
|
318
|
+
"interposer": true,
|
|
319
|
+
"swept": true,
|
|
320
|
+
"sweepApplicable": true,
|
|
321
|
+
"blocks": 512,
|
|
322
|
+
"allocations": 0,
|
|
323
|
+
"locks": 0,
|
|
324
|
+
"syscalls": 0,
|
|
325
|
+
"threadViolations": 0,
|
|
326
|
+
"processErrors": 0,
|
|
327
|
+
"events": []
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
],
|
|
331
|
+
"behaviour": {
|
|
332
|
+
"lifecycle": {
|
|
333
|
+
"cycles": 30,
|
|
334
|
+
"instantiated": 30,
|
|
335
|
+
"failed": 0
|
|
336
|
+
},
|
|
337
|
+
"threads": {
|
|
338
|
+
"before": 2,
|
|
339
|
+
"afterFirstInstantiate": 2,
|
|
340
|
+
"afterAllFreed": 2
|
|
341
|
+
},
|
|
342
|
+
"memory": {
|
|
343
|
+
"rssStartKb": 9120,
|
|
344
|
+
"rssMaxKb": 14988
|
|
345
|
+
},
|
|
346
|
+
"features": {
|
|
347
|
+
"extensionsAsked": [
|
|
348
|
+
"clap.audio-ports",
|
|
349
|
+
"clap.params",
|
|
350
|
+
"clap.state",
|
|
351
|
+
"clap.latency"
|
|
352
|
+
]
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
"logs": [
|
|
356
|
+
{
|
|
357
|
+
"name": "stderr",
|
|
358
|
+
"path": "probes/org.example.plate@1.4.2-356902794a4e.stderr.log",
|
|
359
|
+
"sha256": "c46928bc87de23b9a8bd235aa1fe67ccb1542c8389ad3f02bf4ab5cff506e98f",
|
|
360
|
+
"bytes": 212
|
|
361
|
+
}
|
|
362
|
+
]
|
|
363
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmixer/plugin-qualify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Qualify audio plugins for real-time hosting: scan, offline latency and cost measurement, a mod-host hosting sweep, and a fail-closed rating.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"dist",
|
|
68
68
|
"src",
|
|
69
69
|
"!src/**/*.test.ts",
|
|
70
|
+
"!src/**/*.support.*",
|
|
70
71
|
"tools",
|
|
71
72
|
"!tools/test_*.py",
|
|
72
73
|
"!**/__pycache__",
|
|
@@ -93,5 +94,8 @@
|
|
|
93
94
|
"publishConfig": {
|
|
94
95
|
"access": "public",
|
|
95
96
|
"provenance": true
|
|
97
|
+
},
|
|
98
|
+
"dependencies": {
|
|
99
|
+
"@openmixer/omx-contract": "^2.16.0"
|
|
96
100
|
}
|
|
97
101
|
}
|
package/src/clap-loader.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
CLAP_SCAN_TOOL_DEFAULT,
|
|
26
26
|
CLAP_SCAN_TOOL_ENV,
|
|
27
27
|
CLAP_SYSTEM_DIR,
|
|
28
|
+
CLAP_SYSTEM_DIR_ENV,
|
|
28
29
|
CLAP_USER_DIR,
|
|
29
30
|
} from "./qualify-declarations.js";
|
|
30
31
|
|
|
@@ -428,11 +429,11 @@ export function clapLoader(options: ClapLoaderOptions = {}): PluginLoader {
|
|
|
428
429
|
|
|
429
430
|
/**
|
|
430
431
|
* The directories `--all` walks, in order and once each: every non-empty entry of `CLAP_PATH`, then
|
|
431
|
-
* `~/.clap`, then `/usr/lib/clap
|
|
432
|
+
* `~/.clap`, then `/usr/lib/clap` (or the directory `OMX_CLAP_SYSTEM_DIR` names).
|
|
432
433
|
*/
|
|
433
434
|
export function clapSearchPath(env: Readonly<Record<string, string | undefined>>, home: string): string[] {
|
|
434
435
|
const named = (env[CLAP_PATH_ENV] ?? "").split(":").filter((d) => d !== "");
|
|
435
|
-
return [...new Set([...named, join(home, CLAP_USER_DIR), CLAP_SYSTEM_DIR].map((d) => resolve(d)))];
|
|
436
|
+
return [...new Set([...named, join(home, CLAP_USER_DIR), env[CLAP_SYSTEM_DIR_ENV] || CLAP_SYSTEM_DIR].map((d) => resolve(d)))];
|
|
436
437
|
}
|
|
437
438
|
|
|
438
439
|
export interface ClapDiscoverOptions extends ClapLoaderOptions {
|
package/src/clap-qualify.ts
CHANGED
|
@@ -10,16 +10,9 @@
|
|
|
10
10
|
* read, a prober that is absent, crashed or timed out, is a {@link ClapQualifyError}: nothing was
|
|
11
11
|
* measured, and nothing is rated.
|
|
12
12
|
*/
|
|
13
|
-
import { spawnSync } from "node:child_process";
|
|
14
13
|
import { existsSync } from "node:fs";
|
|
15
14
|
|
|
16
|
-
import {
|
|
17
|
-
CLAP_QUALIFY_TIMEOUT_S,
|
|
18
|
-
CLAP_QUALIFY_TOOL_NAME,
|
|
19
|
-
CLAP_SWEEP_BLOCKS,
|
|
20
|
-
CLAP_SWEEP_FRAMES,
|
|
21
|
-
RT_INTERPOSE_NAME,
|
|
22
|
-
} from "./qualify-declarations.js";
|
|
15
|
+
import { CLAP_QUALIFY_TOOL_NAME, RT_INTERPOSE_NAME } from "./qualify-declarations.js";
|
|
23
16
|
import {
|
|
24
17
|
HOSTING_CODE,
|
|
25
18
|
clapRtReasons,
|
|
@@ -37,7 +30,6 @@ import {
|
|
|
37
30
|
OPENMIXER_CONSOLE_PROFILE,
|
|
38
31
|
type HostProfile,
|
|
39
32
|
} from "./host-profiles.js";
|
|
40
|
-
import type { PluginTarget } from "./loader.js";
|
|
41
33
|
import type { QualifyVerdict } from "./qualify-cli.js";
|
|
42
34
|
import { RATING_SEVERITY, worstRating, type SuitabilityRating } from "./rating.js";
|
|
43
35
|
import { halfPopulatedRefusal, locateNativeTools } from "./tool-paths.js";
|
|
@@ -318,47 +310,7 @@ export function rtSafetyDocumentName(doc: RtSafetyDocument): string {
|
|
|
318
310
|
return `${doc.plugin.binary.sha256}.${doc.console}.rt-safety.json`;
|
|
319
311
|
}
|
|
320
312
|
|
|
321
|
-
// ---
|
|
322
|
-
|
|
323
|
-
/** What a run of the prober gave back; `error` is a spawn failure, `signal` a death. */
|
|
324
|
-
export interface ClapQualifyRunResult {
|
|
325
|
-
readonly status: number | null;
|
|
326
|
-
readonly signal?: string | null;
|
|
327
|
-
readonly stdout: string;
|
|
328
|
-
readonly stderr?: string;
|
|
329
|
-
readonly error?: Error;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
export type ClapQualifyRun = (
|
|
333
|
-
tool: string,
|
|
334
|
-
args: readonly string[],
|
|
335
|
-
env: Readonly<Record<string, string | undefined>>,
|
|
336
|
-
timeoutMs: number,
|
|
337
|
-
) => ClapQualifyRunResult;
|
|
338
|
-
|
|
339
|
-
const spawnQualify: ClapQualifyRun = (tool, args, env, timeoutMs) => {
|
|
340
|
-
const r = spawnSync(tool, [...args], { encoding: "utf8", env: { ...env }, timeout: timeoutMs, maxBuffer: 64 << 20 });
|
|
341
|
-
return {
|
|
342
|
-
status: r.status,
|
|
343
|
-
signal: r.signal,
|
|
344
|
-
stdout: r.stdout ?? "",
|
|
345
|
-
stderr: r.stderr ?? "",
|
|
346
|
-
...(r.error === undefined ? {} : { error: r.error }),
|
|
347
|
-
};
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
export interface ClapQualifyOptions {
|
|
351
|
-
/** The rates the plugin is activated and swept at. */
|
|
352
|
-
readonly rates: readonly number[];
|
|
353
|
-
/** Runs the prober; the default spawns it. */
|
|
354
|
-
readonly run?: ClapQualifyRun;
|
|
355
|
-
/** Where the two settings are read from; the default is `process.env`. */
|
|
356
|
-
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
357
|
-
/** Whether a file exists; the default asks the filesystem. */
|
|
358
|
-
readonly exists?: (path: string) => boolean;
|
|
359
|
-
/** The host extensions the prober offers beyond the console's, each one of {@link OMX_CLAP_HOST_CONFIGURED_EXTENSIONS}. */
|
|
360
|
-
readonly offer?: readonly string[];
|
|
361
|
-
}
|
|
313
|
+
// --- the prober and the interposer a run uses ---------------------------------------------------
|
|
362
314
|
|
|
363
315
|
/**
|
|
364
316
|
* The prober and the interposer this run uses: each its setting, else both from one place, the
|
|
@@ -381,44 +333,6 @@ export function clapQualifyTools(
|
|
|
381
333
|
return { tool: tool!, interposer: interposer! };
|
|
382
334
|
}
|
|
383
335
|
|
|
384
|
-
/**
|
|
385
|
-
* Qualify the plugins of one `.clap` (all of them, or the one `id` names) under the interposer.
|
|
386
|
-
* Absent tools, a crash, a timeout, a load failure or an unreadable line throw: none of them is a
|
|
387
|
-
* measurement.
|
|
388
|
-
*/
|
|
389
|
-
export function qualifyClap(binaryPath: string, id: string | undefined, options: ClapQualifyOptions): ClapQualifyLine[] {
|
|
390
|
-
const env = options.env ?? process.env;
|
|
391
|
-
const { tool, interposer } = clapQualifyTools(env, options.exists ?? existsSync);
|
|
392
|
-
const args = [
|
|
393
|
-
"--rates",
|
|
394
|
-
options.rates.join(","),
|
|
395
|
-
"--block",
|
|
396
|
-
String(CLAP_SWEEP_FRAMES),
|
|
397
|
-
"--blocks",
|
|
398
|
-
String(CLAP_SWEEP_BLOCKS),
|
|
399
|
-
...(options.offer === undefined || options.offer.length === 0 ? [] : ["--offer", options.offer.join(",")]),
|
|
400
|
-
binaryPath,
|
|
401
|
-
...(id === undefined ? [] : [id]),
|
|
402
|
-
];
|
|
403
|
-
const r = (options.run ?? spawnQualify)(tool, args, { ...env, LD_PRELOAD: interposer }, CLAP_QUALIFY_TIMEOUT_S * 1000);
|
|
404
|
-
if (r.error !== undefined) throw new ClapQualifyError(`cannot run ${tool}: ${r.error.message}`);
|
|
405
|
-
if (r.signal) throw new ClapQualifyError(`${binaryPath} killed the prober (${r.signal})`);
|
|
406
|
-
if (r.status !== 0) throw new ClapQualifyError(`${tool} exited ${String(r.status)} on ${binaryPath}: ${(r.stderr ?? "").trim()}`);
|
|
407
|
-
const lines = parseClapQualifyOutput(r.stdout);
|
|
408
|
-
if (lines.length === 0) throw new ClapQualifyError(`${tool} printed nothing for ${binaryPath}`);
|
|
409
|
-
return lines;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// --- the CLI's CLAP pass -------------------------------------------------------------------------
|
|
413
|
-
|
|
414
|
-
/** What the CLAP pass of one run produced: a document per plugin and console, and a row per verdict. */
|
|
415
|
-
export interface ClapQualifyPass {
|
|
416
|
-
readonly documents: readonly RtSafetyDocument[];
|
|
417
|
-
readonly verdicts: readonly QualifyVerdict[];
|
|
418
|
-
/** Each target that could not be measured, with why; its verdict rows read `unknown`, unmeasured. */
|
|
419
|
-
readonly failures: readonly { readonly uri: string; readonly problem: string }[];
|
|
420
|
-
}
|
|
421
|
-
|
|
422
336
|
/**
|
|
423
337
|
* What the prober must offer beyond the console's extensions to measure for `profile`: the extra
|
|
424
338
|
* extensions of its list, when the prober can offer each and the list holds the console's; `undefined`
|
|
@@ -432,48 +346,3 @@ export function proberOffer(profile: HostProfile): readonly string[] | undefined
|
|
|
432
346
|
if (!extra.every((e) => OMX_CLAP_HOST_CONFIGURED_EXTENSIONS.includes(e))) return undefined;
|
|
433
347
|
return OMX_CLAP_HOST_CONFIGURED_EXTENSIONS.filter((e) => extra.includes(e));
|
|
434
348
|
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Qualify every CLAP target, one prober run per target and set of offered extensions (a crash takes
|
|
438
|
-
* only that plugin), and judge each line under every host profile, from the run that offered that
|
|
439
|
-
* profile's extensions. A target without a binary path (a bare `urn:clap:` id) has nothing to load
|
|
440
|
-
* and is a failure, never a pass.
|
|
441
|
-
*/
|
|
442
|
-
export function qualifyClapTargets(
|
|
443
|
-
targets: readonly PluginTarget[],
|
|
444
|
-
options: ClapQualifyOptions & { readonly profiles?: readonly HostProfile[] },
|
|
445
|
-
): ClapQualifyPass {
|
|
446
|
-
const profiles = options.profiles ?? [OPENMIXER_CONSOLE_PROFILE];
|
|
447
|
-
const documents: RtSafetyDocument[] = [];
|
|
448
|
-
const verdicts: QualifyVerdict[] = [];
|
|
449
|
-
const failures: { uri: string; problem: string }[] = [];
|
|
450
|
-
for (const target of targets) {
|
|
451
|
-
const id = target.uri.startsWith("urn:clap:") ? target.uri.slice("urn:clap:".length) : undefined;
|
|
452
|
-
// one run per distinct offer, the console's (none) for a profile the prober cannot stand in for
|
|
453
|
-
const offers = new Map<string, readonly string[]>();
|
|
454
|
-
for (const profile of profiles) {
|
|
455
|
-
const offer = proberOffer(profile) ?? [];
|
|
456
|
-
offers.set(offer.join(","), offer);
|
|
457
|
-
}
|
|
458
|
-
const runs = new Map<string, ClapQualifyLine[]>();
|
|
459
|
-
try {
|
|
460
|
-
if (target.binaryPath === undefined) throw new ClapQualifyError(`${target.uri}: no .clap to load; name the file or a directory holding it`);
|
|
461
|
-
for (const [key, offer] of offers) runs.set(key, qualifyClap(target.binaryPath, id, { ...options, offer }));
|
|
462
|
-
} catch (e) {
|
|
463
|
-
if (!(e instanceof ClapQualifyError)) throw e;
|
|
464
|
-
failures.push({ uri: target.uri, problem: e.message });
|
|
465
|
-
for (const profile of profiles) {
|
|
466
|
-
verdicts.push({ uri: target.uri, host: profile.id, measured: false, rating: "unknown", qualified: false, deciding: "not-measured" });
|
|
467
|
-
}
|
|
468
|
-
continue;
|
|
469
|
-
}
|
|
470
|
-
for (const profile of profiles) {
|
|
471
|
-
for (const line of runs.get((proberOffer(profile) ?? []).join(","))!) {
|
|
472
|
-
const doc = rtSafetyDocument(line, profile);
|
|
473
|
-
documents.push(doc);
|
|
474
|
-
verdicts.push(rtSafetyVerdict(doc));
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
return { documents, verdicts, failures };
|
|
479
|
-
}
|
package/src/cost-fields.ts
CHANGED
|
@@ -70,3 +70,42 @@ export function classifierCostFromDocument(reading: CostAtRate, rate: number): C
|
|
|
70
70
|
[COST_FIELDS.classifier.coreFraction]: fraction,
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The key of one point of a cost grid: the rate and the quantum it was measured at. One place spells it,
|
|
76
|
+
* so the reader that fills a grid and the gate that looks a point up cannot disagree.
|
|
77
|
+
*/
|
|
78
|
+
export function costPointKey(rate: number, quantum: number): string {
|
|
79
|
+
return `${rate}/${quantum}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The percentile the classifier's `nsPerSampleP95` is. A figure taken at another percentile is not that field. */
|
|
83
|
+
export const CLASSIFIER_PERCENTILE = 95;
|
|
84
|
+
|
|
85
|
+
/** One point of a grid as it is measured: the figures {@link gridCostReadings} turns into the classifier's. */
|
|
86
|
+
export interface CostGridPoint {
|
|
87
|
+
readonly rate: number;
|
|
88
|
+
readonly quantum: number;
|
|
89
|
+
readonly outcome: string;
|
|
90
|
+
readonly cost?: { readonly median: number; readonly percentile: { readonly p: number; readonly value: number } };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The classifier's cost readings of a grid, by {@link costPointKey}. A point that did not end `probe.ok`, has no
|
|
95
|
+
* cost, or whose percentile is not {@link CLASSIFIER_PERCENTILE} has no reading: the gate then reads the pair as
|
|
96
|
+
* unmeasured, which is the truth, where a figure under the wrong name would be judged as if it were the P95.
|
|
97
|
+
*/
|
|
98
|
+
export function gridCostReadings(points: readonly CostGridPoint[], okOutcome: string): Record<string, ClassifierCostReading> {
|
|
99
|
+
const out: Record<string, ClassifierCostReading> = {};
|
|
100
|
+
for (const p of points) {
|
|
101
|
+
if (p.outcome !== okOutcome || p.cost === undefined || p.cost.percentile.p !== CLASSIFIER_PERCENTILE) continue;
|
|
102
|
+
const fraction = coreFraction(p.cost.percentile.value, p.rate);
|
|
103
|
+
if (fraction === undefined) continue;
|
|
104
|
+
out[costPointKey(p.rate, p.quantum)] = {
|
|
105
|
+
[COST_FIELDS.classifier.median]: p.cost.median,
|
|
106
|
+
[COST_FIELDS.classifier.percentile]: p.cost.percentile.value,
|
|
107
|
+
[COST_FIELDS.classifier.coreFraction]: fraction,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
package/src/hosting-codes.ts
CHANGED
|
@@ -61,6 +61,7 @@ export const HOSTING_CODE = {
|
|
|
61
61
|
clapFormatNotHosted: 'hosting.clap.format-not-hosted',
|
|
62
62
|
clapExtensionsNotCovered: 'hosting.clap.extensions-not-covered',
|
|
63
63
|
outputNonFinite: 'hosting.output.non-finite',
|
|
64
|
+
workerNonconforming: 'hosting.worker.nonconforming',
|
|
64
65
|
// The pin (spec §11; one-contract §10.3): a host's refusal at add, by the daemon's code.
|
|
65
66
|
integrityUnpinned: 'hosting.integrity.unpinned',
|
|
66
67
|
integrityBinaryMismatch: 'hosting.integrity.binary-mismatch',
|
|
@@ -52,11 +52,19 @@ export interface QualifiedPlugin {
|
|
|
52
52
|
readonly scalingClass?: string;
|
|
53
53
|
readonly paramSweep?: unknown;
|
|
54
54
|
};
|
|
55
|
-
/** Measured cost per rate (Hz as a string key). */
|
|
55
|
+
/** Measured cost: per rate (Hz as a string key), or per rate AND quantum. */
|
|
56
56
|
readonly cpuCost?: {
|
|
57
|
+
/** One figure per rate, at the block the prober ran: the old format. Read only when `perRateQuantum` is absent. */
|
|
57
58
|
readonly perRate?: Readonly<
|
|
58
59
|
Record<string, { readonly coreFractionP95: number; readonly nsPerSampleMedian: number; readonly nsPerSampleP95: number }>
|
|
59
60
|
>;
|
|
61
|
+
/**
|
|
62
|
+
* One figure per rate and quantum, keyed by {@link costPointKey}: a probe output's grid. When this is
|
|
63
|
+
* present it is the only cost there is, so a pair it lacks is unmeasured, never read from `perRate`.
|
|
64
|
+
*/
|
|
65
|
+
readonly perRateQuantum?: Readonly<
|
|
66
|
+
Record<string, { readonly coreFractionP95: number; readonly nsPerSampleMedian: number; readonly nsPerSampleP95: number }>
|
|
67
|
+
>;
|
|
60
68
|
};
|
|
61
69
|
readonly stability?: PluginStabilityMeasurement;
|
|
62
70
|
readonly rtSafety?: PluginRtSafetyMeasurement;
|
|
@@ -306,7 +314,7 @@ export type PluginLv2Features = Omit<FeaturesMeasurement, "unmeasuredReason" | "
|
|
|
306
314
|
|
|
307
315
|
export { DEFAULT_HOSTING_POLICY, type HostingPolicy } from "./qualify-declarations.js";
|
|
308
316
|
import { DEFAULT_HOSTING_POLICY, type HostingPolicy } from "./qualify-declarations.js";
|
|
309
|
-
import { costFieldsMissing } from "./cost-fields.js";
|
|
317
|
+
import { costFieldsMissing, costPointKey } from "./cost-fields.js";
|
|
310
318
|
|
|
311
319
|
/** One declared rate's latency reading from the CLAP qualifier (CLAP spec §3, latency). */
|
|
312
320
|
export interface ClapLatencyAtRate {
|
|
@@ -700,7 +708,13 @@ export function hostingCostReasons(
|
|
|
700
708
|
options: HostingClassifyOptions,
|
|
701
709
|
policy: HostingPolicy,
|
|
702
710
|
): readonly HostingReason[] {
|
|
703
|
-
const
|
|
711
|
+
const grid = descriptor.cpuCost?.perRateQuantum;
|
|
712
|
+
const perRate =
|
|
713
|
+
grid === undefined
|
|
714
|
+
? descriptor.cpuCost?.perRate?.[String(options.rate)]
|
|
715
|
+
: options.quantum === undefined
|
|
716
|
+
? undefined
|
|
717
|
+
: grid[costPointKey(options.rate, options.quantum)];
|
|
704
718
|
if (perRate === undefined) {
|
|
705
719
|
return [
|
|
706
720
|
reason("cost", "objective", "unknown", HOSTING_CODE.costUnmeasured, {
|
|
@@ -761,6 +775,10 @@ export function hostingLatencyReasons(
|
|
|
761
775
|
// one the benchmark declared unmeasurable, has not been judged and does not pass by
|
|
762
776
|
// default. The oracle's first fixture omitted latency and still expected `suitable`,
|
|
763
777
|
// which is how this branch was missing — a self-consistent suite proving nothing.
|
|
778
|
+
// A plugin whose worker interface was refused is not unmeasured by accident: it is not hostable.
|
|
779
|
+
if (typeof latency?.unmeasurable === "string" && latency.unmeasurable.includes(HOSTING_CODE.workerNonconforming)) {
|
|
780
|
+
return [reason("latency", "objective", "unsuitable", HOSTING_CODE.workerNonconforming)];
|
|
781
|
+
}
|
|
764
782
|
if (latency === undefined || latency.unmeasurable !== undefined) {
|
|
765
783
|
return [reason("latency", "objective", "unknown", HOSTING_CODE.latencyUnmeasured)];
|
|
766
784
|
}
|
package/src/index.ts
CHANGED
|
@@ -20,3 +20,8 @@ export * from "./qualify-cli.js";
|
|
|
20
20
|
export * from "./pins-file.js";
|
|
21
21
|
export * from "./measure-host.js";
|
|
22
22
|
export * from "./cost-fields.js";
|
|
23
|
+
export * from "./probe-output.js";
|
|
24
|
+
export * from "./probe-judge.js";
|
|
25
|
+
export * from "./qualification-record.js";
|
|
26
|
+
export * from "./probe-runner.js";
|
|
27
|
+
export * from "./judge-cli.js";
|