11import re
2- import sys
2+ import os
33from pathlib import Path
44from typing import Any
55import pandas as pd
1010
1111IGNORE_COLS = {"bench" , "workers" , "tps" ,
1212 "iterations" , "ns/op" , "B/op" , "allocs/op" }
13- DEFAULT_BENCH_DIR = "bench2 "
13+ DEFAULT_BENCH_DIR = "bench "
1414
1515
1616def _is_number (s : str ) -> bool :
@@ -75,6 +75,10 @@ def simple_parser(path: Path) -> pd.DataFrame:
7575 df [f"{ label } (ms)" ] = df [col ] / 1e6
7676 df = df .drop (columns = [col ])
7777
78+ if 'tps' in df .columns and 'workers' in df .columns :
79+ # Little's Law (latency = concurrency / throughput)
80+ df ['avg (ms)' ] = df ['workers' ] * 1000 / df ['tps' ]
81+
7882 return df
7983
8084
@@ -102,13 +106,21 @@ def _parse_ms(s: str) -> float:
102106 raise ValueError (f"unknown unit: { unit } " )
103107
104108
105- LATENCY_KEYS = {'P5' : 'p5 (ms)' , 'P95' : 'p95 (ms)' , 'P99' : 'p99 (ms)' }
109+ LATENCY_KEYS = {
110+ 'P50 (Median)' : 'p50 (ms)' ,
111+ 'P5' : 'p5 (ms)' ,
112+ 'P95' : 'p95 (ms)' ,
113+ 'P99' : 'p99 (ms)' ,
114+ }
115+ DISPLAY_LATENCY = {'avg (ms)' , 'p50 (ms)' , 'p95 (ms)' }
116+ WORKERS_RE = re .compile (r'^Workers\s+(\d+)' )
106117
107118
108- def parse_parallel_log (path : Path ) -> pd .DataFrame :
119+ def parse_parallel_log (path : Path , default_bench : str | None = None ) -> pd .DataFrame :
109120 text = ANSI_ESCAPE_RE .sub ('' , path .read_text ())
110121 rows : list [dict ] = []
111122 current : dict = {}
123+ has_run_lines = bool (PARALLEL_RUN_RE .search (text ))
112124
113125 for line in text .splitlines ():
114126 line = line .strip ()
@@ -129,6 +141,18 @@ def parse_parallel_log(path: Path) -> pd.DataFrame:
129141 current [k ] = v
130142 continue
131143
144+ if not has_run_lines :
145+ m = WORKERS_RE .match (line )
146+ if m :
147+ workers = int (m .group (1 ))
148+ if current :
149+ rows .append (current )
150+ current = {
151+ 'bench' : default_bench or path .stem ,
152+ 'workers' : workers ,
153+ }
154+ continue
155+
132156 m = PARALLEL_THROUGHPUT_RE .match (line )
133157 if m :
134158 current ['tps' ] = float (m .group (1 ))
@@ -157,31 +181,30 @@ def has_multi_nc(df: pd.DataFrame) -> bool:
157181
158182def _tps_fig (df , color_col , title ):
159183 fig = px .line (df , x = 'workers' , y = 'tps' , color = color_col ,
184+ symbol = color_col ,
160185 markers = True , title = title ,
161- labels = {'workers' : 'Workers' , 'tps' : 'TPS' , color_col : '' })
162- # fig.update_xaxes(
163- # showgrid=True,
164- # # griddash='dash',
165- # )
186+ labels = {'workers' : 'Workers' , 'tps' : 'TPS' , color_col : '' },
187+ symbol_sequence = SHAPES )
166188 fig .update_yaxes (
167- # showgrid=True,
168189 nticks = 25 ,
169- # griddash='dash',
170190 )
171191 fig .update_layout (template = 'plotly_white' ,
172192 hovermode = 'x unified' )
173193 return fig
174194
175195
176196def _latency_fig (df , color_col , dash_col , title ):
177- latency_cols = [c for c in df .columns if c . endswith ( '(ms)' ) ]
197+ latency_cols = [c for c in df .columns if c in DISPLAY_LATENCY ]
178198 if not latency_cols :
179199 return None
180200 melted = df .melt (id_vars = [color_col , 'workers' ], value_vars = latency_cols ,
181201 var_name = 'percentile' , value_name = 'latency' )
202+ dash_map = {'avg (ms)' : 'dash' , 'p50 (ms)' : 'solid' , 'p95 (ms)' : 'dot' }
182203 fig = px .line (melted , x = 'workers' , y = 'latency' ,
183204 color = color_col , line_dash = dash_col , markers = True , title = title ,
184- labels = {'workers' : 'Workers' , 'latency' : 'Latency (ms)' , color_col : '' })
205+ labels = {'workers' : 'Workers' ,
206+ 'latency' : 'Latency (ms)' , color_col : '' },
207+ line_dash_map = dash_map )
185208 fig .update_layout (template = 'plotly_white' , hovermode = 'x unified' )
186209 return fig
187210
@@ -233,6 +256,9 @@ def parse_combined(dfs: dict[str, pd.DataFrame]):
233256 return dct
234257
235258
259+ SHAPES = ['circle' , 'square' , 'diamond' , 'cross' , 'hexagon' , 'star' ]
260+
261+
236262def make_combined_figures (dfs , local_dfs ):
237263 figs = {"_All" : None }
238264 all_dfs = []
@@ -265,12 +291,28 @@ def make_combined_figures(dfs, local_dfs):
265291 return figs
266292
267293
268- def main ():
294+ def _get_dir ():
295+ def_dir = Path (os .environ .get ("DEF_BENCH" , DEFAULT_BENCH_DIR ))
296+
269297 with st .sidebar :
270298 directory = st .text_input (
271- "Directory" , value = DEFAULT_BENCH_DIR , key = "benchdir" )
299+ "Directory" , value = str (def_dir ) if def_dir .exists () else "" , key = "benchdir" )
300+ os .environ ["DEF_BENCH" ] = directory
301+
302+ if not directory :
303+ st .info (f"Please input a Folder in the Sidebar" )
304+ return None
272305 directory = Path (directory )
306+ if not directory .exists ():
307+ st .error (f"Directory `{ directory } ` does not exist" )
308+ return None
309+ return directory
273310
311+
312+ def main ():
313+ directory = _get_dir ()
314+ if not directory :
315+ return
274316 single = {}
275317
276318 for path in sorted (directory .glob ("*.log" )):
0 commit comments