Skip to content

Commit 4c1da07

Browse files
committed
split off discord specific reporter stuff
1 parent 81ee857 commit 4c1da07

8 files changed

Lines changed: 134 additions & 145 deletions

File tree

src/discord-cluster-manager/api/utils.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,10 @@
1212
CLI_TOKEN_URL,
1313
)
1414
from fastapi import HTTPException
15-
from report import RunProgressReporterAPI
15+
from report import Log, RunProgressReporter, RunResultReport, Text
1616
from submission import SubmissionRequest, prepare_submission
1717

1818

19-
class MockProgressReporter:
20-
"""Class that pretends to be a progress reporter,
21-
is used to avoid errors when running submission,
22-
because runners report progress via discord interactions
23-
"""
24-
25-
async def push(self, message: str):
26-
pass
27-
28-
async def update(self, message: str):
29-
pass
30-
31-
3219
async def _handle_discord_oauth(code: str, redirect_uri: str) -> tuple[str, str]:
3320
"""Handles the Discord OAuth code exchange and user info retrieval."""
3421
client_id = CLI_DISCORD_CLIENT_ID
@@ -221,3 +208,20 @@ def add_reporter(title: str):
221208
db.mark_submission_done(sub_id)
222209

223210
return results, [rep.get_message() + "\n" + rep.long_report for rep in reporters]
211+
212+
213+
class RunProgressReporterAPI(RunProgressReporter):
214+
def __init__(self, title: str):
215+
super().__init__(title=title)
216+
self.long_report = ""
217+
218+
async def _update_message(self):
219+
pass
220+
221+
async def display_report(self, title: str, report: RunResultReport):
222+
for part in report.data:
223+
if isinstance(part, Text):
224+
self.long_report += part.text
225+
elif isinstance(part, Log):
226+
self.long_report += f"\n\n## {part.header}:\n"
227+
self.long_report += f"```\n{part.content}```"

src/discord-cluster-manager/cogs/leaderboard_cog.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
)
1111
from discord import app_commands
1212
from discord.ext import commands
13+
from discord_reporter import MultiProgressReporter
1314
from discord_utils import (
1415
get_user_from_id,
1516
leaderboard_name_autocomplete,
@@ -22,7 +23,6 @@
2223
RunItem,
2324
SubmissionItem,
2425
)
25-
from report import MultiProgressReporter
2626
from submission import SubmissionRequest, prepare_submission
2727
from ui.misc import GPUSelectionView
2828
from ui.table import create_table

