Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions scripts/bench-script-command-discovery.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/usr/bin/env node

import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { performance } from 'node:perf_hooks';
import { loadScriptCommandRunner } from './lib/script-command-runner-harness.mjs';

const fileCount = Number(process.env.SUPERCMD_BENCH_SCRIPT_COUNT || 40);
const bodyBytesPerFile = Number(process.env.SUPERCMD_BENCH_SCRIPT_BODY_BYTES || 1024 * 1024);

function formatBytes(bytes) {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
}

function makeLargeScript(title, bodyBytes) {
const header = `#!/bin/bash
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title ${title}
# @raycast.mode fullOutput

# Optional parameters:
# @raycast.packageName Benchmark
# @raycast.icon ⚡
# @raycast.description Large script command fixture
# @raycast.needsConfirmation false

exit 0
`;
const line = `# ${'x'.repeat(253)}\n`;
const repeatCount = Math.max(0, Math.ceil(bodyBytes / Buffer.byteLength(line)));
return header + line.repeat(repeatCount);
}

function snapshot(metrics) {
return {
readFileSyncCalls: metrics.readFileSyncCalls,
readFileSyncBytes: metrics.readFileSyncBytes,
readSyncCalls: metrics.readSyncCalls,
readSyncBytes: metrics.readSyncBytes,
openSyncCalls: metrics.openSyncCalls,
totalBytes: metrics.readFileSyncBytes + metrics.readSyncBytes,
};
}

function printMetric(label, durationMs, metricSnapshot) {
console.log(`${label}: ${durationMs.toFixed(1)}ms`);
console.log(` readFileSync: ${metricSnapshot.readFileSyncCalls} calls, ${formatBytes(metricSnapshot.readFileSyncBytes)}`);
console.log(` readSync: ${metricSnapshot.readSyncCalls} calls, ${formatBytes(metricSnapshot.readSyncBytes)}`);
console.log(` total runner bytes read: ${formatBytes(metricSnapshot.totalBytes)}`);
}

const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'supercmd-script-bench-'));
const scriptsDir = path.join(tempRoot, 'script-commands');
const userDataDir = path.join(tempRoot, 'user-data');
fs.mkdirSync(scriptsDir, { recursive: true });
fs.mkdirSync(userDataDir, { recursive: true });

try {
for (let i = 0; i < fileCount; i += 1) {
const scriptPath = path.join(scriptsDir, `large-command-${String(i + 1).padStart(3, '0')}.sh`);
fs.writeFileSync(scriptPath, makeLargeScript(`Large Command ${i + 1}`, bodyBytesPerFile), {
mode: 0o755,
});
}

process.env.SUPERCMD_SCRIPT_COMMAND_PATHS = scriptsDir;
const { module: runner, metrics, resetMetrics } = await loadScriptCommandRunner({
userDataDir,
scriptCommandFolders: [],
instrumentFs: true,
});

resetMetrics();
const discoveryStart = performance.now();
const commands = runner.discoverScriptCommands();
const discoveryMs = performance.now() - discoveryStart;
const discoveryMetrics = snapshot(metrics);

if (commands.length !== fileCount) {
throw new Error(`Expected ${fileCount} commands, discovered ${commands.length}`);
}

resetMetrics();
const executeStart = performance.now();
await runner.executeScriptCommand(commands[0].id);
const executeMs = performance.now() - executeStart;
const executeMetrics = snapshot(metrics);

console.log('Script command discovery benchmark');
console.log(`files: ${fileCount}`);
console.log(`body bytes per file: ${formatBytes(bodyBytesPerFile)}`);
printMetric('discovery', discoveryMs, discoveryMetrics);
printMetric('cached execution shebang lookup', executeMs, executeMetrics);
} finally {
delete process.env.SUPERCMD_SCRIPT_COMMAND_PATHS;
fs.rmSync(tempRoot, { recursive: true, force: true });
}

178 changes: 178 additions & 0 deletions scripts/lib/script-command-runner-harness.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { build } from 'esbuild';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const runnerPath = path.join(root, 'src/main/script-command-runner.ts');

function makeElectronMockSource(appPaths) {
return `
const paths = ${JSON.stringify(appPaths)};
export const app = {
getPath(name) {
return paths[name] || paths.userData;
},
};
`;
}

function makeSettingsMockSource(scriptCommandFolders) {
return `
export function loadSettings() {
return { scriptCommandFolders: ${JSON.stringify(scriptCommandFolders)} };
}
`;
}

