import assert from 'assert'; import { ChildProcessByStdio, spawn } from 'child_process'; import * as fs from "fs"; import * as path from 'path'; import { createInterface } from 'readline'; import { Readable, Writable } from 'stream'; import { promisify } from "util"; import specTemplate from '../../assets/specTemplate.cfg'; import { RunCPUOptions, runcpuOptions } from '../commands/spec.js'; import { SPECBenchData, benchpath, buildpath, exepath } from './benchData.js'; export function getEnvironment(specdir: string): NodeJS.ProcessEnv { return { ...process.env, SPEC: specdir, PATH: `${path.join(specdir, 'bin')}${path.delimiter}${process.env.PATH}`, }; } export interface SPEC { newConfig: (name: string, content: string) => Promise; benchpath: (bench: SPECBenchData) => string; exepath: (bench: SPECBenchData) => string; buildpath: (bench: SPECBenchData) => string; getEnvironment: () => NodeJS.ProcessEnv; } export function mkSPEC(specRoot: string): SPEC { return { newConfig: async (name: string, content: string) => { await writeFile(path.join(specRoot, "config", name), content); }, benchpath: bench => benchpath(specRoot, bench), exepath: bench => exepath(specRoot, bench), buildpath: bench => buildpath(specRoot, bench), getEnvironment: () => getEnvironment(specRoot), }; } export interface Bench { benchpath: () => string; exepath: () => string; buildpath: () => string; benchData: () => SPECBenchData; spec: () => SPEC; } export function mkBench(spec: SPEC, bench: SPECBenchData): Bench { return { benchpath: () => spec.benchpath(bench), exepath: () => spec.exepath(bench), buildpath: () => spec.buildpath(bench), benchData: () => bench, spec: () => spec, }; } const writeFile = promisify(fs.writeFile); export interface ConfigOptions { optimize: string[]; ldflags: string[]; libs: string[]; openmp: { threads: number; }; compilerPaths: { CXX: string, CC: string, FC: string, }; specialFlags: string, } // Pure function to render configuration export const renderConfig = (options: ConfigOptions): string => { return `# Rendered from TypeScript ${new Date().toLocaleString()}, do not edit!\n\n\n` + specTemplate .replace("@@OPTIMIZE@@", options.optimize.join(" ")) .replace("@@LDFLAGS@@", options.ldflags.join(" ")) .replace("@@CompilerCC@@", options.compilerPaths.CC) .replace("@@CompilerCXX@@", options.compilerPaths.CXX) .replace("@@CompilerFC@@", options.compilerPaths.FC) .replace("@@LIBS@@", options.libs.join(" ")) .replace("@@SpecialFlags@@", options.specialFlags) .replace("@@OpenMPThreads@@", options.openmp.threads.toString()) ; }; export interface SPECResult { outputFiles: { [key: string]: string[], }; } /** * Monitor SPEC2017 runcpu process in a subprocess. */ export async function watchSPEC>(process: T) { const rl = createInterface({ input: process.stdout, terminal: false, }); // Match & extract string like " format CSV -> /path/to/csv" const formatRe = /^\s*format:\s*(\w+)\s*->\s*(.*)$/; return await new Promise((resolve, reject) => { let outputFiles: { [key: string]: string[]; } = {}; rl.on("line", line => { let match; if ((match = formatRe.exec(line)) != null) { const type = match[1]; const paths = match[2].split(',').map(path => path.trim()); outputFiles[type] = paths; } // To match if the line contains: "runcpu finished if (line.startsWith("runcpu finished")) { resolve({ outputFiles }); } }); process.on("error", error => reject(error)); process.on("close", () => resolve({ outputFiles })); }); } export interface SPECResultsTable { baseThreads: string, baseRuntime: string, baseRatio: string, BaseSelected: string, baseStatus: string, peakThreads: string, peakRuntime: string, peakRatio: string, peakSelected: string, peakStatus: string, description?: string, } export const extract = (field: keyof SPECResultsTable) => (o: ParsedResult) => { return Object.fromEntries(Object .entries(o) .filter(([_, b]) => b[field] !== '' && b[field] !== undefined && b.baseStatus === 'S') .map(([a, b]) => [a, parseFloat(b[field]!)])); }; export const extractBaseRatio = extract('baseRatio'); export const baseRatioFromPath = async (path: string) => { const result = parseSPECCSVResultsTable((await fs.promises.readFile(path)).toString()); return extractBaseRatio(result); }; export async function getBaseRatios>(proc: T) { const specResultCSVs = (await watchSPEC(proc)).outputFiles["CSV"]; assert(specResultCSVs !== undefined && specResultCSVs.length === 1); const specCSV = await fs.promises.readFile(specResultCSVs[0], "utf-8"); return extractBaseRatio(parseSPECCSVResultsTable(specCSV)); } export interface ParsedResult { [key: string]: SPECResultsTable; } export function parseSPECCSVResultsTable(data: string): ParsedResult { const lines = data.split("\n"); let index = 0; const eof = () => index >= lines.length; let results: { [key: string]: SPECResultsTable; } = {}; while (!eof()) { const line = lines[index]; if (line.startsWith('"Full Results Table"')) { index += 3; // "Full Results Table" // Benchmark,"Base # Threads","Est. Base Run Time","Est. Base Ratio","Base Selected","Base Status","Peak # Threads","Est. Peak Run Time","Est. Peak Ratio","Peak Selected","Peak Status",Description // 600.perlbench_s,1,65.357247,--,0,VE,,,,,,"test iteration #1" // 602.gcc_s,,,,,NR,,,,,NR // 605.mcf_s,,,,,NR,,,,,NR // 620.omnetpp_s,,,,,NR,,,,,NR // 623.xalancbmk_s,,,,,NR,,,,,NR // 625.x264_s,,,,,NR,,,,,NR // 631.deepsjeng_s,,,,,NR,,,,,NR // 641.leela_s,,,,,NR,,,,,NR // 648.exchange2_s,,,,,NR,,,,,NR // 657.xz_s,,,,,NR,,,,,NR while (!eof()) { const dataLine = lines[index]; if (dataLine.length == 0) break; const fields: (keyof SPECResultsTable)[] = [ "baseThreads", "baseRuntime", "baseRatio", "BaseSelected", "baseStatus", "peakThreads", "peakRuntime", "peakRatio", "peakSelected", "peakStatus", "description", ]; const splitted = dataLine.split(","); const entries: [keyof SPECResultsTable, string][] = splitted.slice(1).map((name, index) => [fields[index], name]); results[splitted[0]] = (Object.fromEntries(entries) as { [K in keyof SPECResultsTable]: string }); index++; } } index++; } return results; } export async function spawnSPECWithID( spec: SPEC, specConfig: ConfigOptions, runcpuConfig: RunCPUOptions, outputPath: string, id: string, ) { const outputRoot = path.resolve(outputPath, id); console.log(`runspec: launch SPEC benchmark at ${id}, config: ${JSON.stringify(specConfig)}`); console.log(`runspec: view output dir: ${outputRoot}`); const config = `${id}.cfg`; await spec.newConfig(config, renderConfig(specConfig)); return spawn('runcpu', runcpuOptions({ outputRoot, config, ...runcpuConfig, }), { env: spec.getEnvironment() }); }