From e62a7fe3948fdb53700b0a8378fede8b1c91ebbb Mon Sep 17 00:00:00 2001 From: undefined Date: Sun, 16 Jun 2024 14:33:54 +0800 Subject: [PATCH] core&judge: probe compiler versions --- packages/hydrojudge/src/compiler.ts | 18 +++++++++++++ packages/hydrojudge/src/hosts/builtin.ts | 18 +++++++++++-- packages/hydrojudge/src/hosts/hydro.ts | 13 ++++++--- packages/hydrooj/setting.yaml | 19 +++++++++++-- packages/hydrooj/src/handler/status.ts | 34 ++++++++++++++++++++++-- packages/utils/lib/lang.ts | 1 + 6 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 packages/hydrojudge/src/compiler.ts diff --git a/packages/hydrojudge/src/compiler.ts b/packages/hydrojudge/src/compiler.ts new file mode 100644 index 000000000..bcf5050ac --- /dev/null +++ b/packages/hydrojudge/src/compiler.ts @@ -0,0 +1,18 @@ +import { LangConfig } from '@hydrooj/utils/lib/lang'; +import { runQueued } from './sandbox'; + +export async function compilerVersions(langs: Record) { + const result = {}; + for (const lang in langs) { + const version = langs[lang].version; + if (!version) continue; + // eslint-disable-next-line no-await-in-loop + const res = await runQueued(version, { + copyIn: {}, + time: 10000, + memory: 256 * 1024 * 1024, + }, 5); + result[lang] = `${res.stdout}\n${res.stderr}`.trim(); + } + return result; +} diff --git a/packages/hydrojudge/src/hosts/builtin.ts b/packages/hydrojudge/src/hosts/builtin.ts index 455132618..9e78e6da2 100644 --- a/packages/hydrojudge/src/hosts/builtin.ts +++ b/packages/hydrojudge/src/hosts/builtin.ts @@ -2,10 +2,13 @@ /* eslint-disable no-await-in-loop */ import path from 'path'; import { fs } from '@hydrooj/utils'; +import * as sysinfo from '@hydrooj/utils/lib/sysinfo'; import { - JudgeHandler, JudgeResultBody, ObjectId, RecordModel, SettingModel, - StorageModel, SystemModel, TaskModel, + db, JudgeHandler, JudgeResultBody, ObjectId, RecordModel, + SettingModel, StorageModel, SystemModel, TaskModel, } from 'hydrooj'; +import { langs } from 'hydrooj/src/model/setting'; +import { compilerVersions } from '../compiler'; import { getConfig } from '../config'; import { FormatError, SystemError } from '../error'; import { Context } from '../judge/interface'; @@ -82,6 +85,7 @@ export async function postInit(ctx) { c.check.addChecker('Judge', (_ctx, log, warn, error) => versionCheck(warn, error)); }); await fs.ensureDir(getConfig('tmp_dir')); + const info = await sysinfo.get(); const handle = async (t) => { const rdoc = await RecordModel.get(t.domainId, t.rid); if (!rdoc) { @@ -92,9 +96,19 @@ export async function postInit(ctx) { }; const parallelism = Math.max(getConfig('parallelism'), 2); const taskConsumer = TaskModel.consume({ type: 'judge' }, handle, true, parallelism); + function collectCompilerInfo() { + const coll = db.collection('status'); + compilerVersions(langs).then((compilers) => coll.updateOne( + { mid: info.mid, type: 'judge' }, + { $set: { compilers } }, + { upsert: true }, + )).catch((e) => logger.error(e)); + } ctx.on('system/setting', () => { taskConsumer.setConcurrency(Math.max(getConfig('parallelism'), 2)); + collectCompilerInfo(); }); + collectCompilerInfo(); TaskModel.consume({ type: 'judge', priority: { $gt: -50 } }, handle); TaskModel.consume({ type: 'generate' }, handle); } diff --git a/packages/hydrojudge/src/hosts/hydro.ts b/packages/hydrojudge/src/hosts/hydro.ts index 9bcad2147..ef501778b 100644 --- a/packages/hydrojudge/src/hosts/hydro.ts +++ b/packages/hydrojudge/src/hosts/hydro.ts @@ -7,6 +7,7 @@ import { fs, pipeRequest } from '@hydrooj/utils'; import { LangConfig } from '@hydrooj/utils/lib/lang'; import * as sysinfo from '@hydrooj/utils/lib/sysinfo'; import type { JudgeResultBody } from 'hydrooj'; +import { compilerVersions } from '../compiler'; import { getConfig } from '../config'; import { FormatError, SystemError } from '../error'; import { Session } from '../interface'; @@ -173,13 +174,19 @@ export default class Hydro implements Session { const content = Object.keys(config).length ? JSON.stringify({ key: 'config', ...config }) : '{"key":"ping"}'; + let compilers = {}; this.ws.on('message', (data) => { if (data.toString() === 'ping') { this.ws.send('pong'); return; } const request = JSON.parse(data.toString()); - if (request.language) this.language = request.language; + if (request.language) { + this.language = request.language; + compilerVersions(this.language).then((res) => { + compilers = res; + }); + } if (request.task) queue.add(() => new JudgeTask(this, request.task).handle().catch((e) => log.error(e))); }); this.ws.on('close', (data, reason) => { @@ -196,10 +203,10 @@ export default class Hydro implements Session { this.ws.send('{"key":"start"}'); if (!this.config.noStatus) { const info = await sysinfo.get(); - this.ws.send(JSON.stringify({ key: 'status', info })); + this.ws.send(JSON.stringify({ key: 'status', info: { ...info, compilers } })); setInterval(async () => { const [mid, inf] = await sysinfo.update(); - this.ws.send(JSON.stringify({ key: 'status', info: { mid, ...inf } })); + this.ws.send(JSON.stringify({ key: 'status', info: { mid, ...inf, compilers } })); }, 1200000); } resolve(null); diff --git a/packages/hydrooj/setting.yaml b/packages/hydrooj/setting.yaml index 5ff8a6ede..e9e3b6501 100644 --- a/packages/hydrooj/setting.yaml +++ b/packages/hydrooj/setting.yaml @@ -23,12 +23,12 @@ langs: - ~ All keys starting with _ will be ignored - ~ Entries named like [foo], [foo].[bar] [foo].[baz] - will be merged into one category. - - ~ A entry named [foo].[bar] will inhert all options + - ~ A entry named [foo].[bar] will inhert all options - defined in entry [foo] if not overwritten - ~ code_file option defaults to foo.[name] - ~ highlight option defaults to [name] - ~ if highlight option was set, monaco option defaults - - to [highlight], otherwise defaults to [name] + - to [highlight], otherwise defaults to [name] - ~ Compiling be be ignored if compile option wasn't set - ~ Key marked with a ? means the option is optional - (name): @@ -49,11 +49,13 @@ langs: code_file: foo.sh execute: /bin/bash foo.sh highlight: bash + version: /bin/bash --version c: compile: /usr/bin/gcc -Wall --std=c99 -o foo foo.c -lm highlight: c monaco: c display: C + version: /usr/bin/gcc --version cc: compile: /usr/bin/g++ -Wall -std=c++14 -o foo foo.cc -lm -I/include code_file: foo.cc @@ -62,6 +64,7 @@ langs: display: C++ address_space_limit: true process_limit: 1 + version: /usr/bin/g++ --version cc.cc98: compile: /usr/bin/g++ -Wall -std=c++98 -o foo foo.cc -lm -I/include display: C++98 @@ -95,6 +98,7 @@ langs: compile: /usr/bin/fpc -O2 -o/w/foo foo.pas highlight: pascal display: Pascal + version: /usr/bin/fpc -iW java: compile: /usr/bin/bash -c "javac -d /w -encoding utf8 ./Main.java && jar cvf Main.jar *.class >/dev/null" code_file: Main.java @@ -105,6 +109,7 @@ langs: monaco: java display: Java compile_time_limit: 30000 + version: /usr/bin/javac -version kt: compile: echo "Invalid language selection" code_file: foo.kt @@ -119,6 +124,7 @@ langs: execute: /usr/bin/java -jar foo.jar display: Kotlin/JVM compile_time_limit: 30000 + version: /usr/bin/kotlinc -version py: compile: /usr/bin/python -c "import py_compile; py_compile.compile('/w/foo.py', '/w/foo', doraise=True)" code_file: foo.py @@ -132,38 +138,47 @@ langs: compile: /usr/bin/python3 -c "import py_compile; py_compile.compile('/w/foo.py', '/w/foo', doraise=True)" execute: /usr/bin/python3 foo display: Python 3 + version: /usr/bin/python3 --version py.pypy3: compile: /bin/bash -c "/usr/bin/pypy3 -c \"import py_compile; py_compile.compile('/w/foo.py', '/w/foo', doraise=True)\" && mv foo.py foo" execute: /bin/bash -c 'mv foo foo.py && /usr/bin/pypy3 -B foo.py' display: PyPy3 + version: /usr/bin/pypy3 --version php: execute: /usr/bin/php foo.php display: PHP + version: /usr/bin/php -v rs: compile: /usr/bin/rustc -O -o /w/foo /w/foo.rs highlight: rust display: Rust + version: /usr/bin/rustc --version hs: compile: /usr/bin/ghc -O -outputdir /tmp -o foo foo.hs highlight: haskell display: Haskell + version: /usr/bin/ghc --version js: execute: /usr/bin/node /w/foo.js highlight: javascript display: NodeJS + version: /usr/bin/node --version go: compile: /usr/bin/go build -o foo foo.go display: Golang + version: /usr/bin/go version rb: execute: /usr/bin/ruby foo.rb highlight: ruby display: Ruby + version: /usr/bin/ruby --version cs: compile: /usr/bin/mcs -optimize+ -out:/w/foo /w/foo.cs execute: /usr/bin/mono foo highlight: csharp monaco: csharp display: 'C#' + version: /usr/bin/mcs --version r: display: R execute: /bin/sh -c "cat /dev/stdin >stdin && /usr/bin/Rscript --vanilla foo.r" diff --git a/packages/hydrooj/src/handler/status.ts b/packages/hydrooj/src/handler/status.ts index 5d0137aef..2f9fc3816 100644 --- a/packages/hydrooj/src/handler/status.ts +++ b/packages/hydrooj/src/handler/status.ts @@ -25,11 +25,41 @@ async function getStatus() { class StatusHandler extends Handler { async get() { const stats = await getStatus(); + const compilers = {}; + const warn = {}; + const result: Array<{ key: string[], message: string }> = []; + // For each language, select the most common compiler message version, + // then merge languages with the same message. for (const stat of stats) { - if (!stat.battery.hasBattery) stat.battery = 'No battery'; + if (!stat.battery?.hasBattery) stat.battery = 'No battery'; else stat.battery = `${stat.battery.type} ${stat.battery.model} ${stat.battery.percent}%${stat.battery.isCharging ? ' Charging' : ''}`; + if (stat.compilers) { + for (const key in stat.compilers) { + if (!compilers[key]) compilers[key] ||= []; + const related = compilers[key].find((i) => i.message === stat.compilers[key]); + if (related) related.related.push(stat._id); + else { + compilers[key].push({ + related: [stat._id], + message: stat.compilers[key], + }); + } + } + } } - this.response.body = { stats }; + for (const key in compilers) { + compilers[key].sort((a, b) => b.related.length - a.related.length); + const message = compilers[key][0].message; + for (let i = 1; i < compilers[key].length; i++) { + for (const id of compilers[key][i].related) { + warn[id] = true; + } + } + const t = result.find((i) => i.message === message); + if (t) t.key.push(key); + else result.push({ key: [key], message }); + } + this.response.body = { stats, compilers: result }; this.response.template = 'status.html'; } } diff --git a/packages/utils/lib/lang.ts b/packages/utils/lib/lang.ts index 14f4c2c38..f794f5a2c 100644 --- a/packages/utils/lib/lang.ts +++ b/packages/utils/lib/lang.ts @@ -24,6 +24,7 @@ export interface LangConfig { comment?: string | [string, string]; compile_time_limit?: number; compile_memory_limit?: number; + version?: string; } export function parseLang(config: string): Record {