@openmixer/plugin-qualify 0.16.1 → 0.17.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 +153 -6
- package/bin/plugin-qualify-records-package.mjs +31 -0
- package/bin/plugin-qualify-sync.mjs +6 -1
- package/bin/plugin-qualify.mjs +12 -1
- package/dist/distributed-records.d.ts +88 -0
- package/dist/distributed-records.js +210 -0
- package/dist/distributed-records.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/measurement-grid.d.ts +49 -0
- package/dist/measurement-grid.js +76 -0
- package/dist/measurement-grid.js.map +1 -0
- package/dist/memory-bound.d.ts +23 -0
- package/dist/memory-bound.js +46 -0
- package/dist/memory-bound.js.map +1 -0
- package/dist/probe-judge.js +22 -1
- package/dist/probe-judge.js.map +1 -1
- package/dist/probe-merge.d.ts +30 -0
- package/dist/probe-merge.js +80 -0
- package/dist/probe-merge.js.map +1 -0
- package/dist/probe-output.d.ts +8 -0
- package/dist/probe-output.js +7 -0
- package/dist/probe-output.js.map +1 -1
- package/dist/probe-runner.d.ts +26 -2
- package/dist/probe-runner.js +38 -10
- package/dist/probe-runner.js.map +1 -1
- package/dist/qualification-record.d.ts +19 -1
- package/dist/qualification-record.js +30 -1
- package/dist/qualification-record.js.map +1 -1
- package/dist/qualify-cli.d.ts +9 -0
- package/dist/qualify-cli.js +30 -1
- package/dist/qualify-cli.js.map +1 -1
- package/dist/qualify-declarations.d.ts +15 -0
- package/dist/qualify-declarations.js +15 -0
- package/dist/qualify-declarations.js.map +1 -1
- package/dist/qualify-options.js +11 -1
- package/dist/qualify-options.js.map +1 -1
- package/dist/qualify-sync.d.ts +18 -1
- package/dist/qualify-sync.js +142 -12
- package/dist/qualify-sync.js.map +1 -1
- package/dist/records-package.d.ts +63 -0
- package/dist/records-package.js +300 -0
- package/dist/records-package.js.map +1 -0
- package/package.json +3 -2
- package/src/distributed-records.ts +251 -0
- package/src/index.ts +5 -0
- package/src/measurement-grid.ts +97 -0
- package/src/memory-bound.ts +55 -0
- package/src/probe-judge.ts +25 -1
- package/src/probe-merge.ts +89 -0
- package/src/probe-output.ts +13 -0
- package/src/probe-runner.ts +75 -13
- package/src/qualification-record.ts +44 -2
- package/src/qualify-cli.ts +32 -1
- package/src/qualify-declarations.ts +17 -0
- package/src/qualify-options.ts +11 -1
- package/src/qualify-sync.ts +147 -11
- package/src/records-package.ts +333 -0
- package/tools/{ai-trace-commit-check.sh → commit-message-check.sh} +16 -16
- package/tools/{ai-trace-patterns.json → commit-message-patterns.json} +1 -1
package/README.md
CHANGED
|
@@ -69,6 +69,81 @@ A named LV2 plugin is measured the same way as a CLAP one: through the probe run
|
|
|
69
69
|
output it writes (`plugin-qualify judge` below). Exit code: `0` every plugin qualified, `1` some did not, `2`
|
|
70
70
|
something could not be measured.
|
|
71
71
|
|
|
72
|
+
## The measurement grid
|
|
73
|
+
|
|
74
|
+
A plugin is measured in cells, each one sample rate and one quantum (block size in frames). The verdict a host
|
|
75
|
+
gets reads the cell of that host's rate and quantum, so the grid has to hold it.
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
plugin-qualify --all --out ./q # the hosts' grid (the default, also --grid-hosts)
|
|
79
|
+
plugin-qualify --all --grid full --out ./q # every --rates rate at every --quanta quantum
|
|
80
|
+
plugin-qualify --grid 48000/128,96000/1024 urn:my:fx # exactly these cells
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- `--grid hosts` (the default; `--grid-hosts` says the same): the rate and quantum of every host the run judges
|
|
84
|
+
(each `--host-profile`, and the console's two tiers, `openmixer-console` and the format's isolated host), then
|
|
85
|
+
quantum 512 at every `--rates` rate, so that a record stays comparable with the ones written before the grid.
|
|
86
|
+
With the four shipped hosts that is seven cells: 96000/1024 (`openmixer-console`), 48000/256 (`jalv`),
|
|
87
|
+
48000/128 (`mod-host`, `omx-clap-host`), and 512 at 44100, 48000, 96000 and 192000.
|
|
88
|
+
- `--grid full`: every `--rates` rate at every `--quanta` quantum, 16 cells with the defaults
|
|
89
|
+
(44100, 48000, 96000, 192000 by 128, 256, 512, 1024).
|
|
90
|
+
- `--grid <rate>/<quantum>,...`: exactly the cells named.
|
|
91
|
+
|
|
92
|
+
The cells are taken in that order: the hosts' own cells first, in the order the hosts are named, then the
|
|
93
|
+
512 cells, then the rest by rate and quantum. `--cells-per-plugin-budget N` keeps the first N, so a first pass
|
|
94
|
+
over a large machine can measure the hosts' cells alone (`--cells-per-plugin-budget 3` with the four shipped hosts).
|
|
95
|
+
`--blocks N` shortens the CLAP sweep of each cell from 512 blocks to N; the LV2 pass has a fixed shape (30
|
|
96
|
+
lifecycles and 3 swept runs per cell) and is not shortened by it.
|
|
97
|
+
|
|
98
|
+
An LV2 plugin is measured on the same grid as a CLAP one. plugin-hostd's runner measures an LV2 plugin at one
|
|
99
|
+
rate per run (the highest it is given), which is why records written before the grid hold LV2 figures at 192000
|
|
100
|
+
only; plugin-qualify now runs it once per cell. A CLAP plugin is run once per quantum, at every rate of the grid
|
|
101
|
+
at that quantum. The runs of one plugin and one offer make one probe output: the grid points of every run, the
|
|
102
|
+
steps of every run, and the run's outcome. The record and its byte copy hold that document.
|
|
103
|
+
|
|
104
|
+
A run that ended badly leaves a grid point at each of its cells with the run's outcome in place of figures, so a
|
|
105
|
+
cell is never silently absent. The cost a host is judged by is read at its cell: a measured cell is compared with
|
|
106
|
+
the host's budget; a cell whose run failed reads `unknown` with that run's outcome (`probe.oom`, say); only a
|
|
107
|
+
cell the grid does not hold reads `hosting.cost.unmeasured-at-rate-and-quantum`.
|
|
108
|
+
|
|
109
|
+
### The memory bound
|
|
110
|
+
|
|
111
|
+
A sampler loading its library can take all the memory of a small machine, and the kernel then kills it, or
|
|
112
|
+
something else. Every run is bounded: `--memory-limit <bytes>` (`512M`, `2G`, or `off`), by default half of
|
|
113
|
+
the machine's RAM. The bound is the runner's data limit (`--data-bytes`, `RLIMIT_DATA` on the measuring child:
|
|
114
|
+
its heap and private writable mappings), and the interposer the child runs under turns an allocation refused
|
|
115
|
+
under that limit into an immediate exit with status 121. A run that ends that way, or by a `SIGKILL` the
|
|
116
|
+
runner did not send (the kernel's out-of-memory killer), is recorded as `probe.oom`: its cells carry the
|
|
117
|
+
outcome and `memoryLimit { bytes, mechanism }`, the plugin's record is still written, and the other cells are
|
|
118
|
+
judged as usual. A plugin whose every run ended `probe.oom` is `unknown` under every host, deciding `probe.oom`.
|
|
119
|
+
A host whose own cell ended `probe.oom` is `unknown` there as well (`memory`, `probe.oom`): the plugin did not
|
|
120
|
+
run within the bound at that host's rate and quantum.
|
|
121
|
+
|
|
122
|
+
### How long a run takes
|
|
123
|
+
|
|
124
|
+
Times from one deep run on a Raspberry Pi 5 (4 cores, 4 GB), 1823 records (494 CLAP, two offers of each of 247,
|
|
125
|
+
and 1329 LV2), six hours in all with the grid of before (CLAP at four rates in one run at quantum 512, LV2 at
|
|
126
|
+
192000/512 alone):
|
|
127
|
+
|
|
128
|
+
| | runs | mean a run | total |
|
|
129
|
+
| --- | --- | --- | --- |
|
|
130
|
+
| CLAP, one run per offer (4 cells) | 494 | 2.8 s | 0.38 h |
|
|
131
|
+
| LV2, one run (1 cell) | 1329 | 10.5 s | 3.86 h |
|
|
132
|
+
| host admissions (jalv, mod-host) and the scan | | | about 1.8 h |
|
|
133
|
+
|
|
134
|
+
A CLAP plugin takes one run per quantum of the grid, an LV2 plugin one run per cell; the admissions do not
|
|
135
|
+
grow with the grid. On that machine, then, about:
|
|
136
|
+
|
|
137
|
+
| grid | CLAP runs a plugin | LV2 runs a plugin | expected |
|
|
138
|
+
| --- | --- | --- | --- |
|
|
139
|
+
| hosts (7 cells) | 4 | 7 | 1.5 h + 27 h + 1.8 h, about 30 h |
|
|
140
|
+
| full (16 cells) | 4 | 16 | 1.5 h + 62 h + 1.8 h, about 65 h |
|
|
141
|
+
| hosts, `--cells-per-plugin-budget 3` | 3 | 3 | 1.1 h + 11.6 h + 1.8 h, about 15 h |
|
|
142
|
+
|
|
143
|
+
These are ceilings: the LV2 time a run was taken at 192000, the dearest rate. `--blocks 128` takes a quarter of
|
|
144
|
+
the CLAP sweep a cell. A first pass with `--cells-per-plugin-budget 3`, then `--grid full` over the plugins worth
|
|
145
|
+
it, keeps a small machine's night bounded.
|
|
146
|
+
|
|
72
147
|
## CLAP plugins: the RT-safety verdict
|
|
73
148
|
|
|
74
149
|
A `.clap` file, or a directory holding one, is scanned by `omx-clap-scan` (from omx-clap-core; set
|
|
@@ -95,7 +170,7 @@ are measured by `tools/lv2-measure.mjs` (below); this entry point does not drive
|
|
|
95
170
|
system lv2 directories) rather than qualify only the CLAP side. A directory on the CLAP path that cannot
|
|
96
171
|
be read (permissions, a symlink loop) also refuses with exit 2, naming it.
|
|
97
172
|
|
|
98
|
-
The prober runs each plugin headless at every
|
|
173
|
+
The prober runs each plugin headless at every cell of the grid (see "The measurement grid"), under an interposer that counts the
|
|
99
174
|
allocations, locks and syscalls its `process()` makes while every parameter is swept, and prints
|
|
100
175
|
the file's SHA-256 beside what it measured. The verdict is the real-time-safety one: **suitable**
|
|
101
176
|
only on a clean count, **unsuitable** on any allocation, lock, syscall, main-thread host call from
|
|
@@ -159,6 +234,13 @@ stays silent, an instrument with no count to hold) is left out of the grid point
|
|
|
159
234
|
`measurements.unmeasured` with its hosting code (`hosting.latency.unmeasured`,
|
|
160
235
|
`hosting.cost.unmeasured-at-rate-and-quantum`).
|
|
161
236
|
|
|
237
|
+
Every cell of a record carries `latency`: the two figures when the run took them, else
|
|
238
|
+
`{ unmeasuredReason, code }`, the contract's word for why and the hosting code a reader classifies by. A latency
|
|
239
|
+
step that did not end well gives the child's reason (`silent-output`, say); a cell whose run failed, its outcome's
|
|
240
|
+
word (`probe-crashed`, or `probe-error` for `probe.oom`); a run that took no latency at all (a runner or a child
|
|
241
|
+
older than the latency arm), `not-attempted`. The declared figure alone, for a plugin whose response could not be
|
|
242
|
+
measured, is not in the probe output today: plugin-hostd's runner writes a cell's latency only with both figures.
|
|
243
|
+
|
|
162
244
|
|
|
163
245
|
The prober also scans: `OMX_CLAP_SCAN=native/build/clap-qualify` makes it the scanner, and its scan
|
|
164
246
|
keeps what a console needs to draw a third-party plugin's controls: an enum parameter's labels (as
|
|
@@ -201,7 +283,8 @@ history outside its grammar is not read and is named on stderr; a history of a v
|
|
|
201
283
|
`tools/lv2-measure.mjs` measures the hosting dimensions (`stability`, `rtSafety`, `features`) through
|
|
202
284
|
the LV2 measurer the package installs, `lv2_measure`: each plugin is loaded through lilv in its own
|
|
203
285
|
process, instantiated and destroyed 30 times, and run under the interposer while every control is
|
|
204
|
-
swept
|
|
286
|
+
swept. `tools/lv2-measure.mjs` measures at the highest `--rates` rate; `plugin-qualify` runs the measurer
|
|
287
|
+
once per cell of its grid (see "The measurement grid"):
|
|
205
288
|
|
|
206
289
|
```
|
|
207
290
|
node tools/lv2-measure.mjs --rates 48000 --dimensions stability,rtSafety,features \
|
|
@@ -418,16 +501,16 @@ The package ships it as a service that runs two minutes after boot and whenever
|
|
|
418
501
|
changes, once the directory has been quiet for a minute. A run finds every `.clap` under the CLAP
|
|
419
502
|
directories and every plugin of every bundle under the LV2 ones, and hashes each one's files as
|
|
420
503
|
plugin-hostd does for a pin. A plugin whose files hash as recorded is skipped. A new or changed one gets the
|
|
421
|
-
quick qualification through the probe runner: its pin, the RT-safety sweep, cost and latency, its
|
|
504
|
+
quick qualification through the probe runner, on the grid `grid` names (see "The measurement grid"): its pin, the RT-safety sweep, cost and latency, its
|
|
422
505
|
qualification record and its descriptor. The soak is not part of it, and every record it writes says so in
|
|
423
506
|
`notMeasured` (`hosting.stability.unsoaked`).
|
|
424
507
|
|
|
425
|
-
Each plugin has `budget_s` seconds
|
|
508
|
+
Each run of the runner (one cell of an LV2 plugin, one quantum of a CLAP one) has `budget_s` seconds, under `memory_limit`. One that crashes or hangs is recorded as `crashed` or `hung`, one that
|
|
426
509
|
fails the sweep is pinned `verdict=refused`, and the run goes on to the next. The pin file and the state are
|
|
427
510
|
written whole after every plugin, so a run cut short keeps what it finished. The last line counts the run:
|
|
428
511
|
|
|
429
512
|
```
|
|
430
|
-
qualified 2, skipped 0, refused 0, crashed 1, hung 1, unmeasured 0, 4.6 s
|
|
513
|
+
qualified 2, skipped 0, refused 0, crashed 1, hung 1, unmeasured 0, distributed 0, 4.6 s
|
|
431
514
|
```
|
|
432
515
|
|
|
433
516
|
A plugin that is no longer found, or whose bytes changed, leaves nothing behind: its pin, its qualification
|
|
@@ -452,17 +535,81 @@ The settings are `/etc/omx/qualify.conf`, one `key value` per line, and each is
|
|
|
452
535
|
| `state_file` | `/var/lib/omx/qualify-sync.json` |
|
|
453
536
|
| `budget_s` | `60` |
|
|
454
537
|
| `host_profile` | `omx-clap-host` |
|
|
538
|
+
| `grid` | `hosts`: the cells of `host_profile` and of the console's two tiers, then 512 at every rate (`full`, or `<rate>/<quantum>,...`) |
|
|
539
|
+
| `cells_per_plugin_budget` | `0`, every cell of the grid |
|
|
540
|
+
| `memory_limit` | half of the machine's RAM (`off` for none) |
|
|
455
541
|
| `hostd_port`, `hostd_feedback_port` | `5555`, the next port |
|
|
542
|
+
| `host_class` | the board's own, from the device tree (`rpi5`); none elsewhere |
|
|
543
|
+
| `distributed_dir` | `/usr/share/omx/qualification` |
|
|
456
544
|
|
|
457
545
|
Those are the service's (`--system`); run by a user, the files go under `$XDG_STATE_HOME/omx`. An LV2
|
|
458
546
|
directory added here belongs in plugin-hostd's `lv2_path` too, or the daemon cannot find the bundle it
|
|
459
|
-
pinned. `--force` qualifies everything again.
|
|
547
|
+
pinned. `--force` qualifies everything again, and `--no-distributed` reads no distributed record.
|
|
460
548
|
|
|
461
549
|
The path unit names the standard folders (`/usr/lib/lv2`, `/usr/lib64/lv2`, `/usr/local/lib/lv2`,
|
|
462
550
|
`/usr/lib/clap`, `/usr/lib64/clap`). The folders the settings file adds (`lv2_path`, `lv2_extra`, `clap_path`,
|
|
463
551
|
`clap_extra`) are written into a drop-in of the unit by a systemd generator at boot and at every
|
|
464
552
|
`systemctl daemon-reload`, so the file is the one place a machine's extra folders are written.
|
|
465
553
|
|
|
554
|
+
### Distributed records
|
|
555
|
+
|
|
556
|
+
A device that ships with a known plugin set need not measure it again at first start. The qualification records
|
|
557
|
+
another device of the same machine class wrote for that set are installed read-only, and the sync takes from them
|
|
558
|
+
every plugin whose files are the bytes that were measured:
|
|
559
|
+
|
|
560
|
+
```
|
|
561
|
+
/usr/share/omx/qualification/<host class>/<set>/ # system (--system)
|
|
562
|
+
$XDG_DATA_HOME/omx/qualification/<host class>/<set>/ # a user run, then each $XDG_DATA_DIRS/omx/qualification
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Each `<set>` directory holds the records (`*.qualification.json`, each with its probe output `*.probe.json`) and a
|
|
566
|
+
`provenance.json` that says where they come from: the set, the host class, the image, the package versions of the
|
|
567
|
+
measuring device, the date and the package that installed them. The host class is a directory name: `host_class`
|
|
568
|
+
in the settings, or, when it is unset, the board's own name read from the device tree (`rpi5` on a Raspberry Pi 5,
|
|
569
|
+
`rpi4` on a Raspberry Pi 4). Where neither gives one, the layer is not read and the run says so.
|
|
570
|
+
|
|
571
|
+
Before a plugin is measured, the sync looks for a distributed record of it: a CLAP record whose binary is the same
|
|
572
|
+
`.clap` path, or an LV2 record of the same plugin uri. The name only finds the record; a record applies when, and
|
|
573
|
+
only when:
|
|
574
|
+
|
|
575
|
+
- every file of its `binary.files` is installed at that path relative to the plugin's root, with that SHA-256,
|
|
576
|
+
computed on the device;
|
|
577
|
+
- its schema is the one this version writes and its probe output is one this version's reader accepts, the copy
|
|
578
|
+
intact (its SHA-256 the one the record names): the same acceptance the sync gives a record of its own, which it
|
|
579
|
+
never measures again for a newer tool;
|
|
580
|
+
- it holds a verdict for the sync's `host_profile`, and that verdict ended `qualified`, `refused`, `crashed` or
|
|
581
|
+
`hung`. One that could not be measured at the source (`unmeasured`) is measured here.
|
|
582
|
+
|
|
583
|
+
A plugin whose distributed records apply is not measured: the records are copied into `qualification_dir` with the
|
|
584
|
+
set's `provenance` object added, its pin is taken from the probe output the record judged, its descriptor is
|
|
585
|
+
written, and the run says
|
|
586
|
+
|
|
587
|
+
```
|
|
588
|
+
distributed: http://distrho.sf.net/plugins/3BandEQ: applied (zynthian-vangelis)
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
A plugin with a distributed record whose files differ is measured as before, and the run says
|
|
592
|
+
`distributed: <uri>: binary differs`; one with no distributed record is measured without a word. The summary counts
|
|
593
|
+
the plugins taken from the layer, `distributed N`, apart from the ones measured and skipped. A later run treats an
|
|
594
|
+
applied plugin like one it measured: skipped while its files are unchanged, measured when they change. `--force`
|
|
595
|
+
measures everything and reads no distributed record; `--no-distributed` keeps the layer out of a run.
|
|
596
|
+
|
|
597
|
+
The package that installs a set is built from a device's `qualification_dir` by `plugin-qualify-records-package`:
|
|
598
|
+
|
|
599
|
+
```
|
|
600
|
+
plugin-qualify-records-package /var/lib/omx/qualification --host-class rpi5 --set zynthian-vangelis \
|
|
601
|
+
--provenance provenance.json --out .
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
It writes `omx-qualification-<host class>-<set>_<version>_all.deb`, the version the build date
|
|
605
|
+
(`2026.10.29`) unless `--version` names one. The package installs the records under
|
|
606
|
+
`/usr/share/omx/qualification/<host class>/<set>/` with `provenance.json`, depends on the plugin-qualify that reads
|
|
607
|
+
the layer, and is `Architecture: all`. `--provenance` names a JSON file (or holds the JSON itself) with what the tool
|
|
608
|
+
cannot know: the image, the package versions and the source revisions of the device. The tool adds the set, the host
|
|
609
|
+
class, the package and the date. A record whose probe output is missing or does not match its SHA-256 is left out
|
|
610
|
+
and named; a record of a plugin that could not be measured (`*.unmeasured.json`) is left out, since it never
|
|
611
|
+
applies. The tool builds the Debian package only; an RPM of a set is not built yet.
|
|
612
|
+
|
|
466
613
|
### The container image
|
|
467
614
|
|
|
468
615
|
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
4
|
+
/**
|
|
5
|
+
* `plugin-qualify-records-package`, the entry point: the Debian package of a device's qualification records. Every
|
|
6
|
+
* decision is in `src/records-package.ts`; this file reads argv, builds the package, names what stayed out on stderr
|
|
7
|
+
* and the package on the last line of stdout.
|
|
8
|
+
*/
|
|
9
|
+
import { buildRecordsPackage, parseRecordsPackageArgs, recordsPackageHelp } from "../dist/index.js";
|
|
10
|
+
|
|
11
|
+
const log = (line) => process.stderr.write(`plugin-qualify-records-package: ${line}\n`);
|
|
12
|
+
|
|
13
|
+
const parsed = parseRecordsPackageArgs(process.argv.slice(2));
|
|
14
|
+
if (!parsed.ok) {
|
|
15
|
+
log(parsed.error);
|
|
16
|
+
process.stderr.write(`\n${recordsPackageHelp()}`);
|
|
17
|
+
process.exit(2);
|
|
18
|
+
}
|
|
19
|
+
if ("help" in parsed) {
|
|
20
|
+
process.stdout.write(recordsPackageHelp());
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
let built;
|
|
24
|
+
try {
|
|
25
|
+
built = buildRecordsPackage(parsed.args);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
log(e.message);
|
|
28
|
+
process.exit(2);
|
|
29
|
+
}
|
|
30
|
+
for (const l of built.left) log(`${l.name}: ${l.why}; left out`);
|
|
31
|
+
process.stdout.write(`${built.path}: ${String(built.records)} records, ${String(built.files)} files\n`);
|
|
@@ -44,7 +44,12 @@ if (!resolved.ok) {
|
|
|
44
44
|
process.exit(2);
|
|
45
45
|
}
|
|
46
46
|
const s = resolved.settings;
|
|
47
|
-
|
|
47
|
+
const grid = s.grid.kind === "cells" ? s.grid.cells.map((c) => `${String(c.rate)}/${String(c.quantum)}`).join(",") : s.grid.kind;
|
|
48
|
+
log(
|
|
49
|
+
`CLAP ${s.clapDirs.join(":")}; LV2 ${s.lv2Dirs.join(":")}; host ${s.hostProfile.id}; grid ${grid}` +
|
|
50
|
+
`${s.cellsPerPluginBudget > 0 ? ` (first ${String(s.cellsPerPluginBudget)} cells)` : ""}; ${String(s.budgetS)} s a run; ` +
|
|
51
|
+
`memory ${s.memory === undefined ? "unbounded" : `${String(s.memory.bytes)} bytes`}`,
|
|
52
|
+
);
|
|
48
53
|
try {
|
|
49
54
|
const summary = await runSync(s, { force: parsed.args.force, log });
|
|
50
55
|
if (summary !== undefined) {
|
package/bin/plugin-qualify.mjs
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
exitCodeFor,
|
|
36
36
|
helpText,
|
|
37
37
|
lv2Loader,
|
|
38
|
+
memoryBoundOf,
|
|
38
39
|
parseQualifyArgs,
|
|
39
40
|
pinsFile,
|
|
40
41
|
qualificationRecordOf,
|
|
@@ -179,7 +180,17 @@ if (parsed.args.all) {
|
|
|
179
180
|
const lv2 = plugins.filter((p) => p.format === "lv2").length;
|
|
180
181
|
process.stderr.write(`plugin-qualify: --all qualifies ${String(lv2)} LV2 and ${String(plugins.length - lv2)} CLAP plugins on the search paths\n`);
|
|
181
182
|
}
|
|
182
|
-
const
|
|
183
|
+
const memory = memoryBoundOf(parsed.args.memoryLimit);
|
|
184
|
+
process.stderr.write(`plugin-qualify: memory bound ${memory === undefined ? "off" : `${String(memory.bytes)} bytes (${memory.mechanism})`}\n`);
|
|
185
|
+
const pass = qualifyTargets(plugins, {
|
|
186
|
+
rates: parsed.args.rates,
|
|
187
|
+
quanta: parsed.args.quanta,
|
|
188
|
+
grid: parsed.args.grid,
|
|
189
|
+
cellsPerPluginBudget: parsed.args.cellsPerPluginBudget,
|
|
190
|
+
...(parsed.args.blocks === undefined ? {} : { blocks: parsed.args.blocks }),
|
|
191
|
+
...(memory === undefined ? {} : { memory }),
|
|
192
|
+
profiles: profiles.profiles,
|
|
193
|
+
});
|
|
183
194
|
for (const f of pass.failures) process.stderr.write(`plugin-qualify: ${f.uri}: ${f.problem}\n`);
|
|
184
195
|
if (parsed.args.out !== undefined) {
|
|
185
196
|
for (const run of mergeRunsByRecord(pass.runs)) {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { type QualificationRecord } from "./qualification-record.js";
|
|
2
|
+
import type { FileDigest, SyncItem, SyncPlugin } from "./qualify-sync.js";
|
|
3
|
+
/** Where a system's packages install the distributed records. */
|
|
4
|
+
export declare const DISTRIBUTED_DIR_SYSTEM = "/usr/share/omx/qualification";
|
|
5
|
+
/** The file of a set that says where its records come from. */
|
|
6
|
+
export declare const DISTRIBUTED_PROVENANCE_FILE = "provenance.json";
|
|
7
|
+
export declare const DISTRIBUTED_PROVENANCE_SCHEMA = "plugin-qualify.distributed-provenance/1";
|
|
8
|
+
/** The first plugin-qualify that reads the layer: what a package of records depends on. */
|
|
9
|
+
export declare const DISTRIBUTED_LAYER_SINCE = "0.17.0";
|
|
10
|
+
/** Where the records of a set come from. The keys below are always there; the rest is what the builder was given. */
|
|
11
|
+
export interface DistributedProvenance {
|
|
12
|
+
readonly schema: typeof DISTRIBUTED_PROVENANCE_SCHEMA;
|
|
13
|
+
readonly set: string;
|
|
14
|
+
readonly hostClass: string;
|
|
15
|
+
/** The package that installed the set. */
|
|
16
|
+
readonly package: {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly version: string;
|
|
19
|
+
};
|
|
20
|
+
/** The day the package was built (YYYY-MM-DD). */
|
|
21
|
+
readonly date: string;
|
|
22
|
+
readonly [key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
/** The device tree's model file, where a board names itself. */
|
|
25
|
+
export declare const BOARD_MODEL_FILE = "/proc/device-tree/model";
|
|
26
|
+
/** The board's own model, or `undefined` on a machine without a device tree. */
|
|
27
|
+
export declare function readBoardModel(): string | undefined;
|
|
28
|
+
/** The host class of a board the layer knows by its device-tree model: `rpi5` for a Raspberry Pi 5 or its compute module. */
|
|
29
|
+
export declare function hostClassOfBoard(model: string | undefined): string | undefined;
|
|
30
|
+
/** One distributed record, read: where it is, what it says, and the set it came with. */
|
|
31
|
+
export interface DistributedRecord {
|
|
32
|
+
readonly path: string;
|
|
33
|
+
readonly record: QualificationRecord;
|
|
34
|
+
readonly set: string;
|
|
35
|
+
readonly provenance: DistributedProvenance;
|
|
36
|
+
}
|
|
37
|
+
/** The layer as one run reads it: the records of the host class, by what finds them. */
|
|
38
|
+
export interface DistributedLayer {
|
|
39
|
+
/** By the `.clap` path the record measured, made absolute. */
|
|
40
|
+
readonly clap: ReadonlyMap<string, readonly DistributedRecord[]>;
|
|
41
|
+
/** By the LV2 plugin's uri. */
|
|
42
|
+
readonly lv2: ReadonlyMap<string, readonly DistributedRecord[]>;
|
|
43
|
+
readonly sets: readonly string[];
|
|
44
|
+
readonly records: number;
|
|
45
|
+
}
|
|
46
|
+
export declare const EMPTY_LAYER: DistributedLayer;
|
|
47
|
+
/**
|
|
48
|
+
* The records of one host class under each directory, in the order given. A set without a provenance that reads, or
|
|
49
|
+
* whose provenance names another set or class, is not read; a record that does not read as a record of this schema is
|
|
50
|
+
* named and left out.
|
|
51
|
+
*/
|
|
52
|
+
export declare function readDistributedLayer(dirs: readonly string[], hostClass: string, log?: (line: string) => void): DistributedLayer;
|
|
53
|
+
/** The uri a record's plugin has on this machine: the installed `.clap`'s path and the id, or the LV2 uri. */
|
|
54
|
+
export declare function distributedUri(item: SyncItem, r: DistributedRecord): string;
|
|
55
|
+
/** What the layer makes of one item. */
|
|
56
|
+
export type DistributedMatch =
|
|
57
|
+
/** No distributed record names the plugin: it is measured, and nothing is said. */
|
|
58
|
+
{
|
|
59
|
+
readonly status: "none";
|
|
60
|
+
}
|
|
61
|
+
/** Every reason a record found was not taken, one line each; the plugin is measured. */
|
|
62
|
+
| {
|
|
63
|
+
readonly status: "ignored";
|
|
64
|
+
readonly lines: readonly string[];
|
|
65
|
+
} | {
|
|
66
|
+
readonly status: "applies";
|
|
67
|
+
readonly set: string;
|
|
68
|
+
readonly records: readonly DistributedRecord[];
|
|
69
|
+
readonly files: readonly string[];
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Whether the item is taken from the layer. The records that find it are tried one set at a time, in the order the
|
|
73
|
+
* layer read them. Of a set, only the records whose every file is installed with the SHA-256 it recorded (computed
|
|
74
|
+
* here by `digest`) count: a record of other bytes is another build of the plugin and is passed over. The set applies
|
|
75
|
+
* when at least one record of these bytes holds a verdict for `host`, and none of those verdicts is one the source
|
|
76
|
+
* could not measure; a set with no record of these bytes is a binary that differs.
|
|
77
|
+
*/
|
|
78
|
+
export declare function matchDistributed(layer: DistributedLayer, item: SyncItem, host: string, digest: (files: readonly string[]) => readonly FileDigest[] | undefined): DistributedMatch;
|
|
79
|
+
/**
|
|
80
|
+
* The records of a match copied into `qualificationDir` and the plugins they make: the probe output first, byte for
|
|
81
|
+
* byte and read back through this version's reader, its SHA-256 the one the record names; then the record with the
|
|
82
|
+
* set's provenance added. A record whose probe output is missing, altered or unreadable throws, naming it, and the
|
|
83
|
+
* caller measures the plugin instead. Records of the other tier (no verdict for `host`) are copied and make no plugin,
|
|
84
|
+
* as the sweep writes them.
|
|
85
|
+
*/
|
|
86
|
+
export declare function applyDistributed(qualificationDir: string, item: SyncItem, match: Extract<DistributedMatch, {
|
|
87
|
+
status: "applies";
|
|
88
|
+
}>, host: string): SyncPlugin[];
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
3
|
+
/**
|
|
4
|
+
* The distributed layer: qualification records another device of the same machine class wrote for a plugin set,
|
|
5
|
+
* installed read-only under `<dir>/<host class>/<set>/` beside the set's `provenance.json`. plugin-qualify-sync reads
|
|
6
|
+
* it before measuring a plugin, and takes a plugin from it instead of measuring it only when every file the record
|
|
7
|
+
* names hashes, here, as it did where it was measured. The name of a plugin finds its record and never decides
|
|
8
|
+
* anything: a record is never trusted by name alone.
|
|
9
|
+
*
|
|
10
|
+
* What applies is copied into the device's own `qualification_dir`, the record with the set's provenance added and
|
|
11
|
+
* its probe output byte for byte, and the plugin's pin is the one that probe output holds. From then on the sync
|
|
12
|
+
* treats the plugin as one it measured.
|
|
13
|
+
*/
|
|
14
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
15
|
+
import { join, resolve } from "node:path";
|
|
16
|
+
import { clapPinUri, pinVerdictOf } from "./pins-file.js";
|
|
17
|
+
import { integrityOfProbeOutput } from "./probe-judge.js";
|
|
18
|
+
import { readProbeOutput, sha256Hex } from "./probe-output.js";
|
|
19
|
+
import { QUALIFICATION_RECORD_SCHEMA } from "./qualification-record.js";
|
|
20
|
+
import { writeFileWhole } from "./whole-file.js";
|
|
21
|
+
/** Where a system's packages install the distributed records. */
|
|
22
|
+
export const DISTRIBUTED_DIR_SYSTEM = "/usr/share/omx/qualification";
|
|
23
|
+
/** The file of a set that says where its records come from. */
|
|
24
|
+
export const DISTRIBUTED_PROVENANCE_FILE = "provenance.json";
|
|
25
|
+
export const DISTRIBUTED_PROVENANCE_SCHEMA = "plugin-qualify.distributed-provenance/1";
|
|
26
|
+
/** The first plugin-qualify that reads the layer: what a package of records depends on. */
|
|
27
|
+
export const DISTRIBUTED_LAYER_SINCE = "0.17.0";
|
|
28
|
+
/** The device tree's model file, where a board names itself. */
|
|
29
|
+
export const BOARD_MODEL_FILE = "/proc/device-tree/model";
|
|
30
|
+
/** The board's own model, or `undefined` on a machine without a device tree. */
|
|
31
|
+
export function readBoardModel() {
|
|
32
|
+
try {
|
|
33
|
+
return readFileSync(BOARD_MODEL_FILE, "utf8");
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** The host class of a board the layer knows by its device-tree model: `rpi5` for a Raspberry Pi 5 or its compute module. */
|
|
40
|
+
export function hostClassOfBoard(model) {
|
|
41
|
+
if (model === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
const m = /^Raspberry Pi (?:Compute Module )?(\d)(?:\s|$)/.exec(model.replace(/\0/g, "").trim());
|
|
44
|
+
return m === null ? undefined : `rpi${m[1]}`;
|
|
45
|
+
}
|
|
46
|
+
export const EMPTY_LAYER = { clap: new Map(), lv2: new Map(), sets: [], records: 0 };
|
|
47
|
+
const RECORD_SUFFIX = ".qualification.json";
|
|
48
|
+
const names = (dir) => {
|
|
49
|
+
try {
|
|
50
|
+
return readdirSync(dir).sort();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* The records of one host class under each directory, in the order given. A set without a provenance that reads, or
|
|
58
|
+
* whose provenance names another set or class, is not read; a record that does not read as a record of this schema is
|
|
59
|
+
* named and left out.
|
|
60
|
+
*/
|
|
61
|
+
export function readDistributedLayer(dirs, hostClass, log = () => { }) {
|
|
62
|
+
const clap = new Map();
|
|
63
|
+
const lv2 = new Map();
|
|
64
|
+
const sets = [];
|
|
65
|
+
let records = 0;
|
|
66
|
+
const add = (map, key, r) => {
|
|
67
|
+
map.set(key, [...(map.get(key) ?? []), r]);
|
|
68
|
+
};
|
|
69
|
+
for (const base of dirs) {
|
|
70
|
+
const classDir = join(base, hostClass);
|
|
71
|
+
for (const set of names(classDir)) {
|
|
72
|
+
const dir = join(classDir, set);
|
|
73
|
+
let provenance;
|
|
74
|
+
try {
|
|
75
|
+
provenance = JSON.parse(readFileSync(join(dir, DISTRIBUTED_PROVENANCE_FILE), "utf8"));
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
if (e.code === "ENOTDIR")
|
|
79
|
+
continue;
|
|
80
|
+
log(`distributed: ${dir}: ${e.code === "ENOENT" ? `no ${DISTRIBUTED_PROVENANCE_FILE}` : `${DISTRIBUTED_PROVENANCE_FILE} does not read`}, not read`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (provenance.schema !== DISTRIBUTED_PROVENANCE_SCHEMA || provenance.set !== set || provenance.hostClass !== hostClass) {
|
|
84
|
+
log(`distributed: ${dir}: its ${DISTRIBUTED_PROVENANCE_FILE} is not of set ${set} and host class ${hostClass} (${DISTRIBUTED_PROVENANCE_SCHEMA}), not read`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
sets.push(set);
|
|
88
|
+
for (const name of names(dir)) {
|
|
89
|
+
if (!name.endsWith(RECORD_SUFFIX))
|
|
90
|
+
continue;
|
|
91
|
+
const path = join(dir, name);
|
|
92
|
+
let record;
|
|
93
|
+
try {
|
|
94
|
+
record = JSON.parse(readFileSync(path, "utf8"));
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
log(`distributed: ${path}: does not read, left out`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (record.schema !== QUALIFICATION_RECORD_SCHEMA || typeof record.binary?.path !== "string" || !Array.isArray(record.binary.files) || record.binary.files.length === 0) {
|
|
101
|
+
log(`distributed: ${path}: not a ${QUALIFICATION_RECORD_SCHEMA} with its binary's files, left out`);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const r = { path, record, set, provenance };
|
|
105
|
+
records++;
|
|
106
|
+
if (record.plugin.format === "clap")
|
|
107
|
+
add(clap, resolve(record.binary.path), r);
|
|
108
|
+
else if (record.plugin.format === "lv2")
|
|
109
|
+
add(lv2, record.plugin.id, r);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return { clap, lv2, sets, records };
|
|
114
|
+
}
|
|
115
|
+
/** The uri a record's plugin has on this machine: the installed `.clap`'s path and the id, or the LV2 uri. */
|
|
116
|
+
export function distributedUri(item, r) {
|
|
117
|
+
return item.format === "clap" ? clapPinUri(join(item.root, item.files[0]), `urn:clap:${r.record.plugin.id}`) : r.record.plugin.id;
|
|
118
|
+
}
|
|
119
|
+
/** What the sync's own judgment would make of the record's verdict for the host, as `outcomeOf` reads a run. */
|
|
120
|
+
function outcomeOfVerdict(v) {
|
|
121
|
+
if (!v.measured)
|
|
122
|
+
return v.deciding === "probe.crashed" ? "crashed" : v.deciding === "probe.timeout" ? "hung" : "unmeasured";
|
|
123
|
+
if (v.rating === "suitable" || v.rating === "conditional")
|
|
124
|
+
return "qualified";
|
|
125
|
+
return v.rating === "unsuitable" ? "refused" : "unmeasured";
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Whether the item is taken from the layer. The records that find it are tried one set at a time, in the order the
|
|
129
|
+
* layer read them. Of a set, only the records whose every file is installed with the SHA-256 it recorded (computed
|
|
130
|
+
* here by `digest`) count: a record of other bytes is another build of the plugin and is passed over. The set applies
|
|
131
|
+
* when at least one record of these bytes holds a verdict for `host`, and none of those verdicts is one the source
|
|
132
|
+
* could not measure; a set with no record of these bytes is a binary that differs.
|
|
133
|
+
*/
|
|
134
|
+
export function matchDistributed(layer, item, host, digest) {
|
|
135
|
+
const found = item.format === "clap" ? layer.clap.get(resolve(join(item.root, item.files[0]))) : layer.lv2.get(item.uri ?? "");
|
|
136
|
+
if (found === undefined || found.length === 0)
|
|
137
|
+
return { status: "none" };
|
|
138
|
+
const lines = [];
|
|
139
|
+
const say = (line) => {
|
|
140
|
+
if (!lines.includes(line))
|
|
141
|
+
lines.push(line);
|
|
142
|
+
};
|
|
143
|
+
for (const set of [...new Set(found.map((r) => r.set))]) {
|
|
144
|
+
// a set may hold records of earlier bytes of the plugin beside the ones of these: only the ones of these bytes count
|
|
145
|
+
const records = found.filter((r) => {
|
|
146
|
+
if (r.set !== set || r.record.plugin.format !== item.format)
|
|
147
|
+
return false;
|
|
148
|
+
const wanted = r.record.binary.files;
|
|
149
|
+
const now = digest(wanted.map((f) => f.path));
|
|
150
|
+
return now !== undefined && wanted.every((f) => now.find((d) => d.path === f.path)?.sha256 === f.sha256);
|
|
151
|
+
});
|
|
152
|
+
if (records.length === 0) {
|
|
153
|
+
say(`distributed: ${distributedUri(item, found.find((r) => r.set === set))}: binary differs`);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const judged = records.filter((r) => r.record.verdicts.some((v) => v.host === host));
|
|
157
|
+
if (judged.length === 0) {
|
|
158
|
+
say(`distributed: ${distributedUri(item, records[0])}: no verdict for ${host} in ${set}`);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const unmeasured = judged.find((r) => outcomeOfVerdict(r.record.verdicts.find((v) => v.host === host)) === "unmeasured");
|
|
162
|
+
if (unmeasured !== undefined) {
|
|
163
|
+
say(`distributed: ${distributedUri(item, unmeasured)}: unmeasured in ${set}, measured here`);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
return { status: "applies", set, records, files: [...new Set(records.flatMap((r) => r.record.binary.files.map((f) => f.path)))] };
|
|
167
|
+
}
|
|
168
|
+
return { status: "ignored", lines };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The records of a match copied into `qualificationDir` and the plugins they make: the probe output first, byte for
|
|
172
|
+
* byte and read back through this version's reader, its SHA-256 the one the record names; then the record with the
|
|
173
|
+
* set's provenance added. A record whose probe output is missing, altered or unreadable throws, naming it, and the
|
|
174
|
+
* caller measures the plugin instead. Records of the other tier (no verdict for `host`) are copied and make no plugin,
|
|
175
|
+
* as the sweep writes them.
|
|
176
|
+
*/
|
|
177
|
+
export function applyDistributed(qualificationDir, item, match, host) {
|
|
178
|
+
const copies = match.records.map((r) => {
|
|
179
|
+
const probeName = r.record.probe.file;
|
|
180
|
+
const probePath = join(r.path, "..", probeName);
|
|
181
|
+
let bytes;
|
|
182
|
+
try {
|
|
183
|
+
bytes = readFileSync(probePath);
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
throw new Error(`${probeName}: ${e.code ?? String(e)}`);
|
|
187
|
+
}
|
|
188
|
+
if (sha256Hex(bytes) !== r.record.probe.sha256)
|
|
189
|
+
throw new Error(`${probeName}: not the probe output the record names (its SHA-256 differs)`);
|
|
190
|
+
const read = readProbeOutput(bytes.toString("utf8"));
|
|
191
|
+
if (!read.ok)
|
|
192
|
+
throw new Error(`${probeName}: ${read.code}: ${read.problem}`);
|
|
193
|
+
return { r, bytes, output: read.output };
|
|
194
|
+
});
|
|
195
|
+
const plugins = [];
|
|
196
|
+
for (const { r, bytes, output } of copies) {
|
|
197
|
+
writeFileWhole(join(qualificationDir, r.record.probe.file), bytes);
|
|
198
|
+
const recordPath = join(qualificationDir, r.path.slice(r.path.lastIndexOf("/") + 1));
|
|
199
|
+
writeFileWhole(recordPath, `${JSON.stringify({ ...r.record, provenance: r.provenance }, null, 2)}\n`);
|
|
200
|
+
const v = r.record.verdicts.find((x) => x.host === host);
|
|
201
|
+
if (v === undefined)
|
|
202
|
+
continue;
|
|
203
|
+
const word = v.measured ? pinVerdictOf(v.rating) : undefined;
|
|
204
|
+
const pin = { uri: distributedUri(item, r), integrity: integrityOfProbeOutput(output), ...(word === undefined ? {} : { verdict: word }) };
|
|
205
|
+
const outcome = outcomeOfVerdict(v);
|
|
206
|
+
plugins.push({ uri: pin.uri, outcome, ...(v.deciding === undefined ? {} : { code: v.deciding }), pin, record: recordPath });
|
|
207
|
+
}
|
|
208
|
+
return plugins;
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=distributed-records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"distributed-records.js","sourceRoot":"","sources":["../src/distributed-records.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,9 +22,14 @@ export * from "./probe-output.js";
|
|
|
22
22
|
export * from "./probe-judge.js";
|
|
23
23
|
export * from "./qualification-record.js";
|
|
24
24
|
export * from "./probe-runner.js";
|
|
25
|
+
export * from "./measurement-grid.js";
|
|
26
|
+
export * from "./memory-bound.js";
|
|
27
|
+
export * from "./probe-merge.js";
|
|
25
28
|
export * from "./judge-cli.js";
|
|
26
29
|
export * from "./whole-file.js";
|
|
27
30
|
export * from "./qualify-sync.js";
|
|
31
|
+
export * from "./distributed-records.js";
|
|
32
|
+
export * from "./records-package.js";
|
|
28
33
|
export * from "./host-admission.js";
|
|
29
34
|
export * from "./history-file.js";
|
|
30
35
|
export * from "./history-adjust.js";
|
package/dist/index.js
CHANGED
|
@@ -24,9 +24,14 @@ export * from "./probe-output.js";
|
|
|
24
24
|
export * from "./probe-judge.js";
|
|
25
25
|
export * from "./qualification-record.js";
|
|
26
26
|
export * from "./probe-runner.js";
|
|
27
|
+
export * from "./measurement-grid.js";
|
|
28
|
+
export * from "./memory-bound.js";
|
|
29
|
+
export * from "./probe-merge.js";
|
|
27
30
|
export * from "./judge-cli.js";
|
|
28
31
|
export * from "./whole-file.js";
|
|
29
32
|
export * from "./qualify-sync.js";
|
|
33
|
+
export * from "./distributed-records.js";
|
|
34
|
+
export * from "./records-package.js";
|
|
30
35
|
export * from "./host-admission.js";
|
|
31
36
|
export * from "./history-file.js";
|
|
32
37
|
export * from "./history-adjust.js";
|