1use full_palette::PURPLE_500;
28use minmax::XMinMax;
29use plotters::coord::types::RangedCoordf64;
30use plotters::coord::types::RangedCoordu64;
31use plotters::coord::Shift;
32
33use crate::plots::colors::*;
34use crate::plots::*;
35
36#[cfg(not(target_arch = "wasm32"))]
37use crate::datastore::Datastore;
38use crate::seriesstore::SeriesStore;
39
40#[cfg(not(target_arch = "wasm32"))]
41use super::congestion_control::draw_congestion_plot;
42#[cfg(not(target_arch = "wasm32"))]
43use super::rtt::draw_rtt_plot;
44
45struct XYMinMax {
46 pub x: XMinMax,
47 pub y_min: u64,
48 pub y_max: u64,
49}
50
51impl XYMinMax {
52 fn init(params: &PlotParameters, ss: &SeriesStore, y_max: u64) -> Self {
53 let x = XMinMax::new(
54 ss.sent_x_min,
55 ss.sent_x_max,
56 params.clamp.start,
57 params.clamp.end,
58 );
59
60 Self { x, y_min: 0, y_max }
61 }
62
63 fn y_range(&self) -> std::ops::Range<u64> {
64 self.y_min..self.y_max
65 }
66}
67
68#[derive(Clone, Debug)]
69pub enum OverviewChartOutputType {
70 Png {
71 output_dir: String,
72 cwnd_y_max: Option<u64>,
73 stream_y_max: Option<u64>,
74 },
75
76 Canvas {
77 main_plot_canvas_id: String,
78 congestion_plot_canvas_id: String,
79 rtt_plot_canvas_id: String,
80 },
81}
82
83impl From<OverviewChartOutputType> for ChartOutputType {
84 fn from(val: OverviewChartOutputType) -> Self {
85 match val {
86 OverviewChartOutputType::Png {
87 output_dir,
88 cwnd_y_max,
89 stream_y_max,
90 } => ChartOutputType::Png {
91 output_dir,
92 cwnd_y_max,
93 stream_y_max,
94 },
95
96 OverviewChartOutputType::Canvas {
99 main_plot_canvas_id,
100 ..
101 } => ChartOutputType::Canvas {
102 canvas_id: main_plot_canvas_id,
103 },
104 }
105 }
106}
107
108fn draw_sent_max_data<DB: DrawingBackend>(
109 ss: &SeriesStore,
110 stream_chart: &mut ChartContext<
111 DB,
112 Cartesian2d<RangedCoordf64, RangedCoordu64>,
113 >,
114) {
115 draw_line(
116 &ss.sent_max_data,
117 Some("Sent MAX_DATA"),
118 BLACK,
119 stream_chart,
120 );
121}
122
123fn draw_cumulative_sent_max_data<DB: DrawingBackend>(
124 ss: &SeriesStore,
125 stream_chart: &mut ChartContext<
126 DB,
127 Cartesian2d<RangedCoordf64, RangedCoordu64>,
128 >,
129) {
130 draw_line(
131 &ss.sum_sent_stream_max_data,
132 Some("Cumulative sent MAX_STREAM_DATA"),
133 CYAN,
134 stream_chart,
135 );
136}
137
138fn draw_sent_max_stream_data<DB: DrawingBackend>(
139 ss: &SeriesStore,
140 stream_chart: &mut ChartContext<
141 DB,
142 Cartesian2d<RangedCoordf64, RangedCoordu64>,
143 >,
144) {
145 let mut label = Some("Sent MAX_STREAM_DATA");
146
147 let streams = &ss.sent_stream_max_data;
148 for series in streams {
149 draw_line(series.1, label, BLUE, stream_chart);
150 label = None;
151 }
152}
153
154fn draw_buffer_reads<DB: DrawingBackend>(
155 ss: &SeriesStore,
156 stream_chart: &mut ChartContext<
157 DB,
158 Cartesian2d<RangedCoordf64, RangedCoordu64>,
159 >,
160) {
161 let mut label = Some("Stream buffer read");
162
163 let streams = &ss.stream_buffer_reads;
164 for series in streams {
165 draw_line(series.1, label, FOREST_GREEN, stream_chart);
166 label = None;
167 }
168}
169
170fn draw_cumulative_buffer_reads<DB: DrawingBackend>(
171 ss: &SeriesStore,
172 stream_chart: &mut ChartContext<
173 DB,
174 Cartesian2d<RangedCoordf64, RangedCoordu64>,
175 >,
176) {
177 draw_line(
178 &ss.sum_stream_buffer_reads,
179 Some("Cumulative stream buffer read"),
180 GREEN,
181 stream_chart,
182 );
183}
184
185fn draw_buffer_writes<DB: DrawingBackend>(
186 ss: &SeriesStore,
187 stream_chart: &mut ChartContext<
188 DB,
189 Cartesian2d<RangedCoordf64, RangedCoordu64>,
190 >,
191) {
192 let mut label = Some("Stream buffer write");
193
194 let streams = &ss.stream_buffer_writes;
195 for series in streams {
196 draw_line(series.1, label, MAGENTA, stream_chart);
197 label = None;
198 }
199}
200
201fn draw_cumulative_buffer_writes<DB: DrawingBackend>(
202 ss: &SeriesStore,
203 stream_chart: &mut ChartContext<
204 DB,
205 Cartesian2d<RangedCoordf64, RangedCoordu64>,
206 >,
207) {
208 let data = &ss.sum_stream_buffer_writes;
209 let label = Some("Cumulative stream buffer writes");
210
211 draw_line(data, label, RGBColor(255, 0, 0), stream_chart);
212}
213
214fn draw_buffer_dropped<DB: DrawingBackend>(
215 ss: &SeriesStore,
216 stream_chart: &mut ChartContext<
217 DB,
218 Cartesian2d<RangedCoordf64, RangedCoordu64>,
219 >,
220) {
221 let mut label = Some("Stream buffer dropped");
222
223 let streams = &ss.stream_buffer_dropped;
224 for series in streams {
225 draw_line(series.1, label, ORANGE, stream_chart);
226 label = None;
227 }
228}
229
230fn draw_cumulative_buffer_dropped<DB: DrawingBackend>(
231 ss: &SeriesStore,
232 stream_chart: &mut ChartContext<
233 DB,
234 Cartesian2d<RangedCoordf64, RangedCoordu64>,
235 >,
236) {
237 let data = &ss.sum_stream_buffer_dropped;
238 let label = Some("Cumulative stream buffer dropped");
239
240 draw_line(data, label, RGBColor(0, 0, 255), stream_chart);
241}
242
243fn draw_sent_stream_data<DB: DrawingBackend>(
244 ss: &SeriesStore,
245 stream_chart: &mut ChartContext<
246 DB,
247 Cartesian2d<RangedCoordf64, RangedCoordu64>,
248 >,
249) {
250 let mut label = Some("Sent stream data");
251
252 let streams = &ss.sent_stream_frames_series;
253 for series in streams {
254 draw_line(series.1, label, PURPLE_500, stream_chart);
255 label = None;
256 }
257}
258
259fn draw_received_max_data<DB: DrawingBackend>(
260 ss: &SeriesStore,
261 stream_chart: &mut ChartContext<
262 DB,
263 Cartesian2d<RangedCoordf64, RangedCoordu64>,
264 >,
265) {
266 draw_line(
267 &ss.received_max_data,
268 Some("Cumulative received MAX_DATA"),
269 MID_GREY,
270 stream_chart,
271 );
272}
273
274fn draw_cumulative_received_stream_max_data<DB: DrawingBackend>(
275 ss: &SeriesStore,
276 stream_chart: &mut ChartContext<
277 DB,
278 Cartesian2d<RangedCoordf64, RangedCoordu64>,
279 >,
280) {
281 draw_line(
282 &ss.sum_received_stream_max_data,
283 Some("Cumulative received MAX_STREAM_DATA"),
284 MUSTARD,
285 stream_chart,
286 );
287}
288
289#[cfg(target_arch = "wasm32")]
290fn draw_main_plot<'a, DB: DrawingBackend + 'a>(
291 filename: &str, params: &PlotParameters, axis: XYMinMax, ss: &SeriesStore,
292 plot: &plotters::drawing::DrawingArea<DB, Shift>,
293) -> ChartContext<'a, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>> {
294 let mut builder = ChartBuilder::on(plot);
295 builder
296 .x_label_area_size(params.area_margin.x)
297 .y_label_area_size(params.area_margin.y);
298
299 if params.display_chart_title {
300 let caption = format!("{} Connection overview", filename);
301 builder.caption(caption, chart_title_style(¶ms.colors.caption));
302 }
303
304 let mut chart = builder
305 .build_cartesian_2d(axis.x.range(), axis.y_range())
306 .unwrap();
307
308 draw_mesh(
309 ¶ms.colors,
310 "Relative time (ms)",
311 "Data (bytes)",
312 params.display_minor_lines,
313 &mut chart,
314 );
315
316 draw_sent_max_data(ss, &mut chart);
317 draw_cumulative_sent_max_data(ss, &mut chart);
318 draw_sent_max_stream_data(ss, &mut chart);
319 draw_buffer_reads(ss, &mut chart);
320 draw_cumulative_buffer_reads(ss, &mut chart);
321 draw_buffer_writes(ss, &mut chart);
322 draw_cumulative_buffer_writes(ss, &mut chart);
323 draw_buffer_dropped(ss, &mut chart);
324 draw_cumulative_buffer_dropped(ss, &mut chart);
325 draw_sent_stream_data(ss, &mut chart);
326 draw_received_max_data(ss, &mut chart);
327 draw_cumulative_received_stream_max_data(ss, &mut chart);
328
329 if params.display_legend {
330 chart
331 .configure_series_labels()
332 .label_font(chart_label_style(¶ms.colors.caption))
333 .background_style(params.colors.fill.mix(0.8))
334 .border_style(params.colors.axis)
335 .position(SeriesLabelPosition::UpperLeft)
336 .draw()
337 .unwrap();
338 }
339
340 chart
341}
342
343fn draw_stream_send_plot<'a, DB: DrawingBackend + 'a>(
344 params: &PlotParameters, axis: XYMinMax, ss: &SeriesStore,
345 plot: &plotters::drawing::DrawingArea<DB, Shift>,
346) -> ChartContext<'a, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>> {
347 let mut builder = ChartBuilder::on(plot);
348 builder
349 .x_label_area_size(params.area_margin.x)
350 .y_label_area_size(params.area_margin.y);
351
352 if params.display_chart_title {
353 builder.caption(
354 "Stream sends",
355 chart_subtitle_style(¶ms.colors.caption),
356 );
357 }
358
359 let mut chart = builder
360 .build_cartesian_2d(axis.x.range(), axis.y_range())
361 .unwrap();
362
363 draw_mesh(
364 ¶ms.colors,
365 "Relative time (ms)",
366 "Data (bytes)",
367 false,
368 &mut chart,
369 );
370
371 draw_cumulative_buffer_writes(ss, &mut chart);
372 draw_cumulative_buffer_dropped(ss, &mut chart);
373 draw_buffer_writes(ss, &mut chart);
374 draw_buffer_dropped(ss, &mut chart);
375 draw_sent_stream_data(ss, &mut chart);
376 draw_received_max_data(ss, &mut chart);
377 draw_cumulative_received_stream_max_data(ss, &mut chart);
378
379 if params.display_legend {
380 chart
381 .configure_series_labels()
382 .label_font(chart_label_style(¶ms.colors.caption))
383 .background_style(params.colors.fill.mix(0.8))
384 .border_style(params.colors.axis)
385 .position(SeriesLabelPosition::UpperLeft)
386 .draw()
387 .unwrap();
388 }
389
390 chart
391}
392
393fn draw_stream_recv_plot<'a, DB: DrawingBackend + 'a>(
394 params: &PlotParameters, axis: XYMinMax, ss: &SeriesStore,
395 plot: &plotters::drawing::DrawingArea<DB, Shift>,
396) -> ChartContext<'a, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>> {
397 let mut builder = ChartBuilder::on(plot);
398 builder
399 .x_label_area_size(params.area_margin.x)
400 .y_label_area_size(params.area_margin.y);
401
402 if params.display_chart_title {
403 builder.caption(
404 "Stream receives",
405 chart_subtitle_style(¶ms.colors.caption),
406 );
407 }
408
409 let mut chart = builder
410 .build_cartesian_2d(axis.x.range(), axis.y_range())
411 .unwrap();
412
413 draw_mesh(
414 ¶ms.colors,
415 "Relative time (ms)",
416 "Data (bytes)",
417 false,
418 &mut chart,
419 );
420
421 draw_sent_max_data(ss, &mut chart);
422 draw_cumulative_sent_max_data(ss, &mut chart);
423 draw_sent_max_stream_data(ss, &mut chart);
424 draw_buffer_reads(ss, &mut chart);
425 draw_cumulative_buffer_reads(ss, &mut chart);
426
427 if params.display_legend {
428 chart
429 .configure_series_labels()
430 .label_font(chart_label_style(¶ms.colors.caption))
431 .background_style(params.colors.fill.mix(0.8))
432 .border_style(params.colors.axis)
433 .position(SeriesLabelPosition::UpperLeft)
434 .draw()
435 .unwrap();
436 }
437
438 chart
439}
440
441#[cfg(not(target_arch = "wasm32"))]
442pub fn plot_connection_overview(
443 params: &PlotParameters, filename: &str, ss: &SeriesStore, ds: &Datastore,
444 ty: &OverviewChartOutputType,
445) {
446 let chart_config = ChartConfig {
447 title: "conn-overview".into(),
448 input_filename: filename.into(),
449 clamp: params.clamp.clone(),
450 app_proto: ds.application_proto,
451 host: ds.host.clone(),
452 session_id: ds.session_id,
453 ty: ty.clone().into(),
454 };
455
456 chart_config.init_chart_dir();
457
458 let chart_path = chart_config.chart_filepath();
459
460 let root = make_chart_bitmap_area(
461 &chart_path,
462 params.chart_size,
463 params.colors,
464 params.chart_margin,
465 );
466
467 let (top_margin, bottom) = root.split_vertically((5).percent());
468 let (stream_plots, cwnd_rtt_area) = bottom.split_vertically((60).percent());
469 let (stream_send_plot, stream_recv_plot) =
470 stream_plots.split_vertically((50).percent());
471 let (congestion_plot, rtt_plot) =
472 cwnd_rtt_area.split_vertically((60).percent());
473
474 let stream_send_y_max = if let Some(y_max) = params.clamp.stream_y_max {
475 y_max
476 } else {
477 ss.y_max_stream_send_plot
478 };
479
480 let stream_recv_y_max = if let Some(y_max) = params.clamp.stream_y_max {
481 y_max
482 } else {
483 ss.y_max_stream_recv_plot
484 };
485
486 let stream_send_axis = XYMinMax::init(params, ss, stream_send_y_max);
487 let stream_recv_axis = XYMinMax::init(params, ss, stream_recv_y_max);
488
489 let cwnd_y_max = if let Some(y_max) = params.cwnd_y_max {
490 y_max
491 } else {
492 ss.y_max_congestion_plot + ss.y_max_congestion_plot / 10
494 };
495
496 let common_axis = super::minmax::XYMinMax::init(
497 ss.sent_x_min..ss.sent_x_max,
498 params.clamp.start,
499 params.clamp.end,
500 0..cwnd_y_max,
501 );
502
503 top_margin
504 .draw_text(
505 format!("{} Connection overview", filename).as_str(),
506 &chart_title_style(¶ms.colors.caption),
507 (0, 0),
508 )
509 .unwrap();
510 draw_stream_send_plot(params, stream_send_axis, ss, &stream_send_plot);
511 draw_stream_recv_plot(params, stream_recv_axis, ss, &stream_recv_plot);
512 draw_congestion_plot(params, &common_axis, ss, ds, &congestion_plot);
513 draw_rtt_plot(params, &common_axis, ss, &rtt_plot);
514}
515
516#[cfg(target_arch = "wasm32")]
517pub fn plot_main_plot<'a>(
518 params: &PlotParameters, filename: &str, ss: &SeriesStore, canvas_id: &str,
519) -> ChartContext<'a, CanvasBackend, Cartesian2d<RangedCoordf64, RangedCoordu64>>
520{
521 let root =
522 make_chart_canvas_area(&canvas_id, params.colors, params.chart_margin);
523
524 let stream_y_max = if let Some(y_max) = params.clamp.stream_y_max {
525 y_max
526 } else {
527 ss.y_max_stream_send_plot.max(ss.y_max_stream_recv_plot)
528 };
529
530 let stream_axis = XYMinMax::init(params, ss, stream_y_max);
531
532 draw_main_plot(filename, params, stream_axis, ss, &root)
533}