Skip to content

Commit 46f679e

Browse files
committed
[scoreboard] Provide a better code structure (extract to functions)
1 parent fd454f0 commit 46f679e

File tree

1 file changed

+181
-138
lines changed

1 file changed

+181
-138
lines changed

scoreboard/main.py

Lines changed: 181 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from pathlib import Path
22
from collections import defaultdict
33
from datetime import datetime
4+
import csv
45
import argparse
56
import subprocess
67
import yaml
7-
import csv
88
from jinja2 import Environment, FileSystemLoader
99
import logging
1010

@@ -16,147 +16,190 @@
1616
script_dir = Path(__file__).parent
1717
tasks_dir = script_dir.parent / "tasks"
1818

19-
directories = defaultdict(dict)
20-
21-
if tasks_dir.exists() and tasks_dir.is_dir():
22-
for task_name_dir in tasks_dir.iterdir():
23-
if task_name_dir.is_dir() and task_name_dir.name not in ["common"]:
24-
task_name = task_name_dir.name
25-
for task_type in task_types:
26-
task_type_dir = task_name_dir / task_type
27-
if task_type_dir.exists() and task_type_dir.is_dir():
28-
if task_name.endswith("_disabled"):
29-
clean_task_name = task_name[: -len("_disabled")]
30-
directories[clean_task_name][task_type] = "disabled"
31-
else:
32-
directories[task_name][task_type] = "done"
33-
34-
config_path = Path(__file__).parent / "data" / "threads-config.yml"
35-
assert config_path.exists(), f"Config file not found: {config_path}"
36-
with open(config_path, "r") as file:
37-
cfg = yaml.safe_load(file)
38-
assert cfg, "Configuration is empty"
39-
eff_num_proc = int(cfg["scoreboard"].get("efficiency", {}).get("num_proc", 1))
40-
deadlines_cfg = cfg["scoreboard"].get("deadlines", {})
41-
plagiarism_config_path = Path(__file__).parent / "data" / "plagiarism.yml"
42-
with open(plagiarism_config_path, "r") as file:
43-
plagiarism_cfg = yaml.safe_load(file)
44-
assert plagiarism_cfg, "Plagiarism configuration is empty"
45-
46-
env = Environment(loader=FileSystemLoader(Path(__file__).parent / "templates"))
47-
48-
49-
perf_stat_file_path = (
50-
script_dir.parent / "build" / "perf_stat_dir" / "task_run_perf_table.csv"
51-
)
52-
53-
# Read and parse performance statistics CSV
54-
perf_stats = dict()
55-
if perf_stat_file_path.exists():
56-
with open(perf_stat_file_path, "r", newline="") as csvfile:
57-
reader = csv.DictReader(csvfile)
58-
for row in reader:
59-
perf_stats[row["Task"]] = {
60-
"seq": row["SEQ"],
61-
"omp": row["OMP"],
62-
"tbb": row["TBB"],
63-
"stl": row["STL"],
64-
"all": row["ALL"],
65-
"mpi": "N/A",
66-
}
67-
else:
68-
logger.warning("Performance stats CSV not found at %s", perf_stat_file_path)
69-
70-
rows = []
71-
for dir in sorted(directories.keys()):
72-
row_types = []
73-
total_count = 0
74-
for task_type in task_types:
75-
max_sol_points = int(cfg["scoreboard"]["task"][task_type]["solution"]["max"])
76-
status = directories[dir].get(task_type)
77-
sol_points = max_sol_points if status in ("done", "disabled") else 0
78-
solution_style = ""
79-
if status == "done":
80-
solution_style = "background-color: lightgreen;"
81-
elif status == "disabled":
82-
solution_style = "background-color: #6495ED;"
83-
84-
task_points = sol_points
85-
is_cheated = (
86-
dir in plagiarism_cfg["plagiarism"][task_type]
87-
or dir.rstrip("_disabled") in plagiarism_cfg["plagiarism"][task_type]
88-
)
89-
plagiarism_points = 0
90-
if is_cheated:
91-
plag_coeff = float(cfg["scoreboard"]["plagiarism"]["coefficient"])
92-
plagiarism_points = -plag_coeff * task_points
93-
task_points += plagiarism_points
94-
95-
perf_val = perf_stats.get(dir, {}).get(task_type, "?")
9619

