t123yh
7 years ago
28 changed files with 1109 additions and 248 deletions
@ -1,7 +1,7 @@
|
||||
{ |
||||
"RabbitMQUrl": "amqp://localhost/", |
||||
"RedisUrl": "redis://127.0.0.1:6379", |
||||
"TestData": "/home/t123yh/syzoj/uploads/testdata", |
||||
"TestData": "/home/t123yh/sync2/syzoj/uploads/testdata", |
||||
"Priority": 1, |
||||
"DataDisplayLimit": 100 |
||||
} |
@ -0,0 +1,190 @@
|
||||
import http = require('http'); |
||||
import socketio = require('socket.io'); |
||||
import diff = require('jsondiffpatch'); |
||||
import jwt = require('jsonwebtoken'); |
||||
|
||||
import { globalConfig as Cfg } from './config'; |
||||
import { convertResult } from '../judgeResult'; |
||||
import { JudgeResult, TaskStatus, CompilationResult, OverallResult } from '../interfaces'; |
||||
|
||||
interface JudgeData { |
||||
running: boolean; |
||||
current?: OverallResult; |
||||
} |
||||
|
||||
interface RoughResult { |
||||
result: string; |
||||
score: number; |
||||
time: number; |
||||
memory: number; |
||||
} |
||||
|
||||
let ioInstance: SocketIO.Server; |
||||
let detailProgressNamespace: SocketIO.Namespace; |
||||
// To do: find a better name
|
||||
let roughProgressNamespace: SocketIO.Namespace; |
||||
// Provide support for NOI contests in which participants
|
||||
// can only see whether his / her submission is successfully compiled.
|
||||
let compileProgressNamespace: SocketIO.Namespace; |
||||
|
||||
const currentJudgeList: JudgeData[] = []; |
||||
const finishedJudgeList = {}; |
||||
|
||||
export function initializeSocketIO(s: http.Server) { |
||||
ioInstance = socketio(http); |
||||
detailProgressNamespace = ioInstance.of('/detail'); |
||||
roughProgressNamespace = ioInstance.of('/rough'); |
||||
compileProgressNamespace = ioInstance.of('/compile'); |
||||
|
||||
// TODO: deduplicate the following code.
|
||||
detailProgressNamespace.on('connection', (socket) => { |
||||
socket.on('join', (reqJwt, cb) => { |
||||
let req; |
||||
try { |
||||
req = jwt.verify(reqJwt, Cfg.token); |
||||
} catch (err) { |
||||
cb({ |
||||
ok: false, |
||||
message: err.toString() |
||||
}); |
||||
return; |
||||
} |
||||
const taskId = req.taskId; |
||||
socket.join(taskId.toString()); |
||||
if (finishedJudgeList[taskId]) { |
||||
cb({ |
||||
ok: true, |
||||
finished: true |
||||
}); |
||||
} else { |
||||
cb({ |
||||
ok: true, |
||||
finished: false, |
||||
current: currentJudgeList[taskId] |
||||
}); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
roughProgressNamespace.on('connection', (socket) => { |
||||
socket.on('join', (reqJwt, cb) => { |
||||
let req; |
||||
try { |
||||
req = jwt.verify(reqJwt, Cfg.token); |
||||
} catch (err) { |
||||
cb({ |
||||
ok: false, |
||||
message: err.toString() |
||||
}); |
||||
return; |
||||
} |
||||
const taskId = req.taskId; |
||||
socket.join(taskId.toString()); |
||||
if (currentJudgeList[taskId]) { |
||||
cb({ |
||||
ok: true, |
||||
running: true, |
||||
finished: false |
||||
}); |
||||
} else if (finishedJudgeList[taskId]) { |
||||
// This is not likely to happen. If some task is processed,
|
||||
// The client should not process it.
|
||||
cb({ |
||||
ok: true, |
||||
running: false, |
||||
finished: true |
||||
}); |
||||
} else { |
||||
cb({ |
||||
ok: true, |
||||
running: false, |
||||
finished: false |
||||
}) |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
compileProgressNamespace.on('connection', (socket) => { |
||||
socket.on('join', (reqJwt, cb) => { |
||||
let req; |
||||
try { |
||||
req = jwt.verify(reqJwt, Cfg.token); |
||||
} catch (err) { |
||||
cb({ |
||||
ok: false, |
||||
message: err.toString() |
||||
}); |
||||
return; |
||||
} |
||||
const taskId = req.taskId; |
||||
socket.join(taskId.toString()); |
||||
if (finishedJudgeList[taskId]) { |
||||
cb({ |
||||
ok: true, |
||||
finished: true |
||||
}); |
||||
} else if (currentJudgeList[taskId] |
||||
&& currentJudgeList[taskId].current |
||||
&& currentJudgeList[taskId].current.compile) { |
||||
cb({ |
||||
ok: true, |
||||
finished: true |
||||
}); |
||||
} else { |
||||
cb({ |
||||
ok: true, |
||||
finished: false |
||||
}); |
||||
} |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
|
||||
export function createTask(taskId: number) { |
||||
detailProgressNamespace.to(taskId.toString()).emit("start"); |
||||
roughProgressNamespace.to(taskId.toString()).emit("start"); |
||||
compileProgressNamespace.to(taskId.toString()).emit("start"); |
||||
currentJudgeList[taskId] = { running: true, current: null }; |
||||
} |
||||
|
||||
export function updateCompileStatus(taskId: number, result: CompilationResult) { |
||||
compileProgressNamespace.to(taskId.toString()).emit('compiled', { |
||||
taskId: taskId, |
||||
result: { |
||||
ok: result.status === TaskStatus.Done, |
||||
message: result.message |
||||
} |
||||
}); |
||||
} |
||||
|
||||
export function updateProgress(taskId: number, data: OverallResult) { |
||||
// currentJudgeList[taskId].current = data;
|
||||
const original = currentJudgeList[taskId].current; |
||||
const delta = diff.diff(original, data); |
||||
detailProgressNamespace.to(taskId.toString()).emit('update', { |
||||
taskId: taskId, |
||||
delta: delta |
||||
}); |
||||
currentJudgeList[taskId].current = data; |
||||
} |
||||
|
||||
export function updateResult(taskId: number, data: OverallResult) { |
||||
const finalResult = convertResult(taskId, data); |
||||
const roughResult = { |
||||
result: finalResult.statusString, |
||||
time: finalResult.time, |
||||
memory: finalResult.memory, |
||||
score: finalResult.score |
||||
}; |
||||
roughProgressNamespace.to(taskId.toString()).emit('finish', { |
||||
taskId: taskId, |
||||
result: roughResult |
||||
}); |
||||
detailProgressNamespace.to(taskId.toString()).emit('finish', { |
||||
taskId: taskId, |
||||
result: data |
||||
}); |
||||
delete currentJudgeList[taskId]; |
||||
finishedJudgeList[taskId] = true; |
||||
} |
@ -0,0 +1,121 @@
|
||||
import { TestData, SubtaskScoringType, TestcaseJudge } from '../interfaces'; |
||||
import { CompilationResult, JudgeResult, TaskStatus, SubtaskResult, TestcaseDetails } from '../../interfaces'; |
||||
import { Language } from '../../languages'; |
||||
import { compile } from './compile'; |
||||
import winston = require('winston'); |
||||
import _ = require('lodash'); |
||||
|
||||
const globalFullScore = 100; |
||||
function calculateSubtaskScore(scoring: SubtaskScoringType, scores: number[]): number { |
||||
if (scoring === SubtaskScoringType.Minimum) { |
||||
return _.min(scores); |
||||
} else if (scoring === SubtaskScoringType.Multiple) { |
||||
return _.reduce(scores, |
||||
(res, cur) => res * cur, 1); |
||||
} else if (scoring === SubtaskScoringType.Summation) { |
||||
return _.sum(scores) / scores.length; |
||||
} |
||||
} |
||||
|
||||
export abstract class JudgerBase { |
||||
priority: number; |
||||
testData: TestData; |
||||
|
||||
constructor(t: TestData, p: number) { |
||||
this.priority = p; |
||||
this.testData = t; |
||||
} |
||||
|
||||
abstract preprocessTestData(): Promise<void>; |
||||
|
||||
abstract compile(): Promise<CompilationResult>; |
||||
|
||||
async judge(reportProgressResult: (p: JudgeResult) => Promise<void>): Promise<JudgeResult> { |
||||
const results: SubtaskResult[] = this.testData.subtasks.map(t => ({ |
||||
cases: t.cases.map(j => ({ |
||||
status: TaskStatus.Waiting |
||||
})), |
||||
status: TaskStatus.Waiting |
||||
})); |
||||
const reportProgress = function () { |
||||
reportProgressResult({ status: TaskStatus.Running, subtasks: results }); |
||||
} |
||||
winston.debug(`Totally ${results.length} subtasks.`); |
||||
|
||||
const judgeTasks: Promise<void>[] = []; |
||||
for (let subtaskIndex = 0; subtaskIndex < this.testData.subtasks.length; subtaskIndex++) { |
||||
const currentResult = results[subtaskIndex]; |
||||
const currentTask = this.testData.subtasks[subtaskIndex]; |
||||
|
||||
judgeTasks.push((async () => { |
||||
// Type minimum is skippable, run one by one
|
||||
if (currentTask.type !== SubtaskScoringType.Summation) { |
||||
let skipped: boolean = true; |
||||
for (let index = 0; index < currentTask.cases.length; index++) { |
||||
const currentTaskResult = currentResult.cases[index]; |
||||
if (skipped) { |
||||
currentTaskResult.status = TaskStatus.Skipped; |
||||
} else { |
||||
winston.verbose(`Judging ${subtaskIndex}, case ${index}.`); |
||||
let score = 0; |
||||
try { |
||||
const taskJudge = await this.judgeTestcase(currentTask.cases[index], async () => { |
||||
currentTaskResult.status = TaskStatus.Running; |
||||
currentResult.status = TaskStatus.Running; |
||||
await reportProgress(); |
||||
}); |
||||
currentTaskResult.status = TaskStatus.Done; |
||||
currentTaskResult.result = taskJudge; |
||||
score = taskJudge.scoringRate; |
||||
} catch (err) { |
||||
currentTaskResult.status = TaskStatus.Failed; |
||||
currentTaskResult.errorMessage = err.toString(); |
||||
winston.warn(`Task runner error: ${err.toString()} (subtask ${subtaskIndex}, case ${index})`); |
||||
} |
||||
if (score === 0 || score === NaN) { |
||||
winston.debug(`Subtask ${subtaskIndex}, case ${index}: zero, skipping the rest.`); |
||||
skipped = true; |
||||
} |
||||
await reportProgress(); |
||||
} |
||||
} |
||||
} else { |
||||
// Non skippable, run all immediately
|
||||
const caseTasks: Promise<void>[] = []; |
||||
for (let index = 0; index < currentTask.cases.length; index++) { |
||||
caseTasks.push((async () => { |
||||
const currentTaskResult = currentResult.cases[index]; |
||||
winston.verbose(`Judging ${subtaskIndex}, case ${index}.`); |
||||
try { |
||||
currentTaskResult.result = await this.judgeTestcase(currentTask.cases[index], async () => { |
||||
currentTaskResult.status = TaskStatus.Running; |
||||
currentResult.status = TaskStatus.Running; |
||||
await reportProgress(); |
||||
}); |
||||
currentTaskResult.status = TaskStatus.Done; |
||||
} catch (err) { |
||||
currentTaskResult.status = TaskStatus.Failed; |
||||
currentTaskResult.errorMessage = err.toString(); |
||||
winston.warn(`Task runner error: ${err.toString()} (subtask ${subtaskIndex}, case ${index})`); |
||||
} |
||||
await reportProgress(); |
||||
})()); |
||||
} |
||||
await Promise.all(caseTasks); |
||||
} |
||||
if (currentResult.cases.some(c => c.status === TaskStatus.Failed)) { |
||||
// If any testcase has failed, the score is invaild.
|
||||
currentResult.score = NaN; |
||||
currentResult.status = TaskStatus.Failed; |
||||
} else { |
||||
currentResult.score = calculateSubtaskScore(currentTask.type, currentResult.cases.map(c => c.result ? c.result.scoringRate : 0)) * currentTask.score; |
||||
currentResult.status = TaskStatus.Done; |
||||
} |
||||
winston.verbose(`Subtask ${subtaskIndex}, finished`); |
||||
})()); |
||||
} |
||||
await Promise.all(judgeTasks); |
||||
return { status: TaskStatus.Done, subtasks: results }; |
||||
} |
||||
protected abstract judgeTestcase(curCase: TestcaseJudge, started: () => Promise<void>): Promise<TestcaseDetails>; |
||||
} |
@ -0,0 +1,20 @@
|
||||
import winston = require('winston'); |
||||
import _ = require('lodash'); |
||||
|
||||
function formatter(args) { |
||||
var msg = args.level + ' - ' + args.message + (_.isEmpty(args.meta) ? '' : (' - ' + JSON.stringify(args.meta))); |
||||
return msg; |
||||
} |
||||
|
||||
export function configureWinston(verbose: boolean) { |
||||
winston.configure({ |
||||
transports: [ |
||||
new (winston.transports.Console)({ formatter: formatter }) |
||||
] |
||||
}); |
||||
if (verbose) { |
||||
(winston as any).level = 'debug'; |
||||
} else { |
||||
(winston as any).level = 'warn'; |
||||
} |
||||
} |
Loading…
Reference in new issue