src/discord-cluster-manager/cogs/submit_cog.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
from consts import GPU, GPU_TO_SM, RankCriterion, SubmissionMode, get_gpu_by_name
1212
from discord import app_commands
1313
from discord.ext import commands
14+
from discord_reporter import MultiProgressReporter
1415
from discord_utils import send_discord_message, with_error_handling
1516
from report import (
16-
MultiProgressReporter,
1717
RunProgressReporter,
1818
generate_report,
1919
make_short_report,

src/discord-cluster-manager/cogs/verify_run_cog.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
from discord import app_commands
1515
from discord.app_commands import Choice
1616
from discord.ext import commands
17+
from discord_reporter import MultiProgressReporter
1718
from discord_utils import send_discord_message, with_error_handling
1819
from leaderboard_db import RunItem, SubmissionItem
19-
from report import MultiProgressReporter
2020
from task import make_task
2121
from utils import setup_logging
2222

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import discord
2+
from discord_utils import _send_split_log
3+
from report import Log, RunProgressReporter, RunResultReport, Text
4+
5+
6+
class MultiProgressReporter:
7+
def __init__(self, interaction: discord.Interaction, header: str):
8+
self.header = header
9+
self.runs = []
10+
self.interaction = interaction
11+
12+
async def show(self):
13+
await self._update_message()
14+
15+
def add_run(self, title: str) -> "RunProgressReporter":
16+
rpr = RunProgressReporterDiscord(self, self.interaction, title)
17+
self.runs.append(rpr)
18+
return rpr
19+
20+
def make_message(self):
21+
formatted_runs = []
22+
for run in self.runs:
23+
formatted_runs.append(run.get_message())
24+
25+
return str.join("\n\n", [f"# {self.header}"] + formatted_runs)
26+
27+
async def _update_message(self):
28+
if self.interaction is None:
29+
return
30+
31+
await self.interaction.edit_original_response(content=self.make_message(), view=None)
32+
33+
34+
class RunProgressReporterDiscord(RunProgressReporter):
35+
def __init__(
36+
self,
37+
root: MultiProgressReporter,
38+
interaction: discord.Interaction,
39+
title: str,
40+
):
41+
super().__init__(title=title)
42+
self.root = root
43+
self.interaction = interaction
44+
45+
async def _update_message(self):
46+
await self.root._update_message()
47+
48+
async def display_report(self, title: str, report: RunResultReport):
49+
thread = await self.interaction.channel.create_thread(
50+
name=title,
51+
type=discord.ChannelType.private_thread,
52+
auto_archive_duration=1440,
53+
)
54+
await thread.add_user(self.interaction.user)
55+
message = ""
56+
for part in report.data:
57+
if isinstance(part, Text):
58+
if len(message) + len(part.text) > 1900:
59+
await thread.send(message)
60+
message = ""
61+
message += part.text
62+
elif isinstance(part, Log):
63+
message = await _send_split_log(thread, message, part.header, part.content)
64+
65+
if len(message) > 0:
66+
await thread.send(message)
67+
68+
await self.push(f"See results at {thread.jump_url}")

src/discord-cluster-manager/discord_utils.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33

44
import discord
5-
from utils import KernelBotError, setup_logging
5+
from utils import KernelBotError, limit_length, setup_logging
66

77
logger = setup_logging(__name__)
88

@@ -103,3 +103,35 @@ async def leaderboard_name_autocomplete(
103103
except Exception as e:
104104
logger.exception("Error in leaderboard autocomplete", exc_info=e)
105105
return []
106+
107+
108+
async def _send_split_log(thread: discord.Thread, partial_message: str, header: str, log: str):
109+
if len(partial_message) + len(log) + len(header) < 1900:
110+
partial_message += f"\n\n## {header}:\n"
111+
partial_message += f"```\n{log}```"
112+
return partial_message
113+
else:
114+
# send previous chunk
115+
if len(partial_message) > 0:
116+
await thread.send(partial_message)
117+
lines = log.splitlines()
118+
chunks = []
119+
partial_message = ""
120+
for line in lines:
121+
if len(partial_message) + len(line) < 1900:
122+
partial_message += line + "\n"
123+
else:
124+
if partial_message != "":
125+
chunks.append(partial_message)
126+
partial_message = line
127+
128+
if partial_message != "":
129+
chunks.append(partial_message)
130+
131+
# now, format the chunks
132+
for i, chunk in enumerate(chunks):
133+
partial_message = f"\n\n## {header} ({i+1}/{len(chunks)}):\n"
134+
partial_message += f"```\n{limit_length(chunk, 1900)}```"
135+
await thread.send(partial_message)
136+
137+
return ""

src/discord-cluster-manager/report.py

Lines changed: 5 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,8 @@
44
from typing import List
55

66
import consts
7-
import discord
87
from run_eval import CompileResult, EvalResult, FullResult, RunResult, SystemInfo
9-
from utils import format_time
10-
11-
12-
def _limit_length(text: str, maxlen: int):
13-
if len(text) > maxlen:
14-
return text[: maxlen - 6] + " [...]"
15-
else:
16-
return text
17-
18-
19-
async def _send_split_log(thread: discord.Thread, partial_message: str, header: str, log: str):
20-
if len(partial_message) + len(log) + len(header) < 1900:
21-
partial_message += f"\n\n## {header}:\n"
22-
partial_message += f"```\n{log}```"
23-
return partial_message
24-
else:
25-
# send previous chunk
26-
if len(partial_message) > 0:
27-
await thread.send(partial_message)
28-
lines = log.splitlines()
29-
chunks = []
30-
partial_message = ""
31-
for line in lines:
32-
if len(partial_message) + len(line) < 1900:
33-
partial_message += line + "\n"
34-
else:
35-
if partial_message != "":
36-
chunks.append(partial_message)
37-
partial_message = line
38-
39-
if partial_message != "":
40-
chunks.append(partial_message)
41-
42-
# now, format the chunks
43-
for i, chunk in enumerate(chunks):
44-
partial_message = f"\n\n## {header} ({i+1}/{len(chunks)}):\n"
45-
partial_message += f"```\n{_limit_length(chunk, 1900)}```"
46-
await thread.send(partial_message)
47-
48-
return ""
8+
from utils import format_time, limit_length
499

5010

5111
@dataclasses.dataclass
@@ -95,7 +55,7 @@ def _generate_compile_report(reporter: "RunResultReport", comp: CompileResult):
9555
# ok, we found nvcc
9656
message += "# Compilation failed\n"
9757
message += "Command "
98-
message += f"```bash\n>{_limit_length(comp.command, 1000)}```\n"
58+
message += f"```bash\n>{limit_length(comp.command, 1000)}```\n"
9959
message += f"exited with code **{comp.exit_code}**."
10060
reporter.add_text(message)
10161

@@ -108,7 +68,7 @@ def _generate_compile_report(reporter: "RunResultReport", comp: CompileResult):
10868
def _generate_crash_report(reporter: "RunResultReport", run: RunResult):
10969
message = "# Running failed\n"
11070
message += "Command "
111-
message += f"```bash\n{_limit_length(run.command, 1000)}```\n"
71+
message += f"```bash\n{limit_length(run.command, 1000)}```\n"
11272
if run.exit_code == consts.ExitCode.TIMEOUT_EXPIRED:
11373
message += f"**timed out** after {float(run.duration):.2f} seconds."
11474
else:
@@ -127,7 +87,7 @@ def _generate_crash_report(reporter: "RunResultReport", run: RunResult):
12787
def _generate_test_report(reporter: "RunResultReport", run: RunResult):
12888
message = "# Testing failed\n"
12989
message += "Command "
130-
message += f"```bash\n{_limit_length(run.command, 1000)}```\n"
90+
message += f"```bash\n{limit_length(run.command, 1000)}```\n"
13191
message += f"ran successfully in {run.duration:.2f} seconds, but did not pass all tests.\n"
13292
reporter.add_text(message)
13393

@@ -392,7 +352,7 @@ def generate_report(result: FullResult) -> RunResultReport: # noqa: C901
392352
# OK, we were successful
393353
message = "# Success!\n"
394354
message += "Command "
395-
message += f"```bash\n{_limit_length(run.command, 1000)}```\n"
355+
message += f"```bash\n{limit_length(run.command, 1000)}```\n"
396356
message += f"ran successfully in {run.duration:.2f} seconds.\n"
397357
report.add_text(message)
398358

@@ -407,34 +367,6 @@ def generate_report(result: FullResult) -> RunResultReport: # noqa: C901
407367
return report
408368

409369

410-
class MultiProgressReporter:
411-
def __init__(self, interaction: discord.Interaction, header: str):
412-
self.header = header
413-
self.runs = []
414-
self.interaction = interaction
415-
416-
async def show(self):
417-
await self._update_message()
418-
419-
def add_run(self, title: str) -> "RunProgressReporter":
420-
rpr = RunProgressReporterDiscord(self, self.interaction, title)
421-
self.runs.append(rpr)
422-
return rpr
423-
424-
def make_message(self):
425-
formatted_runs = []
426-
for run in self.runs:
427-
formatted_runs.append(run.get_message())
428-
429-
return str.join("\n\n", [f"# {self.header}"] + formatted_runs)
430-
431-
async def _update_message(self):
432-
if self.interaction is None:
433-
return
434-
435-
await self.interaction.edit_original_response(content=self.make_message(), view=None)
436-
437-
438370
class RunProgressReporter:
439371
def __init__(self, title: str):
440372
# short report
@@ -465,57 +397,3 @@ async def display_report(self, title: str, report: RunResultReport):
465397

466398
async def _update_message(self):
467399
raise NotImplementedError()
468-
469-
470-
class RunProgressReporterDiscord(RunProgressReporter):
471-
def __init__(
472-
self,
473-
root: MultiProgressReporter,
474-
interaction: discord.Interaction,
475-
title: str,
476-
):
477-
super().__init__(title=title)
478-
self.root = root
479-
self.interaction = interaction
480-
481-
async def _update_message(self):
482-
await self.root._update_message()
483-
484-
async def display_report(self, title: str, report: RunResultReport):
485-
thread = await self.interaction.channel.create_thread(
486-
name=title,
487-
type=discord.ChannelType.private_thread,
488-
auto_archive_duration=1440,
489-
)
490-
await thread.add_user(self.interaction.user)
491-
message = ""
492-
for part in report.data:
493-
if isinstance(part, Text):
494-
if len(message) + len(part.text) > 1900:
495-
await thread.send(message)
496-
message = ""
497-
message += part.text
498-
elif isinstance(part, Log):
499-
message = await _send_split_log(thread, message, part.header, part.content)
500-
501-
if len(message) > 0:
502-
await thread.send(message)
503-
504-
await self.push(f"See results at {thread.jump_url}")
505-
506-
507-
class RunProgressReporterAPI(RunProgressReporter):
508-
def __init__(self, title: str):
509-
super().__init__(title=title)
510-
self.long_report = ""
511-
512-
async def _update_message(self):
513-
pass
514-
515-
async def display_report(self, title: str, report: RunResultReport):
516-
for part in report.data:
517-
if isinstance(part, Text):
518-
self.long_report += part.text
519-
elif isinstance(part, Log):
520-
self.long_report += f"\n\n## {part.header}:\n"
521-
self.long_report += f"```\n{part.content}```"

src/discord-cluster-manager/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,10 @@ def format_time(value: float | str, err: Optional[float | str] = None, scale=Non
135135
return f"{value:.0f} ± {err:.1f} {unit}"
136136
else:
137137
return f"{value:.0f} {unit}"
138+
139+
140+
def limit_length(text: str, maxlen: int):
141+
if len(text) > maxlen:
142+
return text[: maxlen - 6] + " [...]"
143+
else:
144+
return text

0 commit comments

Comments
 (0)