judge: add request trace (#934)

Co-authored-by: panda <panda_dtdyy@outlook.com>
This commit is contained in:
undefinedandpanda authored and GitHub committed 2025-01-26 20:13:53 +08:00
1 parent 74c8b0ef32
commit f3eb6ca55c
9 files changed
+63 -47

No files matched your search

+1
View File
@@ -25,6 +25,7 @@ export default async function compile(
time: lang.compile_time_limit || 10000, time: lang.compile_time_limit || 10000,
memory: lang.compile_memory_limit || 256 * 1024 * 1024, memory: lang.compile_memory_limit || 256 * 1024 * 1024,
}, },
`compile[${lang.key}]`,
3, 3,
); );
// TODO: distinguish user program and checker // TODO: distinguish user program and checker
+1 -1
View File
@@ -11,7 +11,7 @@ export async function compilerVersions(langs: Record<string, LangConfig>) {
copyIn: {}, copyIn: {},
time: 10000, time: 10000,
memory: 256 * 1024 * 1024, memory: 256 * 1024 * 1024,
}, 5); }, 'compilerVersions', 5);
result[lang] = `${res.stdout}\n${res.stderr}`.trim(); result[lang] = `${res.stdout}\n${res.stderr}`.trim();
} }
return result; return result;
+1
View File
@@ -21,6 +21,7 @@ function judgeCase(c: NormalizedCase) {
addressSpaceLimit: address_space_limit, addressSpaceLimit: address_space_limit,
processLimit: process_limit, processLimit: process_limit,
}, },
`judgeCase[${c.id}]<${ctx.rid}>`,
); );
const { const {
code, signalled, time, memory, fileIds, code, signalled, time, memory, fileIds,
@@ -37,6 +37,7 @@ export const judge = async (ctx: JudgeTask) => {
time: parseTimeMS('2s'), time: parseTimeMS('2s'),
memory: parseMemoryMB('256m'), memory: parseMemoryMB('256m'),
}, },
`generate[${i}]<${ctx.rid}>`,
1, 1,
); );
const tmp = path.join(tmpdir(), `${ctx.request.rid}.${i}.in`); const tmp = path.join(tmpdir(), `${ctx.request.rid}.${i}.in`);
@@ -89,6 +90,7 @@ export const judge = async (ctx: JudgeTask) => {
time: parseTimeMS('2s'), time: parseTimeMS('2s'),
memory: parseMemoryMB('256m'), memory: parseMemoryMB('256m'),
}, },
`generate.std[${i}]<${ctx.rid}>`,
1, 1,
); );
const tmp = path.join(tmpdir(), `${ctx.request.rid}.${i}.out`); const tmp = path.join(tmpdir(), `${ctx.request.rid}.${i}.out`);
+2
View File
@@ -25,6 +25,7 @@ export async function judge(ctx: Context) {
time: parseTimeMS(ctx.config.time || '1s'), time: parseTimeMS(ctx.config.time || '1s'),
memory: parseMemoryMB(ctx.config.memory || '256m'), memory: parseMemoryMB(ctx.config.memory || '256m'),
}, },
`hack.validator[${ctx.rid}]`,
); );
if (validateResult.status !== STATUS.STATUS_ACCEPTED) { if (validateResult.status !== STATUS.STATUS_ACCEPTED) {
const message = `${validateResult.stdout || ''}\n${validateResult.stderr || ''}`.trim(); const message = `${validateResult.stdout || ''}\n${validateResult.stderr || ''}`.trim();
@@ -42,6 +43,7 @@ export async function judge(ctx: Context) {
addressSpaceLimit: address_space_limit, addressSpaceLimit: address_space_limit,
processLimit: process_limit, processLimit: process_limit,
}, },
`hack[${ctx.rid}]`,
); );
const { const {
code, signalled, time, memory, code, signalled, time, memory,
+1
View File
@@ -22,6 +22,7 @@ export const judge = async (ctx: JudgeTask) => {
addressSpaceLimit: address_space_limit, addressSpaceLimit: address_space_limit,
processLimit: process_limit, processLimit: process_limit,
}, },
`judge[${ctx.rid}]`,
1, 1,
); );
const { const {
+27 -30
View File
@@ -114,7 +114,7 @@ function proc(params: Parameter): Cmd {
}; };
} }
async function adaptResult(result: SandboxResult, params: Parameter): Promise<SandboxAdaptedResult> { function adaptResult(result: SandboxResult, params: Parameter): SandboxAdaptedResult {
const rate = getConfig('rate') as number; const rate = getConfig('rate') as number;
// FIXME: Signalled? // FIXME: Signalled?
const ret: SandboxAdaptedResult = { const ret: SandboxAdaptedResult = {
@@ -146,13 +146,18 @@ async function adaptResult(result: SandboxResult, params: Parameter): Promise<Sa
} }
export async function runPiped( export async function runPiped(
execute: Parameter[], pipeMapping: Pick<PipeMap, 'in' | 'out' | 'name'>[], execute: Parameter[], pipeMapping: Pick<PipeMap, 'in' | 'out' | 'name'>[], params: Parameter = {}, trace: string = '',
): Promise<SandboxAdaptedResult[]> { ): Promise<SandboxAdaptedResult[]> {
let res: SandboxResult[]; let res: SandboxResult[];
const size = parseMemoryMB(getConfig('stdio_size')); const size = parseMemoryMB(getConfig('stdio_size'));
try { try {
if (!supportOptional) {
const { copyOutOptional } = await client.version();
supportOptional = copyOutOptional;
if (!copyOutOptional) logger.warn('Sandbox version tooooooo low! Please upgrade to at least 1.2.0');
}
const body = { const body = {
cmd: execute.map((exe) => proc(exe)), cmd: execute.map((exe) => proc({ ...exe, ...params })),
pipeMapping: pipeMapping.map((pipe) => ({ pipeMapping: pipeMapping.map((pipe) => ({
proxy: true, proxy: true,
max: 1024 * 1024 * size, max: 1024 * 1024 * size,
@@ -165,14 +170,14 @@ export async function runPiped(
} }
const id = callId++; const id = callId++;
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(body)); if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(body));
res = await client.run(body); res = await client.run(body, trace);
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(res)); if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(res));
} catch (e) { } catch (e) {
if (e instanceof FormatError || e instanceof SystemError) throw e; if (e instanceof FormatError || e instanceof SystemError) throw e;
console.error(e); console.error(e);
throw new SystemError('Sandbox Error', [e]); throw new SystemError('Sandbox Error', [e]);
} }
return await Promise.all(res.map((r) => adaptResult(r, {}))) as SandboxAdaptedResult[]; return res.map((r) => adaptResult(r, params)) as SandboxAdaptedResult[];
} }
export async function del(fileId: string) { export async function del(fileId: string) {
@@ -183,33 +188,25 @@ export async function get(fileId: string) {
return await client.getFile(fileId); return await client.getFile(fileId);
} }
export async function run(execute: string, params?: Parameter): Promise<SandboxAdaptedResult> {
let result: SandboxResult;
try {
if (!supportOptional) {
const res = await client.version();
supportOptional = res.copyOutOptional;
if (!supportOptional) logger.warn('Sandbox version tooooooo low! Please upgrade to at least 1.2.0');
}
const body = { cmd: [proc({ execute, ...params })] };
const id = callId++;
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(body));
const res = await client.run(body);
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(res));
[result] = res;
} catch (e) {
if (e instanceof FormatError || e instanceof SystemError) throw e;
console.error(e);
// FIXME request body larger than maxBodyLength limit
throw new SystemError('Sandbox Error', e.message);
}
return await adaptResult(result, params);
}
const queue = new PQueue({ concurrency: getConfig('concurrency') || getConfig('parallelism') }); const queue = new PQueue({ concurrency: getConfig('concurrency') || getConfig('parallelism') });
export function runQueued(execute: string, params?: Parameter, priority = 0) { export function runQueued(
return queue.add(() => run(execute, params), { priority }) as Promise<SandboxAdaptedResult>; execute: Parameter[], pipeMapping: Pick<PipeMap, 'in' | 'out' | 'name'>[],
params: Parameter, trace?: string, priority?: number,
): Promise<SandboxAdaptedResult[]>;
export function runQueued(execute: string, params: Parameter, trace?: string, priority?: number): Promise<SandboxAdaptedResult>;
export function runQueued(
arg0: string | Parameter[], arg1: Pick<PipeMap, 'in' | 'out' | 'name'>[] | Parameter,
arg2?: string | Parameter, arg3?: string | number, arg4?: number,
) {
const single = !Array.isArray(arg0);
const [execute, pipeMapping, params, trace, priority] = single
? [[{ execute: arg0 }], [], arg1 || {}, arg2 || '', arg3 || 0] as any
: [arg0, arg1, arg2 || {}, arg3 || '', arg4 || 0];
return queue.add(async () => {
const res = await runPiped(execute, pipeMapping, params, trace);
return single ? res[0] : res;
}, { priority });
} }
export async function versionCheck(reportWarn: (str: string) => void, reportError = reportWarn) { export async function versionCheck(reportWarn: (str: string) => void, reportError = reportWarn) {
+27 -15
View File
@@ -3,12 +3,14 @@ import fs from 'fs';
import superagent from 'superagent'; import superagent from 'superagent';
import WebSocket from 'ws'; import WebSocket from 'ws';
import { pipeRequest } from '@hydrooj/utils'; import { pipeRequest } from '@hydrooj/utils';
import { version } from '../../package.json';
import { getConfig } from '../config'; import { getConfig } from '../config';
import { import {
Input, Output, Resize, SandboxRequest, SandboxResult, SandboxVersion, Input, Output, Resize, SandboxRequest, SandboxResult, SandboxVersion,
} from './interface'; } from './interface';
let url; let url: string;
const UA = `HydroJudge/${version} (${Math.random().toString(36).substring(2, 8)})`;
export class Stream extends EventEmitter { export class Stream extends EventEmitter {
private ws: WebSocket; private ws: WebSocket;
@@ -16,7 +18,7 @@ export class Stream extends EventEmitter {
constructor(httpUrl: string, req: SandboxRequest) { constructor(httpUrl: string, req: SandboxRequest) {
super(); super();
const wsUrl = `${httpUrl.replace('http', 'ws')}/stream`; const wsUrl = `${httpUrl.replace('http', 'ws')}/stream`;
this.ws = new WebSocket(wsUrl); this.ws = new WebSocket(wsUrl, { headers: { 'User-Agent': UA } });
this.ws.onmessage = (e) => { this.ws.onmessage = (e) => {
const data: Buffer = e.data as Buffer; const data: Buffer = e.data as Buffer;
switch (data[0]) { switch (data[0]) {
@@ -81,28 +83,38 @@ export class Stream extends EventEmitter {
} }
} }
function _call(method: 'post' | 'get' | 'delete', endpoint: string, trace?: string) {
return superagent[method](`${url}/${endpoint}`).set('User-Agent', trace ? `${UA} (${trace})` : UA);
}
const client = new Proxy({ const client = new Proxy({
run(req: SandboxRequest): Promise<SandboxResult[]> { async run(req: SandboxRequest, trace?: string): Promise<SandboxResult[]> {
return superagent.post(`${url}/run`).send(req).then((res) => res.body); const res = await _call('post', 'run', trace).send(req);
return res.body;
}, },
getFile(fileId: string, dest?: string): Promise<Buffer> { async getFile(fileId: string, dest?: string): Promise<Buffer> {
const req = _call('get', `file/${fileId}`);
if (dest) { if (dest) {
const w = fs.createWriteStream(dest); const w = fs.createWriteStream(dest);
return pipeRequest(superagent.get(`${url}/file/${fileId}`), w, 60000, fileId) as any; return await pipeRequest(req, w, 60000, fileId) as any;
} }
return superagent.get(`${url}/file/${fileId}`).responseType('arraybuffer').then((res) => res.body); const res = await req.responseType('arraybuffer');
return res.body;
}, },
deleteFile(fileId: string): Promise<void> { async deleteFile(fileId: string): Promise<void> {
return superagent.delete(`${url}/file/${fileId}`).then((res) => res.body); const res = await _call('delete', `file/${fileId}`);
return res.body;
}, },
listFiles(): Promise<Record<string, string>> { async listFiles(): Promise<Record<string, string>> {
return superagent.get(`${url}/file`).then((res) => res.body); const res = await _call('get', 'file');
return res.body;
}, },
version(): Promise<SandboxVersion> { async version(): Promise<SandboxVersion> {
return superagent.get(`${url}/version`).then((res) => res.body); const res = await _call('get', 'version');
return res.body;
}, },
config(): Promise<Record<string, any>> { async config(): Promise<Record<string, any>> {
return superagent.get(`${url}/config`).then((res) => res.body); const res = await _call('get', 'config');
return res.body;
}, },
stream(req: SandboxRequest): Stream { stream(req: SandboxRequest): Stream {
return new Stream(url, req); return new Stream(url, req);
+1 -1
View File
@@ -167,7 +167,7 @@ export class JudgeTask {
env: this.env, env: this.env,
time: 5000, time: 5000,
memory: 256, memory: 256,
}); }, `analysis[${this.lang}]<${this.rid}>`, 5);
const out = r.stdout.toString(); const out = r.stdout.toString();
if (out.length) this.next({ compilerText: out.substring(0, 1024) }); if (out.length) this.next({ compilerText: out.substring(0, 1024) });
if (process.env.DEV) console.log(r); if (process.env.DEV) console.log(r);