97-
# Calculate acceleration and efficiency if performance data is available
98-
acceleration = "?"
99-
efficiency = "?"
20+
def discover_tasks(tasks_dir, task_types):
21+
"""Discover tasks and their implementation status from the filesystem."""
22+
directories = defaultdict(dict)
23+
24+
if tasks_dir.exists() and tasks_dir.is_dir():
25+
for task_name_dir in tasks_dir.iterdir():
26+
if task_name_dir.is_dir() and task_name_dir.name not in ["common"]:
27+
task_name = task_name_dir.name
28+
for task_type in task_types:
29+
task_type_dir = task_name_dir / task_type
30+
if task_type_dir.exists() and task_type_dir.is_dir():
31+
if task_name.endswith("_disabled"):
32+
clean_task_name = task_name[: -len("_disabled")]
33+
directories[clean_task_name][task_type] = "disabled"
34+
else:
35+
directories[task_name][task_type] = "done"
36+
37+
return directories
38+
39+
40+
directories = discover_tasks(tasks_dir, task_types)
41+
42+
43+
def load_performance_data(perf_stat_file_path):
44+
"""Load and parse performance statistics from CSV file."""
45+
46+
perf_stats = dict()
47+
if perf_stat_file_path.exists():
48+
with open(perf_stat_file_path, "r", newline="") as csvfile:
49+
reader = csv.DictReader(csvfile)
50+
for row in reader:
51+
perf_stats[row["Task"]] = {
52+
"seq": row["SEQ"],
53+
"omp": row["OMP"],
54+
"tbb": row["TBB"],
55+
"stl": row["STL"],
56+
"all": row["ALL"],
57+
"mpi": "N/A",
58+
}
59+
else:
60+
logger.warning("Performance stats CSV not found at %s", perf_stat_file_path)
61+
return perf_stats
62+
63+
64+
def check_plagiarism_and_calculate_penalty(
65+
dir, task_type, sol_points, plagiarism_cfg, cfg
66+
):
67+
"""Check if task is plagiarized and calculate penalty points."""
68+
is_cheated = (
69+
dir in plagiarism_cfg["plagiarism"][task_type]
70+
or dir.rstrip("_disabled") in plagiarism_cfg["plagiarism"][task_type]
71+
)
72+
plagiarism_points = 0
73+
if is_cheated:
74+
plag_coeff = float(cfg["scoreboard"]["plagiarism"]["coefficient"])
75+
plagiarism_points = -plag_coeff * sol_points
76+
return is_cheated, plagiarism_points
77+
78+
79+
def calculate_deadline_penalty(dir, task_type, status, deadlines_cfg, tasks_dir):
80+
"""Calculate deadline penalty points based on git commit timestamp."""
81+
deadline_points = 0
82+
deadline_str = deadlines_cfg.get(task_type)
83+
if status == "done" and deadline_str:
10084
try:
101-
perf_float = float(perf_val)
102-
if perf_float > 0:
103-
speedup = 1.0 / perf_float
104-
acceleration = f"{speedup:.2f}"
105-
efficiency = f"{speedup / eff_num_proc * 100:.2f}%"
106-
except (ValueError, TypeError):
85+
deadline_dt = datetime.fromisoformat(deadline_str)
86+
git_cmd = [
87+
"git",
88+
"log",
89+
"-1",
90+
"--format=%ct",
91+
str(
92+
tasks_dir
93+
/ (dir + ("_disabled" if status == "disabled" else ""))
94+
/ task_type
95+
),
96+
]
97+
result = subprocess.run(git_cmd, capture_output=True, text=True)
98+
if result.stdout.strip().isdigit():
99+
commit_dt = datetime.fromtimestamp(int(result.stdout.strip()))
100+
days_late = (commit_dt - deadline_dt).days
101+
if days_late > 0:
102+
deadline_points = -days_late
103+
except Exception:
107104
pass
105+
return deadline_points
106+
107+
108+
def main():
109+
"""Main function to generate the scoreboard."""
110+
config_path = Path(__file__).parent / "data" / "threads-config.yml"
111+
assert config_path.exists(), f"Config file not found: {config_path}"
112+
with open(config_path, "r") as file:
113+
cfg = yaml.safe_load(file)
114+
assert cfg, "Configuration is empty"
115+
eff_num_proc = int(cfg["scoreboard"].get("efficiency", {}).get("num_proc", 1))
116+
deadlines_cfg = cfg["scoreboard"].get("deadlines", {})
117+
plagiarism_config_path = Path(__file__).parent / "data" / "plagiarism.yml"
118+
with open(plagiarism_config_path, "r") as file:
119+
plagiarism_cfg = yaml.safe_load(file)
120+
assert plagiarism_cfg, "Plagiarism configuration is empty"
121+
122+
env = Environment(loader=FileSystemLoader(Path(__file__).parent / "templates"))
123+
124+
perf_stat_file_path = (
125+
script_dir.parent / "build" / "perf_stat_dir" / "task_run_perf_table.csv"
126+
)
127+
128+
# Read and parse performance statistics CSV
129+
perf_stats = load_performance_data(perf_stat_file_path)
130+
131+
rows = []
132+
for dir in sorted(directories.keys()):
133+
row_types = []
134+
total_count = 0
135+
for task_type in task_types:
136+
max_sol_points = int(
137+
cfg["scoreboard"]["task"][task_type]["solution"]["max"]
138+
)
139+
status = directories[dir].get(task_type)
140+
sol_points = max_sol_points if status in ("done", "disabled") else 0
141+
solution_style = ""
142+
if status == "done":
143+
solution_style = "background-color: lightgreen;"
144+
elif status == "disabled":
145+
solution_style = "background-color: #6495ED;"
146+
147+
task_points = sol_points
148+
is_cheated, plagiarism_points = check_plagiarism_and_calculate_penalty(
149+
dir, task_type, sol_points, plagiarism_cfg, cfg
150+
)
151+
task_points += plagiarism_points
152+
153+
perf_val = perf_stats.get(dir, {}).get(task_type, "?")
108154

