Skip to content

Commit bfc4b98

Browse files
committed
admin interface for milestones
1 parent 6b06905 commit bfc4b98

1 file changed

Lines changed: 145 additions & 9 deletions

File tree

src/kernelbot/cogs/admin_cog.py

Lines changed: 145 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import asyncio
12
import json
23
import subprocess
34
import tempfile
@@ -19,8 +20,9 @@
1920
)
2021
from kernelbot.env import env
2122
from kernelbot.ui.misc import ConfirmationView, DeleteConfirmationModal, GPUSelectionView
22-
from libkernelbot.consts import GitHubGPU, ModalGPU
23+
from libkernelbot.consts import GitHubGPU, ModalGPU, SubmissionMode, get_gpu_by_name
2324
from libkernelbot.leaderboard_db import LeaderboardDoesNotExist, LeaderboardItem, SubmissionItem
25+
from libkernelbot.submission import compute_score
2426
from libkernelbot.task import LeaderboardDefinition, make_task_definition
2527
from libkernelbot.utils import (
2628
KernelBotError,
@@ -122,6 +124,10 @@ def __init__(self, bot: "ClusterBot"):
122124
name="set-forum-ids", description="Sets forum IDs"
123125
)(self.set_forum_ids)
124126

127+
self.trigger_milestones = bot.admin_group.command(
128+
name="trigger-milestones", description="Trigger running of milestones"
129+
)(self.trigger_milestones)
130+
125131
self._scheduled_cleanup_temp_users.start()
126132

