1use crate::datastore::ApplicationProto;
28use crate::datastore::Datastore;
29use colors::PlotColors;
30use plotters::coord::ranged1d::ValueFormatter;
31use plotters::coord::types::RangedCoordf64;
32use plotters::coord::types::RangedCoordu64;
33use plotters::prelude::*;
34#[cfg(target_arch = "wasm32")]
35use plotters_canvas::CanvasBackend;
36
37#[derive(Clone, Debug)]
38pub enum ChartOutputType {
39 Png {
40 output_dir: String,
41 cwnd_y_max: Option<u64>,
42 stream_y_max: Option<u64>,
43 },
44
45 Canvas {
46 canvas_id: String,
47 },
48}
49
50#[derive(Clone, Debug)]
51struct ChartConfig {
52 pub title: String,
53 pub input_filename: String,
54 pub clamp: ClampParams,
58 pub ty: ChartOutputType,
59 pub app_proto: ApplicationProto,
60 pub host: Option<String>,
61 pub session_id: Option<i64>,
62}
63
64impl ChartConfig {
65 pub fn chart_name(&self) -> String {
66 let proto = match self.app_proto {
67 ApplicationProto::Http2 => "h2",
68 ApplicationProto::Http3 => "h3",
69 };
70
71 let host = match (&self.host, &self.ty) {
72 (Some(h), ChartOutputType::Png { .. }) => {
73 format!("-{}", h.replace(":", "_"))
76 },
77
78 (Some(h), ChartOutputType::Canvas { .. }) => h.to_string(),
79
80 _ => "".to_string(),
81 };
82
83 let session = if let Some(s) = self.session_id {
84 format!("-session{}", s)
85 } else {
86 "".to_string()
87 };
88
89 let start = if let Some(s) = self.clamp.start {
90 format!("-s{}", s)
91 } else {
92 "".to_string()
93 };
94
95 let end = if let Some(e) = self.clamp.end {
96 format!("-e{}", e)
97 } else {
98 "".to_string()
99 };
100
101 match self.ty {
102 ChartOutputType::Png {
103 cwnd_y_max,
104 stream_y_max,
105 ..
106 } => {
107 let mut name = format!(
108 "{}-{}{}{}-{}{}{}",
109 self.input_filename,
110 proto,
111 host,
112 session,
113 self.title,
114 start,
115 end,
116 );
117
118 if let Some(y_max) = stream_y_max {
119 name.push_str(&format!("-stream_max_y{}", y_max));
120 }
121
122 if let Some(y_max) = cwnd_y_max {
123 name.push_str(&format!("-cwnd_max_y{}", y_max));
124 }
125
126 name
127 },
128
129 ChartOutputType::Canvas { .. } => format!(
130 "{}-{}{}{}-{}{}{}",
131 self.input_filename, proto, host, session, self.title, start, end
132 ),
133 }
134 }
135
136 pub fn chart_filepath(&self) -> String {
137 match &self.ty {
138 ChartOutputType::Png { output_dir, .. } => {
139 format!("{}/{}.png", output_dir, self.chart_name())
140 },
141
142 ChartOutputType::Canvas { .. } => self.chart_name(),
144 }
145 }
146
147 fn init_chart_dir(&self) {
148 match &self.ty {
149 ChartOutputType::Png { .. } => {
150 let output_filename = self.chart_filepath();
151 let path = std::path::Path::new(&output_filename);
152 if let Some(dir) = path.parent() {
153 std::fs::create_dir_all(dir).unwrap();
154 }
155 },
156
157 ChartOutputType::Canvas { .. } => (),
159 }
160 }
161
162 #[cfg(target_arch = "wasm32")]
163 fn canvas_id(&self) -> Option<String> {
164 match &self.ty {
165 ChartOutputType::Canvas { canvas_id } => Some(canvas_id.clone()),
166 ChartOutputType::Png { .. } => None,
167 }
168 }
169}
170
171#[cfg(not(target_arch = "wasm32"))]
172pub fn make_chart_bitmap_area(
173 path: &str, size: ChartSize, colors: PlotColors, margin: ChartMargin,
174) -> DrawingArea<plotters::prelude::BitMapBackend<'_>, plotters::coord::Shift> {
175 let backend = BitMapBackend::new(path, (size.width, size.height));
176 let area = backend.into_drawing_area();
177 area.fill(&colors.fill).unwrap();
178 area.margin(margin.top, margin.bottom, margin.left, margin.right)
179}
180
181#[cfg(target_arch = "wasm32")]
182pub fn make_chart_canvas_area(
183 canvas_id: &str, colors: PlotColors, margin: ChartMargin,
184) -> plotters::drawing::DrawingArea<CanvasBackend, plotters::coord::Shift> {
185 let backend = CanvasBackend::new(canvas_id)
186 .unwrap_or_else(|| panic!("cannot find canvas {}", canvas_id));
187 let area = backend.into_drawing_area();
188 area.fill(&colors.fill).unwrap();
189 area.margin(margin.top, margin.bottom, margin.left, margin.right)
190}
191
192fn make_chart_config(
193 title: &str, params: &PlotParameters, filename: &str, ds: &Datastore,
194 ty: &ChartOutputType,
195) -> ChartConfig {
196 let chart_config = ChartConfig {
197 title: title.into(),
198 input_filename: filename.into(),
199 clamp: params.clamp.clone(),
200 app_proto: ds.application_proto,
201 host: ds.host.clone(),
202 session_id: ds.session_id,
203 ty: ty.clone(),
204 };
205
206 chart_config.init_chart_dir();
207
208 chart_config
209}
210
211pub fn chart_title_style(color: &RGBColor) -> TextStyle<'_> {
212 TextStyle::from(("sans-serif", 40).into_font()).color(color)
213}
214
215pub fn chart_subtitle_style(color: &RGBColor) -> TextStyle<'_> {
216 TextStyle::from(("sans-serif", 20).into_font()).color(color)
217}
218
219pub fn chart_label_style(color: &RGBColor) -> TextStyle<'_> {
220 TextStyle::from(("sans-serif", 15).into_font()).color(color)
221}
222
223fn draw_mesh<XT, YT, X, Y, DB: DrawingBackend>(
224 colors: &PlotColors, x_desc: &str, y_desc: &str, draw_minor_lines: bool,
225 chart: &mut ChartContext<DB, Cartesian2d<X, Y>>,
226) where
227 X: Ranged<ValueType = XT> + ValueFormatter<XT>,
228 Y: Ranged<ValueType = YT> + ValueFormatter<YT>,
229{
230 let mut mesh = chart.configure_mesh();
231
232 mesh.axis_style(colors.axis)
233 .x_desc(x_desc)
234 .y_desc(y_desc)
235 .bold_line_style(colors.bold_line.mix(0.5));
236
237 if !draw_minor_lines {
241 mesh.light_line_style(colors.bold_line.mix(0.0));
242 } else {
245 mesh.light_line_style(colors.light_line.mix(0.2));
246 }
247
248 mesh.label_style(chart_label_style(&colors.caption))
249 .draw()
250 .unwrap();
251}
252
253#[derive(Clone, Default, Debug)]
254pub struct ClampParams {
255 pub start: Option<f64>,
256 pub end: Option<f64>,
257 pub stream_y_max: Option<u64>,
258}
259
260#[derive(Clone, Copy, Debug, Default)]
261pub struct ChartSize {
262 pub width: u32,
263 pub height: u32,
264}
265
266#[derive(Clone, Copy, Debug, Default)]
267pub struct ChartMargin {
268 pub top: u32,
269 pub bottom: u32,
270 pub left: u32,
271 pub right: u32,
272}
273
274#[derive(Clone, Copy, Debug, Default)]
275pub struct AreaMargin {
276 pub x: u32,
277 pub y: u32,
278}
279
280pub struct PlotParameters {
281 pub clamp: ClampParams,
282 pub cwnd_y_max: Option<u64>,
283 pub chart_size: ChartSize,
284 pub colors: PlotColors,
285 pub chart_margin: ChartMargin,
286 pub area_margin: AreaMargin,
287 pub display_chart_title: bool,
288 pub display_legend: bool,
289 pub display_minor_lines: bool,
290}
291
292fn draw_line<DB: DrawingBackend>(
293 data: &[(f64, u64)], label: Option<&str>, colour: RGBColor,
294 chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,
295) {
296 let c = chart
297 .draw_series(LineSeries::new(data.to_vec(), colour))
298 .unwrap();
299
300 if let Some(l) = label {
301 c.label(l).legend(move |(x, y)| {
302 PathElement::new(vec![(x, y), (x + 20, y)], colour)
303 });
304 }
305}
306
307pub mod colors;
308pub mod congestion_control;
309pub mod conn_flow_control;
310pub mod conn_overview;
311pub mod minmax;
312pub mod packet_received;
313pub mod packet_sent;
314pub mod pending;
315pub mod rtt;
316pub mod stream_multiplex;
317pub mod stream_sparks;