Skip to content

Commit 64f31de

Browse files
committed
scripts: add job progress plot utility
Generated by Claude. Release Notes: None Epic: None
1 parent 9668fd5 commit 64f31de

File tree

1 file changed

+90
-0
lines changed

1 file changed

+90
-0
lines changed

scripts/job-progress-plot

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env python3
2+
import sys, re
3+
from datetime import datetime, timedelta
4+
5+
def plot_job(job_id, file_path):
6+
# Parse data
7+
data = []
8+
jobs_data = {}
9+
with open(file_path) as f:
10+
for i, line in enumerate(f):
11+
if i == 0 and line.startswith('job_id'): continue
12+
parts = line.strip().split('\t')
13+
if len(parts) >= 3:
14+
try:
15+
job_id_in_file = int(parts[0])
16+
fraction = parts[2]
17+
if fraction not in ('NULL', '', '0'):
18+
ts = datetime.fromisoformat(re.sub(r'[+-]\d{2}$', '', parts[1]))
19+
pct = float(fraction) * 100
20+
if job_id_in_file not in jobs_data:
21+
jobs_data[job_id_in_file] = []
22+
jobs_data[job_id_in_file].append((ts, pct))
23+
if job_id_in_file == job_id:
24+
ts = datetime.fromisoformat(re.sub(r'[+-]\d{2}$', '', parts[1]))
25+
pct = 0.0 if parts[2] in ('NULL', '') else float(parts[2]) * 100
26+
data.append((ts, pct))
27+
except: pass
28+
29+
if not data:
30+
result = [f"No data for job {job_id}. Jobs with progress:"]
31+
for jid in sorted(jobs_data.keys()):
32+
job_points = sorted(jobs_data[jid])
33+
duration = (job_points[-1][0] - job_points[0][0]).total_seconds() / 60
34+
first_pct = job_points[0][1]
35+
last_pct = job_points[-1][1]
36+
result.append(f"{jid}: {duration:.1f}min {first_pct:.1f}% -> {last_pct:.1f}%")
37+
return "\n".join(result)
38+
data.sort()
39+
40+
# Calculate dimensions
41+
start, end = data[0][0], data[-1][0]
42+
dur = (end - start).total_seconds() / 60
43+
scale = 0.25 if dur < 60 else 0.5 if dur <= 120 else 3.0
44+
width = min(120, max(1, int(dur / scale)))
45+
46+
# Plot grid
47+
grid = [[' '] * width for _ in range(20)]
48+
for ts, pct in data:
49+
x = min(width-1, int((ts - start).total_seconds() / 60 / scale))
50+
y = max(0, min(19, 19 - int(pct / 5)))
51+
grid[y][x] = '*'
52+
53+
# Output
54+
final_pct, final_y = data[-1][1], max(0, min(19, 19 - int(data[-1][1] / 5)))
55+
lines = []
56+
for i in range(20):
57+
val = (19 - i) * 5
58+
if i == 0: val = 100 # Fix first line to show 100%
59+
left = f"{val:3d}% " if val % 10 == 0 else " "
60+
right = f"| {final_pct:.1f}%" if i == final_y else "|"
61+
lines.append(left + "|" + ''.join(grid[i]) + right)
62+
63+
lines.append(" +" + "-" * width)
64+
time_labels = " "
65+
if width <= 30:
66+
# For short jobs, show only start and end
67+
start_label = start.strftime("%H:%M")
68+
end_label = end.strftime("%H:%M")
69+
time_labels += start_label + " " * (width - 10) + end_label
70+
else:
71+
for i in range(0, width, max(1, width // 8)):
72+
label = (start + timedelta(minutes=i * scale)).strftime("%H:%M")
73+
time_labels += label if i == 0 else " " * max(0, 6 + i - len(time_labels)) + label
74+
lines.append(time_labels)
75+
return "\n".join(lines)
76+
77+
if __name__ == "__main__":
78+
if len(sys.argv) != 3:
79+
print("Usage: job-progress-plot <job-id> <file>", file=sys.stderr)
80+
sys.exit(1)
81+
try:
82+
result = plot_job(int(sys.argv[1]), sys.argv[2])
83+
if result.startswith("No data for job"):
84+
print(result, file=sys.stderr)
85+
sys.exit(1)
86+
else:
87+
print(result)
88+
except Exception as e:
89+
print(f"Error: {e}", file=sys.stderr)
90+
sys.exit(1)

0 commit comments

Comments
 (0)