127133
# --------------------------------------------------------------------------
@@ -162,6 +168,7 @@ async def leaderboard_create_local(
162168
interaction: discord.Interaction,
163169
directory: str,
164170
gpu: Optional[app_commands.Choice[str]],
171+
milestones: Optional[bool] = False,
165172
):
166173
is_admin = await self.admin_check(interaction)
167174
if not is_admin:
@@ -180,20 +187,19 @@ async def leaderboard_create_local(
180187
leaderboard_name = directory.name + "-dev"
181188

182189
# create-local overwrites existing leaderboard
190+
forum_channel = self.bot.get_channel(self.bot.leaderboard_forum_id)
191+
forum_thread = None
192+
183193
with self.bot.leaderboard_db as db:
184194
try:
185195
old_lb = db.get_leaderboard(leaderboard_name)
196+
forum_id = old_lb["forum_id"]
197+
forum_thread = await self.bot.fetch_channel(forum_id)
186198
except LeaderboardDoesNotExist:
187199
pass
188200
db.delete_leaderboard(leaderboard_name, force=True)
189201

190-
# get existing forum thread or create new one
191-
forum_channel = self.bot.get_channel(self.bot.leaderboard_forum_id)
192-
forum_thread = None
193-
if old_lb:
194-
forum_id = old_lb["forum_id"]
195-
forum_thread = await self.bot.fetch_channel(forum_id)
196-
202+
# create new forum thread if none exists
197203
if forum_thread is None:
198204
forum_thread = await forum_channel.create_thread(
199205
name=leaderboard_name,
@@ -216,6 +222,11 @@ async def leaderboard_create_local(
216222
interaction,
217223
f"Leaderboard '{leaderboard_name}' created.",
218224
)
225+
else:
226+
raise KernelBotError(f"Error creating leaderboard '{leaderboard_name}'")
227+
228+
if milestones:
229+
await self._submit_milestones(interaction, leaderboard_name)
219230

220231
def _parse_deadline(self, deadline: str):
221232
# Try parsing with time first
@@ -354,14 +365,23 @@ async def create_leaderboard_in_db(
354365

355366
with self.bot.leaderboard_db as db:
356367
try:
357-
db.create_leaderboard(
368+
lb_id = db.create_leaderboard(
358369
name=leaderboard_name,
359370
deadline=date_value,
360371
definition=definition,
361372
gpu_types=selected_gpus,
362373
creator_id=interaction.user.id,
363374
forum_id=forum_id,
364375
)
376+
377+
# create entry in milestones table.
378+
for milestone in definition.milestones:
379+
db.create_milestone(
380+
lb_id,
381+
milestone.name,
382+
milestone.code,
383+
description=milestone.description,
384+
)
365385
except KernelBotError as e:
366386
await send_discord_message(
367387
interaction,
@@ -371,6 +391,122 @@ async def create_leaderboard_in_db(
371391
return False
372392
return True
373393

394+
async def _submit_milestones(
395+
self, interaction: discord.Interaction, leaderboard_name: str, gpus: Optional[list] = None
396+
):
397+
backend = self.bot.backend
398+
399+
with self.bot.leaderboard_db as db:
400+
leaderboard_item = db.get_leaderboard(leaderboard_name)
401+
milestones = db.get_leaderboard_milestones(leaderboard_item["id"])
402+
403+
task: "LeaderboardTask" = leaderboard_item["task"]
404+
405+
# ok, submit all that are missing
406+
submit_tasks = []
407+
from kernelbot.discord_reporter import MultiProgressReporterDiscord
408+
409+
reporters = MultiProgressReporterDiscord(interaction)
410+
await reporters.show(f"Milestone runs for {leaderboard_name}")
411+
412+
async def submit_milestone(milestone, gpu, reporter):
413+
result = await backend.submit_leaderboard(
414+
-1,
415+
milestone["code"],
416+
"milestone.py",
417+
gpu,
418+
reporter,
419+
task,
420+
SubmissionMode.LEADERBOARD,
421+
None,
422+
)
423+
424+
# we do not allow milestone runs to fail
425+
if not result.success:
426+
logger.error(f"Milestone run failed: {result}")
427+
raise KernelBotError(f"Milestone run failed: {result.error}")
428+
429+
for key, value in result.runs.items():
430+
if not value.run.success or not value.run.passed:
431+
logger.error(f"Milestone run {key} failed: {value}")
432+
raise KernelBotError(f"Milestone run {key} failed.")
433+
434+
with backend.db as db:
435+
for key, value in result.runs.items():
436+
db.create_submission_run(
437+
milestone=milestone["id"],
438+
start=value.start,
439+
end=value.end,
440+
mode=key,
441+
runner=gpu.name,
442+
score=compute_score(result, task, -1),
443+
secret=False,
444+
compilation=value.compilation,
445+
result=value.run,
446+
system=result.system,
447+
)
448+
449+
if gpus is None:
450+
gpus = leaderboard_item["gpu_types"]
451+
452+
for milestone in milestones:
453+
with backend.db as db:
454+
existing_runs = db.get_runs_generic(milestone_id=milestone["id"])
455+
# create tasks
456+
for gpu in gpus:
457+
if gpu in [r["runner"] for r in existing_runs]:
458+
await send_discord_message(
459+
interaction,
460+
f"Skipping {gpu}; milestone run already exists.",
461+
ephemeral=True,
462+
)
463+
continue
464+
submit_tasks.append(
465+
submit_milestone(
466+
milestone,
467+
get_gpu_by_name(gpu),
468+
reporters.add_run(f"Milestone {milestone['name']} on {gpu}"),
469+
)
470+
)
471+
472+
await send_discord_message(
473+
interaction,
474+
f"Submitted {len(submit_tasks)} milestone runs for {len(milestones)} milestones.",
475+
ephemeral=True,
476+
)
477+
478+
# Execute all milestone submissions
479+
await asyncio.gather(*submit_tasks)
480+
481+
@app_commands.describe(
482+
leaderboard_name="Name of Leaderboard",
483+
gpu="Select GPU. Leave empty to run for all GPUs.",
484+
rerun="Force re-running existing milestones.",
485+
)
486+
@app_commands.autocomplete(leaderboard_name=leaderboard_name_autocomplete)
487+
@with_error_handling
488+
async def trigger_milestones(
489+
self,
490+
interaction: discord.Interaction,
491+
leaderboard_name: str,
492+
gpu: Optional[str],
493+
rerun: Optional[bool] = False,
494+
):
495+
if not await self.admin_check(interaction):
496+
await send_discord_message(
497+
interaction, "You do not have permission to trigger milestones.", ephemeral=True
498+
)
499+
return
500+
501+
if rerun:
502+
if gpu is not None:
503+
raise KernelBotError("Cannot specify `rerun` and `gpu` at the same time")
504+
with self.bot.backend.db as db:
505+
db.delete_milestone_runs(leaderboard_name)
506+
507+
await interaction.response.defer(ephemeral=True)
508+
await self._submit_milestones(interaction, leaderboard_name, gpus=gpu)
509+
374510
@discord.app_commands.describe(leaderboard_name="Name of the leaderboard")
375511
@discord.app_commands.autocomplete(leaderboard_name=leaderboard_name_autocomplete)
376512
@with_error_handling

0 commit comments

Comments
 (0)