Skip to content

Commit 60f966d

Browse files
committed
Implement analyst evaluation mode
The idea behind this mode is that we are not really shadowing and trust the event feed results. However, we want to judge "interesting" runs locally to get useful information without the judging capacity to judge all testcases due to limited judgehost assignment. We do not consider 'TLE' or 'AC' interesting, as rerunning will not yield much more information. We consider 'WA' very interesting and prioritize the judging, but allow manual judging to overtake the priority. We consider 'CE' somewhat interesting, but downprioritize them a lot. For other verdicts, keep the normal priority.
1 parent f1e5ffd commit 60f966d

File tree

6 files changed

+63
-12
lines changed

6 files changed

+63
-12
lines changed

etc/db-config.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,13 @@
118118
type: int
119119
default_value: 1
120120
public: false
121-
description: Lazy evaluation of results? If enabled, stops judging as soon as a highest priority result is found, otherwise always all testcases will be judged. On request will not auto-start judging and is typically used when running as analyst system.
121+
description: Lazy evaluation of results? If enabled, stops judging as soon as a highest priority result is found, otherwise always all testcases will be judged. On request will not auto-start judging. Analyst mode tries to judge only interesting testcases.
122122
options:
123123
1: Lazy
124124
2: Full judging
125125
3: Only on request
126-
regex: /^[123]$/
126+
4: Analyst mode
127+
regex: /^[1234]$/
127128
error_message: A value between 1 and 3 is required.
128129
- name: judgehost_warning
129130
type: int

webapp/src/Controller/API/JudgehostController.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,7 +1076,7 @@ private function addSingleJudgingRun(
10761076
throw new BadMethodCallException('internal bug: the evaluated result changed during judging');
10771077
}
10781078

