feat:add prototype timer for program running and compiling
This commit is contained in:
parent
3929c055eb
commit
0eab496c8d
@ -10,157 +10,177 @@ import { performance } from 'perf_hooks';
|
|||||||
const docker = new Docker();
|
const docker = new Docker();
|
||||||
|
|
||||||
const createFilesTar = async (files: { name: string; content: string }[]) => {
|
const createFilesTar = async (files: { name: string; content: string }[]) => {
|
||||||
const pack = tar.pack();
|
const pack = tar.pack();
|
||||||
|
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
if (file.name.includes('..') || path.isAbsolute(file.name)) {
|
if (file.name.includes('..') || path.isAbsolute(file.name)) {
|
||||||
throw new Error(`非法文件路径: ${file.name}`);
|
throw new Error(`非法文件路径: ${file.name}`);
|
||||||
}
|
}
|
||||||
if (!/\.(c|java|py|txt)$/i.test(file.name)) {
|
if (!/\.(c|java|py|txt)$/i.test(file.name)) {
|
||||||
throw new Error(`禁止的文件类型: ${file.name}`);
|
throw new Error(`禁止的文件类型: ${file.name}`);
|
||||||
}
|
}
|
||||||
pack.entry({ name: file.name }, file.content);
|
pack.entry({ name: file.name }, file.content);
|
||||||
});
|
});
|
||||||
|
|
||||||
pack.finalize();
|
pack.finalize();
|
||||||
return Readable.from(pack) as unknown as NodeJS.ReadableStream;
|
return Readable.from(pack) as unknown as NodeJS.ReadableStream;
|
||||||
};
|
};
|
||||||
|
|
||||||
const readContainerFile = async (container: Docker.Container, filePath: string) => {
|
const readContainerFile = async (container: Docker.Container, filePath: string) => {
|
||||||
try {
|
try {
|
||||||
const archive = await container.getArchive({ path: filePath });
|
const archive = await container.getArchive({ path: filePath });
|
||||||
const extract = tar.extract();
|
const extract = tar.extract();
|
||||||
let fileContent = '';
|
let fileContent = '';
|
||||||
|
|
||||||
extract.on('entry', (header, stream, next) => {
|
extract.on('entry', (header, stream, next) => {
|
||||||
stream.on('data', (chunk) => {
|
stream.on('data', (chunk) => {
|
||||||
fileContent += chunk.toString();
|
fileContent += chunk.toString();
|
||||||
});
|
});
|
||||||
stream.on('end', next);
|
stream.on('end', next);
|
||||||
stream.resume();
|
stream.resume();
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
extract.on('finish', () => resolve(fileContent));
|
extract.on('finish', () => resolve(fileContent));
|
||||||
extract.on('error', reject);
|
extract.on('error', reject);
|
||||||
archive.pipe(extract);
|
archive.pipe(extract);
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const runMultiFileCodeWithOptionalTestData = async (params: {
|
export const runMultiFileCodeWithOptionalTestData = async (params: {
|
||||||
files: { name: string; content: string }[];
|
files: { name: string; content: string }[];
|
||||||
language: string;
|
language: string;
|
||||||
testDataFiles?: string[];
|
testDataFiles?: string[];
|
||||||
expectedAnswerFiles?: string[];
|
expectedAnswerFiles?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
const totalStart = performance.now();
|
const totalStart = performance.now();
|
||||||
let stageStart: number;
|
let stageStart: number;
|
||||||
const { files, language } = params;
|
const { files, language } = params;
|
||||||
let container: Docker.Container | null = null;
|
let container: Docker.Container | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 阶段1: 参数校验
|
// 阶段1: 参数校验
|
||||||
stageStart = performance.now();
|
stageStart = performance.now();
|
||||||
if (!files?.length || !language) {
|
if (!files?.length || !language) {
|
||||||
throw new Error('必须提供代码文件和语言类型');
|
throw new Error('必须提供代码文件和语言类型');
|
||||||
|
}
|
||||||
|
const supportedLanguages = ['c', 'java', 'python'];
|
||||||
|
if (!supportedLanguages.includes(language)) {
|
||||||
|
throw new Error(`不支持的语言类型: ${language}`);
|
||||||
|
}
|
||||||
|
console.log(`[PERF] 参数校验耗时: ${(performance.now() - stageStart).toFixed(2)}ms`);
|
||||||
|
|
||||||
|
// 阶段2: 准备编译命令
|
||||||
|
stageStart = performance.now();
|
||||||
|
const compileAndRunCmd = {
|
||||||
|
c: `cd ${dockerConfig.WorkingDir} &&
|
||||||
|
start_time=$(date +%s%N) &&
|
||||||
|
gcc *.c -o main &&
|
||||||
|
compile_time=$(($(date +%s%N) - $start_time)) &&
|
||||||
|
echo "编译时间: $((compile_time / 1000000))ms" &&
|
||||||
|
start_time=$(date +%s%N) &&
|
||||||
|
./main &&
|
||||||
|
run_time=$(($(date +%s%N) - $start_time)) &&
|
||||||
|
echo "运行时间: $((run_time / 1000000))ms"`,
|
||||||
|
java: `cd ${dockerConfig.WorkingDir} &&
|
||||||
|
start_time=$(date +%s%N) &&
|
||||||
|
javac *.java &&
|
||||||
|
compile_time=$(($(date +%s%N) - $start_time)) &&
|
||||||
|
echo "编译时间: $((compile_time / 1000000))ms" &&
|
||||||
|
start_time=$(date +%s%N) &&
|
||||||
|
java Main &&
|
||||||
|
run_time=$(($(date +%s%N) - $start_time)) &&
|
||||||
|
echo "运行时间: $((run_time / 1000000))ms"`,
|
||||||
|
python: `cd ${dockerConfig.WorkingDir} &&
|
||||||
|
start_time=$(date +%s%N) &&
|
||||||
|
python3 script.py &&
|
||||||
|
run_time=$(($(date +%s%N) - $start_time)) &&
|
||||||
|
echo "运行时间: $((run_time / 1000000))ms"`
|
||||||
|
}[language];
|
||||||
|
console.log(`[PERF] 命令准备耗时: ${(performance.now() - stageStart).toFixed(2)}ms`);
|
||||||
|
|
||||||
|
// 阶段3: 创建容器
|
||||||
|
stageStart = performance.now();
|
||||||
|
container = await docker.createContainer({
|
||||||
|
Image: dockerConfig.Image,
|
||||||
|
Cmd: ['sh', '-c', compileAndRunCmd],
|
||||||
|
WorkingDir: dockerConfig.WorkingDir,
|
||||||
|
HostConfig: {
|
||||||
|
AutoRemove: false,
|
||||||
|
Memory: 256 * 1024 * 1024,
|
||||||
|
NetworkMode: 'none',
|
||||||
|
Binds: [`${dockerConfig.WorkingDir}:/workspace`]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(`[PERF] 容器创建耗时: ${(performance.now() - stageStart).toFixed(2)}ms`);
|
||||||
|
|
||||||
|
// 阶段4: 文件上传
|
||||||
|
stageStart = performance.now();
|
||||||
|
const tarStream = await createFilesTar(files);
|
||||||
|
await container.putArchive(tarStream, {
|
||||||
|
path: dockerConfig.WorkingDir
|
||||||
|
});
|
||||||
|
console.log(`[PERF] 文件上传耗时: ${(performance.now() - stageStart).toFixed(2)}ms`);
|
||||||
|
|
||||||
|
// 阶段5: 执行容器
|
||||||
|
stageStart = performance.now();
|
||||||
|
await container.start();
|
||||||
|
let output = '';
|
||||||
|
const logs = await container.logs({
|
||||||
|
stdout: true,
|
||||||
|
stderr: true,
|
||||||
|
follow: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const logStream = new Writable({
|
||||||
|
write(chunk, _, callback) {
|
||||||
|
output += chunk.toString()
|
||||||
|
.replace(/[\x00-\x1F\x7F]/g, '') // Remove non-printable characters
|
||||||
|
.trim();
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logs.pipe(logStream);
|
||||||
|
await new Promise(resolve => logStream.on('finish', resolve));
|
||||||
|
await container.wait();
|
||||||
|
console.log(`[PERF] 容器执行耗时: ${(performance.now() - stageStart).toFixed(2)}ms`);
|
||||||
|
|
||||||
|
// 阶段6: 读取结果
|
||||||
|
stageStart = performance.now();
|
||||||
|
const resultFilePath = path.posix.join(
|
||||||
|
dockerConfig.WorkingDir,
|
||||||
|
dockerConfig.ResultOutputFile
|
||||||
|
);
|
||||||
|
const resultContent = await readContainerFile(container, resultFilePath);
|
||||||
|
console.log(`[PERF] 结果读取耗时: ${(performance.now() - stageStart).toFixed(2)}ms`);
|
||||||
|
|
||||||
|
return resultContent
|
||||||
|
? { output, resultFileContent: resultContent }
|
||||||
|
: { output };
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('执行流程错误:', error);
|
||||||
|
return {
|
||||||
|
error: `执行错误: ${(error as Error).message}`
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
// 阶段7: 清理容器
|
||||||
|
stageStart = performance.now();
|
||||||
|
if (container) {
|
||||||
|
try {
|
||||||
|
await container.remove({ force: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('容器清理失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`[PERF] 容器清理耗时: ${(performance.now() - stageStart).toFixed(2)}ms`);
|
||||||
|
|
||||||
|
// 总耗时统计
|
||||||
|
const totalTime = performance.now() - totalStart;
|
||||||
|
console.log(`[PERF] 总运行时间: ${totalTime.toFixed(2)}ms`);
|
||||||
}
|
}
|
||||||
const supportedLanguages = ['c', 'java', 'python'];
|
|
||||||
if (!supportedLanguages.includes(language)) {
|
|
||||||
throw new Error(`不支持的语言类型: ${language}`);
|
|
||||||
}
|
|
||||||
console.log(`[PERF] 参数校验耗时: ${(performance.now() - stageStart).toFixed(2)}ms`);
|
|
||||||
|
|
||||||
// 阶段2: 准备编译命令
|
|
||||||
stageStart = performance.now();
|
|
||||||
const compileAndRunCmd = {
|
|
||||||
c: `cd ${dockerConfig.WorkingDir} && gcc *.c -o main && ./main`,
|
|
||||||
java: `cd ${dockerConfig.WorkingDir} && javac *.java && java Main`,
|
|
||||||
python: `cd ${dockerConfig.WorkingDir} && python3 script.py`
|
|
||||||
}[language];
|
|
||||||
console.log(`[PERF] 命令准备耗时: ${(performance.now() - stageStart).toFixed(2)}ms`);
|
|
||||||
|
|
||||||
// 阶段3: 创建容器
|
|
||||||
stageStart = performance.now();
|
|
||||||
container = await docker.createContainer({
|
|
||||||
Image: dockerConfig.Image,
|
|
||||||
Cmd: ['sh', '-c', compileAndRunCmd],
|
|
||||||
WorkingDir: dockerConfig.WorkingDir,
|
|
||||||
HostConfig: {
|
|
||||||
AutoRemove: false,
|
|
||||||
Memory: 256 * 1024 * 1024,
|
|
||||||
NetworkMode: 'none',
|
|
||||||
Binds: [`${dockerConfig.WorkingDir}:/workspace`]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log(`[PERF] 容器创建耗时: ${(performance.now() - stageStart).toFixed(2)}ms`);
|
|
||||||
|
|
||||||
// 阶段4: 文件上传
|
|
||||||
stageStart = performance.now();
|
|
||||||
const tarStream = await createFilesTar(files);
|
|
||||||
await container.putArchive(tarStream, {
|
|
||||||
path: dockerConfig.WorkingDir
|
|
||||||
});
|
|
||||||
console.log(`[PERF] 文件上传耗时: ${(performance.now() - stageStart).toFixed(2)}ms`);
|
|
||||||
|
|
||||||
// 阶段5: 执行容器
|
|
||||||
stageStart = performance.now();
|
|
||||||
await container.start();
|
|
||||||
let output = '';
|
|
||||||
const logs = await container.logs({
|
|
||||||
stdout: true,
|
|
||||||
stderr: true,
|
|
||||||
follow: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const logStream = new Writable({
|
|
||||||
write(chunk, _, callback) {
|
|
||||||
output += chunk.toString()
|
|
||||||
.replace(/[\x00-\x1F\x7F]/g, '')
|
|
||||||
.trim();
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
logs.pipe(logStream);
|
|
||||||
await new Promise(resolve => logStream.on('finish', resolve));
|
|
||||||
await container.wait();
|
|
||||||
console.log(`[PERF] 容器执行耗时: ${(performance.now() - stageStart).toFixed(2)}ms`);
|
|
||||||
|
|
||||||
// 阶段6: 读取结果
|
|
||||||
stageStart = performance.now();
|
|
||||||
const resultFilePath = path.posix.join(
|
|
||||||
dockerConfig.WorkingDir,
|
|
||||||
dockerConfig.ResultOutputFile
|
|
||||||
);
|
|
||||||
const resultContent = await readContainerFile(container, resultFilePath);
|
|
||||||
console.log(`[PERF] 结果读取耗时: ${(performance.now() - stageStart).toFixed(2)}ms`);
|
|
||||||
|
|
||||||
return resultContent
|
|
||||||
? { output, resultFileContent: resultContent }
|
|
||||||
: { output };
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('执行流程错误:', error);
|
|
||||||
return {
|
|
||||||
error: `执行错误: ${(error as Error).message}`
|
|
||||||
};
|
|
||||||
} finally {
|
|
||||||
// 阶段7: 清理容器
|
|
||||||
stageStart = performance.now();
|
|
||||||
if (container) {
|
|
||||||
try {
|
|
||||||
await container.remove({ force: true });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('容器清理失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(`[PERF] 容器清理耗时: ${(performance.now() - stageStart).toFixed(2)}ms`);
|
|
||||||
|
|
||||||
// 总耗时统计
|
|
||||||
const totalTime = performance.now() - totalStart;
|
|
||||||
console.log(`[PERF] 总运行时间: ${totalTime.toFixed(2)}ms`);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user