Skip to content

Commit 8718f30

Browse files
committed
add new output formats csv and pretty
Signed-off-by: Justin Stitt <[email protected]>
1 parent 3ab5c5f commit 8718f30

File tree

1 file changed

+129
-33
lines changed

1 file changed

+129
-33
lines changed

scripts/visualize-builds.py

Lines changed: 129 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
#!/usr/bin/env python3
22
# pylint: disable=invalid-name
33

4+
import argparse
5+
import csv
6+
import sys
47
from utils import get_config_from_generator
58

69
HOURS = 24
710

11+
CRON_TO_DAY = {
12+
0: "Sunday",
13+
1: "Monday",
14+
2: "Tuesday",
15+
3: "Wednesday",
16+
4: "Thursday",
17+
5: "Friday",
18+
6: "Saturday",
19+
}
20+
821

922
def generate_days():
1023
DAYS = 7
@@ -13,26 +26,25 @@ def generate_days():
1326

1427
def populate_days(days):
1528
config = get_config_from_generator()
16-
for tree in config['tree_schedules']:
17-
name = tree['name']
29+
for tree in config["tree_schedules"]:
30+
name = tree["name"]
1831

19-
schedule = tree['schedule'].split(' ')
20-
schedule_days = tuple(map(int, schedule[-1].split(',')))
21-
schedule_hours = tuple(map(int, schedule[1].split(',')))
32+
schedule = tree["schedule"].split(" ")
33+
schedule_days = tuple(map(int, schedule[-1].split(",")))
34+
schedule_hours = tuple(map(int, schedule[1].split(",")))
2235

2336
# Roughly estimate how many hours each build runs, rounded up to nearest whole hour
24-
if 'android' in name or 'tip' in name:
25-
DURATION = 2
26-
elif 'mainline' in name or 'stable' in name:
27-
DURATION = 5
28-
elif 'next' in name:
29-
DURATION = 6
30-
else: # older stable
31-
DURATION = 4
37+
duration = 4 # assume older stable
38+
if "android" in name or "tip" in name:
39+
duration = 2
40+
elif "mainline" in name or "stable" in name:
41+
duration = 5
42+
elif "next" in name:
43+
duration = 6
3244

3345
for day in schedule_days:
3446
for hour in schedule_hours:
35-
for running_hour in range(hour, hour + DURATION):
47+
for running_hour in range(hour, hour + duration):
3648
loop_day = day
3749
loop_hour = running_hour
3850
# handle builds crossing a day
@@ -47,20 +59,11 @@ def populate_days(days):
4759

4860
def visualize_data(days):
4961
CELL_WIDTH = 5
50-
CRON_TO_DAY = {
51-
0: 'Sunday',
52-
1: 'Monday',
53-
2: 'Tuesday',
54-
3: 'Wednesday',
55-
4: 'Thursday',
56-
5: 'Friday',
57-
6: 'Saturday',
58-
}
59-
DIVIDE = ' | '
60-
END = ' |'
61-
BLANK_DAY = ' ' * 12
62+
DIVIDE = " | "
63+
END = " |"
64+
BLANK_DAY = " " * 12
6265
BLANK_ROW = f"{DIVIDE}{BLANK_DAY}{DIVIDE}{' ' * HOURS * CELL_WIDTH}{END}"
63-
HORIZONTAL_BORDER = ' ' + '-' * (len(BLANK_ROW) - 1)
66+
HORIZONTAL_BORDER = " " + "-" * (len(BLANK_ROW) - 1)
6467

6568
print(HORIZONTAL_BORDER)
6669
print(BLANK_ROW)
@@ -71,26 +74,119 @@ def visualize_data(days):
7174
values = []
7275
for builds in hours.values():
7376
values.append(f"{len(builds):{CELL_WIDTH}}")
74-
row.append(''.join(values))
77+
row.append("".join(values))
7578

7679
print(f"{DIVIDE}{DIVIDE.join(row)}{END}")
7780
print(BLANK_ROW)
7881

7982
print(HORIZONTAL_BORDER)
8083

