@openmixer/plugin-qualify 0.18.0 → 0.18.1
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 +84 -2
- package/bin/plugin-qualify-sync.mjs +3 -1
- package/dist/distributed-records.d.ts +4 -2
- package/dist/distributed-records.js +1 -1
- package/dist/distributed-records.js.map +1 -1
- package/dist/host-admission.d.ts +2 -0
- package/dist/host-admission.js +12 -3
- package/dist/host-admission.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/probe-judge.d.ts +11 -0
- package/dist/probe-judge.js +4 -1
- package/dist/probe-judge.js.map +1 -1
- package/dist/probe-runner.js +3 -2
- package/dist/probe-runner.js.map +1 -1
- package/dist/qualification-record.d.ts +90 -8
- package/dist/qualification-record.js +18 -13
- package/dist/qualification-record.js.map +1 -1
- package/dist/qualify-adapter-pass.d.ts +67 -0
- package/dist/qualify-adapter-pass.js +255 -0
- package/dist/qualify-adapter-pass.js.map +1 -0
- package/dist/qualify-declarations.d.ts +25 -0
- package/dist/qualify-declarations.js +17 -1
- package/dist/qualify-declarations.js.map +1 -1
- package/dist/qualify-rejudge.d.ts +81 -0
- package/dist/qualify-rejudge.js +421 -0
- package/dist/qualify-rejudge.js.map +1 -0
- package/dist/qualify-sync.d.ts +72 -1
- package/dist/qualify-sync.js +176 -17
- package/dist/qualify-sync.js.map +1 -1
- package/fixtures/records-0.16.1/http___example.org_raffo@unversioned-2060c09ff626.default.probe.json +46 -0
- package/fixtures/records-0.16.1/http___example.org_raffo@unversioned-2060c09ff626.default.qualification.json +185 -0
- package/fixtures/records-0.16.1/http___lv2plug.in_plugins_eg-amp@unversioned-f046a60efd45.default.probe.json +51 -0
- package/fixtures/records-0.16.1/http___lv2plug.in_plugins_eg-amp@unversioned-f046a60efd45.default.qualification.json +232 -0
- package/fixtures/records-0.16.1/https___github.com_lucianodato_noise-repellent@unversioned-c3b5e21dd9a7.default.probe.json +51 -0
- package/fixtures/records-0.16.1/https___github.com_lucianodato_noise-repellent@unversioned-c3b5e21dd9a7.default.qualification.json +245 -0
- package/fixtures/records-0.16.1/in.lsp-plug.impulse_responses_mono@unversioned-d5235d2f5f53.custom.probe.json +52 -0
- package/fixtures/records-0.16.1/in.lsp-plug.impulse_responses_mono@unversioned-d5235d2f5f53.custom.qualification.json +145 -0
- package/fixtures/records-0.16.1/in.lsp-plug.trigger_midi_mono@unversioned-d5235d2f5f53.custom.probe.json +52 -0
- package/fixtures/records-0.16.1/in.lsp-plug.trigger_midi_mono@unversioned-d5235d2f5f53.custom.qualification.json +155 -0
- package/package.json +1 -1
- package/src/distributed-records.ts +1 -1
- package/src/host-admission.ts +13 -3
- package/src/index.ts +2 -0
- package/src/probe-judge.ts +8 -1
- package/src/probe-runner.ts +2 -2
- package/src/qualification-record.ts +100 -15
- package/src/qualify-adapter-pass.ts +278 -0
- package/src/qualify-declarations.ts +19 -1
- package/src/qualify-rejudge.ts +452 -0
- package/src/qualify-sync.ts +192 -17
- package/tools/lv2_resolve.py +49 -0
- package/tools/mod-host-admit.mjs +21 -5
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "plugin-qualify.qualification-record/1",
|
|
3
|
+
"plugin": {
|
|
4
|
+
"format": "clap",
|
|
5
|
+
"id": "in.lsp-plug.impulse_responses_mono"
|
|
6
|
+
},
|
|
7
|
+
"binary": {
|
|
8
|
+
"path": "/usr/lib/clap/lsp-plugins.clap",
|
|
9
|
+
"sha256": "d5235d2f5f53eb3641dc9225ff5a912b5eae718cd0abbecd46350dba666f19a2",
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "lsp-plugins.clap",
|
|
13
|
+
"sha256": "d5235d2f5f53eb3641dc9225ff5a912b5eae718cd0abbecd46350dba666f19a2"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"layout": {
|
|
17
|
+
"scheme": "omx-layout/1",
|
|
18
|
+
"sha256": "86f71f835f0db0150b6b219b738b97ae0d9d423d2305e29ebdb172b8e20a6381"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"verdicts": [
|
|
22
|
+
{
|
|
23
|
+
"host": "omx-clap-host",
|
|
24
|
+
"measured": true,
|
|
25
|
+
"rating": "unsuitable",
|
|
26
|
+
"qualified": false,
|
|
27
|
+
"hosting": "isolated",
|
|
28
|
+
"arm": "declaration",
|
|
29
|
+
"deciding": "hosting.rt-safety.violations-in-run",
|
|
30
|
+
"reasons": [
|
|
31
|
+
{
|
|
32
|
+
"dimension": "rtSafety",
|
|
33
|
+
"rating": "unsuitable",
|
|
34
|
+
"code": "hosting.rt-safety.violations-in-run",
|
|
35
|
+
"params": {
|
|
36
|
+
"allocations": 0,
|
|
37
|
+
"syscalls": 1,
|
|
38
|
+
"locks": 0
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"tiers": {
|
|
45
|
+
"isolated": {
|
|
46
|
+
"host": "omx-clap-host",
|
|
47
|
+
"measured": true,
|
|
48
|
+
"qualified": false,
|
|
49
|
+
"rating": "unsuitable",
|
|
50
|
+
"deciding": "hosting.rt-safety.violations-in-run"
|
|
51
|
+
},
|
|
52
|
+
"offeredOn": [],
|
|
53
|
+
"dimmed": "omx-clap-host: hosting.rt-safety.violations-in-run"
|
|
54
|
+
},
|
|
55
|
+
"cost": [
|
|
56
|
+
{
|
|
57
|
+
"host": "omx-clap-host",
|
|
58
|
+
"rate": 48000,
|
|
59
|
+
"quantum": 128,
|
|
60
|
+
"reasons": [
|
|
61
|
+
{
|
|
62
|
+
"dimension": "cost",
|
|
63
|
+
"rating": "unknown",
|
|
64
|
+
"code": "hosting.cost.unmeasured-at-rate-and-quantum",
|
|
65
|
+
"params": {
|
|
66
|
+
"rate": 48000,
|
|
67
|
+
"quantum": 128
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"measurements": {
|
|
74
|
+
"grid": [
|
|
75
|
+
{
|
|
76
|
+
"rate": 48000,
|
|
77
|
+
"quantum": 512,
|
|
78
|
+
"outcome": "probe.ok",
|
|
79
|
+
"latency": {
|
|
80
|
+
"declaredFrames": 0,
|
|
81
|
+
"measuredFrames": 0
|
|
82
|
+
},
|
|
83
|
+
"cost": {
|
|
84
|
+
"median": 213.506,
|
|
85
|
+
"percentile": {
|
|
86
|
+
"p": 95,
|
|
87
|
+
"value": 230.143
|
|
88
|
+
},
|
|
89
|
+
"blocks": 512,
|
|
90
|
+
"warmupBlocks": 64
|
|
91
|
+
},
|
|
92
|
+
"rtSafety": {
|
|
93
|
+
"interposer": true,
|
|
94
|
+
"swept": true,
|
|
95
|
+
"sweepApplicable": true,
|
|
96
|
+
"blocks": 512,
|
|
97
|
+
"allocations": 0,
|
|
98
|
+
"locks": 0,
|
|
99
|
+
"syscalls": 1,
|
|
100
|
+
"threadViolations": 0,
|
|
101
|
+
"processErrors": 0,
|
|
102
|
+
"events": [
|
|
103
|
+
"nanosleep"
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
"noteSweep": {
|
|
107
|
+
"applicable": false
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
"notMeasured": [
|
|
113
|
+
{
|
|
114
|
+
"dimension": "stability",
|
|
115
|
+
"code": "hosting.stability.unsoaked"
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"tool": {
|
|
119
|
+
"runner": "plugin-hostd-probe 0.13.1",
|
|
120
|
+
"child": "clap-qualify",
|
|
121
|
+
"interposer": "rt_interpose.so"
|
|
122
|
+
},
|
|
123
|
+
"measuredAt": "2026-10-28T20:56:58+00:00",
|
|
124
|
+
"probe": {
|
|
125
|
+
"file": "in.lsp-plug.impulse_responses_mono@unversioned-d5235d2f5f53.custom.probe.json",
|
|
126
|
+
"sha256": "bc818dfeff46238de3b5558ab8d54aa5677a18f6e3cad79c67bc117313d0a79d",
|
|
127
|
+
"bytes": 3894,
|
|
128
|
+
"protocol": {
|
|
129
|
+
"name": "plugin-hostd.probe-protocol",
|
|
130
|
+
"version": 1
|
|
131
|
+
},
|
|
132
|
+
"reason": "on-request",
|
|
133
|
+
"outcome": "probe.ok",
|
|
134
|
+
"complete": true,
|
|
135
|
+
"machineClass": {
|
|
136
|
+
"arch": "aarch64",
|
|
137
|
+
"family": "arm",
|
|
138
|
+
"coreBand": "3-4",
|
|
139
|
+
"clockBand": "1.5-2.5GHz",
|
|
140
|
+
"preemptRt": false,
|
|
141
|
+
"cstatesLimited": false
|
|
142
|
+
},
|
|
143
|
+
"meetsProtocol": false
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": "plugin-hostd.probe-output",
|
|
3
|
+
"formatVersion": "1.1",
|
|
4
|
+
"run": {
|
|
5
|
+
"id": "20261028T210152Z-321dd-bb8cc3c7",
|
|
6
|
+
"startedAt": "2026-10-28T21:01:52+00:00",
|
|
7
|
+
"elapsedMs": 332,
|
|
8
|
+
"complete": true,
|
|
9
|
+
"outcome": "probe.ok",
|
|
10
|
+
"reason": "on-request",
|
|
11
|
+
"protocol": {"name": "plugin-hostd.probe-protocol", "version": 1},
|
|
12
|
+
"tools": {"runner": "plugin-hostd-probe 0.13.1", "child": "clap-qualify", "interposer": "rt_interpose.so"},
|
|
13
|
+
"via": "runner"
|
|
14
|
+
},
|
|
15
|
+
"plugin": {"format": "clap", "id": "in.lsp-plug.trigger_midi_mono"},
|
|
16
|
+
"binary": {"path": "/usr/lib/clap/lsp-plugins.clap", "sha256": "d5235d2f5f53eb3641dc9225ff5a912b5eae718cd0abbecd46350dba666f19a2", "files": [{"path": "lsp-plugins.clap", "sha256": "d5235d2f5f53eb3641dc9225ff5a912b5eae718cd0abbecd46350dba666f19a2"}], "layout": {"scheme": "omx-layout/1", "sha256": "5a2b073f777379e75dcd931ff9930352d12dcfab9e1fb599d3b1d45d62fe3e1d"}},
|
|
17
|
+
"machine": {
|
|
18
|
+
"class": {"arch": "aarch64", "family": "arm", "coreBand": "3-4", "clockBand": "1.5-2.5GHz", "preemptRt": false, "cstatesLimited": false},
|
|
19
|
+
"profile": {"governor": "powersave", "turbo": false, "smt": false, "preempt": "PREEMPT",
|
|
20
|
+
"cpuidle": [],
|
|
21
|
+
"meetsProtocol": false, "unmet": ["probe.profile.governor", "probe.profile.not-pinned"]},
|
|
22
|
+
"pinnedCpus": "0-3"
|
|
23
|
+
},
|
|
24
|
+
"offer": {"id": "custom", "clapExtensions": ["clap.log", "clap.thread-check", "clap.latency", "clap.params", "clap.audio-ports", "clap.state", "clap.note-ports", "clap.note-name", "clap.voice-info", "clap.event-registry", "clap.timer-support", "clap.posix-fd-support", "clap.thread-pool", "clap.tail", "clap.surround/4", "clap.ambisonic/3", "clap.audio-ports-config", "clap.preset-load/2", "clap.track-info/1", "clap.remote-controls/2"], "realtime": "none"},
|
|
25
|
+
"limits": {"set": "default",
|
|
26
|
+
"applied": [
|
|
27
|
+
{"limit": "wall-clock", "value": 60, "unit": "s", "by": "supervisor"},
|
|
28
|
+
{"limit": "cpu-time", "value": 540, "unit": "s", "by": "rlimit"},
|
|
29
|
+
{"limit": "memory", "value": 4294967296, "unit": "bytes", "by": "rlimit"},
|
|
30
|
+
{"limit": "files", "value": 256, "unit": "count", "by": "rlimit"},
|
|
31
|
+
{"limit": "file-size", "value": 67108864, "unit": "bytes", "by": "rlimit"},
|
|
32
|
+
{"limit": "core-dump", "value": 0, "unit": "bytes", "by": "rlimit"},
|
|
33
|
+
{"limit": "rt-priority", "value": 0, "by": "rlimit"},
|
|
34
|
+
{"limit": "no-new-privs", "by": "prctl"}
|
|
35
|
+
],
|
|
36
|
+
"unavailable": [
|
|
37
|
+
{"limit": "tasks", "code": "probe.limit.not-implemented"}
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"steps": [
|
|
41
|
+
{"step": "hash", "outcome": "probe.ok", "elapsedMs": 0},
|
|
42
|
+
{"step": "lifecycle", "outcome": "probe.ok", "elapsedMs": 332, "exitStatus": 0},
|
|
43
|
+
{"step": "instantiate", "outcome": "probe.ok", "elapsedMs": 0, "detail": "read from the child's line"},
|
|
44
|
+
{"step": "latency", "rate": 48000, "quantum": 512, "outcome": "probe.ok", "elapsedMs": 0, "detail": "read from the child's line"},
|
|
45
|
+
{"step": "rt-safety", "outcome": "probe.ok", "elapsedMs": 0, "detail": "counts are totals over every rate, read from the child's line"},
|
|
46
|
+
{"step": "note-sweep", "outcome": "probe.ok", "elapsedMs": 0, "detail": "counts are totals over every rate, read from the child's line"}
|
|
47
|
+
],
|
|
48
|
+
"grid": [
|
|
49
|
+
{"rate": 48000, "quantum": 512, "outcome": "probe.ok", "latency": {"declaredFrames": 0, "measuredFrames": 0, "method": "impulse-onset"}, "cost": {"unit": "ns-per-sample", "median": 47.9961, "percentile": {"p": 95, "value": 57.2365}, "min": 31.5742, "max": 114.258, "blocks": 512, "warmupBlocks": 64}, "rtSafety": {"interposer": true, "swept": true, "sweepApplicable": true, "blocks": 512, "allocations": 0, "locks": 0, "syscalls": 0, "threadViolations": 0, "processErrors": 0, "events": []}, "noteSweep": {"applicable": true, "dialect": "midi", "allocations": 0, "locks": 0, "syscalls": 1}}
|
|
50
|
+
],
|
|
51
|
+
"logs": [{"name": "stderr", "path": "/tmp/plugin-qualify-probe-0t8B87/child.stderr.log", "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "bytes": 0}]
|
|
52
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "plugin-qualify.qualification-record/1",
|
|
3
|
+
"plugin": {
|
|
4
|
+
"format": "clap",
|
|
5
|
+
"id": "in.lsp-plug.trigger_midi_mono"
|
|
6
|
+
},
|
|
7
|
+
"binary": {
|
|
8
|
+
"path": "/usr/lib/clap/lsp-plugins.clap",
|
|
9
|
+
"sha256": "d5235d2f5f53eb3641dc9225ff5a912b5eae718cd0abbecd46350dba666f19a2",
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "lsp-plugins.clap",
|
|
13
|
+
"sha256": "d5235d2f5f53eb3641dc9225ff5a912b5eae718cd0abbecd46350dba666f19a2"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"layout": {
|
|
17
|
+
"scheme": "omx-layout/1",
|
|
18
|
+
"sha256": "5a2b073f777379e75dcd931ff9930352d12dcfab9e1fb599d3b1d45d62fe3e1d"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"verdicts": [
|
|
22
|
+
{
|
|
23
|
+
"host": "omx-clap-host",
|
|
24
|
+
"measured": true,
|
|
25
|
+
"rating": "unsuitable",
|
|
26
|
+
"qualified": false,
|
|
27
|
+
"hosting": "isolated",
|
|
28
|
+
"arm": "declaration",
|
|
29
|
+
"deciding": "hosting.rt-safety.violations-under-notes",
|
|
30
|
+
"reasons": [
|
|
31
|
+
{
|
|
32
|
+
"dimension": "rtSafety",
|
|
33
|
+
"rating": "suitable",
|
|
34
|
+
"code": "hosting.rt-safety.clean-under-sweep",
|
|
35
|
+
"params": {
|
|
36
|
+
"blocks": 512
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"dimension": "rtSafety",
|
|
41
|
+
"rating": "unsuitable",
|
|
42
|
+
"code": "hosting.rt-safety.violations-under-notes",
|
|
43
|
+
"params": {
|
|
44
|
+
"allocations": 0,
|
|
45
|
+
"syscalls": 1,
|
|
46
|
+
"locks": 0
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"tiers": {
|
|
53
|
+
"isolated": {
|
|
54
|
+
"host": "omx-clap-host",
|
|
55
|
+
"measured": true,
|
|
56
|
+
"qualified": false,
|
|
57
|
+
"rating": "unsuitable",
|
|
58
|
+
"deciding": "hosting.rt-safety.violations-under-notes"
|
|
59
|
+
},
|
|
60
|
+
"offeredOn": [],
|
|
61
|
+
"dimmed": "omx-clap-host: hosting.rt-safety.violations-under-notes"
|
|
62
|
+
},
|
|
63
|
+
"cost": [
|
|
64
|
+
{
|
|
65
|
+
"host": "omx-clap-host",
|
|
66
|
+
"rate": 48000,
|
|
67
|
+
"quantum": 128,
|
|
68
|
+
"reasons": [
|
|
69
|
+
{
|
|
70
|
+
"dimension": "cost",
|
|
71
|
+
"rating": "unknown",
|
|
72
|
+
"code": "hosting.cost.unmeasured-at-rate-and-quantum",
|
|
73
|
+
"params": {
|
|
74
|
+
"rate": 48000,
|
|
75
|
+
"quantum": 128
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
"measurements": {
|
|
82
|
+
"grid": [
|
|
83
|
+
{
|
|
84
|
+
"rate": 48000,
|
|
85
|
+
"quantum": 512,
|
|
86
|
+
"outcome": "probe.ok",
|
|
87
|
+
"latency": {
|
|
88
|
+
"declaredFrames": 0,
|
|
89
|
+
"measuredFrames": 0
|
|
90
|
+
},
|
|
91
|
+
"cost": {
|
|
92
|
+
"median": 47.9961,
|
|
93
|
+
"percentile": {
|
|
94
|
+
"p": 95,
|
|
95
|
+
"value": 57.2365
|
|
96
|
+
},
|
|
97
|
+
"blocks": 512,
|
|
98
|
+
"warmupBlocks": 64
|
|
99
|
+
},
|
|
100
|
+
"rtSafety": {
|
|
101
|
+
"interposer": true,
|
|
102
|
+
"swept": true,
|
|
103
|
+
"sweepApplicable": true,
|
|
104
|
+
"blocks": 512,
|
|
105
|
+
"allocations": 0,
|
|
106
|
+
"locks": 0,
|
|
107
|
+
"syscalls": 0,
|
|
108
|
+
"threadViolations": 0,
|
|
109
|
+
"processErrors": 0,
|
|
110
|
+
"events": []
|
|
111
|
+
},
|
|
112
|
+
"noteSweep": {
|
|
113
|
+
"applicable": true,
|
|
114
|
+
"dialect": "midi",
|
|
115
|
+
"allocations": 0,
|
|
116
|
+
"locks": 0,
|
|
117
|
+
"syscalls": 1
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
"notMeasured": [
|
|
123
|
+
{
|
|
124
|
+
"dimension": "stability",
|
|
125
|
+
"code": "hosting.stability.unsoaked"
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
"tool": {
|
|
129
|
+
"runner": "plugin-hostd-probe 0.13.1",
|
|
130
|
+
"child": "clap-qualify",
|
|
131
|
+
"interposer": "rt_interpose.so"
|
|
132
|
+
},
|
|
133
|
+
"measuredAt": "2026-10-28T21:01:52+00:00",
|
|
134
|
+
"probe": {
|
|
135
|
+
"file": "in.lsp-plug.trigger_midi_mono@unversioned-d5235d2f5f53.custom.probe.json",
|
|
136
|
+
"sha256": "17986ad33bc5087175fcce9512761cc28fb66d18beb01598ee2e72b5c5302fa6",
|
|
137
|
+
"bytes": 3978,
|
|
138
|
+
"protocol": {
|
|
139
|
+
"name": "plugin-hostd.probe-protocol",
|
|
140
|
+
"version": 1
|
|
141
|
+
},
|
|
142
|
+
"reason": "on-request",
|
|
143
|
+
"outcome": "probe.ok",
|
|
144
|
+
"complete": true,
|
|
145
|
+
"machineClass": {
|
|
146
|
+
"arch": "aarch64",
|
|
147
|
+
"family": "arm",
|
|
148
|
+
"coreBand": "3-4",
|
|
149
|
+
"clockBand": "1.5-2.5GHz",
|
|
150
|
+
"preemptRt": false,
|
|
151
|
+
"cstatesLimited": false
|
|
152
|
+
},
|
|
153
|
+
"meetsProtocol": false
|
|
154
|
+
}
|
|
155
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmixer/plugin-qualify",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"description": "Qualify audio plugins for real-time hosting: scan, offline latency and cost measurement, and a fail-closed rating.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -153,7 +153,7 @@ export function distributedUri(item: SyncItem, r: DistributedRecord): string {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
/** What the sync's own judgment would make of the record's verdict for the host, as `outcomeOf` reads a run. */
|
|
156
|
-
function outcomeOfVerdict(v: RecordVerdict): SyncOutcome {
|
|
156
|
+
export function outcomeOfVerdict(v: Pick<RecordVerdict, "measured" | "rating" | "deciding">): SyncOutcome {
|
|
157
157
|
if (!v.measured) return v.deciding === "probe.crashed" ? "crashed" : v.deciding === "probe.timeout" ? "hung" : "unmeasured";
|
|
158
158
|
if (v.rating === "suitable" || v.rating === "conditional") return "qualified";
|
|
159
159
|
return v.rating === "unsuitable" ? "refused" : "unmeasured";
|
package/src/host-admission.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { spawnSync } from "node:child_process";
|
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
|
|
14
14
|
import type { HostProfile } from "./host-profiles.js";
|
|
15
|
+
import { ADMIT_STEP_MS_ENV } from "./qualify-declarations.js";
|
|
15
16
|
|
|
16
17
|
/** The profile ids whose verdict is measured by a run. */
|
|
17
18
|
export const ADMITTED_HOSTS = ["mod-host", "jalv"] as const;
|
|
@@ -29,13 +30,16 @@ export function admittedHostOf(profile: HostProfile): AdmittedHost | undefined {
|
|
|
29
30
|
*/
|
|
30
31
|
export const ADAPTER_HOST = "omx-clap-host";
|
|
31
32
|
|
|
33
|
+
/** The profiles whose LV2 plugins run under that adapter: omx-clap-host, and the Pi boards it runs on (`host-profiles.ts`). */
|
|
34
|
+
export const ADAPTER_RUN_HOSTS: readonly string[] = [ADAPTER_HOST, "openmixer-pi-4gb", "openmixer-pi-8gb"];
|
|
35
|
+
|
|
32
36
|
/**
|
|
33
37
|
* Whether this run decides the profile's verdict for a plugin of this format. A mod-host or jalv run always does, an
|
|
34
38
|
* unmeasured one making the verdict unknown; the adapter's only when it measured, since the omx-clap-host verdict
|
|
35
39
|
* otherwise stands on the measurement.
|
|
36
40
|
*/
|
|
37
41
|
export function runDecides(profile: HostProfile, format: string, admission: HostAdmission): boolean {
|
|
38
|
-
return admittedHostOf(profile) !== undefined || (profile.id
|
|
42
|
+
return admittedHostOf(profile) !== undefined || (ADAPTER_RUN_HOSTS.includes(profile.id) && format === "lv2" && admission.measured);
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
/** What running the plugin under one host came to. */
|
|
@@ -144,7 +148,10 @@ const MOD_HOST_ADMIT = fileURLToPath(new URL("../tools/mod-host-admit.mjs", impo
|
|
|
144
148
|
/** The runs against the installed hosts. */
|
|
145
149
|
export const SPAWNED_ADMISSIONS: AdmissionRuns = {
|
|
146
150
|
adapter(uri, env) {
|
|
147
|
-
|
|
151
|
+
// two commands at the limit the pass gives each, and the tool's own start and end: it kills the host itself first
|
|
152
|
+
const stepMs = Number(env?.[ADMIT_STEP_MS_ENV] ?? 0);
|
|
153
|
+
const timeout = stepMs > 0 ? 2 * stepMs + 15000 : MOD_HOST_ADMISSION_MS;
|
|
154
|
+
const r = spawnSync(process.execPath, [MOD_HOST_ADMIT, uri, ADAPTER_HOST], { encoding: "utf8", timeout, killSignal: "SIGKILL", ...(env === undefined ? {} : { env: { ...env } }) });
|
|
148
155
|
if (r.error !== undefined || r.status !== 0) {
|
|
149
156
|
throw new Error(`${ADAPTER_HOST} admission of ${uri} gave no answer (${r.error?.message ?? `exit ${String(r.status)}`}): ${(r.stderr ?? "").trim()}`);
|
|
150
157
|
}
|
|
@@ -158,7 +165,10 @@ export const SPAWNED_ADMISSIONS: AdmissionRuns = {
|
|
|
158
165
|
return JSON.parse(r.stdout) as ModHostRun;
|
|
159
166
|
},
|
|
160
167
|
jalv(uri, env) {
|
|
161
|
-
|
|
168
|
+
// SIGKILL at the deadline: jalv hosting some plugins ignores SIGTERM, and one stuck in jack_client_open never reaches
|
|
169
|
+
// the loop that reads the flag TERM sets, so a TERM left spawnSync waiting with it (#187). Alive at the deadline is
|
|
170
|
+
// all the verdict asks, so nothing is lost by not letting it exit cleanly.
|
|
171
|
+
const r = spawnSync("jalv", ["-n", "plugin-qualify", uri], { encoding: "utf8", timeout: JALV_HOSTED_MS, killSignal: "SIGKILL", input: "", ...(env === undefined ? {} : { env: { ...env } }) });
|
|
162
172
|
const code = (r.error as NodeJS.ErrnoException | undefined)?.code;
|
|
163
173
|
if (code === "ENOENT") return { installed: false, stillRunning: false, status: null, signal: null, stderr: "" };
|
|
164
174
|
return { installed: true, stillRunning: code === "ETIMEDOUT", status: r.status, signal: code === "ETIMEDOUT" ? null : r.signal, stderr: r.stderr ?? "" };
|
package/src/index.ts
CHANGED
|
@@ -31,6 +31,8 @@ export * from "./probe-merge.js";
|
|
|
31
31
|
export * from "./judge-cli.js";
|
|
32
32
|
export * from "./whole-file.js";
|
|
33
33
|
export * from "./qualify-sync.js";
|
|
34
|
+
export * from "./qualify-rejudge.js";
|
|
35
|
+
export * from "./qualify-adapter-pass.js";
|
|
34
36
|
export * from "./distributed-records.js";
|
|
35
37
|
export * from "./records-package.js";
|
|
36
38
|
export * from "./host-admission.js";
|
package/src/probe-judge.ts
CHANGED
|
@@ -51,6 +51,11 @@ export interface JudgeFacts {
|
|
|
51
51
|
* judged by it as well as by its declaration; one that has none is judged from its declaration alone.
|
|
52
52
|
*/
|
|
53
53
|
readonly admissions?: Readonly<Record<string, HostAdmission>>;
|
|
54
|
+
/**
|
|
55
|
+
* The run each profile's verdict needs and the figures do not hold, by profile id (`qualify-rejudge.ts`): an `unknown`
|
|
56
|
+
* reason in that verdict, so that a verdict never claims a run nobody made.
|
|
57
|
+
*/
|
|
58
|
+
readonly owed?: Readonly<Record<string, RecordReason>>;
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
/** A sum of one count over the points, or `undefined` when a point lacks it: a missing count is not zero. */
|
|
@@ -244,7 +249,7 @@ function costOf(output: ProbeOutput, profile: HostProfile): HostJudgment["cost"]
|
|
|
244
249
|
* first word; a grid point that failed under a run that says `probe.ok` is a document that contradicts
|
|
245
250
|
* itself, and is read as the failure it records rather than as a pass that skipped the point.
|
|
246
251
|
*/
|
|
247
|
-
function failedOutcome(output: ProbeOutput): string | undefined {
|
|
252
|
+
export function failedOutcome(output: ProbeOutput): string | undefined {
|
|
248
253
|
if (output.run.outcome !== PROBE_OK) return output.run.outcome;
|
|
249
254
|
// a cell that outgrew the memory bound is a recorded outcome of that cell, judged per host below, not the run's end
|
|
250
255
|
return output.grid.find((p) => p.outcome !== PROBE_OK && p.outcome !== PROBE_NOT_ATTEMPTED && !isOutOfMemory(p.outcome))?.outcome;
|
|
@@ -313,6 +318,8 @@ export function judgeProbeOutput(output: ProbeOutput, profiles: readonly HostPro
|
|
|
313
318
|
};
|
|
314
319
|
}
|
|
315
320
|
}
|
|
321
|
+
const owed = facts.owed?.[profile.id];
|
|
322
|
+
if (owed !== undefined) reasons = [owed, ...reasons];
|
|
316
323
|
const rating = worstRating(reasons.map((r) => r.rating));
|
|
317
324
|
const deciding = decidingOf(reasons, rating);
|
|
318
325
|
return {
|
package/src/probe-runner.ts
CHANGED
|
@@ -22,7 +22,7 @@ import type { HostProfile } from "./host-profiles.js";
|
|
|
22
22
|
import { OPENMIXER_CONSOLE_PROFILE, shippedHostProfile } from "./host-profiles.js";
|
|
23
23
|
import { IN_PROCESS_TIER_HOST, ISOLATED_TIER_HOST } from "./qualification-record.js";
|
|
24
24
|
import type { PluginTarget } from "./loader.js";
|
|
25
|
-
import {
|
|
25
|
+
import { ADAPTER_RUN_HOSTS, SPAWNED_ADMISSIONS, admitPlugin, admitUnderAdapter, admittedHostOf, type AdmissionRuns, type HostAdmission } from "./host-admission.js";
|
|
26
26
|
import { judgeProbeOutput, judgmentVerdicts, type Judgment } from "./probe-judge.js";
|
|
27
27
|
import { readProbeOutput, type ProbeOutput } from "./probe-output.js";
|
|
28
28
|
import { budgetCells, gridCells, hostCells, runnerInvocations, type GridCell, type GridSpec, type RunnerInvocation } from "./measurement-grid.js";
|
|
@@ -299,7 +299,7 @@ export function qualifyTargets(
|
|
|
299
299
|
// an LV2 plugin is also run once under plugin-hostd's LV2 adapter, recorded beside the verdicts (#223)
|
|
300
300
|
const adapter = target.format === "lv2" ? admitUnderAdapter(target.uri, admissionRuns, admissionEnv) : undefined;
|
|
301
301
|
// and that run decides its omx-clap-host verdict, the isolated tier
|
|
302
|
-
if (adapter !== undefined) admissions[
|
|
302
|
+
if (adapter !== undefined) for (const host of ADAPTER_RUN_HOSTS) admissions[host] = adapter;
|
|
303
303
|
if (target.format === "lv2") {
|
|
304
304
|
for (const profile of judgedProfiles) {
|
|
305
305
|
const host = admittedHostOf(profile);
|
|
@@ -43,6 +43,28 @@ export interface RecordVerdict {
|
|
|
43
43
|
readonly reasons: readonly RecordReason[];
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* An LV2 plugin's run under the adapter. The top is what the judge reads. `--adapter-only` adds the versions of the hosts
|
|
48
|
+
* it ran under, when, and how each cell went.
|
|
49
|
+
*/
|
|
50
|
+
export interface AdapterBlock {
|
|
51
|
+
readonly host: string;
|
|
52
|
+
readonly measured: boolean;
|
|
53
|
+
readonly admitted: boolean;
|
|
54
|
+
readonly code?: string;
|
|
55
|
+
readonly params: Readonly<Record<string, string | number>>;
|
|
56
|
+
readonly versions?: Readonly<Record<string, string>>;
|
|
57
|
+
readonly measuredAt?: string;
|
|
58
|
+
readonly cells?: readonly {
|
|
59
|
+
readonly rate: number;
|
|
60
|
+
readonly quantum: number;
|
|
61
|
+
readonly measured: boolean;
|
|
62
|
+
readonly admitted: boolean;
|
|
63
|
+
readonly code?: string;
|
|
64
|
+
readonly params: Readonly<Record<string, string | number>>;
|
|
65
|
+
}[];
|
|
66
|
+
}
|
|
67
|
+
|
|
46
68
|
/** One tier of the console as a verdict: the host it is judged under and whether the plugin qualified there. */
|
|
47
69
|
export interface TierVerdict {
|
|
48
70
|
readonly host: string;
|
|
@@ -50,9 +72,15 @@ export interface TierVerdict {
|
|
|
50
72
|
readonly qualified: boolean;
|
|
51
73
|
readonly rating: string;
|
|
52
74
|
readonly deciding?: string;
|
|
75
|
+
/** The isolated tier of a `conditional` plugin: it is offered, and runs alone in a worker of its own, never grouped. */
|
|
76
|
+
readonly runsAlone?: true;
|
|
53
77
|
}
|
|
54
78
|
|
|
55
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* The console's two tiers (what the console's roster offers): in-process where its verdict qualified; isolated where its
|
|
81
|
+
* verdict qualified or is `conditional`, which pins `isolated` and runs alone (`runsAlone`); dimmed with the reason when
|
|
82
|
+
* neither. `qualified` stays `rating === "suitable"`, as the interchange's reader holds it.
|
|
83
|
+
*/
|
|
56
84
|
export interface RecordTiers {
|
|
57
85
|
readonly inProcess?: TierVerdict;
|
|
58
86
|
readonly isolated?: TierVerdict;
|
|
@@ -117,8 +145,11 @@ const word = (key: keyof typeof MEASUREMENT_UNMEASURED_REASONS): string => MEASU
|
|
|
117
145
|
/** The same, for the test. */
|
|
118
146
|
export const memoryOfCellForTest = (p: ProbeGridPoint): Pick<RecordGridPoint, "memory"> => memoryOfCell(p);
|
|
119
147
|
|
|
148
|
+
/** The contract's word for a cell whose run ended with this outcome, `probe-error` for one it has no word of. */
|
|
149
|
+
export const unmeasuredWordOf = (outcome: string): string => OUTCOME_WORD[outcome] ?? word("probeError");
|
|
150
|
+
|
|
120
151
|
/** A cell's footprint with what it comes to; only a footprint with no reason beside it comes to anything a client may add up. */
|
|
121
|
-
function memoryOfCell(p: ProbeGridPoint): Pick<RecordGridPoint, "memory"> {
|
|
152
|
+
export function memoryOfCell(p: ProbeGridPoint): Pick<RecordGridPoint, "memory"> {
|
|
122
153
|
if (p.memory === undefined) return {};
|
|
123
154
|
const first = p.memory.unmeasuredReason === undefined ? firstInstanceKb(p.memory) : undefined;
|
|
124
155
|
const per = p.memory.unmeasuredReason === undefined ? perInstanceKb(p.memory) : undefined;
|
|
@@ -181,6 +212,51 @@ export interface HistoryBlock {
|
|
|
181
212
|
readonly verdicts: readonly ({ readonly host: string } & Adjustment)[];
|
|
182
213
|
}
|
|
183
214
|
|
|
215
|
+
/** One measurement a record lacks, and what it keeps from being decided. */
|
|
216
|
+
export interface OwedMeasurement {
|
|
217
|
+
/** `adapter-run`: the plugin under omx-clap-host's LV2 adapter. `host-run`: under the host named. `qualification`: the whole of it. */
|
|
218
|
+
readonly measurement: "adapter-run" | "host-run" | "qualification";
|
|
219
|
+
readonly host?: string;
|
|
220
|
+
readonly code: string;
|
|
221
|
+
readonly detail?: string;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* The verdicts are not the ones of the run that took the figures: `plugin-qualify-sync --rejudge` derived them later, from
|
|
226
|
+
* those figures, under the rules of `by`. `figures` says who took the figures and when; the verdicts claim nothing more.
|
|
227
|
+
*/
|
|
228
|
+
export interface RejudgedBlock {
|
|
229
|
+
/** The UTC time the verdicts last changed. */
|
|
230
|
+
readonly at: string;
|
|
231
|
+
/** The plugin-qualify whose rules judged them. */
|
|
232
|
+
readonly by: string;
|
|
233
|
+
readonly figures: {
|
|
234
|
+
readonly tool: Readonly<Record<string, string>>;
|
|
235
|
+
readonly measuredAt: string;
|
|
236
|
+
readonly runId: string;
|
|
237
|
+
/** The footprints, when a memory-only pass took them later than the rest. */
|
|
238
|
+
readonly memory?: { readonly tool: Readonly<Record<string, string>>; readonly measuredAt: string };
|
|
239
|
+
/** The adapter run, when an adapter-only pass took it later than the rest. */
|
|
240
|
+
readonly adapter?: { readonly versions: Readonly<Record<string, string>>; readonly measuredAt: string };
|
|
241
|
+
};
|
|
242
|
+
/** What these verdicts could not decide and left `unknown`; absent when nothing is owed. */
|
|
243
|
+
readonly owes?: readonly OwedMeasurement[];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** The memory-only pass that took this record's footprints: every other figure is older and comes from `tool`. */
|
|
247
|
+
export interface MemoryRunBlock {
|
|
248
|
+
readonly tool: Readonly<Record<string, string>>;
|
|
249
|
+
readonly measuredAt: string;
|
|
250
|
+
readonly runId: string;
|
|
251
|
+
readonly cells: readonly {
|
|
252
|
+
readonly rate: number;
|
|
253
|
+
readonly quantum: number;
|
|
254
|
+
/** How the cell's run ended; its figures are in the grid's point of that cell. */
|
|
255
|
+
readonly outcome: string;
|
|
256
|
+
readonly memoryLimit?: { readonly bytes: number; readonly mechanism: string };
|
|
257
|
+
}[];
|
|
258
|
+
}
|
|
259
|
+
|
|
184
260
|
export interface QualificationRecord {
|
|
185
261
|
readonly schema: typeof QUALIFICATION_RECORD_SCHEMA;
|
|
186
262
|
readonly plugin: { readonly format: string; readonly id: string; readonly name?: string };
|
|
@@ -192,7 +268,7 @@ export interface QualificationRecord {
|
|
|
192
268
|
* An LV2 plugin run under plugin-hostd's LV2 adapter (omx-clap-host), as under mod-host: admitted, or refused with
|
|
193
269
|
* its code, or `measured: false` when it was not tried. Beside the verdicts, which it does not change (#223).
|
|
194
270
|
*/
|
|
195
|
-
readonly adapter?:
|
|
271
|
+
readonly adapter?: AdapterBlock;
|
|
196
272
|
readonly tiers: RecordTiers;
|
|
197
273
|
/** The cost reasons at each host's rate and quantum, beside the verdicts; an instrument's with the notes it held. */
|
|
198
274
|
readonly cost: readonly {
|
|
@@ -223,6 +299,10 @@ export interface QualificationRecord {
|
|
|
223
299
|
readonly history?: readonly HistoryBlock[];
|
|
224
300
|
/** Where the record came from when it was not measured on this device: the distributed set's provenance, as the set's package wrote it. */
|
|
225
301
|
readonly provenance?: Readonly<Record<string, unknown>>;
|
|
302
|
+
/** Present when the verdicts were derived later from the figures below, measuring nothing. */
|
|
303
|
+
readonly rejudged?: RejudgedBlock;
|
|
304
|
+
/** Present when a memory-only pass merged footprints into the grid. */
|
|
305
|
+
readonly memoryRun?: MemoryRunBlock;
|
|
226
306
|
/** The probe output this record judged. */
|
|
227
307
|
readonly probe: {
|
|
228
308
|
readonly file: string;
|
|
@@ -264,18 +344,32 @@ function tierOf(judgment: Judgment, host: string | undefined): TierVerdict | und
|
|
|
264
344
|
export function tiersOf(judgment: Judgment): RecordTiers {
|
|
265
345
|
const inProcess = tierOf(judgment, IN_PROCESS_TIER_HOST);
|
|
266
346
|
const isolated = tierOf(judgment, ISOLATED_TIER_HOST[judgment.output.plugin.format]);
|
|
347
|
+
const runsAlone = isolated?.measured === true && isolated.rating === "conditional";
|
|
267
348
|
const offeredOn: ("in-process" | "isolated")[] = [];
|
|
268
349
|
if (inProcess?.qualified === true) offeredOn.push("in-process");
|
|
269
|
-
if (isolated?.qualified === true) offeredOn.push("isolated");
|
|
350
|
+
if (isolated?.qualified === true || runsAlone) offeredOn.push("isolated");
|
|
270
351
|
const why = [inProcess, isolated].flatMap((t) => (t === undefined ? [] : [`${t.host}: ${t.deciding ?? t.rating}`]));
|
|
271
352
|
return {
|
|
272
353
|
...(inProcess === undefined ? {} : { inProcess }),
|
|
273
|
-
...(isolated === undefined ? {} : { isolated }),
|
|
354
|
+
...(isolated === undefined ? {} : { isolated: { ...isolated, ...(runsAlone ? { runsAlone: true as const } : {}) } }),
|
|
274
355
|
offeredOn,
|
|
275
356
|
...(offeredOn.length === 0 ? { dimmed: why.length === 0 ? "no tier was judged" : why.join("; ") } : {}),
|
|
276
357
|
};
|
|
277
358
|
}
|
|
278
359
|
|
|
360
|
+
/** A judgment's verdicts as the record writes them. */
|
|
361
|
+
export const verdictRows = (judgment: Judgment): RecordVerdict[] =>
|
|
362
|
+
judgment.hosts.map((h) => ({
|
|
363
|
+
host: h.verdict.host,
|
|
364
|
+
measured: h.verdict.measured,
|
|
365
|
+
rating: h.verdict.rating,
|
|
366
|
+
qualified: h.verdict.qualified,
|
|
367
|
+
hosting: h.hosting,
|
|
368
|
+
arm: h.arm,
|
|
369
|
+
...(h.verdict.deciding === undefined ? {} : { deciding: h.verdict.deciding }),
|
|
370
|
+
reasons: h.reasons,
|
|
371
|
+
}));
|
|
372
|
+
|
|
279
373
|
export function buildQualificationRecord(
|
|
280
374
|
judgment: Judgment,
|
|
281
375
|
copy: { file: string; sha256: string; bytes: number },
|
|
@@ -287,16 +381,7 @@ export function buildQualificationRecord(
|
|
|
287
381
|
plugin: { format: o.plugin.format, id: o.plugin.id, ...(o.plugin.name === undefined ? {} : { name: o.plugin.name }) },
|
|
288
382
|
...(o.plugin.version === undefined ? {} : { testedVersion: o.plugin.version }),
|
|
289
383
|
binary: o.binary,
|
|
290
|
-
verdicts: judgment
|
|
291
|
-
host: h.verdict.host,
|
|
292
|
-
measured: h.verdict.measured,
|
|
293
|
-
rating: h.verdict.rating,
|
|
294
|
-
qualified: h.verdict.qualified,
|
|
295
|
-
hosting: h.hosting,
|
|
296
|
-
arm: h.arm,
|
|
297
|
-
...(h.verdict.deciding === undefined ? {} : { deciding: h.verdict.deciding }),
|
|
298
|
-
reasons: h.reasons,
|
|
299
|
-
})),
|
|
384
|
+
verdicts: verdictRows(judgment),
|
|
300
385
|
...(judgment.adapter === undefined ? {} : { adapter: judgment.adapter }),
|
|
301
386
|
tiers: tiersOf(judgment),
|
|
302
387
|
cost: judgment.hosts.map((h) => ({ host: h.verdict.host, ...h.cost })),
|