2121from pathlib import Path
2222from typing import TYPE_CHECKING
2323
24+ import jinja2
2425import toml
25- from jinja2 import Template
2626from rich .console import Console
2727from rich .table import Table
2828
@@ -63,19 +63,19 @@ def __init__(self, system: System, test_scenario: TestScenario, results_root: Pa
6363 ]
6464
6565 def generate (self ) -> None :
66- self .load_tdef_res ()
66+ self .load_tdef_with_results ()
6767
6868 console = Console (record = True )
6969 for op_type , metric in self .report_configs :
7070 table = self .create_table (op_type , metric )
7171 console .print (table )
7272 console .print ()
7373
74- template_path = Path (__file__ ).parent .parent .parent / "util" / "nixl_report_template.jinja2"
75- with open (template_path , "r" ) as f :
76- template = Template (f .read ())
77-
7874 bokeh_script , bokeh_div = self .get_bokeh_html ()
75+
76+ template = jinja2 .Environment (
77+ loader = jinja2 .FileSystemLoader (Path (__file__ ).parent .parent .parent / "util" )
78+ ).get_template ("nixl_report_template.jinja2" )
7979 html_content = template .render (
8080 title = f"{ self .test_scenario .name } NIXL Bench Report" ,
8181 bokeh_script = bokeh_script ,
@@ -87,19 +87,54 @@ def generate(self) -> None:
8787 with open (html_file , "w" ) as f :
8888 f .write (html_content )
8989
90- logging .info (f"Interactive HTML report created: { html_file } " )
90+ logging .info (f"NIXL summary report created: { html_file } " )
9191
92- def load_tdef_res (self ):
92+ def load_tdef_with_results (self ) -> None :
9393 super ().load_test_runs ()
94- self .tdef_res : list [TdefResult ] = []
9594 self .trs = [tr for tr in self .trs if isinstance (tr .test .test_definition , NIXLBenchTestDefinition )]
9695
9796 for tr in self .trs :
9897 tr_file = toml .load (tr .output_path / "test-run.toml" )
9998 tdef = NIXLBenchTestDefinition .model_validate (tr_file ["test_definition" ])
10099 self .tdef_res .append (TdefResult (tdef , lazy .pd .read_csv (tr .output_path / "nixlbench.csv" )))
101100
101+ def create_table (self , op_type : str , metric : str ) -> Table :
102+ df = self .construct_df (op_type , metric )
103+ table = Table (title = f"{ self .test_scenario .name } : { op_type } { self .metric2col [metric ]} " , title_justify = "left" )
104+ for col in df .columns :
105+ table .add_column (col , justify = "right" , style = "cyan" )
106+
107+ for _ , row in df .iterrows ():
108+ block_size = row ["block_size" ].astype (int )
109+ batch_size = row ["batch_size" ].astype (int )
110+ table .add_row (str (block_size ), str (batch_size ), * [str (x ) for x in row .values [2 :]])
111+ return table
112+
113+ def get_bokeh_html (self ) -> tuple [str , str ]:
114+ charts : list [bk .figure ] = []
115+ for op_type , metric in self .report_configs :
116+ if chart := self .create_chart (op_type , metric ):
117+ charts .append (chart )
118+
119+ # layout with 2 charts per row
120+ rows = []
121+ for i in range (0 , len (charts ), 2 ):
122+ if i + 1 < len (charts ):
123+ rows .append (lazy .bokeh_layouts .row (charts [i ], charts [i + 1 ]))
124+ else :
125+ rows .append (lazy .bokeh_layouts .row (charts [i ]))
126+ layout = lazy .bokeh_layouts .column (* rows , name = "charts_layout" )
127+
128+ bokeh_script , bokeh_div = lazy .bokeh_embed .components (layout )
129+ return bokeh_script , bokeh_div
130+
102131 def construct_df (self , op_type : str , metric : str ) -> pd .DataFrame :
132+ """
133+ Construct a `DataFrame` with results for all test runs.
134+
135+ Block size and Batch size are taken only once assuming they are the same across all test runs.
136+ `op_type` is used to filter the test runs.
137+ """
103138 final_df = lazy .pd .DataFrame ()
104139
105140 for tdef_res in self .tdef_res :
@@ -137,6 +172,8 @@ def create_chart(self, op_type: str, metric: str) -> bk.figure | None:
137172 width = 800 ,
138173 height = 500 ,
139174 tools = "pan,box_zoom,wheel_zoom,reset,save" ,
175+ active_drag = "pan" ,
176+ active_scroll = "wheel_zoom" ,
140177 x_axis_type = "log" ,
141178 )
142179
@@ -164,34 +201,3 @@ def create_chart(self, op_type: str, metric: str) -> bk.figure | None:
164201 p .y_range = lazy .bokeh_models .Range1d (start = 0.0 , end = y_max * 1.1 )
165202
166203 return p
167-
168- def create_table (self , op_type : str , metric : str ) -> Table :
169- df = self .construct_df (op_type , metric )
170- table = Table (title = f"{ self .test_scenario .name } : { op_type } { self .metric2col [metric ]} " )
171- for col in df .columns :
172- table .add_column (col , justify = "right" , style = "cyan" )
173-
174- for _ , row in df .iterrows ():
175- block_size = row ["block_size" ].astype (int )
176- batch_size = row ["batch_size" ].astype (int )
177- table .add_row (str (block_size ), str (batch_size ), * [str (x ) for x in row .values [2 :]])
178- return table
179-
180- def get_bokeh_html (self ) -> tuple [str , str ]:
181- charts : list [bk .figure ] = []
182- for op_type , metric in self .report_configs :
183- chart = self .create_chart (op_type , metric )
184- if chart :
185- charts .append (chart )
186-
187- # layout with 2 charts per row
188- rows = []
189- for i in range (0 , len (charts ), 2 ):
190- if i + 1 < len (charts ):
191- rows .append (lazy .bokeh_layouts .row (charts [i ], charts [i + 1 ]))
192- else :
193- rows .append (lazy .bokeh_layouts .row (charts [i ]))
194- layout = lazy .bokeh_layouts .column (* rows , name = "charts_layout" )
195-
196- bokeh_script , bokeh_div = lazy .bokeh_embed .components (layout )
197- return bokeh_script , bokeh_div
0 commit comments