From 96e21b22828abdc67787e1597b5fd748c4c0a47f Mon Sep 17 00:00:00 2001 From: Babayaga Date: Tue, 14 Apr 2026 13:43:50 +0200 Subject: [PATCH] media:cpp docs | http | tests --- packages/media/cpp/orchestrator/reports.js | 27 +- .../media/cpp/orchestrator/test-media.mjs | 295 +++++++++++------- packages/media/cpp/tests/test-report-last.md | 60 ++-- packages/media/cpp/todos.md | 4 + 4 files changed, 235 insertions(+), 151 deletions(-) create mode 100644 packages/media/cpp/todos.md diff --git a/packages/media/cpp/orchestrator/reports.js b/packages/media/cpp/orchestrator/reports.js index f1ec9fae..6049b0b9 100644 --- a/packages/media/cpp/orchestrator/reports.js +++ b/packages/media/cpp/orchestrator/reports.js @@ -298,17 +298,30 @@ export function renderMarkdownReport(payload) { .replace(/\r\n/g, '
') .replace(/\n/g, '
'); + const fmtBytesCell = (n) => { + if (n == null || n === '') return '—'; + const num = Number(n); + if (!Number.isFinite(num)) return escCell(String(n)); + return `${formatBytes(num)} (${num} B)`; + }; + const fmtMsCell = (ms) => { + if (ms == null || ms === '') return '—'; + const n = Number(ms); + if (!Number.isFinite(n)) return '—'; + return String(Math.round(n * 100) / 100); + }; + if (Array.isArray(payload.images) && payload.images.length > 0) { lines.push('## Images & transfers'); lines.push(''); - lines.push('| Label | Size | Dimensions / detail |'); - lines.push('| --- | --- | --- |'); + lines.push('| Label | Input | Output | Time (ms) | Detail |'); + lines.push('| --- | --- | --- | ---: | --- |'); for (const row of payload.images) { const label = escCell(row.label ?? '—'); - const bytes = - row.bytes != null && row.bytes !== '' - ? `${formatBytes(Number(row.bytes))} (${row.bytes} B)` - : '—'; + const outRaw = row.outputBytes != null ? row.outputBytes : row.bytes; + const inputCol = fmtBytesCell(row.inputBytes); + const outputCol = fmtBytesCell(outRaw); + const timeCol = fmtMsCell(row.computeMs != null ? row.computeMs : row.ms); const parts = []; if (row.widthPx != null && row.heightPx != null) { parts.push(`${row.widthPx}×${row.heightPx} px`); @@ -317,7 +330,7 @@ export function renderMarkdownReport(payload) { if (row.note) parts.push(String(row.note)); if (row.detail) parts.push(String(row.detail)); const detailCol = parts.length ? escCell(parts.join(' · ')) : '—'; - lines.push(`| ${label} | ${bytes} | ${detailCol} |`); + lines.push(`| ${label} | ${inputCol} | ${outputCol} | ${timeCol} | ${detailCol} |`); } lines.push(''); } diff --git a/packages/media/cpp/orchestrator/test-media.mjs b/packages/media/cpp/orchestrator/test-media.mjs index bf0446d7..a574cf0f 100644 --- a/packages/media/cpp/orchestrator/test-media.mjs +++ b/packages/media/cpp/orchestrator/test-media.mjs @@ -158,7 +158,7 @@ function registerFixtureImages(rep, assetsDir) { const d = describePngFile(p); rep.addImage({ label, - bytes: d.bytes, + inputBytes: d.bytes, widthPx: d.widthPx, heightPx: d.heightPx, note: 'on-disk fixture', @@ -171,7 +171,7 @@ function timeSync(rep, label, fn, detailFn) { const result = fn(); const ms = performance.now() - t0; rep.step(label, ms, detailFn ? detailFn(result) : ''); - return result; + return { result, ms }; } async function timeAsync(rep, label, fn, detailFn) { @@ -179,7 +179,7 @@ async function timeAsync(rep, label, fn, detailFn) { const result = await fn(); const ms = performance.now() - t0; rep.step(label, ms, detailFn ? detailFn(result) : ''); - return result; + return { result, ms }; } function writeTestReportFile(rep, metricsCollector, startedAtIso) { @@ -220,9 +220,10 @@ function writeTestReportFile(rep, metricsCollector, startedAtIso) { async function multipartResizeTests(base, inPng, rep) { const pngBytes = readFileSync(inPng); const srcDim = pngDimensionsFromBuffer(pngBytes); + const inPngSize = fileByteSize(inPng) ?? pngBytes.length; rep.addImage({ label: 'multipart upload source (PNG body in form)', - bytes: pngBytes.length, + inputBytes: inPngSize, widthPx: srcDim?.width ?? null, heightPx: srcDim?.height ?? null, note: 'fixture bytes attached to each multipart request', @@ -233,25 +234,30 @@ async function multipartResizeTests(base, inPng, rep) { form.append('file', blob(), 'square-64.png'); form.append('max_width', '32'); form.append('max_height', '32'); - const r1 = await timeAsync( + const { result: r1, ms: r1Ms } = await timeAsync( rep, 'multipart POST (file → jpeg)', - () => - fetch(`${base}/v1/resize`, { + async () => { + const res = await fetch(`${base}/v1/resize`, { method: 'POST', body: form, signal: AbortSignal.timeout(timeouts.httpMs), - }), - (res) => `HTTP ${res.status}, ${res.headers.get('content-type') ?? ''}`, + }); + const ab = await res.arrayBuffer(); + return { res, ab }; + }, + (x) => `HTTP ${x.res.status}, ${x.res.headers.get('content-type') ?? ''}`, ); - assert(r1.ok, 'multipart: field file'); - assert(r1.headers.get('content-type') === 'image/jpeg', 'multipart default format → image/jpeg'); + assert(r1.res.ok, 'multipart: field file'); + assert(r1.res.headers.get('content-type') === 'image/jpeg', 'multipart default format → image/jpeg'); { - const ab = await r1.arrayBuffer(); + const ab = r1.ab; rep.addImage({ label: 'multipart HTTP response (→ jpeg)', - bytes: ab.byteLength, - contentType: r1.headers.get('content-type') ?? '', + inputBytes: inPngSize, + outputBytes: ab.byteLength, + computeMs: r1Ms, + contentType: r1.res.headers.get('content-type') ?? '', note: 'resized output body', }); assert(Buffer.from(ab).length > 0, 'multipart file: non-empty body'); @@ -261,25 +267,30 @@ async function multipartResizeTests(base, inPng, rep) { fImage.append('image', blob(), 'x.png'); fImage.append('max_width', '24'); fImage.append('format', 'webp'); - const r2 = await timeAsync( + const { result: r2, ms: r2Ms } = await timeAsync( rep, 'multipart POST (image → webp)', - () => - fetch(`${base}/v1/resize`, { + async () => { + const res = await fetch(`${base}/v1/resize`, { method: 'POST', body: fImage, signal: AbortSignal.timeout(timeouts.httpMs), - }), - (res) => `HTTP ${res.status}, ${res.headers.get('content-type') ?? ''}`, + }); + const ab = await res.arrayBuffer(); + return { res, ab }; + }, + (x) => `HTTP ${x.res.status}, ${x.res.headers.get('content-type') ?? ''}`, ); - assert(r2.ok, 'multipart: field image + format webp'); - assert(r2.headers.get('content-type') === 'image/webp', 'multipart webp → image/webp'); + assert(r2.res.ok, 'multipart: field image + format webp'); + assert(r2.res.headers.get('content-type') === 'image/webp', 'multipart webp → image/webp'); { - const ab = await r2.arrayBuffer(); + const ab = r2.ab; rep.addImage({ label: 'multipart HTTP response (→ webp)', - bytes: ab.byteLength, - contentType: r2.headers.get('content-type') ?? '', + inputBytes: inPngSize, + outputBytes: ab.byteLength, + computeMs: r2Ms, + contentType: r2.res.headers.get('content-type') ?? '', note: 'resized output body', }); assert(Buffer.from(ab).byteLength > 8, 'multipart webp: RIFF/WebP header size'); @@ -288,25 +299,30 @@ async function multipartResizeTests(base, inPng, rep) { const fUpload = new FormData(); fUpload.append('upload', blob(), 'up.png'); fUpload.append('max_width', '20'); - const r3 = await timeAsync( + const { result: r3, ms: r3Ms } = await timeAsync( rep, 'multipart POST (upload alias → jpeg)', - () => - fetch(`${base}/v1/resize`, { + async () => { + const res = await fetch(`${base}/v1/resize`, { method: 'POST', body: fUpload, signal: AbortSignal.timeout(timeouts.httpMs), - }), - (res) => `HTTP ${res.status}, ${res.headers.get('content-type') ?? ''}`, + }); + const ab = await res.arrayBuffer(); + return { res, ab }; + }, + (x) => `HTTP ${x.res.status}, ${x.res.headers.get('content-type') ?? ''}`, ); - assert(r3.ok, 'multipart: field upload'); - assert(r3.headers.get('content-type') === 'image/jpeg', 'multipart upload alias → jpeg'); + assert(r3.res.ok, 'multipart: field upload'); + assert(r3.res.headers.get('content-type') === 'image/jpeg', 'multipart upload alias → jpeg'); { - const ab = await r3.arrayBuffer(); + const ab = r3.ab; rep.addImage({ label: 'multipart HTTP response (upload field → jpeg)', - bytes: ab.byteLength, - contentType: r3.headers.get('content-type') ?? '', + inputBytes: inPngSize, + outputBytes: ab.byteLength, + computeMs: r3Ms, + contentType: r3.res.headers.get('content-type') ?? '', note: 'resized output body', }); assert(Buffer.from(ab).length > 0, 'multipart upload: non-empty body'); @@ -316,54 +332,62 @@ async function multipartResizeTests(base, inPng, rep) { fPng.append('file', blob(), 'square-64.png'); fPng.append('max_width', '16'); fPng.append('format', 'png'); - const r4 = await timeAsync( + const { result: r4, ms: r4Ms } = await timeAsync( rep, 'multipart POST (file → png)', - () => - fetch(`${base}/v1/resize`, { + async () => { + const res = await fetch(`${base}/v1/resize`, { method: 'POST', body: fPng, signal: AbortSignal.timeout(timeouts.httpMs), - }), - (res) => `HTTP ${res.status}, ${res.headers.get('content-type') ?? ''}`, + }); + const ab = await res.arrayBuffer(); + return { res, ab }; + }, + (x) => `HTTP ${x.res.status}, ${x.res.headers.get('content-type') ?? ''}`, ); - assert(r4.ok, 'multipart: format png'); - assert(r4.headers.get('content-type') === 'image/png', 'multipart png → image/png'); + assert(r4.res.ok, 'multipart: format png'); + assert(r4.res.headers.get('content-type') === 'image/png', 'multipart png → image/png'); { - const ab = await r4.arrayBuffer(); + const ab = r4.ab; const dim = pngDimensionsFromBuffer(Buffer.from(ab)); rep.addImage({ label: 'multipart HTTP response (→ png)', - bytes: ab.byteLength, + inputBytes: inPngSize, + outputBytes: ab.byteLength, + computeMs: r4Ms, widthPx: dim?.width ?? null, heightPx: dim?.height ?? null, - contentType: r4.headers.get('content-type') ?? '', + contentType: r4.res.headers.get('content-type') ?? '', note: 'resized output body', }); } const noFile = new FormData(); noFile.append('max_width', '10'); - const r5 = await timeAsync( + const { result: r5, ms: r5Ms } = await timeAsync( rep, 'multipart POST (no file → 400)', - () => - fetch(`${base}/v1/resize`, { + async () => { + const res = await fetch(`${base}/v1/resize`, { method: 'POST', body: noFile, signal: AbortSignal.timeout(timeouts.httpMs), - }), - (res) => `HTTP ${res.status}`, + }); + const errText = await res.text(); + return { res, errText }; + }, + (x) => `HTTP ${x.res.status}`, ); - assert(r5.status === 400, 'multipart without image → 400'); - const errText = await r5.text(); + assert(r5.res.status === 400, 'multipart without image → 400'); rep.addImage({ label: 'multipart HTTP 400 error body', - bytes: Buffer.byteLength(errText, 'utf8'), - contentType: r5.headers.get('content-type') ?? '', + outputBytes: Buffer.byteLength(r5.errText, 'utf8'), + computeMs: r5Ms, + contentType: r5.res.headers.get('content-type') ?? '', note: 'JSON error', }); - const jErr = JSON.parse(errText); + const jErr = JSON.parse(r5.errText); assert(jErr?.error && typeof jErr.error === 'string', 'multipart 400 JSON error body'); } @@ -383,7 +407,7 @@ async function suiteMultipartOnly(assetsDir, rep) { try { await timeAsync(rep, 'wait until HTTP server accepts', () => waitListen('127.0.0.1', port, 'serve'), () => `127.0.0.1:${port}`); const base = `http://127.0.0.1:${port}`; - const h = await timeAsync( + const { result: h } = await timeAsync( rep, 'GET /health', () => fetch(`${base}/health`, { signal: AbortSignal.timeout(timeouts.httpMs) }), @@ -418,7 +442,7 @@ async function suiteRest(assetsDir, rep) { await timeAsync(rep, 'wait until HTTP server accepts', () => waitListen('127.0.0.1', port, 'serve'), () => `127.0.0.1:${port}`); const base = `http://127.0.0.1:${port}`; - const h = await timeAsync( + const { result: h } = await timeAsync( rep, 'GET /health', () => fetch(`${base}/health`, { signal: AbortSignal.timeout(timeouts.httpMs) }), @@ -430,11 +454,12 @@ async function suiteRest(assetsDir, rep) { const outDir = mkdtempSync(join(tmpdir(), 'media-rest-')); const outPng = join(outDir, 'out-32.png'); - const r1 = await timeAsync( + const inPngBytes = fileByteSize(inPng); + const { result: r1, ms: r1Ms } = await timeAsync( rep, 'POST /v1/resize JSON → PNG on disk', - () => - fetch(`${base}/v1/resize`, { + async () => { + const res = await fetch(`${base}/v1/resize`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -444,18 +469,23 @@ async function suiteRest(assetsDir, rep) { max_height: 32, }), signal: AbortSignal.timeout(timeouts.httpMs), - }), - (res) => `HTTP ${res.status}`, + }); + const j = await res.json(); + return { res, j }; + }, + (x) => `HTTP ${x.res.status}`, ); - assert(r1.ok, 'POST /v1/resize ok'); - const j1 = await r1.json(); + assert(r1.res.ok, 'POST /v1/resize ok'); + const j1 = r1.j; assert(j1?.ok === true, 'resize response ok'); assert(existsSync(outPng), 'output png exists'); { const d = describePngFile(outPng); rep.addImage({ label: 'REST JSON → disk (out-32.png)', - bytes: d.bytes, + inputBytes: inPngBytes, + outputBytes: d.bytes, + computeMs: r1Ms, widthPx: d.widthPx, heightPx: d.heightPx, note: 'server wrote from JSON resize', @@ -463,11 +493,11 @@ async function suiteRest(assetsDir, rep) { } const outJpg = join(outDir, 'out.jpg'); - const r2 = await timeAsync( + const { result: r2, ms: r2Ms } = await timeAsync( rep, 'POST /v1/resize JSON → JPEG on disk', - () => - fetch(`${base}/v1/resize`, { + async () => { + const res = await fetch(`${base}/v1/resize`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -477,17 +507,22 @@ async function suiteRest(assetsDir, rep) { format: 'jpeg', }), signal: AbortSignal.timeout(timeouts.httpMs), - }), - (res) => `HTTP ${res.status}`, + }); + const j = await res.json(); + return { res, j }; + }, + (x) => `HTTP ${x.res.status}`, ); - assert(r2.ok, 'POST /v1/resize jpeg'); + assert(r2.res.ok, 'POST /v1/resize jpeg'); assert(existsSync(outJpg), 'output jpg exists'); { const b = fileByteSize(outJpg); if (b != null) { rep.addImage({ label: 'REST JSON → disk (out.jpg)', - bytes: b, + inputBytes: inPngBytes, + outputBytes: b, + computeMs: r2Ms, contentType: 'image/jpeg', note: 'server wrote from JSON resize', }); @@ -496,7 +531,7 @@ async function suiteRest(assetsDir, rep) { await multipartResizeTests(base, inPng, rep); - const bad = await timeAsync( + const { result: bad } = await timeAsync( rep, 'POST /v1/resize JSON missing input file → 500', () => @@ -537,7 +572,7 @@ async function suiteDstTemplateRest(assetsDir, rep) { const outDir = mkdtempSync(join(tmpdir(), 'media-dst-rest-')); const outPattern = join(outDir, '${SRC_NAME}_thumb.webp'); - const rt = await timeAsync( + const { result: rt } = await timeAsync( rep, 'POST ${SRC_NAME}_thumb.webp template', () => @@ -561,7 +596,7 @@ async function suiteDstTemplateRest(assetsDir, rep) { assert(existsSync(expectedWebp), `expected ${expectedWebp}`); const outAmp = join(outDir, '&{SRC_NAME}_tiny.png'); - const r2 = await timeAsync( + const { result: r2 } = await timeAsync( rep, 'POST &{SRC_NAME} + expand_glob false', () => @@ -583,7 +618,7 @@ async function suiteDstTemplateRest(assetsDir, rep) { const subDir = join(assetsDir, 'tpl-sub'); const outNested = join(subDir, '${SRC_NAME}_nested.png'); - const rn = await timeAsync( + const { result: rn } = await timeAsync( rep, 'POST nested ${SRC_NAME} template', () => @@ -604,7 +639,7 @@ async function suiteDstTemplateRest(assetsDir, rep) { assert(existsSync(expectedNested), `nested template ${expectedNested}`); const outViaSrcDir = '${SRC_DIR}/tpl-from-srcdir/${SRC_NAME}_sd.webp'; - const rsd = await timeAsync( + const { result: rsd } = await timeAsync( rep, 'POST ${SRC_DIR} in output path', () => @@ -654,8 +689,8 @@ async function suiteIpcTcp(assetsDir, rep) { const outDir = mkdtempSync(join(tmpdir(), 'media-ipc-tcp-')); const outPng = join(outDir, 'ipc-out.png'); - const sock = await timeAsync(rep, 'TCP connect (line 1)', () => connectTcp('127.0.0.1', port), () => ''); - const res = await timeAsync( + const { result: sock } = await timeAsync(rep, 'TCP connect (line 1)', () => connectTcp('127.0.0.1', port), () => ''); + const { result: res, ms: ipcMs } = await timeAsync( rep, 'IPC JSON line (ok resize)', () => @@ -676,17 +711,20 @@ async function suiteIpcTcp(assetsDir, rep) { assert(existsSync(outPng), 'IPC output file exists'); { const d = describePngFile(outPng); + const inB = fileByteSize(inPng); rep.addImage({ label: 'IPC TCP → disk (ipc-out.png)', - bytes: d.bytes, + inputBytes: inB, + outputBytes: d.bytes, + computeMs: ipcMs, widthPx: d.widthPx, heightPx: d.heightPx, note: 'line-JSON resize', }); } - const sock2 = await timeAsync(rep, 'TCP connect (line 2)', () => connectTcp('127.0.0.1', port), () => ''); - const res2 = await timeAsync( + const { result: sock2 } = await timeAsync(rep, 'TCP connect (line 2)', () => connectTcp('127.0.0.1', port), () => ''); + const { result: res2 } = await timeAsync( rep, 'IPC JSON line (missing input)', () => @@ -722,8 +760,8 @@ async function suiteDstTemplateIpcTcp(assetsDir, rep) { const outDir = mkdtempSync(join(tmpdir(), 'media-dst-ipc-')); const outPattern = join(outDir, '${SRC_NAME}_ipc.webp'); - const sock = await timeAsync(rep, 'TCP connect', () => connectTcp('127.0.0.1', port), () => ''); - const res = await timeAsync( + const { result: sock } = await timeAsync(rep, 'TCP connect', () => connectTcp('127.0.0.1', port), () => ''); + const { result: res } = await timeAsync( rep, 'IPC JSON line (${SRC_NAME}_ipc.webp)', () => @@ -762,7 +800,7 @@ function suiteDstTemplateCli(assetsDir, rep) { rep.beginSuite('CLI: resize --src / --dst templates'); try { - const r = timeSync( + const { result: r } = timeSync( rep, 'spawnSync resize (template dst)', () => @@ -804,7 +842,7 @@ function suiteUrlResizeCli(rep) { /* ignore */ } } - const r = timeSync( + const { result: r, ms: urlMs } = timeSync( rep, `spawnSync resize URL → ${expect}`, () => @@ -821,6 +859,13 @@ function suiteUrlResizeCli(rep) { ); assert(r.status === 0, `resize URL exit 0 (${expect}): ${r.stderr || r.stdout}`); assert(existsSync(outPath), `expected output ${outPath}`); + const outSz = fileByteSize(outPath); + rep.addImage({ + label: `CLI URL resize → ${expect}`, + outputBytes: outSz, + computeMs: urlMs, + note: 'HTTPS source + resize', + }); } } finally { rep.endSuite(); @@ -838,26 +883,35 @@ function suiteGlobBatchCli(assetsDir, rep) { assert(existsSync(rootPng), `fixture ${rootPng}`); assert(existsSync(leafPng), `fixture ${leafPng}`); - const globIn = join(assetsDir, 'glob-in', '**', '*.png').replace(/\\/g, '/'); const dstTmpl = '${SRC_DIR}/out/${SRC_NAME}_medium.jpg'; + const jobs = [ + { src: rootPng, out: join(assetsDir, 'glob-in', 'out', 'root_medium.jpg') }, + { src: leafPng, out: join(assetsDir, 'glob-in', 'sub', 'out', 'leaf_medium.jpg') }, + ]; rep.beginSuite('CLI: recursive glob batch + dst templates'); try { - const r = timeSync( - rep, - 'spawnSync resize (glob **/*.png → _medium.jpg)', - () => - spawnSync(EXE, ['resize', '--src', globIn, '--dst', dstTmpl, '--max-width', '40', '--format', 'jpeg'], { - encoding: 'utf8', - }), - (x) => `exit ${x.status}, 2 files`, - ); - assert(r.status === 0, `glob batch exit 0, stderr: ${r.stderr}`); - - const rootOut = join(assetsDir, 'glob-in', 'out', 'root_medium.jpg'); - const leafOut = join(assetsDir, 'glob-in', 'sub', 'out', 'leaf_medium.jpg'); - assert(existsSync(rootOut), `expected ${rootOut}`); - assert(existsSync(leafOut), `expected ${leafOut}`); + for (const job of jobs) { + const srcAbs = resolve(job.src).replace(/\\/g, '/'); + const { result: r, ms } = timeSync( + rep, + `spawnSync resize (${basename(job.src)} → _medium.jpg)`, + () => + spawnSync(EXE, ['resize', '--src', srcAbs, '--dst', dstTmpl, '--max-width', '40', '--format', 'jpeg'], { + encoding: 'utf8', + }), + (x) => `exit ${x.status}`, + ); + assert(r.status === 0, `glob batch exit 0, stderr: ${r.stderr}`); + assert(existsSync(job.out), `expected ${job.out}`); + rep.addImage({ + label: `CLI glob: ${basename(job.src)} → ${basename(job.out)}`, + inputBytes: fileByteSize(job.src), + outputBytes: fileByteSize(job.out), + computeMs: ms, + note: '${SRC_DIR}/out/${SRC_NAME}_medium.jpg', + }); + } } finally { rep.endSuite(); } @@ -903,28 +957,32 @@ function suiteGlobBatchRawArw(assetsDir, rep) { return; } - // Explicit --src list: recursive *.arw glob is case-sensitive; enumerate paths so .ARW matches. - const absSrc = arwFiles.map((p) => resolve(p).replace(/\\/g, '/')); - const srcArgs = absSrc.flatMap((p) => ['--src', p]); const dstTmpl = '${SRC_DIR}/out/${SRC_NAME}_medium.jpg'; rep.beginSuite('CLI: recursive glob batch (raw **/*.arw) + dst templates'); try { - const r = timeSync( - rep, - `spawnSync resize (${arwFiles.length}× --src .arw/.ARW → _medium.jpg)`, - () => - spawnSync(EXE, ['resize', ...srcArgs, '--dst', dstTmpl, '--max-width', '40', '--format', 'jpeg'], { - encoding: 'utf8', - }), - (x) => `exit ${x.status}, ${arwFiles.length} file(s)`, - ); - assert(r.status === 0, `glob batch raw exit 0, stderr: ${r.stderr}`); - for (const arwPath of arwFiles) { + const srcAbs = resolve(arwPath).replace(/\\/g, '/'); const stem = basename(arwPath).replace(/\.arw$/i, ''); const expected = join(dirname(arwPath), 'out', `${stem}_medium.jpg`); + const { result: r, ms } = timeSync( + rep, + `spawnSync resize (${basename(arwPath)} → _medium.jpg)`, + () => + spawnSync(EXE, ['resize', '--src', srcAbs, '--dst', dstTmpl, '--max-width', '40', '--format', 'jpeg'], { + encoding: 'utf8', + }), + (x) => `exit ${x.status}`, + ); + assert(r.status === 0, `glob batch raw exit 0, stderr: ${r.stderr}`); assert(existsSync(expected), `expected ${expected}`); + rep.addImage({ + label: `CLI raw: ${basename(arwPath)} → ${basename(expected)}`, + inputBytes: fileByteSize(arwPath), + outputBytes: fileByteSize(expected), + computeMs: ms, + note: 'ARW → JPEG', + }); } } finally { rep.endSuite(); @@ -964,7 +1022,7 @@ async function suiteIpcUnix(assetsDir, rep) { throw new Error('unix socket path did not appear'); }, () => path); - const res = await timeAsync( + const { result: res, ms: udsMs } = await timeAsync( rep, 'Unix connect + IPC JSON line', async () => { @@ -996,9 +1054,12 @@ async function suiteIpcUnix(assetsDir, rep) { assert(existsSync(outPng), 'UDS output file exists'); { const d = describePngFile(outPng); + const inB = fileByteSize(inPng); rep.addImage({ label: 'IPC Unix → disk (uds-out.png)', - bytes: d.bytes, + inputBytes: inB, + outputBytes: d.bytes, + computeMs: udsMs, widthPx: d.widthPx, heightPx: d.heightPx, note: 'UDS line-JSON resize', diff --git a/packages/media/cpp/tests/test-report-last.md b/packages/media/cpp/tests/test-report-last.md index 99c7a218..eb1a041c 100644 --- a/packages/media/cpp/tests/test-report-last.md +++ b/packages/media/cpp/tests/test-report-last.md @@ -5,37 +5,37 @@ | Key | Value | | --- | --- | | Result | PASS | -| Assertions passed | 4 | +| Assertions passed | 8 | | Assertions failed | 0 | | CWD | `C:\Users\zx\Desktop\polymech\polymech-mono\packages\media\cpp` | | Test binary | `C:\Users\zx\Desktop\polymech\polymech-mono\packages\media\cpp\dist\pm-image.exe` | | Assets dir | `C:\Users\zx\Desktop\polymech\polymech-mono\packages\media\cpp\tests\assets` | -| CLI args | `--url-only` | -| Wall clock (total script) | **1221.57 ms** | +| CLI args | `--glob-batch-only --glob-raw` | +| Wall clock (total script) | **10594.59 ms** | ## Timing | Metric | Value | | --- | --- | -| Started (ISO) | 2026-04-14T11:31:18.200Z | -| Finished (ISO) | 2026-04-14T11:31:19.422Z | -| Wall time (perf) | 1225.109 ms | -| Wall time (clock) | 1225 ms | +| Started (ISO) | 2026-04-14T11:43:16.994Z | +| Finished (ISO) | 2026-04-14T11:43:27.589Z | +| Wall time (perf) | 10597.975 ms | +| Wall time (clock) | 10598 ms | ## Process (Node) | Metric | Value | | --- | --- | -| PID | 33980 | +| PID | 28312 | | Node | v24.13.0 | -| process.uptime() | 1.266 s | -| CPU user (process.cpuUsage Δ) | 0.000 ms (0 µs) | +| process.uptime() | 10.633 s | +| CPU user (process.cpuUsage Δ) | 32.000 ms (32000 µs) | | CPU system (process.cpuUsage Δ) | 0.000 ms (0 µs) | -| CPU user (resourceUsage) | 62.000 ms | +| CPU user (resourceUsage) | 78.000 ms | | CPU system (resourceUsage) | 15.000 ms | | Max RSS (resourceUsage) | 39 MB | -| RSS | 39 MB (40816640 B) | -| Heap used | 6.0 MB | +| RSS | 39 MB (40824832 B) | +| Heap used | 6.2 MB | | Heap total | 11 MB | | External | 2.2 MB | | Array buffers | 16 KB | @@ -50,33 +50,39 @@ | CPUs | 16 | | CPU model | AMD Ryzen 7 3700X 8-Core Processor | | RAM total | 64 GB | -| RAM free | 38 GB | -| RAM used | 26 GB | +| RAM free | 36 GB | +| RAM used | 27 GB | | Load avg (1/5/15) | 0.00 / 0.00 / 0.00 | -| OS uptime | 123.71 h | +| OS uptime | 123.91 h | ## Images & transfers -| Label | Size | Dimensions / detail | -| --- | --- | --- | -| fixture square-64.png | 551 B (551 B) | 64×64 px · on-disk fixture | -| fixture checker-128x128.png | 455 B (455 B) | 128×128 px · on-disk fixture | -| fixture glob-in/root.png | 384 B (384 B) | 48×48 px · on-disk fixture | -| fixture glob-in/sub/leaf.png | 88 B (88 B) | 24×24 px · on-disk fixture | +| Label | Input | Output | Time (ms) | Detail | +| --- | --- | --- | ---: | --- | +| fixture square-64.png | 551 B (551 B) | — | — | 64×64 px · on-disk fixture | +| fixture checker-128x128.png | 455 B (455 B) | — | — | 128×128 px · on-disk fixture | +| fixture glob-in/root.png | 384 B (384 B) | — | — | 48×48 px · on-disk fixture | +| fixture glob-in/sub/leaf.png | 88 B (88 B) | — | — | 24×24 px · on-disk fixture | +| CLI raw: DSC02717.ARW → DSC02717_medium.jpg | 24 MB (24824576 B) | 1.1 KB (1123 B) | 2555.37 | ARW → JPEG | +| CLI raw: DSC02718.ARW → DSC02718_medium.jpg | 24 MB (24791808 B) | 1.0 KB (1039 B) | 2669.04 | ARW → JPEG | +| CLI raw: DSC02719.ARW → DSC02719_medium.jpg | 24 MB (24836864 B) | 1.1 KB (1125 B) | 2660.4 | ARW → JPEG | +| CLI raw: DSC02720.ARW → DSC02720_medium.jpg | 24 MB (24820480 B) | 1.0 KB (1029 B) | 2701.57 | ARW → JPEG | ## Integration: performance by suite -### CLI: resize HTTPS URL (picsum.photos) +### CLI: recursive glob batch (raw **/*.arw) + dst templates -- **Suite wall time:** 1216.33 ms +- **Suite wall time:** 10589.98 ms | Step | ms | Detail | | --- | ---: | --- | -| spawnSync resize URL → 200.jpg | 251.58 | exit 0, network+libvips | -| spawnSync resize URL → 1600.jpg | 963.61 | exit 0, network+libvips | +| spawnSync resize (DSC02717.ARW → _medium.jpg) | 2555.37 | exit 0 | +| spawnSync resize (DSC02718.ARW → _medium.jpg) | 2669.04 | exit 0 | +| spawnSync resize (DSC02719.ARW → _medium.jpg) | 2660.4 | exit 0 | +| spawnSync resize (DSC02720.ARW → _medium.jpg) | 2701.57 | exit 0 | --- -*Written 2026-04-14T11:31:19.422Z* +*Written 2026-04-14T11:43:27.589Z* --- diff --git a/packages/media/cpp/todos.md b/packages/media/cpp/todos.md new file mode 100644 index 00000000..bbfdb465 --- /dev/null +++ b/packages/media/cpp/todos.md @@ -0,0 +1,4 @@ +## Videos + +https://www.mltframework.org/features/ +https://github.com/olive-editor/olive?tab=readme-ov-file