function makeInstrumentedFsSource(metricsKey) {
return `
import realFs from 'node:fs';

const metrics = globalThis[${JSON.stringify(metricsKey)}];

function countReadFile(value, options) {
if (Buffer.isBuffer(value)) return value.byteLength;
const encoding = typeof options === 'string'
? options
: options && typeof options === 'object' && options.encoding
? options.encoding
: 'utf8';
return Buffer.byteLength(String(value), encoding);
}

export const constants = realFs.constants;
export const existsSync = realFs.existsSync.bind(realFs);
export const mkdirSync = realFs.mkdirSync.bind(realFs);
export const readdirSync = realFs.readdirSync.bind(realFs);
export const writeFileSync = realFs.writeFileSync.bind(realFs);
export const chmodSync = realFs.chmodSync.bind(realFs);
export const unlinkSync = realFs.unlinkSync.bind(realFs);
export const openSync = (...args) => {
metrics.openSyncCalls += 1;
return realFs.openSync(...args);
};
export const closeSync = realFs.closeSync.bind(realFs);
export const accessSync = realFs.accessSync.bind(realFs);
export const readFileSync = (filePath, options) => {
const value = realFs.readFileSync(filePath, options);
metrics.readFileSyncCalls += 1;
metrics.readFileSyncBytes += countReadFile(value, options);
return value;
};
export const readSync = (...args) => {
const bytesRead = realFs.readSync(...args);
metrics.readSyncCalls += 1;
metrics.readSyncBytes += Math.max(0, Number(bytesRead) || 0);
return bytesRead;
};

export default {
...realFs,
constants,
existsSync,
mkdirSync,
readdirSync,
writeFileSync,
chmodSync,
unlinkSync,
openSync,
closeSync,
accessSync,
readFileSync,
readSync,
};
`;
}

function resetMetrics(metrics) {
for (const key of Object.keys(metrics)) {
metrics[key] = 0;
}
}

export async function loadScriptCommandRunner({
homeDir = os.homedir(),
tempDir = os.tmpdir(),
userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'supercmd-user-data-')),
scriptCommandFolders = [],
instrumentFs = false,
} = {}) {
const metricsKey = `__supercmdScriptCommandFsMetrics_${Date.now()}_${Math.random()
.toString(36)
.slice(2)}`;
const metrics = {
readFileSyncCalls: 0,
readFileSyncBytes: 0,
readSyncCalls: 0,
readSyncBytes: 0,
openSyncCalls: 0,
};
globalThis[metricsKey] = metrics;

const plugins = [
{
name: 'supercmd-script-command-runner-mocks',
setup(pluginBuild) {
pluginBuild.onResolve({ filter: /^electron$/ }, () => ({
path: 'electron-mock',
namespace: 'supercmd-mocks',
}));
pluginBuild.onLoad({ filter: /^electron-mock$/, namespace: 'supercmd-mocks' }, () => ({
contents: makeElectronMockSource({ home: homeDir, temp: tempDir, userData: userDataDir }),
loader: 'js',
}));

pluginBuild.onResolve({ filter: /^\.\/settings-store$/ }, () => ({
path: 'settings-store-mock',
namespace: 'supercmd-mocks',
}));
pluginBuild.onLoad({ filter: /^settings-store-mock$/, namespace: 'supercmd-mocks' }, () => ({
contents: makeSettingsMockSource(scriptCommandFolders),
loader: 'js',
}));
},
},
];

if (instrumentFs) {
plugins.push({
name: 'supercmd-script-command-fs-instrumentation',
setup(pluginBuild) {
pluginBuild.onResolve({ filter: /^fs$/ }, () => ({
path: 'fs-instrumented',
namespace: 'supercmd-fs',
}));
pluginBuild.onLoad({ filter: /^fs-instrumented$/, namespace: 'supercmd-fs' }, () => ({
contents: makeInstrumentedFsSource(metricsKey),
loader: 'js',
}));
},
});
}

const result = await build({
entryPoints: [runnerPath],
bundle: true,
platform: 'node',
format: 'esm',
write: false,
plugins,
});

const output = result.outputFiles[0]?.text;
if (!output) {
throw new Error('Failed to bundle script-command-runner.ts');
}

const moduleUrl = `data:text/javascript;base64,${Buffer.from(output).toString('base64')}#${metricsKey}`;
const module = await import(moduleUrl);

return {
module,
metrics,
resetMetrics: () => resetMetrics(metrics),
root,
};
}
Loading
Loading