Skip to content

Commit 46d12ed

Browse files
KevinjilDOMjudge team
authored andcommitted
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 d6dd5b3 commit 46d12ed

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
@@ -74,6 +74,7 @@ class DOMJudgeService
7474
final public const EVAL_LAZY = 1;
7575
final public const EVAL_FULL = 2;
7676
final public const EVAL_DEMAND = 3;
77+
final public const EVAL_ANALYST = 4;
7778

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

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

1198-
$this->actuallyCreateJudgetasks($priority, $judging, $overshoot);
1199+
$this->actuallyCreateJudgetasks($priority, $judging, $overshoot, $valid);
11991200

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

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

1587-
private function actuallyCreateJudgetasks(int $priority, Judging $judging, int $overshoot = 0): void
1588+
private function actuallyCreateJudgetasks(int $priority, Judging $judging, int $overshoot = 0, bool $valid = true): void
15881589
{
15891590
$submission = $judging->getSubmission();
15901591
$problem = $submission->getContestProblem();
15911592
// We use a mass insert query, since that is way faster than doing a separate insert for each testcase.
1592-
// We first insert judgetasks, then select their ID's and finally insert the judging runs.
1593+
// We first insert judgetasks, then select their IDs and finally insert the judging runs.
15931594

15941595
// Step 1: Create the template for the judgetasks.
15951596
$compileExecutable = $submission->getLanguage()->getCompileExecutable()->getImmutableExecutable();
15961597
$judgetaskInsertParams = [
15971598
':type' => JudgeTaskType::JUDGING_RUN,
15981599
':submitid' => $submission->getSubmitid(),
15991600
':priority' => $priority,
1601+
':valid' => $valid ? 1 : 0,
16001602
':jobid' => $judging->getJudgingid(),
16011603
':uuid' => $judging->getUuid(),
16021604
':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;
@@ -1777,13 +1778,13 @@ protected function importRun(Event $event, EventData $data): void
17771778
}
17781779

17791780
// First, load the external run.
1781+
$persist = false;
17801782
$run = $this->em
17811783
->getRepository(ExternalRun::class)
17821784
->findOneBy([
17831785
'contest' => $this->getSourceContest(),
17841786
'externalid' => $runId,
17851787
]);
1786-
$persist = false;
17871788
if (!$run) {
17881789
$run = new ExternalRun();
17891790
$run
@@ -1858,9 +1859,50 @@ protected function importRun(Event $event, EventData $data): void
18581859
if ($persist) {
18591860
$this->em->persist($run);
18601861
}
1862+
1863+
$lazyEval = $this->config->get('lazy_eval_results');
1864+
if ($lazyEval === DOMJudgeService::EVAL_ANALYST) {
1865+
// Check if we want to judge this testcase locally to provide useful information for analysts
1866+
$priority = $this->getAnalystRunPriority($run);
1867+
if ($priority !== null) {
1868+
// Make the judgetask valid and assign running priority if no judgehost has picked it up yet.
1869+
$this->em->createQueryBuilder()
1870+
->update(JudgeTask::class, 'jt')
1871+
->set('jt.valid', true)
1872+
->set('jt.priority', $priority)
1873+
->andWhere('jt.testcase_id = :testcase_id')
1874+
->andWhere('jt.submission = :submission')
1875+
->andWhere('jt.judgehost IS NULL')
1876+
->setParameter('testcase_id', $testcase->getTestcaseid())
1877+
->setParameter('submission', $externalJudgement->getSubmission())
1878+
->getQuery()
1879+
->execute();
1880+
}
1881+
}
1882+
18611883
$this->em->flush();
18621884
}
18631885

1886+
/**
1887+
* Checks if this run is interesting to judge locally for more analysis results.
1888+
* @param ExternalRun $run
1889+
* @return int The judging priority if it should be run locally, null otherwise.
1890+
*/
1891+
protected function getAnalystRunPriority(ExternalRun $run): int | null {
1892+
return match ($run->getResult()) {
1893+
// We will not get any new useful information for TLE testcases, while they take a lot of judgedaemon time.
1894+
'timelimit' => null,
1895+
// We often do not get new useful information for judging correct testcases.
1896+
'correct' => null,
1897+
// Wrong answers are interesting for the analysts, assign a high priority but below manual judging.
1898+
'wrong-answer' => -9,
1899+
// Compile errors could be interesting to see what went wrong, assign a low priority.
1900+
'compiler-error' => 9,
1901+
// Otherwise, judge with normal priority.
1902+
default => 0,
1903+
};
1904+
}
1905+
18641906
protected function processPendingEvents(string $type, string|int $id): void
18651907
{
18661908
// Process pending events.

webapp/src/Service/SubmissionService.php

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

735-
$this->dj->maybeCreateJudgeTasks($judging,
736-
$source === SubmissionSource::PROBLEM_IMPORT ? JudgeTask::PRIORITY_LOW : JudgeTask::PRIORITY_DEFAULT);
735+
$priority = match ($source) {
736+
SubmissionSource::PROBLEM_IMPORT => JudgeTask::PRIORITY_LOW,
737+
default => JudgeTask::PRIORITY_DEFAULT,
738+
};
739+
// Create judgetask as invalid when evaluating as analyst.
740+
$lazyEval = $this->config->get('lazy_eval_results');
741+
$this->dj->maybeCreateJudgeTasks($judging, $priority, valid: $lazyEval !== DOMJudgeService::EVAL_ANALYST);
737742
}
738743

739744
$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)