Browse Source

Better progress feedback when judging

pull/6/head
Menci 6 years ago
parent
commit
c764fa5010
  1. 40
      libs/judger.js
  2. 14
      libs/submissions_process.js
  3. 4
      modules/submission.js
  4. 8
      static/style.css
  5. 2
      views/header.ejs
  6. 25
      views/status_label.ejs
  7. 20
      views/submission.ejs
  8. 4
      views/submissions.ejs
  9. 3
      views/submissions_item.ejs
  10. 9
      views/util.ejs

40
libs/judger.js

@ -13,6 +13,21 @@ let amqpConnection;
let amqpSendChannel;
let amqpConsumeChannel;
const judgeStateCache = new Map();
function getRunningTaskStatusString(result) {
let isPending = status => [0, 1].includes(status);
let allFinished = 0, allTotal = 0;
for (let subtask of result.judge.subtasks) {
for (let curr of subtask.cases) {
allTotal++;
if (!isPending(curr.status)) allFinished++;
}
}
return `Running ${allFinished}/${allTotal}`;
}
async function connect () {
amqpConnection = await amqp.connect(syzoj.config.rabbitMQ);
amqpSendChannel = await amqpConnection.createChannel();
@ -48,11 +63,6 @@ async function connect () {
judge_state.result = convertedResult.result;
await judge_state.save();
await judge_state.updateRelatedInfo();
} else if(data.type === interface.ProgressReportType.Progress) {
if(!judge_state) return;
judge_state.score = convertedResult.score;
judge_state.total_time = convertedResult.time;
judge_state.max_memory = convertedResult.memory;
} else if(data.type == interface.ProgressReportType.Compiled) {
if(!judge_state) return;
judge_state.compilation = data.progress;
@ -60,6 +70,7 @@ async function connect () {
} else {
winston.error("Unsupported result type: " + data.type);
}
})(msg).then(async() => {
amqpConsumeChannel.ack(msg)
}, async(err) => {
@ -78,18 +89,35 @@ async function connect () {
(async (result) => {
if (result.type === interface.ProgressReportType.Started) {
socketio.createTask(result.taskId);
judgeStateCache.set(data.taskId, {
result: 'Compiling',
score: 0,
time: 0,
memory: 0
})
} else if (result.type === interface.ProgressReportType.Compiled) {
socketio.updateCompileStatus(result.taskId, result.progress);
} else if (result.type === interface.ProgressReportType.Progress) {
const convertedResult = judgeResult.convertResult(data.taskId, data.progress);
judgeStateCache.set(data.taskId, {
result: getRunningTaskStatusString(data.progress),
score: convertedResult.score,
time: convertedResult.time,
memory: convertedResult.memory
});
socketio.updateProgress(result.taskId, result.progress);
} else if (result.type === interface.ProgressReportType.Finished) {
socketio.updateResult(result.taskId, result.progress);
setTimeout(() => {
judgeStateCache.delete(result.taskId);
}, 5000);
} else if (result.type === interface.ProgressReportType.Reported) {
socketio.cleanupProgress(result.taskId);
}
})(data).then(async() => {
progressChannel.ack(msg)
}, async(err) => {
console.log(err);
winston.error('Error handling progress', err);
progressChannel.nack(msg, false, false);
});
@ -144,3 +172,5 @@ module.exports.judge = async function (judge_state, problem, priority) {
amqpSendChannel.sendToQueue('judge', msgPack.encode({ content: content, extraData: extraData }), { priority: priority });
}
module.exports.getCachedJudgeState = taskId => judgeStateCache.get(taskId);

14
libs/submissions_process.js

@ -1,3 +1,5 @@
const { getCachedJudgeState } = require('./judger');
const getSubmissionInfo = (s, displayConfig) => ({
submissionId: s.id,
taskId: s.task_id,
@ -10,10 +12,18 @@ const getSubmissionInfo = (s, displayConfig) => ({
submitTime: syzoj.utils.formatDate(s.submit_time),
});
const getRoughResult = (x, displayConfig) => {
const getRoughResult = (x, displayConfig, roughOnly) => {
if (displayConfig.showResult) {
if (x.pending) {
return null;
let res = getCachedJudgeState(x.task_id) || null
if (!res) return null;
if (roughOnly) {
res.result = 'Judging';
res.time = res.memory = res.score = 0;
}
return res;
} else {
return {
result: x.status,

4
modules/submission.js

@ -111,7 +111,7 @@ app.get('/submissions', async (req, res) => {
type: 'rough',
displayConfig: displayConfig
}, syzoj.config.session_secret) : null,
result: getRoughResult(x, displayConfig),
result: getRoughResult(x, displayConfig, true),
running: false,
})),
paginate: paginate,
@ -170,7 +170,7 @@ app.get('/submission/:id', async (req, res) => {
displayConfig.showRejudge = await judge.problem.isAllowedEditBy(res.locals.user);
res.render('submission', {
info: getSubmissionInfo(judge, displayConfig),
roughResult: getRoughResult(judge, displayConfig),
roughResult: getRoughResult(judge, displayConfig, false),
code: (judge.problem.type !== 'submit-answer') ? judge.code.toString("utf8") : '',
formattedCode: judge.formattedCode ? judge.formattedCode.toString("utf8") : null,
preferFormattedCode: res.locals.user ? res.locals.user.prefer_formatted_code : false,

8
static/style.css

@ -147,7 +147,13 @@ body > .ui.page.dimmer {
:not(.status_detail).status.running,
.title:hover .status_detail.status.running,
.title.active .status_detail.status.running {
.title.active .status_detail.status.running,
:not(.status_detail).status.pending,
.title:hover .status_detail.status.pending,
.title.active .status_detail.status.pending,
:not(.status_detail).status.judging,
.title:hover .status_detail.status.judging,
.title.active .status_detail.status.judging {
color: #6cf;
}

2
views/header.ejs

@ -11,7 +11,7 @@
<link href="/mathjax.css?20181105" rel="stylesheet">
<link href="https://cdnjs.loli.net/ajax/libs/KaTeX/0.10.0/katex.min.css" rel="stylesheet">
<link href="https://cdnjs.loli.net/ajax/libs/morris.js/0.5.1/morris.css" rel="stylesheet">
<link href="/style.css?2018110801" rel="stylesheet">
<link href="/style.css?20181211" rel="stylesheet">
<link href="https://fonts.loli.net/css?family=Fira+Mono" rel="stylesheet">
<link href="https://fonts.loli.net/css?family=Lato:400,700,400italic,700italic&subset=latin" rel="stylesheet">
<link href="https://fonts.loli.net/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i,800,800i&amp;subset=latin-ext" rel="stylesheet">

25
views/status_label.ejs

@ -11,6 +11,7 @@ const iconList = {
'Waiting': 'hourglass half',
'Running': 'spinner',
'Compiling': 'spinner',
'Judging': 'spinner',
'Compile Error': 'code',
'Submitted': 'checkmark', // NOI contests
'System Error': 'server',
@ -24,14 +25,26 @@ Vue.component('status-label', {
props: ['status', 'indetail', 'progress'],
computed: {
icon() {
if (this.status in iconList) {
return iconList[this.status];
var x = this.status.startsWith('Running') ? 'Running' : this.status;
if (x in iconList) {
return iconList[x];
} else {
return 'man';
}
},
colorClass() {
return (this.indetail ? 'status_detail ' : '') + this.status.toLowerCase().split(' ').join('_');
var x = this.status.startsWith('Running') ? 'Running' : this.status;
return (this.indetail ? 'status_detail ' : '') + x.toLowerCase().split(' ').join('_');
},
displayStatus() {
return this.status.startsWith('Running') ? 'Running' : this.status;
},
getProgress() {
if (this.status.startsWith('Running ')) {
var tmp = this.status.split(' ')[1].trim().split('/');
return { total: tmp[1], finished: tmp[0] };
}
return this.progress;
}
}
})
@ -39,11 +52,11 @@ Vue.component('status-label', {
<script type="text/x-template" id="statusIconTemplate">
<span class="status" :class="colorClass">
<i class="icon" :class="icon"></i>
<template v-if="['Running', 'Waiting'].includes(status) && progress && progress.total">
<span style="text-align: left; display: inline-block; width: 58px; ">{{ status }}</span>{{ progress.finished }}/{{ progress.total }}
<template v-if="['Running', 'Waiting'].includes(displayStatus) && getProgress && getProgress.total">
<span style="text-align: left; display: inline-block; width: 58px; ">{{ displayStatus }}</span>{{getProgress.finished }}/{{ getProgress.total }}
</template>
<template v-else>
{{ status }}
{{ displayStatus }}
</template>
</span>
</script>

20
views/submission.ejs

@ -261,17 +261,17 @@ const vueApp = new Vue({
checkTestcaseOK(c) {
return c.status === TaskStatus.Done;
},
getProgress(i) {
getProgress(index) {
if (!this.detailResult || !this.detailResult.judge || !this.detailResult.judge.subtasks) return {
finished: 0,
total: 0
};
let isPending = status => [TaskStatus.Waiting, TaskStatus.Running].includes(status);
let subtaskProgress = [], allFinished = 0, allTotal = 0;
for (let i in this.detailResult.judge.subtasks) {
let subtaskFinished = 0, subtaskTotal = 0;
for (let j in this.detailResult.judge.subtasks[i].cases) {
var isPending = status => [TaskStatus.Waiting, TaskStatus.Running].includes(status);
var subtaskProgress = [], allFinished = 0, allTotal = 0;
for (var i in this.detailResult.judge.subtasks) {
var subtaskFinished = 0, subtaskTotal = 0;
for (var j in this.detailResult.judge.subtasks[i].cases) {
subtaskTotal++, allTotal++;
if (!isPending(this.detailResult.judge.subtasks[i].cases[j].status)) subtaskFinished++, allFinished++;
}
@ -282,12 +282,12 @@ const vueApp = new Vue({
});
}
let allProgress = {
var allProgress = {
finished: allFinished,
total: allTotal
};
return typeof i === 'undefined' ? allProgress : subtaskProgress[i];
return typeof index === 'undefined' ? allProgress : subtaskProgress[index];
}
},
mounted() {
@ -336,9 +336,9 @@ if (token != null) {
socket.close();
} else {
if (data.running) {
vueApp.roughData.running = true;
// vueApp.roughData.running = true;
vueApp.detailResult = data.current.content;
vueApp.roughData.result = data.roughResult;
// vueApp.roughData.result = data.roughResult;
currentVersion = data.current.version;
}
}

4
views/submissions.ejs

@ -55,7 +55,7 @@
<div class="item" data-value=""><b>不限</b><i class="dropdown icon" style="visibility: hidden; "></i></div>
<% for (let status in this.icon) { %>
<% if (this.iconHidden.includes(status)) continue; %>
<div class="item" data-value="<%= status %>"><span class="status <%= status.toLowerCase().split(' ').join('_') %>"><i class="<%= this.icon[status] %> icon"></i> <b><%= status %></b></div>
<div class="item" data-value="<%= status === 'Pending' ? 'Waiting' : status %>"><span class="status <%= status.toLowerCase().split(' ').join('_') %>"><i class="<%= this.icon[status] %> icon"></i> <b><%= status %></b></div>
<% } %>
</div>
</div>
@ -96,7 +96,7 @@
</tr>
</thead>
<tbody>
<tr v-for="item in items" :config="displayConfig" :show-rejudge="false" :data="item" is='submission-item'>
<tr v-for="item in items" :config="displayConfig" :show-rejudge="false" :data="item" is='submission-item' :rough="true">
</tr>
</tbody>
</table>

3
views/submissions_item.ejs

@ -20,7 +20,7 @@ const userUrl = <%- JSON.stringify(syzoj.utils.makeUrl(['user', '20000528'])) %>
Vue.component('submission-item', {
template: '#submissionItemTemplate',
props: ['data', 'config', 'showRejudge', 'progress', 'compiling'],
props: ['data', 'config', 'showRejudge', 'progress', 'compiling', 'rough'],
computed: {
statusString() {
const data = this.data;
@ -28,6 +28,7 @@ Vue.component('submission-item', {
if (data.result) {
return data.result.result;
} else if (data.running) {
if (this.rough) return 'Judging';
if (this.compiling) return 'Compiling';
return 'Running';
} else return 'Waiting';

9
views/util.ejs

@ -44,6 +44,8 @@ this.icon = {
'Waiting': 'hourglass half',
'Running': 'spinner',
'Compiling': 'spinner',
'Judging': 'spinner',
'Pending': 'hourglass half',
'Compile Error': 'code',
'Submitted': 'checkmark', // NOI contests
'System Error': 'server',
@ -55,7 +57,12 @@ this.icon = {
this.iconHidden = [
'Success',
'Submitted'
'Submitted',
'Compiling',
'Running',
'Waiting',
'Judging',
'Skipped'
];
%>

Loading…
Cancel
Save