33import argparse
44import json
55from pathlib import Path
6- from typing import Dict , List , Tuple , TypedDict
6+ from typing import TYPE_CHECKING , Callable , Dict , List , Tuple , TypedDict
77
88import matplotlib .pyplot as plt
99
10+ if TYPE_CHECKING :
11+ from matplotlib .axes import Axes
12+
1013
1114class LatencyStats (TypedDict , total = False ):
1215 p50 : int
@@ -75,6 +78,28 @@ def _top_endpoints(report: ReportDict, top_n: int = 10) -> List[Tuple[str, Endpo
7578 return items [:top_n ]
7679
7780
81+ def _save_bar_chart (
82+ labels : List [str ],
83+ title : str ,
84+ ylabel : str ,
85+ out_path : Path ,
86+ plot_bars : Callable [[Axes , range ], None ],
87+ ) -> Path :
88+ x = range (len (labels ))
89+ fig , ax = plt .subplots (figsize = (max (10 , len (labels ) * 0.6 ), 5 ))
90+ plot_bars (ax , x )
91+ ax .set_ylabel (ylabel )
92+ ax .set_title (title )
93+ ax .set_xticks (list (x ))
94+ ax .set_xticklabels (labels , rotation = 45 , ha = "right" )
95+ ax .grid (True , axis = "y" , alpha = 0.3 )
96+ ax .legend ()
97+ fig .tight_layout ()
98+ fig .savefig (out_path )
99+ plt .close (fig )
100+ return out_path
101+
102+
78103def plot_endpoint_latency (report : ReportDict , out_dir : Path , top_n : int = 10 ) -> Path :
79104 data = _top_endpoints (report , top_n )
80105 if not data :
@@ -86,24 +111,14 @@ def plot_endpoint_latency(report: ReportDict, out_dir: Path, top_n: int = 10) ->
86111 p90 = [v .get ("latency_ms_success" , empty_latency ).get ("p90" , 0 ) for _ , v in data ]
87112 p99 = [v .get ("latency_ms_success" , empty_latency ).get ("p99" , 0 ) for _ , v in data ]
88113
89- x = range (len (labels ))
90114 width = 0.25
91115
92- fig , ax = plt .subplots (figsize = (max (10 , len (labels ) * 0.6 ), 5 ))
93- ax .bar ([i - width for i in x ], p50 , width = width , label = "p50" , color = "#22c55e" )
94- ax .bar (x , p90 , width = width , label = "p90" , color = "#eab308" )
95- ax .bar ([i + width for i in x ], p99 , width = width , label = "p99" , color = "#ef4444" )
96- ax .set_ylabel ("Latency (ms)" )
97- ax .set_title ("Success Latency by Endpoint (Top N)" )
98- ax .set_xticks (list (x ))
99- ax .set_xticklabels (labels , rotation = 45 , ha = "right" )
100- ax .grid (True , axis = "y" , alpha = 0.3 )
101- ax .legend ()
102- out_path = out_dir / "endpoint_latency.png"
103- fig .tight_layout ()
104- fig .savefig (out_path )
105- plt .close (fig )
106- return out_path
116+ def bars (ax : Axes , x : range ) -> None :
117+ ax .bar ([i - width for i in x ], p50 , width = width , label = "p50" , color = "#22c55e" )
118+ ax .bar (x , p90 , width = width , label = "p90" , color = "#eab308" )
119+ ax .bar ([i + width for i in x ], p99 , width = width , label = "p99" , color = "#ef4444" )
120+
121+ return _save_bar_chart (labels , "Success Latency by Endpoint (Top N)" , "Latency (ms)" , out_dir / "endpoint_latency.png" , bars )
107122
108123
109124def plot_endpoint_throughput (report : ReportDict , out_dir : Path , top_n : int = 10 ) -> Path :
@@ -116,23 +131,13 @@ def plot_endpoint_throughput(report: ReportDict, out_dir: Path, top_n: int = 10)
116131 errors = [v .get ("errors" , 0 ) for _ , v in data ]
117132 successes = [t - e for t , e in zip (total , errors )]
118133
119- x = range (len (labels ))
120134 width = 0.45
121135
122- fig , ax = plt .subplots (figsize = (max (10 , len (labels ) * 0.6 ), 5 ))
123- ax .bar (x , successes , width = width , label = "Success" , color = "#22c55e" )
124- ax .bar (x , errors , width = width , bottom = successes , label = "Errors" , color = "#ef4444" )
125- ax .set_ylabel ("Requests" )
126- ax .set_title ("Endpoint Throughput (Top N)" )
127- ax .set_xticks (list (x ))
128- ax .set_xticklabels (labels , rotation = 45 , ha = "right" )
129- ax .grid (True , axis = "y" , alpha = 0.3 )
130- ax .legend ()
131- out_path = out_dir / "endpoint_throughput.png"
132- fig .tight_layout ()
133- fig .savefig (out_path )
134- plt .close (fig )
135- return out_path
136+ def bars (ax : Axes , x : range ) -> None :
137+ ax .bar (x , successes , width = width , label = "Success" , color = "#22c55e" )
138+ ax .bar (x , errors , width = width , bottom = successes , label = "Errors" , color = "#ef4444" )
139+
140+ return _save_bar_chart (labels , "Endpoint Throughput (Top N)" , "Requests" , out_dir / "endpoint_throughput.png" , bars )
136141
137142
138143def generate_plots (report_path : str | Path , output_dir : str | Path | None = None ) -> List [Path ]:
0 commit comments