109-
# Calculate deadline penalty points
110-
deadline_points = 0
111-
deadline_str = deadlines_cfg.get(task_type)
112-
if status == "done" and deadline_str:
155+
# Calculate acceleration and efficiency if performance data is available
156+
acceleration = "?"
157+
efficiency = "?"
113158
try:
114-
deadline_dt = datetime.fromisoformat(deadline_str)
115-
git_cmd = [
116-
"git",
117-
"log",
118-
"-1",
119-
"--format=%ct",
120-
str(
121-
tasks_dir
122-
/ (dir + ("_disabled" if status == "disabled" else ""))
123-
/ task_type
124-
),
125-
]
126-
result = subprocess.run(git_cmd, capture_output=True, text=True)
127-
if result.stdout.strip().isdigit():
128-
commit_dt = datetime.fromtimestamp(int(result.stdout.strip()))
129-
days_late = (commit_dt - deadline_dt).days
130-
if days_late > 0:
131-
deadline_points = -days_late
132-
except Exception:
159+
perf_float = float(perf_val)
160+
if perf_float > 0:
161+
speedup = 1.0 / perf_float
162+
acceleration = f"{speedup:.2f}"
163+
efficiency = f"{speedup / eff_num_proc * 100:.2f}%"
164+
except (ValueError, TypeError):
133165
pass
134166

135-
row_types.append(
136-
{
137-
"solution_points": sol_points,
138-
"solution_style": solution_style,
139-
"perf": perf_val,
140-
"acceleration": acceleration,
141-
"efficiency": efficiency,
142-
"deadline_points": deadline_points,
143-
"plagiarised": is_cheated,
144-
"plagiarism_points": plagiarism_points,
145-
}
146-
)
147-
total_count += task_points
148-
149-
rows.append({"task": dir, "types": row_types, "total": total_count})
150-
151-
template = env.get_template("index.html.j2")
152-
html_content = template.render(task_types=task_types, rows=rows)
153-
154-
parser = argparse.ArgumentParser(description="Generate HTML scoreboard.")
155-
parser.add_argument("-o", "--output", type=str, required=True, help="Output file path")
156-
args = parser.parse_args()
157-
158-
output_file = Path(args.output) / "index.html"
159-
with open(output_file, "w") as file:
160-
file.write(html_content)
161-
162-
logger.info("HTML page generated at %s", output_file)
167+
# Calculate deadline penalty points
168+
deadline_points = calculate_deadline_penalty(
169+
dir, task_type, status, deadlines_cfg, tasks_dir
170+
)
171+
172+
row_types.append(
173+
{
174+
"solution_points": sol_points,
175+
"solution_style": solution_style,
176+
"perf": perf_val,
177+
"acceleration": acceleration,
178+
"efficiency": efficiency,
179+
"deadline_points": deadline_points,
180+
"plagiarised": is_cheated,
181+
"plagiarism_points": plagiarism_points,
182+
}
183+
)
184+
total_count += task_points
185+
186+
rows.append({"task": dir, "types": row_types, "total": total_count})
187+
188+
template = env.get_template("index.html.j2")
189+
html_content = template.render(task_types=task_types, rows=rows)
190+
191+
parser = argparse.ArgumentParser(description="Generate HTML scoreboard.")
192+
parser.add_argument(
193+
"-o", "--output", type=str, required=True, help="Output file path"
194+
)
195+
args = parser.parse_args()
196+
197+
output_file = Path(args.output) / "index.html"
198+
with open(output_file, "w") as file:
199+
file.write(html_content)
200+
201+
logger.info("HTML page generated at %s", output_file)
202+
203+
204+
if __name__ == "__main__":
205+
main()

0 commit comments

Comments
 (0)