From f149f5656b1ab85625af8f91d9c8c5b35d7cfee4 Mon Sep 17 00:00:00 2001 From: hewenyang Date: Tue, 13 Feb 2018 17:50:02 +0800 Subject: [PATCH] Handle finished judge reports at web side --- libs/judgeResult.js | 78 +++++++++++++++++++++++++++++++++++++++ libs/judger.js | 51 ++++++++++++++++++++++--- libs/judger_interfaces.js | 53 ++++++++++++++++++++++++++ modules/api_v2.js | 49 ------------------------ modules/winston.js | 23 ++++++++++++ 5 files changed, 199 insertions(+), 55 deletions(-) create mode 100644 libs/judgeResult.js create mode 100644 libs/judger_interfaces.js create mode 100644 modules/winston.js diff --git a/libs/judgeResult.js b/libs/judgeResult.js new file mode 100644 index 0000000..88959cf --- /dev/null +++ b/libs/judgeResult.js @@ -0,0 +1,78 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const _ = require("lodash"); +const winston = require("winston"); +const interfaces_1 = require("./judger_interfaces"); +const compileError = "Compile Error", systemError = "System Error", testdataError = "No Testdata"; +exports.statusToString = {}; +exports.statusToString[interfaces_1.TestcaseResultType.Accepted] = "Accepted"; +exports.statusToString[interfaces_1.TestcaseResultType.WrongAnswer] = "Wrong Answer"; +exports.statusToString[interfaces_1.TestcaseResultType.PartiallyCorrect] = "Partially Correct"; +exports.statusToString[interfaces_1.TestcaseResultType.MemoryLimitExceeded] = "Memory Limit Exceeded"; +exports.statusToString[interfaces_1.TestcaseResultType.TimeLimitExceeded] = "Time Limit Exceeded"; +exports.statusToString[interfaces_1.TestcaseResultType.OutputLimitExceeded] = "Output Limit Exceeded"; +exports.statusToString[interfaces_1.TestcaseResultType.RuntimeError] = "Runtime Error"; +exports.statusToString[interfaces_1.TestcaseResultType.FileError] = "File Error"; +exports.statusToString[interfaces_1.TestcaseResultType.JudgementFailed] = "Judgement Failed"; +exports.statusToString[interfaces_1.TestcaseResultType.InvalidInteraction] = "Invalid Interaction"; +function firstNonAC(t) { + if (t.every(v => v === interfaces_1.TestcaseResultType.Accepted)) { + return interfaces_1.TestcaseResultType.Accepted; + } + else { + return t.find(r => r !== interfaces_1.TestcaseResultType.Accepted); + } +} +exports.firstNonAC = firstNonAC; +function convertResult(taskId, source) { + winston.debug(`Converting result for ${taskId}`, source); + let time = null, memory = null, score = null, done = true, statusString = null; + if (source.compile && source.compile.status === interfaces_1.TaskStatus.Failed) { + statusString = compileError; + } + else if (source.error != null) { + done = false; + if (source.error === interfaces_1.ErrorType.TestDataError) { + statusString = testdataError; + } + else { + statusString = systemError; + } + } + else if (source.judge != null && source.judge.subtasks != null) { + const forEveryTestcase = function (map, reduce) { + const list = source.judge.subtasks.map(s => reduce(s.cases.filter(c => c.result != null).map(c => map(c.result)))); + if (list.every(x => x == null)) + return null; + else + return reduce(list); + }; + time = forEveryTestcase(c => c.time, _.sum); + memory = forEveryTestcase(c => c.memory, _.max); + if (source.judge.subtasks.some(s => s.cases.some(c => c.status === interfaces_1.TaskStatus.Failed))) { + winston.debug(`Some subtasks failed, returning system error`); + statusString = systemError; + } + else { + score = _.sum(source.judge.subtasks.map(s => s.score)); + const finalResult = forEveryTestcase(c => c.type, firstNonAC); + statusString = exports.statusToString[finalResult]; + } + } + else { + statusString = systemError; + } + const result = { + taskId: taskId, + time: time, + memory: memory, + score: score, + statusNumber: done ? interfaces_1.TaskStatus.Done : interfaces_1.TaskStatus.Failed, + statusString: statusString, + result: source + }; + winston.debug(`Result for ${taskId}`, result); + return result; +} +exports.convertResult = convertResult; +//# sourceMappingURL=judgeResult.js.map diff --git a/libs/judger.js b/libs/judger.js index 85b40e5..b5ed963 100644 --- a/libs/judger.js +++ b/libs/judger.js @@ -6,29 +6,68 @@ const amqp = require('amqplib'); const util = require('util'); const winston = require('winston'); const msgPack = require('msgpack-lite'); +const interface = require('./judger_interfaces'); +const judgeResult = require('./judgeResult'); let amqpConnection; -let publicChannel; +let amqpSendChannel; +let amqpConsumeChannel; async function connect () { amqpConnection = await amqp.connect(syzoj.config.rabbitMQ); - publicChannel = await amqpConnection.createChannel(); - await publicChannel.assertQueue('judge', { + amqpSendChannel = await amqpConnection.createChannel(); + await amqpSendChannel.assertQueue('judge', { maxPriority: 5, durable: true }); - await publicChannel.assertQueue('result', { + await amqpSendChannel.assertQueue('result', { durable: true }); - await publicChannel.assertExchange('progress', 'fanout', { + await amqpSendChannel.assertExchange('progress', 'fanout', { durable: false }); + amqpConsumeChannel = await amqpConnection.createChannel(); + amqpConsumeChannel.prefetch(1); + winston.debug('Winston test'); + amqpConsumeChannel.consume('result', async (msg) => { + (async(msg) => { + const data = msgPack.decode(msg.content); + winston.verbose('Received report for task ' + data.taskId); + let JudgeState = syzoj.model('judge_state'); + let judge_state = await JudgeState.findOne({ where: { task_id: data.taskId } }); + if(data.type === interface.ProgressReportType.Finished) { + const convertedResult = judgeResult.convertResult(data.taskId, data.progress); + winston.verbose('Reporting report finished: ' + data.taskId); + const payload = msgPack.encode({ type: interface.ProgressReportType.Reported, taskId: data.taskId }); + amqpSendChannel.publish('progress', '', payload); + if(!judge_state) return; + judge_state.score = convertedResult.score; + judge_state.pending = false; + judge_state.status = convertedResult.statusString; + judge_state.total_time = convertedResult.time; + judge_state.max_memory = convertedResult.memory; + judge_state.result = convertedResult.result; + await judge_state.save(); + await judge_state.updateRelatedInfo(); + } else { + if(!judge_state) return; + judge_state.compilation = data.progress; + await judge_state.save(); + } + })(msg).then(async() => { + amqpConsumeChannel.ack(msg) + }, async(err) => { + winston.error('Error handling report', err); + amqpConsumeChannel.nack(msg, false, false); + }); + }); amqpConnection.on('error', (err) => { winston.error('RabbitMQ connection failure: ${err.toString()}'); amqpConnection.close(); process.exit(1); }); } + module.exports.judge = async function (judge_state, problem, priority) { let type, param, extraFile = null; switch (problem.type) { @@ -71,7 +110,7 @@ module.exports.judge = async function (judge_state, problem, priority) { }; // TODO: parse extraFileLocation - publicChannel.sendToQueue('judge', msgPack.encode({ content: req.content, extraData: null }), { priority: priority }); + amqpSendChannel.sendToQueue('judge', msgPack.encode({ content: req.content, extraData: null }), { priority: priority }); } connect(); diff --git a/libs/judger_interfaces.js b/libs/judger_interfaces.js new file mode 100644 index 0000000..a463f1a --- /dev/null +++ b/libs/judger_interfaces.js @@ -0,0 +1,53 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var RPCTaskType; +(function (RPCTaskType) { + RPCTaskType[RPCTaskType["Compile"] = 1] = "Compile"; + RPCTaskType[RPCTaskType["RunStandard"] = 2] = "RunStandard"; + RPCTaskType[RPCTaskType["RunSubmitAnswer"] = 3] = "RunSubmitAnswer"; + RPCTaskType[RPCTaskType["RunInteraction"] = 4] = "RunInteraction"; +})(RPCTaskType = exports.RPCTaskType || (exports.RPCTaskType = {})); +; +var ErrorType; +(function (ErrorType) { + ErrorType[ErrorType["SystemError"] = 0] = "SystemError"; + ErrorType[ErrorType["TestDataError"] = 1] = "TestDataError"; +})(ErrorType = exports.ErrorType || (exports.ErrorType = {})); +var TaskStatus; +(function (TaskStatus) { + TaskStatus[TaskStatus["Waiting"] = 0] = "Waiting"; + TaskStatus[TaskStatus["Running"] = 1] = "Running"; + TaskStatus[TaskStatus["Done"] = 2] = "Done"; + TaskStatus[TaskStatus["Failed"] = 3] = "Failed"; + TaskStatus[TaskStatus["Skipped"] = 4] = "Skipped"; +})(TaskStatus = exports.TaskStatus || (exports.TaskStatus = {})); +var TestcaseResultType; +(function (TestcaseResultType) { + TestcaseResultType[TestcaseResultType["Accepted"] = 1] = "Accepted"; + TestcaseResultType[TestcaseResultType["WrongAnswer"] = 2] = "WrongAnswer"; + TestcaseResultType[TestcaseResultType["PartiallyCorrect"] = 3] = "PartiallyCorrect"; + TestcaseResultType[TestcaseResultType["MemoryLimitExceeded"] = 4] = "MemoryLimitExceeded"; + TestcaseResultType[TestcaseResultType["TimeLimitExceeded"] = 5] = "TimeLimitExceeded"; + TestcaseResultType[TestcaseResultType["OutputLimitExceeded"] = 6] = "OutputLimitExceeded"; + TestcaseResultType[TestcaseResultType["FileError"] = 7] = "FileError"; + TestcaseResultType[TestcaseResultType["RuntimeError"] = 8] = "RuntimeError"; + TestcaseResultType[TestcaseResultType["JudgementFailed"] = 9] = "JudgementFailed"; + TestcaseResultType[TestcaseResultType["InvalidInteraction"] = 10] = "InvalidInteraction"; +})(TestcaseResultType = exports.TestcaseResultType || (exports.TestcaseResultType = {})); +var RPCReplyType; +(function (RPCReplyType) { + RPCReplyType[RPCReplyType["Started"] = 1] = "Started"; + RPCReplyType[RPCReplyType["Finished"] = 2] = "Finished"; + RPCReplyType[RPCReplyType["Error"] = 3] = "Error"; +})(RPCReplyType = exports.RPCReplyType || (exports.RPCReplyType = {})); +var ProgressReportType; +(function (ProgressReportType) { + ProgressReportType[ProgressReportType["Started"] = 1] = "Started"; + ProgressReportType[ProgressReportType["Compiled"] = 2] = "Compiled"; + ProgressReportType[ProgressReportType["Progress"] = 3] = "Progress"; + ProgressReportType[ProgressReportType["Finished"] = 4] = "Finished"; + ProgressReportType[ProgressReportType["Reported"] = 5] = "Reported"; +})(ProgressReportType = exports.ProgressReportType || (exports.ProgressReportType = {})); +exports.redisBinarySuffix = '-bin'; +exports.redisMetadataSuffix = '-meta'; +//# sourceMappingURL=interfaces.js.map \ No newline at end of file diff --git a/modules/api_v2.js b/modules/api_v2.js index 3dd25de..3819433 100644 --- a/modules/api_v2.js +++ b/modules/api_v2.js @@ -111,52 +111,3 @@ app.apiRouter.post('/api/v2/markdown', async (req, res) => { res.send(e); } }); - -app.apiRouter.post('/api/v2/judge/compiled', async (req, res) => { - try { - if (req.get('Token') !== syzoj.config.judge_token) return res.status(403).send({ err: 'Incorrect token' }); - let data = req.body; - - let JudgeState = syzoj.model('judge_state'); - let judge_state = await JudgeState.findOne({ where: { task_id: req.body.taskId } }); - if (!judge_state) { - res.send({ return: 1 }); // The task might have been rejudging. - return; - } - judge_state.compilation = req.body.result; - await judge_state.save(); - - res.send({ return: 0 }); - } catch (e) { - syzoj.log(e); - res.status(500).send(e); - } -}); - -app.apiRouter.post('/api/v2/judge/finished', async (req, res) => { - try { - if (req.get('Token') !== syzoj.config.judge_token) return res.status(403).send({ err: 'Incorrect token' }); - let data = req.body; - - let JudgeState = syzoj.model('judge_state'); - let judge_state = await JudgeState.findOne({ where: { task_id: req.body.taskId } }); - if (!judge_state) { - res.send({ return: 1 }); // The task might have been rejudging. - return; - } - // await judge_state.updateResult(JSON.parse(req.body)); - judge_state.score = data.score; - judge_state.pending = false; - judge_state.status = data.statusString; - judge_state.total_time = data.time; - judge_state.max_memory = data.memory; - judge_state.result = data.result; - await judge_state.save(); - await judge_state.updateRelatedInfo(); - - res.send({ return: 0 }); - } catch (e) { - syzoj.log(e); - res.status(500).send(e); - } -}); diff --git a/modules/winston.js b/modules/winston.js new file mode 100644 index 0000000..4849203 --- /dev/null +++ b/modules/winston.js @@ -0,0 +1,23 @@ +const winston = require('winston'); +const _ = require('lodash'); +const util = require('util'); + +function formatter(args) { + var msg = args.level + ' - ' + args.message + (_.isEmpty(args.meta) ? '' : (' - ' + util.inspect(args.meta))); + return msg; +} + +function configureWinston(verbose) { + winston.configure({ + transports: [ + new (winston.transports.Console)({ formatter: formatter }) + ] + }); + if (verbose) { + winston.level = 'debug'; + } else { + winston.level = 'info'; + } +} + +configureWinston(false);