1079-
if ($lazyEval !== DOMJudgeService::EVAL_FULL) {
1079+
if ($lazyEval !== DOMJudgeService::EVAL_FULL && $lazyEval !== DOMJudgeService::EVAL_ANALYST) {
10801080
// We don't want to continue on this problem, even if there's spare resources.
10811081
$this->em->getConnection()->executeStatement(
10821082
'UPDATE judgetask SET valid=0, priority=:priority'
@@ -1086,7 +1086,7 @@ private function addSingleJudgingRun(
10861086
'jobid' => $judgingRun->getJudgingid(),
10871087
]
10881088
);
1089-
} else {
1089+
} elseif ($lazyEval !== DOMJudgeService::EVAL_ANALYST) {
10901090
// Decrease priority of remaining unassigned judging runs.
10911091
$this->em->getConnection()->executeStatement(
10921092
'UPDATE judgetask SET priority=:priority'

webapp/src/Service/DOMJudgeService.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class DOMJudgeService
7373
final public const EVAL_LAZY = 1;
7474
final public const EVAL_FULL = 2;
7575
final public const EVAL_DEMAND = 3;
76+
final public const EVAL_ANALYST = 4;
7677

7778
// Regex external identifiers must adhere to. Note that we are not checking whether it
7879
// does not start with a dot or dash or ends with a dot. We could but it would make the
@@ -1181,7 +1182,7 @@ public function unblockJudgeTasks(): void
11811182
}
11821183
}
11831184

1184-
public function maybeCreateJudgeTasks(Judging $judging, int $priority = JudgeTask::PRIORITY_DEFAULT, bool $manualRequest = false, int $overshoot = 0): void
1185+
public function maybeCreateJudgeTasks(Judging $judging, int $priority = JudgeTask::PRIORITY_DEFAULT, bool $manualRequest = false, int $overshoot = 0, bool $valid = true): void
11851186
{
11861187
$submission = $judging->getSubmission();
11871188
$problem = $submission->getContestProblem();
@@ -1194,7 +1195,7 @@ public function maybeCreateJudgeTasks(Judging $judging, int $priority = JudgeTas
11941195
return;
11951196
}
11961197

1197-
$this->actuallyCreateJudgetasks($priority, $judging, $overshoot);
1198+
$this->actuallyCreateJudgetasks($priority, $judging, $overshoot, $valid);
11981199

11991200
$team = $submission->getTeam();
12001201
$result = $this->em->createQueryBuilder()
@@ -1212,7 +1213,7 @@ public function maybeCreateJudgeTasks(Judging $judging, int $priority = JudgeTas
12121213

12131214
// Teams that submit frequently slow down the judge queue but should not be able to starve other teams of their
12141215
// deserved and timely judgement.
1215-
// For every "recent" pending job in the queue by that team, add a penalty (60s). Our definiition of "recent"
1216+
// For every "recent" pending job in the queue by that team, add a penalty (60s). Our definition of "recent"
12161217
// includes all submissions that have been placed at a virtual time (including penalty) more recent than 60s
12171218
// ago. This is done in order to avoid punishing teams who submit while their submissions are stuck in the queue
12181219
// for other reasons, for example an internal error for a problem or language.
@@ -1575,19 +1576,20 @@ private function allowJudge(ContestProblem $problem, Submission $submission, Lan
15751576
return !$evalOnDemand;
15761577
}
15771578

1578-
private function actuallyCreateJudgetasks(int $priority, Judging $judging, int $overshoot = 0): void
1579+
private function actuallyCreateJudgetasks(int $priority, Judging $judging, int $overshoot = 0, bool $valid = true): void
15791580
{
15801581
$submission = $judging->getSubmission();
15811582
$problem = $submission->getContestProblem();
15821583
// We use a mass insert query, since that is way faster than doing a separate insert for each testcase.
1583-
// We first insert judgetasks, then select their ID's and finally insert the judging runs.
1584+
// We first insert judgetasks, then select their IDs and finally insert the judging runs.
15841585

15851586
// Step 1: Create the template for the judgetasks.
15861587
$compileExecutable = $submission->getLanguage()->getCompileExecutable()->getImmutableExecutable();
15871588
$judgetaskInsertParams = [
15881589
':type' => JudgeTaskType::JUDGING_RUN,
15891590
':submitid' => $submission->getSubmitid(),
15901591
':priority' => $priority,
1592+
':valid' => $valid ? 1 : 0,
15911593
':jobid' => $judging->getJudgingid(),
15921594
':uuid' => $judging->getUuid(),
15931595
':compile_script_id' => $compileExecutable->getImmutableExecId(),

webapp/src/Service/ExternalContestSourceService.php

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use App\Entity\ExternalJudgement;
2828
use App\Entity\ExternalRun;
2929
use App\Entity\ExternalSourceWarning;
30+
use App\Entity\JudgeTask;
3031
use App\Entity\Language;
3132
use App\Entity\Problem;
3233
use App\Entity\Submission;
@@ -1758,13 +1759,13 @@ protected function importRun(Event $event, EventData $data): void
17581759
}
17591760

17601761
// First, load the external run.
1762+
$persist = false;
17611763
$run = $this->em
17621764
->getRepository(ExternalRun::class)
17631765
->findOneBy([
17641766
'contest' => $this->getSourceContest(),
17651767
'externalid' => $runId,
17661768
]);
1767-
$persist = false;
17681769
if (!$run) {
17691770
$run = new ExternalRun();
17701771
$run
@@ -1839,9 +1840,50 @@ protected function importRun(Event $event, EventData $data): void
18391840
if ($persist) {
18401841
$this->em->persist($run);
18411842
}
1843+
1844+
$lazyEval = $this->config->get('lazy_eval_results');
1845+
if ($lazyEval === DOMJudgeService::EVAL_ANALYST) {
1846+
// Check if we want to judge this testcase locally to provide useful information for analysts
1847+
$priority = $this->getAnalystRunPriority($run);
1848+
if ($priority !== null) {
1849+
// Make the judgetask valid and assign running priority if no judgehost has picked it up yet.
1850+
$this->em->createQueryBuilder()
1851+
->update(JudgeTask::class, 'jt')
1852+
->set('jt.valid', true)
1853+
->set('jt.priority', $priority)
1854+
->andWhere('jt.testcase_id = :testcase_id')
1855+
->andWhere('jt.submission = :submission')
1856+
->andWhere('jt.judgehost IS NULL')
1857+
->setParameter('testcase_id', $testcase->getTestcaseid())
1858+
->setParameter('submission', $externalJudgement->getSubmission())
1859+
->getQuery()
1860+
->execute();
1861+
}
1862+
}
1863+
18421864
$this->em->flush();
18431865
}
18441866

1867+
/**
1868+
* Checks if this run is interesting to judge locally for more analysis results.
1869+
* @param ExternalRun $run
1870+
* @return int The judging priority if it should be run locally, null otherwise.
1871+
*/
1872+
protected function getAnalystRunPriority(ExternalRun $run): int | null {
1873+
return match ($run->getResult()) {
1874+
// We will not get any new useful information for TLE testcases, while they take a lot of judgedaemon time.
1875+
'timelimit' => null,
1876+
// We often do not get new useful information for judging correct testcases.
1877+
'correct' => null,
1878+
// Wrong answers are interesting for the analysts, assign a high priority but below manual judging.
1879+
'wrong-answer' => -9,
1880+
// Compile errors could be interesting to see what went wrong, assign a low priority.
1881+
'compiler-error' => 9,
1882+
// Otherwise, judge with normal priority.
1883+
default => 0,
1884+
};
1885+
}
1886+
18451887
protected function processPendingEvents(string $type, string|int $id): void
18461888
{
18471889
// Process pending events.

webapp/src/Service/SubmissionService.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -726,8 +726,13 @@ public function submitSolution(
726726
// This is so that we can use the submitid/judgingid below.
727727
$this->em->flush();
728728

729-
$this->dj->maybeCreateJudgeTasks($judging,
730-
$source === SubmissionSource::PROBLEM_IMPORT ? JudgeTask::PRIORITY_LOW : JudgeTask::PRIORITY_DEFAULT);
729+
$priority = match ($source) {
730+
SubmissionSource::PROBLEM_IMPORT => JudgeTask::PRIORITY_LOW,
731+
default => JudgeTask::PRIORITY_DEFAULT,
732+
};
733+
// Create judgetask as invalid when evaluating as analyst.
734+
$lazyEval = $this->config->get('lazy_eval_results');
735+
$this->dj->maybeCreateJudgeTasks($judging, $priority, valid: $lazyEval !== DOMJudgeService::EVAL_ANALYST);
731736
}
732737

733738
$this->em->wrapInTransaction(function () use ($contest, $submission) {

webapp/tests/Unit/Integration/QueuetaskIntegrationTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ protected function setUp(): void
6060
'shadow_mode' => 0,
6161
'sourcefiles_limit' => 1,
6262
'sourcesize_limit' => 1024*256,
63+
'lazy_eval_results' => 1,
6364
];
6465

6566
$this->config = $this->createMock(ConfigurationService::class);

0 commit comments

Comments
 (0)