@openmixer/plugin-qualify 0.18.0 → 0.18.2
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 +93 -3
- package/bin/plugin-qualify-sync.mjs +3 -1
- package/dist/clap-qualify.d.ts +3 -1
- package/dist/clap-qualify.js +3 -1
- package/dist/clap-qualify.js.map +1 -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 +21 -0
- package/dist/host-admission.js +33 -3
- package/dist/host-admission.js.map +1 -1
- package/dist/hostd-pin-files.js +3 -1
- package/dist/hostd-pin-files.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/measurement/format.d.ts +10 -0
- package/dist/measurement/format.js +8 -0
- package/dist/measurement/format.js.map +1 -1
- package/dist/measurement/probe-json.js +2 -1
- package/dist/measurement/probe-json.js.map +1 -1
- package/dist/probe-judge.d.ts +11 -0
- package/dist/probe-judge.js +6 -3
- 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 +113 -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 +30 -0
- package/dist/qualify-declarations.js +24 -1
- package/dist/qualify-declarations.js.map +1 -1
- package/dist/qualify-isolated.d.ts +70 -0
- package/dist/qualify-isolated.js +184 -0
- package/dist/qualify-isolated.js.map +1 -0
- package/dist/qualify-rejudge.d.ts +82 -0
- package/dist/qualify-rejudge.js +434 -0
- package/dist/qualify-rejudge.js.map +1 -0
- package/dist/qualify-sync.d.ts +97 -1
- package/dist/qualify-sync.js +276 -24
- 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/clap-qualify.ts +5 -1
- package/src/distributed-records.ts +1 -1
- package/src/host-admission.ts +40 -3
- package/src/hostd-pin-files.ts +3 -1
- package/src/index.ts +2 -0
- package/src/measurement/format.ts +17 -0
- package/src/measurement/probe-json.ts +2 -1
- package/src/probe-judge.ts +10 -3
- package/src/probe-runner.ts +2 -2
- package/src/qualification-record.ts +126 -15
- package/src/qualify-adapter-pass.ts +278 -0
- package/src/qualify-declarations.ts +26 -1
- package/src/qualify-isolated.ts +201 -0
- package/src/qualify-rejudge.ts +466 -0
- package/src/qualify-sync.ts +295 -24
- package/tools/isolated-step.mjs +122 -0
- package/tools/lv2_resolve.py +49 -0
- package/tools/mod-host-admit.mjs +99 -7
|
@@ -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.2",
|
|
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",
|
package/src/clap-qualify.ts
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
import type { QualifyVerdict } from "./qualify-cli.js";
|
|
37
37
|
import { RATING_SEVERITY, worstRating, type SuitabilityRating } from "./rating.js";
|
|
38
38
|
import { halfPopulatedRefusal, locateNativeTools } from "./tool-paths.js";
|
|
39
|
-
import { readIntegrity, readMemoryFootprint, type IntegrityMeasurement, type MemoryFootprint, type VoicesHeld } from "./measurement/format.js";
|
|
39
|
+
import { readIntegrity, readMemoryFootprint, readTimingScheduling, type IntegrityMeasurement, type MemoryFootprint, type TimingScheduling, type VoicesHeld } from "./measurement/format.js";
|
|
40
40
|
import { isVoiceBasis } from "./measurement/vocabulary.js";
|
|
41
41
|
import { coreFraction } from "./cost-fields.js";
|
|
42
42
|
|
|
@@ -77,6 +77,8 @@ export interface ClapCostFigure {
|
|
|
77
77
|
readonly blocks: number;
|
|
78
78
|
/** For an instrument: the notes held while it was timed. Absent for an effect. */
|
|
79
79
|
readonly voices?: VoicesHeld;
|
|
80
|
+
/** What these blocks were timed under. */
|
|
81
|
+
readonly scheduling?: TimingScheduling;
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
/** One rate the cost arm did not time, and why; an instrument's declared capacity is kept when it has one. */
|
|
@@ -267,6 +269,7 @@ function readCostAtRate(v: unknown, where: string, notes: boolean): ClapCostAtRa
|
|
|
267
269
|
}
|
|
268
270
|
const blocks = count(v, "blocks", where);
|
|
269
271
|
if (blocks === 0) return bad(`${where} timed no block`);
|
|
272
|
+
const scheduling = readTimingScheduling(v["scheduling"]);
|
|
270
273
|
return {
|
|
271
274
|
nsPerSampleMedian: figure(v, "nsPerSampleMedian", where),
|
|
272
275
|
nsPerSampleP95: figure(v, "nsPerSampleP95", where),
|
|
@@ -277,6 +280,7 @@ function readCostAtRate(v: unknown, where: string, notes: boolean): ClapCostAtRa
|
|
|
277
280
|
...(voices?.held === undefined || voices.basis === undefined
|
|
278
281
|
? {}
|
|
279
282
|
: { voices: { held: voices.held, basis: voices.basis, ...(voices.capacity === undefined ? {} : { capacity: voices.capacity }) } }),
|
|
283
|
+
...(scheduling === undefined ? {} : { scheduling }),
|
|
280
284
|
};
|
|
281
285
|
}
|
|
282
286
|
|
|
@@ -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. */
|
|
@@ -74,6 +78,28 @@ export interface ModHostRun {
|
|
|
74
78
|
/** False when the host never took a connection (omx-clap-host with no JACK server): no plugin was loaded. */
|
|
75
79
|
readonly started?: boolean;
|
|
76
80
|
readonly steps: readonly ModHostStep[];
|
|
81
|
+
/**
|
|
82
|
+
* An instrument under the adapter, played one held note at its JACK ports: the peak in linear magnitude, or why it could
|
|
83
|
+
* not be read. Absent for a plugin that is no instrument, and for any plugin under mod-host.
|
|
84
|
+
*/
|
|
85
|
+
readonly sound?: { readonly peak?: number; readonly problem?: string };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** At or under this magnitude (-120 dBFS) a held note is taken as digital silence: clap-qualify's own SILENCE. */
|
|
89
|
+
export const SILENT_PEAK = 1e-6;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The codes of an adapter run that took the instrument and could not show it sounding. No record of an instrument that makes
|
|
93
|
+
* no sound reads as qualified; but a sampler with nothing loaded is silent too, so this reads `unknown`, not refused: the
|
|
94
|
+
* plugin stays loadable and its pin has no verdict word (`host-admission.test.ts`).
|
|
95
|
+
*/
|
|
96
|
+
export const ADAPTER_SILENT_CODE = `${ADAPTER_HOST}.silent-under-note`;
|
|
97
|
+
export const ADAPTER_LEVEL_UNMEASURED_CODE = `${ADAPTER_HOST}.level-unmeasured`;
|
|
98
|
+
const UNPROVEN_SOUND: readonly string[] = [ADAPTER_SILENT_CODE, ADAPTER_LEVEL_UNMEASURED_CODE];
|
|
99
|
+
|
|
100
|
+
/** The rating a run that did not admit the plugin gives its host's verdict. */
|
|
101
|
+
export function admissionRating(admission: HostAdmission): "unsuitable" | "unknown" {
|
|
102
|
+
return !admission.measured || UNPROVEN_SOUND.includes(admission.code ?? "") ? "unknown" : "unsuitable";
|
|
77
103
|
}
|
|
78
104
|
|
|
79
105
|
/** The verdict of a run of a host that speaks mod-host's protocol: the first step that did not answer `resp 0` decides. */
|
|
@@ -91,6 +117,11 @@ export function classifyProtocolRun(host: ProtocolHost, run: ModHostRun): HostAd
|
|
|
91
117
|
if (s.resp < 0) return refused(host, `resp-${-s.resp}`, { step: s.cmd, resp: s.resp });
|
|
92
118
|
}
|
|
93
119
|
if (run.steps.length === 0) return refused(host, "no-answer");
|
|
120
|
+
if (run.sound !== undefined) {
|
|
121
|
+
const { peak, problem } = run.sound;
|
|
122
|
+
if (peak === undefined) return { host, measured: true, admitted: false, code: ADAPTER_LEVEL_UNMEASURED_CODE, params: { problem: problem ?? "no figure" } };
|
|
123
|
+
if (!(peak > SILENT_PEAK)) return { host, measured: true, admitted: false, code: ADAPTER_SILENT_CODE, params: { peak } };
|
|
124
|
+
}
|
|
94
125
|
return admitted(host);
|
|
95
126
|
}
|
|
96
127
|
|
|
@@ -144,7 +175,10 @@ const MOD_HOST_ADMIT = fileURLToPath(new URL("../tools/mod-host-admit.mjs", impo
|
|
|
144
175
|
/** The runs against the installed hosts. */
|
|
145
176
|
export const SPAWNED_ADMISSIONS: AdmissionRuns = {
|
|
146
177
|
adapter(uri, env) {
|
|
147
|
-
|
|
178
|
+
// two commands at the limit the pass gives each, and the tool's own start and end: it kills the host itself first
|
|
179
|
+
const stepMs = Number(env?.[ADMIT_STEP_MS_ENV] ?? 0);
|
|
180
|
+
const timeout = stepMs > 0 ? 2 * stepMs + 15000 : MOD_HOST_ADMISSION_MS;
|
|
181
|
+
const r = spawnSync(process.execPath, [MOD_HOST_ADMIT, uri, ADAPTER_HOST], { encoding: "utf8", timeout, killSignal: "SIGKILL", ...(env === undefined ? {} : { env: { ...env } }) });
|
|
148
182
|
if (r.error !== undefined || r.status !== 0) {
|
|
149
183
|
throw new Error(`${ADAPTER_HOST} admission of ${uri} gave no answer (${r.error?.message ?? `exit ${String(r.status)}`}): ${(r.stderr ?? "").trim()}`);
|
|
150
184
|
}
|
|
@@ -158,7 +192,10 @@ export const SPAWNED_ADMISSIONS: AdmissionRuns = {
|
|
|
158
192
|
return JSON.parse(r.stdout) as ModHostRun;
|
|
159
193
|
},
|
|
160
194
|
jalv(uri, env) {
|
|
161
|
-
|
|
195
|
+
// SIGKILL at the deadline: jalv hosting some plugins ignores SIGTERM, and one stuck in jack_client_open never reaches
|
|
196
|
+
// the loop that reads the flag TERM sets, so a TERM left spawnSync waiting with it (#187). Alive at the deadline is
|
|
197
|
+
// all the verdict asks, so nothing is lost by not letting it exit cleanly.
|
|
198
|
+
const r = spawnSync("jalv", ["-n", "plugin-qualify", uri], { encoding: "utf8", timeout: JALV_HOSTED_MS, killSignal: "SIGKILL", input: "", ...(env === undefined ? {} : { env: { ...env } }) });
|
|
162
199
|
const code = (r.error as NodeJS.ErrnoException | undefined)?.code;
|
|
163
200
|
if (code === "ENOENT") return { installed: false, stillRunning: false, status: null, signal: null, stderr: "" };
|
|
164
201
|
return { installed: true, stillRunning: code === "ETIMEDOUT", status: r.status, signal: code === "ETIMEDOUT" ? null : r.signal, stderr: r.stderr ?? "" };
|
package/src/hostd-pin-files.ts
CHANGED
|
@@ -67,7 +67,9 @@ export function hostdPinFiles(
|
|
|
67
67
|
}
|
|
68
68
|
if (root === undefined || names.length === 0) return { ok: false, detail: "plugin-hostd --pin-files printed no root or no file" };
|
|
69
69
|
try {
|
|
70
|
-
|
|
70
|
+
// a name is the word of a pin, percent-encoded when the file's name needs it (Surge XT's libSurge%20XT.so): the pin
|
|
71
|
+
// keeps it as written, the file is the name it decodes to, as the daemon opens it
|
|
72
|
+
const files = names.map((path) => ({ path, sha256: createHash("sha256").update(read(join(root!, decodeURIComponent(path)))).digest("hex") }));
|
|
71
73
|
return { ok: true, root, files: files.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)) };
|
|
72
74
|
} catch (e) {
|
|
73
75
|
return { ok: false, detail: `a file plugin-hostd names cannot be read: ${e instanceof Error ? e.message : String(e)}` };
|
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";
|
|
@@ -309,6 +309,20 @@ export interface MemoryFootprint {
|
|
|
309
309
|
readonly unmeasuredDetail?: string;
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
/** The scheduling a cost was timed under, read on the thread that ran the clock. */
|
|
313
|
+
export interface TimingScheduling {
|
|
314
|
+
/** One of plugin-hostd's probe policy names: `other`, `fifo`... */
|
|
315
|
+
readonly policy: string;
|
|
316
|
+
readonly priority: number;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** The scheduling a measurer printed, or `undefined` unless both its fields are there. */
|
|
320
|
+
export function readTimingScheduling(value: unknown): TimingScheduling | undefined {
|
|
321
|
+
if (!isRecord(value) || typeof value.policy !== "string" || value.policy === "") return undefined;
|
|
322
|
+
const priority = finiteNumber(value.priority);
|
|
323
|
+
return priority === undefined ? undefined : { policy: value.policy, priority };
|
|
324
|
+
}
|
|
325
|
+
|
|
312
326
|
/** One rate's cost reading. Per SAMPLE, so it is independent of the block size. */
|
|
313
327
|
export interface CostAtRate {
|
|
314
328
|
readonly nsPerSampleMedian: number;
|
|
@@ -327,6 +341,8 @@ export interface CostAtRate {
|
|
|
327
341
|
readonly silentOutput?: boolean;
|
|
328
342
|
/** 1.4: for an instrument, the notes held while it was timed. Absent for an effect. */
|
|
329
343
|
readonly voices?: VoicesHeld;
|
|
344
|
+
/** What these blocks were timed under; a figure from before the measurers printed it has none. */
|
|
345
|
+
readonly scheduling?: TimingScheduling;
|
|
330
346
|
/** 1.6: this instance's memory footprint at the same rate. */
|
|
331
347
|
readonly memory?: MemoryFootprint;
|
|
332
348
|
}
|
|
@@ -964,6 +980,7 @@ function readCostAtRate(value: unknown): CostAtRate | undefined {
|
|
|
964
980
|
...optional("warmupNsPerSampleMedian", finiteNumber(value.warmupNsPerSampleMedian)),
|
|
965
981
|
...optional("silentOutput", booleanValue(value.silentOutput)),
|
|
966
982
|
...optional("voices", readVoicesHeld(value.voices)),
|
|
983
|
+
...optional("scheduling", readTimingScheduling(value.scheduling)),
|
|
967
984
|
...optional("memory", readMemoryFootprint(value.memory)),
|
|
968
985
|
};
|
|
969
986
|
}
|
|
@@ -35,7 +35,7 @@ import type {
|
|
|
35
35
|
PluginTopology,
|
|
36
36
|
} from "./format.js";
|
|
37
37
|
import { booleanValue, finiteNumber, isRecord, nonEmptyString, optional, positiveNumber } from "./guards.js";
|
|
38
|
-
import { readVoicesHeld } from "./format.js";
|
|
38
|
+
import { readTimingScheduling, readVoicesHeld } from "./format.js";
|
|
39
39
|
import type { MeasurementDimension } from "./vocabulary.js";
|
|
40
40
|
|
|
41
41
|
/** One free-text prober failure, and the vocabulary term it means. */
|
|
@@ -201,6 +201,7 @@ function readCost(value: unknown): CostMeasurement | undefined {
|
|
|
201
201
|
...optional("warmupNsPerSampleMedian", finiteNumber(reading.warmupNsPerSampleMedian)),
|
|
202
202
|
...optional("silentOutput", booleanValue(reading.silentOutput)),
|
|
203
203
|
...optional("voices", readVoicesHeld(reading.voices)),
|
|
204
|
+
...optional("scheduling", readTimingScheduling(reading.scheduling)),
|
|
204
205
|
};
|
|
205
206
|
}
|
|
206
207
|
}
|
package/src/probe-judge.ts
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
type HostingReason,
|
|
35
35
|
} from "./hosting-suitability.js";
|
|
36
36
|
import type { HostProfile } from "./host-profiles.js";
|
|
37
|
-
import { runDecides, type HostAdmission } from "./host-admission.js";
|
|
37
|
+
import { admissionRating, runDecides, type HostAdmission } from "./host-admission.js";
|
|
38
38
|
import { PROBE_NOT_ATTEMPTED, PROBE_OK, type ProbeGridPoint, type ProbeOutput } from "./probe-output.js";
|
|
39
39
|
import type { QualifyVerdict } from "./qualify-cli.js";
|
|
40
40
|
import { RATING_SEVERITY, worstRating, type SuitabilityRating } from "./rating.js";
|
|
@@ -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;
|
|
@@ -303,7 +308,7 @@ export function judgeProbeOutput(output: ProbeOutput, profiles: readonly HostPro
|
|
|
303
308
|
if (!admission.measured || !admission.admitted) {
|
|
304
309
|
// the host could not be run, or the plugin did not survive it: the run decides, whatever the declaration says
|
|
305
310
|
const code = admission.code ?? `${admission.host}.not-admitted`;
|
|
306
|
-
const reason: RecordReason = { dimension: "hostAdmission", rating: admission
|
|
311
|
+
const reason: RecordReason = { dimension: "hostAdmission", rating: admissionRating(admission), code, params: admission.params };
|
|
307
312
|
return {
|
|
308
313
|
verdict: { uri, host: profile.id, measured: admission.measured, rating: reason.rating, qualified: false, deciding: code },
|
|
309
314
|
hosting: "isolated",
|
|
@@ -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);
|