@openmixer/plugin-qualify 0.0.0-stage → 0.4.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/LICENSE +674 -0
- package/README.md +203 -2
- package/bin/plugin-qualify.mjs +77 -0
- package/dist/clap-loader.d.ts +147 -0
- package/dist/clap-loader.js +252 -0
- package/dist/clap-loader.js.map +1 -0
- package/dist/clap-qualify.d.ts +126 -0
- package/dist/clap-qualify.js +272 -0
- package/dist/clap-qualify.js.map +1 -0
- package/dist/host-profiles.d.ts +42 -0
- package/dist/host-profiles.js +106 -0
- package/dist/host-profiles.js.map +1 -0
- package/dist/hosting-codes.d.ts +62 -0
- package/dist/hosting-codes.js +72 -0
- package/dist/hosting-codes.js.map +1 -0
- package/dist/hosting-suitability.d.ts +362 -0
- package/dist/hosting-suitability.js +669 -0
- package/dist/hosting-suitability.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +26 -0
- package/dist/loader.js +9 -0
- package/dist/loader.js.map +1 -0
- package/dist/lv2-loader.d.ts +7 -0
- package/dist/lv2-loader.js +50 -0
- package/dist/lv2-loader.js.map +1 -0
- package/dist/measurement/cli.d.ts +168 -0
- package/dist/measurement/cli.js +353 -0
- package/dist/measurement/cli.js.map +1 -0
- package/dist/measurement/fixtures.d.ts +23 -0
- package/dist/measurement/fixtures.js +129 -0
- package/dist/measurement/fixtures.js.map +1 -0
- package/dist/measurement/format.d.ts +483 -0
- package/dist/measurement/format.js +627 -0
- package/dist/measurement/format.js.map +1 -0
- package/dist/measurement/guards.d.ts +33 -0
- package/dist/measurement/guards.js +61 -0
- package/dist/measurement/guards.js.map +1 -0
- package/dist/measurement/index.d.ts +36 -0
- package/dist/measurement/index.js +39 -0
- package/dist/measurement/index.js.map +1 -0
- package/dist/measurement/merge.d.ts +143 -0
- package/dist/measurement/merge.js +315 -0
- package/dist/measurement/merge.js.map +1 -0
- package/dist/measurement/plan.d.ts +127 -0
- package/dist/measurement/plan.js +141 -0
- package/dist/measurement/plan.js.map +1 -0
- package/dist/measurement/probe-json.d.ts +61 -0
- package/dist/measurement/probe-json.js +253 -0
- package/dist/measurement/probe-json.js.map +1 -0
- package/dist/measurement/provenance.d.ts +91 -0
- package/dist/measurement/provenance.js +173 -0
- package/dist/measurement/provenance.js.map +1 -0
- package/dist/measurement/sweep-jsonl.d.ts +40 -0
- package/dist/measurement/sweep-jsonl.js +154 -0
- package/dist/measurement/sweep-jsonl.js.map +1 -0
- package/dist/measurement/vocabulary.d.ts +125 -0
- package/dist/measurement/vocabulary.js +217 -0
- package/dist/measurement/vocabulary.js.map +1 -0
- package/dist/qualify-cli.d.ts +83 -0
- package/dist/qualify-cli.js +224 -0
- package/dist/qualify-cli.js.map +1 -0
- package/dist/qualify-declarations.d.ts +98 -0
- package/dist/qualify-declarations.js +76 -0
- package/dist/qualify-declarations.js.map +1 -0
- package/dist/qualify-options.d.ts +22 -0
- package/dist/qualify-options.js +36 -0
- package/dist/qualify-options.js.map +1 -0
- package/dist/rating.d.ts +32 -0
- package/dist/rating.js +30 -0
- package/dist/rating.js.map +1 -0
- package/dist/tool-paths.d.ts +12 -0
- package/dist/tool-paths.js +29 -0
- package/dist/tool-paths.js.map +1 -0
- package/fixtures/clap-scan/controls.json +8 -0
- package/fixtures/clap-scan/fake.json +16 -0
- package/fixtures/clap-scan/mixed.json +18 -0
- package/fixtures/zero-path.lv2/manifest.ttl +3 -0
- package/fixtures/zero-path.lv2/zero-path.c +85 -0
- package/fixtures/zero-path.lv2/zero-path.ttl +21 -0
- package/package.json +94 -4
- package/src/clap-loader.ts +393 -0
- package/src/clap-qualify.ts +387 -0
- package/src/host-profiles.ts +122 -0
- package/src/hosting-codes.ts +71 -0
- package/src/hosting-suitability.ts +998 -0
- package/src/index.ts +19 -0
- package/src/loader.ts +30 -0
- package/src/lv2-loader.ts +50 -0
- package/src/measurement/cli.ts +458 -0
- package/src/measurement/fixtures.ts +141 -0
- package/src/measurement/format.ts +1064 -0
- package/src/measurement/guards.ts +64 -0
- package/src/measurement/index.ts +39 -0
- package/src/measurement/merge.ts +434 -0
- package/src/measurement/plan.ts +219 -0
- package/src/measurement/probe-json.ts +271 -0
- package/src/measurement/provenance.ts +220 -0
- package/src/measurement/sweep-jsonl.ts +173 -0
- package/src/measurement/vocabulary.ts +298 -0
- package/src/qualify-cli.ts +277 -0
- package/src/qualify-declarations.ts +117 -0
- package/src/qualify-options.ts +49 -0
- package/src/rating.ts +53 -0
- package/src/tool-paths.ts +32 -0
- package/tools/_declarations.py +21 -0
- package/tools/benchmark.py +2462 -0
- package/tools/gen-python-declarations.d.mts +4 -0
- package/tools/gen-python-declarations.mjs +92 -0
- package/tools/hosting-sweep.mjs +51 -0
- package/tools/lv2-measure.mjs +204 -0
- package/tools/lv2_presets.py +132 -0
- package/tools/scan.py +432 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a run happened, and under what settings — assembled from what the machine will
|
|
3
|
+
* actually tell us.
|
|
4
|
+
*
|
|
5
|
+
* The discipline this module enforces is the one `data/CPU_COST_PROVENANCE.md` states in
|
|
6
|
+
* prose: *"realtime priority / mlock — no, both need privileges the benchmark must not
|
|
7
|
+
* require; recorded as `false` rather than assumed."* Every fact here is READ at run time
|
|
8
|
+
* or omitted. Nothing is defaulted, nothing is inferred from the platform, and a `/sys`
|
|
9
|
+
* file that will not open produces an absent field rather than a plausible value.
|
|
10
|
+
*
|
|
11
|
+
* The two `false`s are the exception and they are required fields: `realtimePriority` and
|
|
12
|
+
* `memoryLocked` say "we did not run with this", which is a finding a comparison needs,
|
|
13
|
+
* and leaving them absent would let a reader assume the good case.
|
|
14
|
+
*/
|
|
15
|
+
import type { CostMethod, LatencyMethod, MeasurementHost, MeasurementRun } from "./format.js";
|
|
16
|
+
import { type MeasurementDimension } from "./vocabulary.js";
|
|
17
|
+
/** The machine facts this module reads, injectable so the collector is testable. */
|
|
18
|
+
export interface HostProbes {
|
|
19
|
+
hostname(): string;
|
|
20
|
+
kernelRelease(): string;
|
|
21
|
+
osName(): string;
|
|
22
|
+
cpuModel(): string | undefined;
|
|
23
|
+
cpuCount(): number;
|
|
24
|
+
/** Contents of a `/sys` or `/proc` file, or `undefined` when it cannot be read. */
|
|
25
|
+
readSystemFile(path: string): string | undefined;
|
|
26
|
+
}
|
|
27
|
+
/** The real machine. */
|
|
28
|
+
export declare const SYSTEM_HOST_PROBES: HostProbes;
|
|
29
|
+
/** Facts the caller knows that the machine cannot be asked for. */
|
|
30
|
+
export interface HostFactInput {
|
|
31
|
+
/** The core the prober pinned to, when it pinned. */
|
|
32
|
+
readonly pinnedCpu?: number;
|
|
33
|
+
/** Whether the prober ran with realtime scheduling — a claim the caller must make. */
|
|
34
|
+
readonly realtimePriority: boolean;
|
|
35
|
+
/** Whether the prober's pages were locked. */
|
|
36
|
+
readonly memoryLocked: boolean;
|
|
37
|
+
/** Versions of what did the probing, e.g. `{ python: "3.14.6" }`. */
|
|
38
|
+
readonly toolchain?: Readonly<Record<string, string>>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Read the governor, the max frequency and the turbo state for a core, plus the CPU model
|
|
42
|
+
* and count. Any file that will not open yields an absent field.
|
|
43
|
+
*
|
|
44
|
+
* `pinnedCpuKind` deliberately reports what the kernel exposes rather than a classification
|
|
45
|
+
* of our own: on a hybrid CPU the two `cpuinfo_max_freq` populations are what distinguishes
|
|
46
|
+
* a performance core from an efficiency one, so the frequency IS the evidence and the label
|
|
47
|
+
* is only offered when the split is unambiguous.
|
|
48
|
+
*/
|
|
49
|
+
export declare function collectHostFacts(input: HostFactInput, probes?: HostProbes): MeasurementHost;
|
|
50
|
+
/**
|
|
51
|
+
* The host block from `benchmark.py --cost-provenance`'s JSON, or `undefined`.
|
|
52
|
+
*
|
|
53
|
+
* That file is the model this format generalises, so the mapping is mostly renaming. The
|
|
54
|
+
* one judgement: its `realtimePriority` / `memoryLocked` are already explicit booleans, and
|
|
55
|
+
* a file that omits them is treated as not having run privileged — the safe reading, and
|
|
56
|
+
* the one that matches what the prober can actually do without privileges.
|
|
57
|
+
*/
|
|
58
|
+
export declare function hostFromProbeProvenance(value: unknown): MeasurementHost | undefined;
|
|
59
|
+
/** The cost method block from `benchmark.py --cost-provenance`'s JSON, or `undefined`. */
|
|
60
|
+
export declare function costMethodFromProbeProvenance(value: unknown): CostMethod | undefined;
|
|
61
|
+
/** The caveat that rides on every run carrying cost figures. */
|
|
62
|
+
export declare const RELATIVE_RANKING_NOTE: string;
|
|
63
|
+
/** The caveat on a run carrying only latency figures — portable, but not unconditionally. */
|
|
64
|
+
export declare const LATENCY_ONLY_NOTE: string;
|
|
65
|
+
/** The caveat on a run carrying the hosting dimensions — survival, RT-safety, features. */
|
|
66
|
+
export declare const HOSTING_NOTE: string;
|
|
67
|
+
/** Everything {@link buildRun} needs that is not read off the machine. */
|
|
68
|
+
export interface RunInput {
|
|
69
|
+
readonly id: string;
|
|
70
|
+
readonly measuredAt: string;
|
|
71
|
+
readonly dimensions: readonly MeasurementDimension[];
|
|
72
|
+
readonly rates: readonly number[];
|
|
73
|
+
readonly tool: {
|
|
74
|
+
readonly name: string;
|
|
75
|
+
readonly version: string;
|
|
76
|
+
};
|
|
77
|
+
readonly host: MeasurementHost;
|
|
78
|
+
readonly latencyMethod?: LatencyMethod;
|
|
79
|
+
readonly costMethod?: CostMethod;
|
|
80
|
+
readonly elapsedSeconds?: number;
|
|
81
|
+
readonly label?: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Assemble a run record.
|
|
85
|
+
*
|
|
86
|
+
* `relativeRankingOnly` is derived, not passed: it is true exactly when the run carries cost
|
|
87
|
+
* figures, and letting a caller set it would let a caller unset it. The note follows the
|
|
88
|
+
* same rule — a latency-only run gets the version caveat, a cost run gets the machine one,
|
|
89
|
+
* and a run carrying both gets both, because both apply.
|
|
90
|
+
*/
|
|
91
|
+
export declare function buildRun(input: RunInput): MeasurementRun;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
3
|
+
/**
|
|
4
|
+
* Where a run happened, and under what settings — assembled from what the machine will
|
|
5
|
+
* actually tell us.
|
|
6
|
+
*
|
|
7
|
+
* The discipline this module enforces is the one `data/CPU_COST_PROVENANCE.md` states in
|
|
8
|
+
* prose: *"realtime priority / mlock — no, both need privileges the benchmark must not
|
|
9
|
+
* require; recorded as `false` rather than assumed."* Every fact here is READ at run time
|
|
10
|
+
* or omitted. Nothing is defaulted, nothing is inferred from the platform, and a `/sys`
|
|
11
|
+
* file that will not open produces an absent field rather than a plausible value.
|
|
12
|
+
*
|
|
13
|
+
* The two `false`s are the exception and they are required fields: `realtimePriority` and
|
|
14
|
+
* `memoryLocked` say "we did not run with this", which is a finding a comparison needs,
|
|
15
|
+
* and leaving them absent would let a reader assume the good case.
|
|
16
|
+
*/
|
|
17
|
+
import { hostname as osHostname, cpus as osCpus, release as osRelease, type as osType } from "node:os";
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import { booleanValue, finiteNumber, isRecord, nonEmptyString, optional } from "./guards.js";
|
|
20
|
+
import { MEASUREMENT_DIMENSIONS } from "./vocabulary.js";
|
|
21
|
+
/** The real machine. */
|
|
22
|
+
export const SYSTEM_HOST_PROBES = {
|
|
23
|
+
hostname: () => osHostname(),
|
|
24
|
+
kernelRelease: () => osRelease(),
|
|
25
|
+
osName: () => osType(),
|
|
26
|
+
cpuModel: () => osCpus()[0]?.model,
|
|
27
|
+
cpuCount: () => osCpus().length,
|
|
28
|
+
readSystemFile: (path) => {
|
|
29
|
+
try {
|
|
30
|
+
return readFileSync(path, "utf8").trim();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Read the governor, the max frequency and the turbo state for a core, plus the CPU model
|
|
39
|
+
* and count. Any file that will not open yields an absent field.
|
|
40
|
+
*
|
|
41
|
+
* `pinnedCpuKind` deliberately reports what the kernel exposes rather than a classification
|
|
42
|
+
* of our own: on a hybrid CPU the two `cpuinfo_max_freq` populations are what distinguishes
|
|
43
|
+
* a performance core from an efficiency one, so the frequency IS the evidence and the label
|
|
44
|
+
* is only offered when the split is unambiguous.
|
|
45
|
+
*/
|
|
46
|
+
export function collectHostFacts(input, probes = SYSTEM_HOST_PROBES) {
|
|
47
|
+
const cpu = input.pinnedCpu;
|
|
48
|
+
const base = cpu === undefined ? undefined : `/sys/devices/system/cpu/cpu${cpu}/cpufreq`;
|
|
49
|
+
const governor = base === undefined ? undefined : nonEmptyString(probes.readSystemFile(`${base}/scaling_governor`));
|
|
50
|
+
const maxKHzText = base === undefined ? undefined : probes.readSystemFile(`${base}/cpuinfo_max_freq`);
|
|
51
|
+
const maxKHz = maxKHzText === undefined ? undefined : finiteNumber(Number(maxKHzText));
|
|
52
|
+
const noTurbo = probes.readSystemFile("/sys/devices/system/cpu/intel_pstate/no_turbo");
|
|
53
|
+
const turboEnabled = noTurbo === undefined ? undefined : noTurbo.trim() === "0";
|
|
54
|
+
const cpuCount = probes.cpuCount();
|
|
55
|
+
return {
|
|
56
|
+
hostname: probes.hostname(),
|
|
57
|
+
realtimePriority: input.realtimePriority,
|
|
58
|
+
memoryLocked: input.memoryLocked,
|
|
59
|
+
...optional("kernel", nonEmptyString(probes.kernelRelease())),
|
|
60
|
+
...optional("os", nonEmptyString(probes.osName())),
|
|
61
|
+
...optional("cpuModel", nonEmptyString(probes.cpuModel())),
|
|
62
|
+
...optional("cpuCount", cpuCount > 0 ? cpuCount : undefined),
|
|
63
|
+
...optional("pinnedCpu", cpu),
|
|
64
|
+
...optional("pinnedCpuMaxKHz", maxKHz),
|
|
65
|
+
...optional("governor", governor),
|
|
66
|
+
...optional("turboEnabled", turboEnabled),
|
|
67
|
+
...optional("toolchain", input.toolchain),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The host block from `benchmark.py --cost-provenance`'s JSON, or `undefined`.
|
|
72
|
+
*
|
|
73
|
+
* That file is the model this format generalises, so the mapping is mostly renaming. The
|
|
74
|
+
* one judgement: its `realtimePriority` / `memoryLocked` are already explicit booleans, and
|
|
75
|
+
* a file that omits them is treated as not having run privileged — the safe reading, and
|
|
76
|
+
* the one that matches what the prober can actually do without privileges.
|
|
77
|
+
*/
|
|
78
|
+
export function hostFromProbeProvenance(value) {
|
|
79
|
+
if (!isRecord(value))
|
|
80
|
+
return undefined;
|
|
81
|
+
const host = nonEmptyString(value.host);
|
|
82
|
+
if (host === undefined)
|
|
83
|
+
return undefined;
|
|
84
|
+
const python = nonEmptyString(value.python);
|
|
85
|
+
return {
|
|
86
|
+
hostname: host,
|
|
87
|
+
realtimePriority: booleanValue(value.realtimePriority) ?? false,
|
|
88
|
+
memoryLocked: booleanValue(value.memoryLocked) ?? false,
|
|
89
|
+
...optional("kernel", nonEmptyString(value.kernel)),
|
|
90
|
+
...optional("cpuModel", nonEmptyString(value.cpuModel)),
|
|
91
|
+
...optional("cpuCount", finiteNumber(value.cpuCount)),
|
|
92
|
+
...optional("pinnedCpu", finiteNumber(value.pinnedCpu)),
|
|
93
|
+
...optional("pinnedCpuKind", nonEmptyString(value.pinnedCpuKind)),
|
|
94
|
+
...optional("pinnedCpuMaxKHz", finiteNumber(value.pinnedCpuMaxKHz)),
|
|
95
|
+
...optional("governor", nonEmptyString(value.governor)),
|
|
96
|
+
...optional("turboEnabled", booleanValue(value.turboEnabled)),
|
|
97
|
+
...optional("toolchain", python === undefined ? undefined : { python }),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** The cost method block from `benchmark.py --cost-provenance`'s JSON, or `undefined`. */
|
|
101
|
+
export function costMethodFromProbeProvenance(value) {
|
|
102
|
+
if (!isRecord(value))
|
|
103
|
+
return undefined;
|
|
104
|
+
const blockFrames = finiteNumber(value.blockFrames);
|
|
105
|
+
const warmupBlocks = finiteNumber(value.warmupBlocks);
|
|
106
|
+
const percentile = finiteNumber(value.percentile);
|
|
107
|
+
const stimulus = nonEmptyString(value.stimulus);
|
|
108
|
+
if (blockFrames === undefined || warmupBlocks === undefined || percentile === undefined || stimulus === undefined) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
method: "block-time-percentile",
|
|
113
|
+
blockFrames,
|
|
114
|
+
warmupBlocks,
|
|
115
|
+
percentile,
|
|
116
|
+
stimulus,
|
|
117
|
+
controls: nonEmptyString(value.controls) ?? "lv2:default on every control input",
|
|
118
|
+
...optional("minTimedBlocks", finiteNumber(value.minTimedBlocks)),
|
|
119
|
+
...optional("maxTimedBlocks", finiteNumber(value.maxTimedBlocks)),
|
|
120
|
+
...optional("targetSecondsPerPlugin", finiteNumber(value.targetSecondsPerPlugin)),
|
|
121
|
+
...optional("timingFloorNsPerBlock", finiteNumber(value.timingFloorNsPerBlock)),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/** The caveat that rides on every run carrying cost figures. */
|
|
125
|
+
export const RELATIVE_RANKING_NOTE = "A RELATIVE ranking measured on one machine, not a guarantee on any other. Different silicon, " +
|
|
126
|
+
"a different governor or a busier machine will produce different absolute figures; what carries " +
|
|
127
|
+
"over is the ORDER and the rough ratios.";
|
|
128
|
+
/** The caveat on a run carrying only latency figures — portable, but not unconditionally. */
|
|
129
|
+
export const LATENCY_ONLY_NOTE = "Latency is mostly algorithmic, so these figures travel between machines better than cost does. " +
|
|
130
|
+
"They are still tied to the PLUGIN VERSIONS recorded here: a new release moves an FFT window and " +
|
|
131
|
+
"the figure becomes wrong without becoming absent.";
|
|
132
|
+
/** The caveat on a run carrying the hosting dimensions — survival, RT-safety, features. */
|
|
133
|
+
export const HOSTING_NOTE = "Whether a plugin survives and keeps run() free of allocations, locks and syscalls was measured " +
|
|
134
|
+
"on the host named here, against its libc, lilv and the plugin builds installed at the time. An " +
|
|
135
|
+
"upgrade of any of them is a different host: carry the figures across and re-judge, never trust.";
|
|
136
|
+
/**
|
|
137
|
+
* Assemble a run record.
|
|
138
|
+
*
|
|
139
|
+
* `relativeRankingOnly` is derived, not passed: it is true exactly when the run carries cost
|
|
140
|
+
* figures, and letting a caller set it would let a caller unset it. The note follows the
|
|
141
|
+
* same rule — a latency-only run gets the version caveat, a cost run gets the machine one,
|
|
142
|
+
* and a run carrying both gets both, because both apply.
|
|
143
|
+
*/
|
|
144
|
+
export function buildRun(input) {
|
|
145
|
+
const dimensions = MEASUREMENT_DIMENSIONS.filter((d) => input.dimensions.includes(d));
|
|
146
|
+
const hasCost = dimensions.includes("cost");
|
|
147
|
+
const hasLatency = dimensions.includes("latency");
|
|
148
|
+
const hasHosting = dimensions.some((d) => d === "stability" || d === "rtSafety" || d === "features");
|
|
149
|
+
const note = [
|
|
150
|
+
hasCost ? RELATIVE_RANKING_NOTE : undefined,
|
|
151
|
+
hasLatency ? LATENCY_ONLY_NOTE : undefined,
|
|
152
|
+
hasHosting ? HOSTING_NOTE : undefined,
|
|
153
|
+
]
|
|
154
|
+
.filter((part) => part !== undefined)
|
|
155
|
+
.join(" ");
|
|
156
|
+
return {
|
|
157
|
+
id: input.id,
|
|
158
|
+
measuredAt: input.measuredAt,
|
|
159
|
+
dimensions,
|
|
160
|
+
rates: [...input.rates].sort((a, b) => a - b),
|
|
161
|
+
tool: input.tool,
|
|
162
|
+
host: input.host,
|
|
163
|
+
method: {
|
|
164
|
+
...optional("latency", input.latencyMethod),
|
|
165
|
+
...optional("cost", input.costMethod),
|
|
166
|
+
},
|
|
167
|
+
relativeRankingOnly: hasCost,
|
|
168
|
+
note: note.length > 0 ? note : RELATIVE_RANKING_NOTE,
|
|
169
|
+
...optional("elapsedSeconds", input.elapsedSeconds),
|
|
170
|
+
...optional("label", input.label),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=provenance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provenance.js","sourceRoot":"","sources":["../../src/measurement/provenance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hosting sweep's JSONL → the interchange format's three hosting dimensions.
|
|
3
|
+
*
|
|
4
|
+
* `lv2-measure` (a C lilv host, run under an
|
|
5
|
+
* allocation-and-syscall interposer) writes one JSON line per plugin, and the sweep ran it in THREE passes a
|
|
6
|
+
* reader has to put back together:
|
|
7
|
+
*
|
|
8
|
+
* - the QUICK pass (`all-measurements.jsonl`): every plugin, 30 lifecycles, the interposer's
|
|
9
|
+
* counts, the required features, the threads;
|
|
10
|
+
* - the DEEP lifecycle pass (`lifecycle-1000.jsonl`): the candidates again at 1000 lifecycles —
|
|
11
|
+
* where present it SUPERSEDES the quick pass's lifecycle and threads, and a crash in it is a
|
|
12
|
+
* crash the quick pass could not have seen;
|
|
13
|
+
* - the SOAK (`soak*.jsonl`): a show-length render per rate with every control swept slowly
|
|
14
|
+
* across its range (`lv2-measure.c`'s soak mode), one line per plugin.
|
|
15
|
+
*
|
|
16
|
+
* Like `probe-json.ts`, this is a TRANSLATION and never a judgement. An exit status of 139 is
|
|
17
|
+
* written as the signal it is (128 + SIGSEGV); whether that crash is the plugin's fault, whether a
|
|
18
|
+
* soak held, whether a count is a measurement at all — those are the consumer's rules, and a
|
|
19
|
+
* reader that decided them would be a second classifier ahead of the real one.
|
|
20
|
+
*/
|
|
21
|
+
import type { MeasuredPlugin } from "./format.js";
|
|
22
|
+
/** The sweep's three passes, each a list of parsed JSONL lines. Only `measurements` is required. */
|
|
23
|
+
export interface SweepPasses {
|
|
24
|
+
readonly measurements: readonly unknown[];
|
|
25
|
+
readonly lifecycle?: readonly unknown[];
|
|
26
|
+
readonly soak?: readonly unknown[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Every plugin the sweep INSTANTIATED, as a {@link MeasuredPlugin} carrying the three hosting
|
|
30
|
+
* dimensions.
|
|
31
|
+
*
|
|
32
|
+
* A line for a plugin the tool could not find or instantiate carries no topology, and the
|
|
33
|
+
* format's plugin entry requires one (rule 2: no invented numbers). It is left out rather than
|
|
34
|
+
* written with a reason: its three blocks would all be `probe-failed`, which by the merge's own
|
|
35
|
+
* rule override nothing — a line with no reading and no power to change a figure.
|
|
36
|
+
*/
|
|
37
|
+
export declare function measuredPluginsFromSweep(passes: SweepPasses): MeasuredPlugin[];
|
|
38
|
+
/** Parse JSONL text: every line that is a JSON object, in order. A plugin's own stdout banner
|
|
39
|
+
* before the JSON is the sweep harness's problem and already stripped there. */
|
|
40
|
+
export declare function parseJsonl(text: string): unknown[];
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
3
|
+
/**
|
|
4
|
+
* The hosting sweep's JSONL → the interchange format's three hosting dimensions.
|
|
5
|
+
*
|
|
6
|
+
* `lv2-measure` (a C lilv host, run under an
|
|
7
|
+
* allocation-and-syscall interposer) writes one JSON line per plugin, and the sweep ran it in THREE passes a
|
|
8
|
+
* reader has to put back together:
|
|
9
|
+
*
|
|
10
|
+
* - the QUICK pass (`all-measurements.jsonl`): every plugin, 30 lifecycles, the interposer's
|
|
11
|
+
* counts, the required features, the threads;
|
|
12
|
+
* - the DEEP lifecycle pass (`lifecycle-1000.jsonl`): the candidates again at 1000 lifecycles —
|
|
13
|
+
* where present it SUPERSEDES the quick pass's lifecycle and threads, and a crash in it is a
|
|
14
|
+
* crash the quick pass could not have seen;
|
|
15
|
+
* - the SOAK (`soak*.jsonl`): a show-length render per rate with every control swept slowly
|
|
16
|
+
* across its range (`lv2-measure.c`'s soak mode), one line per plugin.
|
|
17
|
+
*
|
|
18
|
+
* Like `probe-json.ts`, this is a TRANSLATION and never a judgement. An exit status of 139 is
|
|
19
|
+
* written as the signal it is (128 + SIGSEGV); whether that crash is the plugin's fault, whether a
|
|
20
|
+
* soak held, whether a count is a measurement at all — those are the consumer's rules, and a
|
|
21
|
+
* reader that decided them would be a second classifier ahead of the real one.
|
|
22
|
+
*/
|
|
23
|
+
import { readLifecycle, readSoakAtRate, readThreads } from "./format.js";
|
|
24
|
+
import { booleanValue, finiteNumber, isRecord, mapArray, nonEmptyString, optional, positiveNumber } from "./guards.js";
|
|
25
|
+
/** A process exit status that IS a signal death, as the signal's name. Anything else is not a
|
|
26
|
+
* crash this reader can name — a timeout (124) or an early refusal is the run failing, not the
|
|
27
|
+
* plugin dying, and it is not written as a crash. */
|
|
28
|
+
const SIGNAL_EXITS = {
|
|
29
|
+
134: "SIGABRT",
|
|
30
|
+
135: "SIGBUS",
|
|
31
|
+
139: "SIGSEGV",
|
|
32
|
+
};
|
|
33
|
+
function crashOf(row) {
|
|
34
|
+
if (row.crashed !== true)
|
|
35
|
+
return undefined;
|
|
36
|
+
const exit = finiteNumber(row.exit);
|
|
37
|
+
const signal = exit === undefined ? undefined : SIGNAL_EXITS[exit];
|
|
38
|
+
return signal === undefined ? undefined : { signal };
|
|
39
|
+
}
|
|
40
|
+
/** One soak line's per-rate map. The tool writes `block` and a redundant `rate` inside each
|
|
41
|
+
* reading; the rate is the key, and `block` is the format's `blockFrames`. */
|
|
42
|
+
function soakOf(row) {
|
|
43
|
+
if (!isRecord(row) || !isRecord(row.perRate))
|
|
44
|
+
return undefined;
|
|
45
|
+
const perRate = {};
|
|
46
|
+
for (const [key, reading] of Object.entries(row.perRate)) {
|
|
47
|
+
const rate = positiveNumber(Number(key));
|
|
48
|
+
if (rate === undefined || !Number.isInteger(rate) || !isRecord(reading))
|
|
49
|
+
continue;
|
|
50
|
+
const parsed = readSoakAtRate({ ...reading, blockFrames: reading.block });
|
|
51
|
+
if (parsed !== undefined)
|
|
52
|
+
perRate[String(rate)] = parsed;
|
|
53
|
+
}
|
|
54
|
+
// lv2-measure's soak mode sweeps every control across its range for the whole render.
|
|
55
|
+
return { sweptParams: true, perRate };
|
|
56
|
+
}
|
|
57
|
+
function stabilityOf(quick, deep, soakRow) {
|
|
58
|
+
const lifecycle = readLifecycle(deep?.lifecycle) ?? readLifecycle(quick.lifecycle);
|
|
59
|
+
if (lifecycle === undefined)
|
|
60
|
+
return { unmeasuredReason: "probe-error", unmeasuredDetail: "no lifecycle in the sweep line" };
|
|
61
|
+
const threads = readThreads(deep?.threads) ?? readThreads(quick.threads);
|
|
62
|
+
const crashes = [crashOf(quick), deep === undefined ? undefined : crashOf(deep)].filter((c) => c !== undefined);
|
|
63
|
+
const soak = soakRow === undefined ? undefined : soakOf(soakRow);
|
|
64
|
+
return {
|
|
65
|
+
lifecycle,
|
|
66
|
+
...optional("threads", threads),
|
|
67
|
+
crashes,
|
|
68
|
+
...optional("soak", soak),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function rtSafetyOf(row) {
|
|
72
|
+
const rt = row.rtSafety;
|
|
73
|
+
if (!isRecord(rt))
|
|
74
|
+
return { unmeasuredReason: "probe-error", unmeasuredDetail: "no interposer block in the sweep line" };
|
|
75
|
+
return {
|
|
76
|
+
...optional("rate", positiveNumber(row.rate)),
|
|
77
|
+
...optional("blockFrames", positiveNumber(row.block)),
|
|
78
|
+
...optional("blocks", finiteNumber(rt.blocks)),
|
|
79
|
+
...optional("repeats", finiteNumber(rt.repeats)),
|
|
80
|
+
...optional("interposer", booleanValue(rt.interposer)),
|
|
81
|
+
...optional("swept", booleanValue(rt.swept)),
|
|
82
|
+
...optional("sweepApplicable", booleanValue(rt.sweepApplicable)),
|
|
83
|
+
...optional("allocationsInRun", finiteNumber(rt.allocationsInRun)),
|
|
84
|
+
...optional("allocationsMin", finiteNumber(rt.allocationsMin)),
|
|
85
|
+
...optional("locksInRun", finiteNumber(rt.locksInRun)),
|
|
86
|
+
...optional("syscallsInRun", finiteNumber(rt.syscallsInRun)),
|
|
87
|
+
...optional("variable", booleanValue(rt.variable)),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function featuresOf(row) {
|
|
91
|
+
const required = mapArray(row.requiredFeatures, nonEmptyString);
|
|
92
|
+
if (required === undefined)
|
|
93
|
+
return { unmeasuredReason: "probe-error", unmeasuredDetail: "no feature list in the sweep line" };
|
|
94
|
+
return {
|
|
95
|
+
required,
|
|
96
|
+
...optional("optional", mapArray(row.optionalFeatures, nonEmptyString)),
|
|
97
|
+
...optional("cvPorts", finiteNumber(row.cvPorts)),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function byUri(rows) {
|
|
101
|
+
const out = new Map();
|
|
102
|
+
for (const row of rows ?? []) {
|
|
103
|
+
if (!isRecord(row))
|
|
104
|
+
continue;
|
|
105
|
+
const uri = nonEmptyString(row.uri);
|
|
106
|
+
if (uri !== undefined)
|
|
107
|
+
out.set(uri, row);
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Every plugin the sweep INSTANTIATED, as a {@link MeasuredPlugin} carrying the three hosting
|
|
113
|
+
* dimensions.
|
|
114
|
+
*
|
|
115
|
+
* A line for a plugin the tool could not find or instantiate carries no topology, and the
|
|
116
|
+
* format's plugin entry requires one (rule 2: no invented numbers). It is left out rather than
|
|
117
|
+
* written with a reason: its three blocks would all be `probe-failed`, which by the merge's own
|
|
118
|
+
* rule override nothing — a line with no reading and no power to change a figure.
|
|
119
|
+
*/
|
|
120
|
+
export function measuredPluginsFromSweep(passes) {
|
|
121
|
+
const deep = byUri(passes.lifecycle);
|
|
122
|
+
const soak = byUri(passes.soak);
|
|
123
|
+
const out = [];
|
|
124
|
+
for (const row of passes.measurements) {
|
|
125
|
+
if (!isRecord(row))
|
|
126
|
+
continue;
|
|
127
|
+
const uri = nonEmptyString(row.uri);
|
|
128
|
+
const audioInputs = finiteNumber(row.audioInputs);
|
|
129
|
+
const audioOutputs = finiteNumber(row.audioOutputs);
|
|
130
|
+
if (uri === undefined || audioInputs === undefined || audioOutputs === undefined)
|
|
131
|
+
continue;
|
|
132
|
+
out.push({
|
|
133
|
+
uri,
|
|
134
|
+
topology: {
|
|
135
|
+
audioInputs,
|
|
136
|
+
audioOutputs,
|
|
137
|
+
...optional("controlInputs", finiteNumber(row.controlInputs)),
|
|
138
|
+
},
|
|
139
|
+
stability: stabilityOf(row, deep.get(uri), soak.get(uri)),
|
|
140
|
+
rtSafety: rtSafetyOf(row),
|
|
141
|
+
features: featuresOf(row),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
/** Parse JSONL text: every line that is a JSON object, in order. A plugin's own stdout banner
|
|
147
|
+
* before the JSON is the sweep harness's problem and already stripped there. */
|
|
148
|
+
export function parseJsonl(text) {
|
|
149
|
+
return text
|
|
150
|
+
.split("\n")
|
|
151
|
+
.filter((line) => line.trim().startsWith("{"))
|
|
152
|
+
.map((line) => JSON.parse(line));
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=sweep-jsonl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sweep-jsonl.js","sourceRoot":"","sources":["../../src/measurement/sweep-jsonl.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The controlled vocabularies the interchange format speaks.
|
|
3
|
+
*
|
|
4
|
+
* This is the boring file that decides whether two projects' measurements can be merged
|
|
5
|
+
* Everyone who has written an LV2 prober has invented their own
|
|
6
|
+
* words for "there was nothing to measure here", so nobody's results combine. The words
|
|
7
|
+
* are fixed here, each with the ONE property a merger actually needs to branch on —
|
|
8
|
+
* whether the plugin has nothing to measure or whether OUR PROBE failed — and each with
|
|
9
|
+
* a plain description a UI can render.
|
|
10
|
+
*
|
|
11
|
+
* Everything is a data table, not a switch. Adding a term is one row; a consumer that
|
|
12
|
+
* meets a term it does not know must be able to fall back on the `kind`, which is why the
|
|
13
|
+
* readers below return a term record rather than a bare string.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* What an absent figure means for a MERGE, and it is the only distinction that changes
|
|
17
|
+
* behaviour:
|
|
18
|
+
*
|
|
19
|
+
* - `nothing-to-measure` — a property of the PLUGIN. An instrument has no audio input to
|
|
20
|
+
* drive; a meter has no audio output to read. Any prober on any machine reaches the
|
|
21
|
+
* same conclusion, so this outcome is as portable as a measurement and may override one.
|
|
22
|
+
* - `probe-failed` — a property of THIS RUN. The plugin crashed the worker, timed out,
|
|
23
|
+
* refused to instantiate. It says nothing about the plugin's latency or cost, so it
|
|
24
|
+
* must NOT override a figure someone else managed to take.
|
|
25
|
+
* - `not-attempted` — the run did not cover this plugin or this dimension at all. Neither
|
|
26
|
+
* a finding nor a failure; it is the shape a PARTIAL run has, and partial runs are
|
|
27
|
+
* first-class here.
|
|
28
|
+
*/
|
|
29
|
+
export type UnmeasuredKind = "nothing-to-measure" | "probe-failed" | "not-attempted";
|
|
30
|
+
/** One term of the "why is there no figure" vocabulary. */
|
|
31
|
+
export interface UnmeasuredTerm {
|
|
32
|
+
readonly code: string;
|
|
33
|
+
readonly kind: UnmeasuredKind;
|
|
34
|
+
/** Plain description, English. A UI localises from `code`; this is for logs and specs. */
|
|
35
|
+
readonly description: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Every reason a figure may be absent. These were derived from a sweep of 958 plugins — each
|
|
39
|
+
* one is an outcome that sweep actually produced, not a hypothetical.
|
|
40
|
+
*/
|
|
41
|
+
export declare const UNMEASURED_TERMS: readonly UnmeasuredTerm[];
|
|
42
|
+
/**
|
|
43
|
+
* The vocabulary term for a code, or `undefined` when the code is not one of ours.
|
|
44
|
+
*
|
|
45
|
+
* A third-party document carrying an unknown reason is not an error — the format is meant
|
|
46
|
+
* to be extended by people we will never meet. {@link unmeasuredKind} is what a merger
|
|
47
|
+
* should consult, and it is deliberately conservative about strangers.
|
|
48
|
+
*/
|
|
49
|
+
export declare function unmeasuredTerm(code: string | undefined): UnmeasuredTerm | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* How a merger must treat an absence reason. An UNKNOWN code resolves to `probe-failed`,
|
|
52
|
+
* the conservative answer: a reason we cannot interpret must never be allowed to delete
|
|
53
|
+
* somebody else's real measurement, and treating it as a plugin property would do exactly
|
|
54
|
+
* that.
|
|
55
|
+
*/
|
|
56
|
+
export declare function unmeasuredKind(code: string | undefined): UnmeasuredKind;
|
|
57
|
+
/**
|
|
58
|
+
* The rate-scaling laws, DERIVED from readings at several rates and never assumed from
|
|
59
|
+
* what a plugin is called. The population behind each term is in
|
|
60
|
+
* `CATALOG_PROVENANCE.md`'s 2026-07-28 rows.
|
|
61
|
+
*/
|
|
62
|
+
export interface ScalingTerm {
|
|
63
|
+
readonly code: string;
|
|
64
|
+
readonly description: string;
|
|
65
|
+
}
|
|
66
|
+
export declare const SCALING_TERMS: readonly ScalingTerm[];
|
|
67
|
+
/** True when `code` is a scaling term this version of the format defines. */
|
|
68
|
+
export declare function isKnownScalingCode(code: string): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Why a derived scaling class is not the naive fit over every reading — `below-budget`,
|
|
71
|
+
* where the readings fit no law but are all small enough that the disagreement is the onset
|
|
72
|
+
* detector's rather than the plugin's, and `informational-rate`, where only a reading below
|
|
73
|
+
* the operational rates breaks the law (`lv2-measurement-interchange.md` §1a).
|
|
74
|
+
*/
|
|
75
|
+
export declare const SCALING_NOTE_TERMS: readonly [{
|
|
76
|
+
readonly code: "below-budget";
|
|
77
|
+
readonly description: string;
|
|
78
|
+
}, {
|
|
79
|
+
readonly code: "informational-rate";
|
|
80
|
+
readonly description: string;
|
|
81
|
+
}];
|
|
82
|
+
/** One of the {@link SCALING_NOTE_TERMS} codes — the words `latency.scalingNote` may carry. */
|
|
83
|
+
export type ScalingNoteCode = (typeof SCALING_NOTE_TERMS)[number]["code"];
|
|
84
|
+
/**
|
|
85
|
+
* Why one rate's reading was excluded from the fit and from every rate resolution. A
|
|
86
|
+
* failed reading served as an exact answer is worse than no reading, because a zero reads
|
|
87
|
+
* as confidence.
|
|
88
|
+
*/
|
|
89
|
+
export declare const EXCLUSION_TERMS: readonly ScalingTerm[];
|
|
90
|
+
/** The measurement methods the format has names for. */
|
|
91
|
+
export declare const METHOD_TERMS: readonly ScalingTerm[];
|
|
92
|
+
/** True when `code` is a method term this version of the format defines. */
|
|
93
|
+
export declare function isKnownMethodCode(code: string): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* The measurable dimensions. A document declares which of them it covers.
|
|
96
|
+
*
|
|
97
|
+
* `latency` and `cost` are 1.0's. `stability`, `rtSafety` and `features` joined in 1.1 for the
|
|
98
|
+
* hosting verdict — whether a plugin survives, whether its `run()` is RT-clean, what it
|
|
99
|
+
* demands of a host — and merge under exactly the same per-plugin, per-DIMENSION precedence.
|
|
100
|
+
*/
|
|
101
|
+
export declare const MEASUREMENT_DIMENSIONS: readonly ["latency", "cost", "stability", "rtSafety", "features"];
|
|
102
|
+
/**
|
|
103
|
+
* The dimensions the standalone prober (`scan.py` + `benchmark.py`, driven by `lv2-measure.mjs`
|
|
104
|
+
* and the console's Setup analysis) can measure. The three hosting dimensions come from a
|
|
105
|
+
* different tool — the `lv2-measure.c` sweep, read by `sweep-jsonl.ts` — so a run asked of the
|
|
106
|
+
* prober for `stability` would be asking it for something it cannot take.
|
|
107
|
+
*/
|
|
108
|
+
export declare const PROBED_DIMENSIONS: readonly ["latency", "cost"];
|
|
109
|
+
/** One measurable dimension. */
|
|
110
|
+
export type MeasurementDimension = (typeof MEASUREMENT_DIMENSIONS)[number];
|
|
111
|
+
/** `value` as a dimension, or `undefined` when it is not one. */
|
|
112
|
+
export declare function readDimension(value: unknown): MeasurementDimension | undefined;
|
|
113
|
+
/**
|
|
114
|
+
* The four ratings a verdict speaks (`lv2-measurement-interchange.md` §3b) — the ONE list; the
|
|
115
|
+
* rating vocabulary (`rating.ts`) derives its type from it, so the document and the classifier
|
|
116
|
+
* can never disagree on a word.
|
|
117
|
+
*/
|
|
118
|
+
export declare const VERDICT_RATINGS: readonly ["suitable", "conditional", "unknown", "unsuitable"];
|
|
119
|
+
export type VerdictRating = (typeof VERDICT_RATINGS)[number];
|
|
120
|
+
/** How a host runs its plugins (`2026-09-25-plugin-qualify.md` §3a). */
|
|
121
|
+
export declare const HOST_ISOLATIONS: readonly ["in-process", "per-process", "shared-process"];
|
|
122
|
+
export type HostIsolation = (typeof HOST_ISOLATIONS)[number];
|
|
123
|
+
/** Whether a host aligns on a plugin's reported latency (`compensated`) or only passes it on. */
|
|
124
|
+
export declare const HOST_LATENCY_HANDLING: readonly ["compensated", "uncompensated"];
|
|
125
|
+
export type HostLatencyHandling = (typeof HOST_LATENCY_HANDLING)[number];
|