11#!/usr/bin/env python3
22# pylint: disable=invalid-name
33
4+ import argparse
5+ import csv
6+ import sys
47from utils import get_config_from_generator
58
69HOURS = 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
922def generate_days ():
1023 DAYS = 7
@@ -13,26 +26,25 @@ def generate_days():
1326
1427def 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
4860def 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+
89161def 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