81-
print(f"{DIVIDE}{BLANK_DAY}{DIVIDE} 0", end='')
84+
print(f"{DIVIDE}{BLANK_DAY}{DIVIDE} 0", end="")
8285
for val in (val for val in range(1, HOURS) if val % 3 == 0):
83-
print(f"{val:{3 * CELL_WIDTH}}", end='')
86+
print(f"{val:{3 * CELL_WIDTH}}", end="")
8487
print(f"{' ' * 10}{END}")
8588

8689
print(HORIZONTAL_BORDER)
8790

8891

92+
def output_csv(days, output_file=None):
93+
output = output_file if output_file else sys.stdout
94+
writer = csv.writer(output)
95+
96+
header = ["Day"] + [str(hour) for hour in range(HOURS)]
97+
writer.writerow(header)
98+
99+
for day, hours in days.items():
100+
row = [CRON_TO_DAY[day]]
101+
for hour in range(HOURS):
102+
row.append(len(hours[hour]))
103+
writer.writerow(row)
104+
105+
106+
def output_pretty_table(days, output_file=None):
107+
output = output_file if output_file else sys.stdout
108+
109+
header_cells = ["Day"] + [f"{hour:2d}" for hour in range(HOURS)]
110+
col_widths = [max(10, len(cell)) for cell in header_cells]
111+
112+
for day, hours in days.items():
113+
col_widths[0] = max(col_widths[0], len(CRON_TO_DAY[day]))
114+
for hour in range(HOURS):
115+
col_widths[hour + 1] = max(col_widths[hour + 1], len(str(len(hours[hour]))))
116+
117+
top_border = "┌" + "┬".join("─" * width for width in col_widths) + "┐"
118+
print(top_border, file=output)
119+
120+
header_row = (
121+
"│"
122+
+ "│".join(f"{cell:^{width}}" for cell, width in zip(header_cells, col_widths))
123+
+ "│"
124+
)
125+
print(header_row, file=output)
126+
header_sep = "├" + "┼".join("─" * width for width in col_widths) + "┤"
127+
print(header_sep, file=output)
128+
129+
for day, hours in days.items():
130+
row_cells = [CRON_TO_DAY[day]] + [
131+
str(len(hours[hour])) for hour in range(HOURS)
132+
]
133+
data_row = (
134+
"│"
135+
+ "│".join(f"{cell:^{width}}" for cell, width in zip(row_cells, col_widths))
136+
+ "│"
137+
)
138+
print(data_row, file=output)
139+
140+
# Bottom border
141+
bottom_border = "└" + "┴".join("─" * width for width in col_widths) + "┘"
142+
print(bottom_border, file=output)
143+
144+
145+
def parse_args():
146+
parser = argparse.ArgumentParser(
147+
description="Visualize build distribution across days and hours"
148+
)
149+
parser.add_argument(
150+
"--format",
151+
choices=["table", "csv", "pretty"],
152+
default="pretty",
153+
help="Output format (default: pretty)",
154+
)
155+
parser.add_argument(
156+
"--output", "-o", type=str, help="Output file (default: stdout)"
157+
)
158+
return parser.parse_args()
159+
160+
89161
def main():
162+
args = parse_args()
163+
90164
days = generate_days()
91165
populate_days(days)
92-
visualize_data(days)
93-
94166

95-
if __name__ == '__main__':
167+
output_file = None
168+
if args.output:
169+
output_file = open(args.output, "w", newline="")
170+
171+
try:
172+
if args.format == "csv":
173+
output_csv(days, output_file)
174+
elif args.format == "pretty":
175+
output_pretty_table(days, output_file)
176+
else: # regular table
177+
if (
178+
output_file
179+
): # evil hack so I don't have to rewrite visualize_data() prints
180+
original_stdout = sys.stdout
181+
sys.stdout = output_file
182+
visualize_data(days)
183+
sys.stdout = original_stdout
184+
else:
185+
visualize_data(days)
186+
finally:
187+
if output_file:
188+
output_file.close()
189+
190+
191+
if __name__ == "__main__":
96192
main()

0 commit comments

Comments
 (0)