core&judge: probe compiler versions

This commit is contained in:
undefined committed 2024-06-16 14:33:54 +08:00
1 parent 5803a1d94d
commit e62a7fe394
6 files changed
+94 -9

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
import { LangConfig } from '@hydrooj/utils/lib/lang';
import { runQueued } from './sandbox';
export async function compilerVersions(langs: Record<string, LangConfig>) {
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;
}
+16 -2
View File
@@ -2,10 +2,13 @@
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
import path from 'path'; import path from 'path';
import { fs } from '@hydrooj/utils'; import { fs } from '@hydrooj/utils';
import * as sysinfo from '@hydrooj/utils/lib/sysinfo';
import { import {
JudgeHandler, JudgeResultBody, ObjectId, RecordModel, SettingModel, db, JudgeHandler, JudgeResultBody, ObjectId, RecordModel,
StorageModel, SystemModel, TaskModel, SettingModel, StorageModel, SystemModel, TaskModel,
} from 'hydrooj'; } from 'hydrooj';
import { langs } from 'hydrooj/src/model/setting';
import { compilerVersions } from '../compiler';
import { getConfig } from '../config'; import { getConfig } from '../config';
import { FormatError, SystemError } from '../error'; import { FormatError, SystemError } from '../error';
import { Context } from '../judge/interface'; 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)); c.check.addChecker('Judge', (_ctx, log, warn, error) => versionCheck(warn, error));
}); });
await fs.ensureDir(getConfig('tmp_dir')); await fs.ensureDir(getConfig('tmp_dir'));
const info = await sysinfo.get();
const handle = async (t) => { const handle = async (t) => {
const rdoc = await RecordModel.get(t.domainId, t.rid); const rdoc = await RecordModel.get(t.domainId, t.rid);
if (!rdoc) { if (!rdoc) {
@@ -92,9 +96,19 @@ export async function postInit(ctx) {
}; };
const parallelism = Math.max(getConfig('parallelism'), 2); const parallelism = Math.max(getConfig('parallelism'), 2);
const taskConsumer = TaskModel.consume({ type: 'judge' }, handle, true, parallelism); 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', () => { ctx.on('system/setting', () => {
taskConsumer.setConcurrency(Math.max(getConfig('parallelism'), 2)); taskConsumer.setConcurrency(Math.max(getConfig('parallelism'), 2));
collectCompilerInfo();
}); });
collectCompilerInfo();
TaskModel.consume({ type: 'judge', priority: { $gt: -50 } }, handle); TaskModel.consume({ type: 'judge', priority: { $gt: -50 } }, handle);
TaskModel.consume({ type: 'generate' }, handle); TaskModel.consume({ type: 'generate' }, handle);
} }
+10 -3
View File
@@ -7,6 +7,7 @@ import { fs, pipeRequest } from '@hydrooj/utils';
import { LangConfig } from '@hydrooj/utils/lib/lang'; import { LangConfig } from '@hydrooj/utils/lib/lang';
import * as sysinfo from '@hydrooj/utils/lib/sysinfo'; import * as sysinfo from '@hydrooj/utils/lib/sysinfo';
import type { JudgeResultBody } from 'hydrooj'; import type { JudgeResultBody } from 'hydrooj';
import { compilerVersions } from '../compiler';
import { getConfig } from '../config'; import { getConfig } from '../config';
import { FormatError, SystemError } from '../error'; import { FormatError, SystemError } from '../error';
import { Session } from '../interface'; import { Session } from '../interface';
@@ -173,13 +174,19 @@ export default class Hydro implements Session {
const content = Object.keys(config).length const content = Object.keys(config).length
? JSON.stringify({ key: 'config', ...config }) ? JSON.stringify({ key: 'config', ...config })
: '{"key":"ping"}'; : '{"key":"ping"}';
let compilers = {};
this.ws.on('message', (data) => { this.ws.on('message', (data) => {
if (data.toString() === 'ping') { if (data.toString() === 'ping') {
this.ws.send('pong'); this.ws.send('pong');
return; return;
} }
const request = JSON.parse(data.toString()); 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))); if (request.task) queue.add(() => new JudgeTask(this, request.task).handle().catch((e) => log.error(e)));
}); });
this.ws.on('close', (data, reason) => { this.ws.on('close', (data, reason) => {
@@ -196,10 +203,10 @@ export default class Hydro implements Session {
this.ws.send('{"key":"start"}'); this.ws.send('{"key":"start"}');
if (!this.config.noStatus) { if (!this.config.noStatus) {
const info = await sysinfo.get(); 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 () => { setInterval(async () => {
const [mid, inf] = await sysinfo.update(); 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); }, 1200000);
} }
resolve(null); resolve(null);
+17 -2
View File
@@ -23,12 +23,12 @@ langs:
- ~ All keys starting with _ will be ignored - ~ All keys starting with _ will be ignored
- ~ Entries named like [foo], [foo].[bar] [foo].[baz] - ~ Entries named like [foo], [foo].[bar] [foo].[baz]
- will be merged into one category. - 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 - defined in entry [foo] if not overwritten
- ~ code_file option defaults to foo.[name] - ~ code_file option defaults to foo.[name]
- ~ highlight option defaults to [name] - ~ highlight option defaults to [name]
- ~ if highlight option was set, monaco option defaults - ~ 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 - ~ Compiling be be ignored if compile option wasn't set
- ~ Key marked with a ? means the option is optional - ~ Key marked with a ? means the option is optional
- (name): - (name):
@@ -49,11 +49,13 @@ langs:
code_file: foo.sh code_file: foo.sh
execute: /bin/bash foo.sh execute: /bin/bash foo.sh
highlight: bash highlight: bash
version: /bin/bash --version
c: c:
compile: /usr/bin/gcc -Wall --std=c99 -o foo foo.c -lm compile: /usr/bin/gcc -Wall --std=c99 -o foo foo.c -lm
highlight: c highlight: c
monaco: c monaco: c
display: C display: C
version: /usr/bin/gcc --version
cc: cc:
compile: /usr/bin/g++ -Wall -std=c++14 -o foo foo.cc -lm -I/include compile: /usr/bin/g++ -Wall -std=c++14 -o foo foo.cc -lm -I/include
code_file: foo.cc code_file: foo.cc
@@ -62,6 +64,7 @@ langs:
display: C++ display: C++
address_space_limit: true address_space_limit: true
process_limit: 1 process_limit: 1
version: /usr/bin/g++ --version
cc.cc98: cc.cc98:
compile: /usr/bin/g++ -Wall -std=c++98 -o foo foo.cc -lm -I/include compile: /usr/bin/g++ -Wall -std=c++98 -o foo foo.cc -lm -I/include
display: C++98 display: C++98
@@ -95,6 +98,7 @@ langs:
compile: /usr/bin/fpc -O2 -o/w/foo foo.pas compile: /usr/bin/fpc -O2 -o/w/foo foo.pas
highlight: pascal highlight: pascal
display: Pascal display: Pascal
version: /usr/bin/fpc -iW
java: java:
compile: /usr/bin/bash -c "javac -d /w -encoding utf8 ./Main.java && jar cvf Main.jar *.class >/dev/null" compile: /usr/bin/bash -c "javac -d /w -encoding utf8 ./Main.java && jar cvf Main.jar *.class >/dev/null"
code_file: Main.java code_file: Main.java
@@ -105,6 +109,7 @@ langs:
monaco: java monaco: java
display: Java display: Java
compile_time_limit: 30000 compile_time_limit: 30000
version: /usr/bin/javac -version
kt: kt:
compile: echo "Invalid language selection" compile: echo "Invalid language selection"
code_file: foo.kt code_file: foo.kt
@@ -119,6 +124,7 @@ langs:
execute: /usr/bin/java -jar foo.jar execute: /usr/bin/java -jar foo.jar
display: Kotlin/JVM display: Kotlin/JVM
compile_time_limit: 30000 compile_time_limit: 30000
version: /usr/bin/kotlinc -version
py: py:
compile: /usr/bin/python -c "import py_compile; py_compile.compile('/w/foo.py', '/w/foo', doraise=True)" compile: /usr/bin/python -c "import py_compile; py_compile.compile('/w/foo.py', '/w/foo', doraise=True)"
code_file: foo.py 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)" compile: /usr/bin/python3 -c "import py_compile; py_compile.compile('/w/foo.py', '/w/foo', doraise=True)"
execute: /usr/bin/python3 foo execute: /usr/bin/python3 foo
display: Python 3 display: Python 3
version: /usr/bin/python3 --version
py.pypy3: 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" 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' execute: /bin/bash -c 'mv foo foo.py && /usr/bin/pypy3 -B foo.py'
display: PyPy3 display: PyPy3
version: /usr/bin/pypy3 --version
php: php:
execute: /usr/bin/php foo.php execute: /usr/bin/php foo.php
display: PHP display: PHP
version: /usr/bin/php -v
rs: rs:
compile: /usr/bin/rustc -O -o /w/foo /w/foo.rs compile: /usr/bin/rustc -O -o /w/foo /w/foo.rs
highlight: rust highlight: rust
display: Rust display: Rust
version: /usr/bin/rustc --version
hs: hs:
compile: /usr/bin/ghc -O -outputdir /tmp -o foo foo.hs compile: /usr/bin/ghc -O -outputdir /tmp -o foo foo.hs
highlight: haskell highlight: haskell
display: Haskell display: Haskell
version: /usr/bin/ghc --version
js: js:
execute: /usr/bin/node /w/foo.js execute: /usr/bin/node /w/foo.js
highlight: javascript highlight: javascript
display: NodeJS display: NodeJS
version: /usr/bin/node --version
go: go:
compile: /usr/bin/go build -o foo foo.go compile: /usr/bin/go build -o foo foo.go
display: Golang display: Golang
version: /usr/bin/go version
rb: rb:
execute: /usr/bin/ruby foo.rb execute: /usr/bin/ruby foo.rb
highlight: ruby highlight: ruby
display: Ruby display: Ruby
version: /usr/bin/ruby --version
cs: cs:
compile: /usr/bin/mcs -optimize+ -out:/w/foo /w/foo.cs compile: /usr/bin/mcs -optimize+ -out:/w/foo /w/foo.cs
execute: /usr/bin/mono foo execute: /usr/bin/mono foo
highlight: csharp highlight: csharp
monaco: csharp monaco: csharp
display: 'C#' display: 'C#'
version: /usr/bin/mcs --version
r: r:
display: R display: R
execute: /bin/sh -c "cat /dev/stdin >stdin && /usr/bin/Rscript --vanilla foo.r" execute: /bin/sh -c "cat /dev/stdin >stdin && /usr/bin/Rscript --vanilla foo.r"
+32 -2
View File
@@ -25,11 +25,41 @@ async function getStatus() {
class StatusHandler extends Handler { class StatusHandler extends Handler {
async get() { async get() {
const stats = await getStatus(); 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) { 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' : ''}`; 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'; this.response.template = 'status.html';
} }
} }
+1
View File
@@ -24,6 +24,7 @@ export interface LangConfig {
comment?: string | [string, string]; comment?: string | [string, string];
compile_time_limit?: number; compile_time_limit?: number;
compile_memory_limit?: number; compile_memory_limit?: number;
version?: string;
} }
export function parseLang(config: string): Record<string, LangConfig> { export function parseLang(config: string): Record<string, LangConfig> {