1#![allow(unused_assignments)]
35
36use full_palette::PURPLE_500;
37use plotters::coord::types::RangedCoordf64;
38use plotters::coord::types::RangedCoordu64;
39
40use tabled::Tabled;
41
42use crate::datastore::ApplicationProto;
43use crate::datastore::VantagePoint;
44use crate::plots::colors::*;
45use crate::plots::*;
46use crate::request_stub::HttpRequestStub;
47
48use crate::datastore::Datastore;
49use crate::seriesstore::SeriesStore;
50
51#[allow(unused_assignments)]
52pub enum TransmissionType {
53 Upload,
54 Download,
55}
56
57#[derive(Debug)]
58pub struct SparkPlotsParams {
59 pub clamp: ClampParams,
60 pub bidi_only: bool,
61 pub label_area_width: u32,
62 pub label_area_height: u32,
63 pub caption_area_height: u32,
64 pub caption_area_width: u32,
65 pub spark_offset_x: u32,
66 pub spark_offset_y: u32,
67 pub spark_dimension_x: u32,
68 pub spark_dimension_y: u32,
69 pub sparks_per_row: u32,
70 pub captions_on_top: bool,
71 pub colors: PlotColors,
72}
73
74impl Default for SparkPlotsParams {
75 fn default() -> Self {
76 Self {
77 clamp: Default::default(),
78 bidi_only: true,
79 label_area_width: 20,
80 label_area_height: 20,
81 caption_area_height: 100,
82 caption_area_width: 0,
83 spark_offset_x: 50,
84 spark_offset_y: 30,
85 spark_dimension_x: 150,
86 spark_dimension_y: 150,
87 sparks_per_row: 10,
88 captions_on_top: true,
89 colors: Default::default(),
90 }
91 }
92}
93
94#[derive(Tabled)]
95enum SparkCaption {
96 UniStream {
97 summary: String,
98 },
99
100 RequestAtServer {
101 summary: String,
102 method: String,
103 path: String,
104 client_content_length: String,
105 server_content_length: String,
106 client_pri_hdr: String,
107 server_pri_hdr: String,
108 duration_rx_hdr_tx_hdr: String,
109 duration_rx_hdr_tx_first_data: String,
110 duration_rx_hdr_tx_last_data: String,
111 duration_tx_first_data_tx_last_data: String,
112 },
113
114 RequestAtClient {
115 summary: String,
116 method: String,
117 path: String,
118 client_content_length: String,
119 server_content_length: String,
120 client_pri_hdr: String,
121 client_pri_update: String,
122 server_pri_hdr: String,
123 duration_tx_hdr_rx_hdr: String,
124 duration_tx_hdr_rx_first_data: String,
125 duration_tx_hdr_rx_last_data: String,
126 duration_tx_first_data_tx_last_data: String,
127 },
128}
129
130impl SparkCaption {
131 fn request_at_client_from_stub(
132 application_proto: ApplicationProto, req: &HttpRequestStub,
133 ) -> Self {
134 let summary = format!(
135 "ID= {}, status= {}, proto= {:?}",
136 req.stream_id, req.status, application_proto
137 );
138 let empty = &"".to_string();
139
140 let duration_tx_hdr_rx_hdr =
141 match req.at_client_deltas.as_ref().unwrap().tx_hdr_rx_hdr.inner {
142 Some(t) => format!("d_tx_hdr_rx_hdr: {:.2}ms", t),
143
144 None => empty.to_owned(),
145 };
146
147 let duration_tx_hdr_rx_first_data = match req
148 .at_client_deltas
149 .as_ref()
150 .unwrap()
151 .tx_hdr_rx_first_data
152 .inner
153 {
154 Some(t) => format!("d_tx_hdr_rx_data_first: {:.2}ms", t),
155
156 None => empty.to_owned(),
157 };
158
159 let duration_tx_hdr_rx_last_data = match req
160 .at_client_deltas
161 .as_ref()
162 .unwrap()
163 .tx_hdr_rx_last_data
164 .inner
165 {
166 Some(t) => format!("d_tx_hdr_rx_data_last: {:.2}ms", t),
167
168 None => empty.to_owned(),
169 };
170
171 let duration_tx_first_data_tx_last_data = match req
172 .at_client_deltas
173 .as_ref()
174 .unwrap()
175 .tx_first_data_tx_last_data
176 .inner
177 {
178 Some(t) => format!("d_tx_data_first_tx_data_last: {:.2}ms", t),
179
180 None => empty.to_owned(),
181 };
182
183 SparkCaption::RequestAtClient {
184 summary,
185 method: format!("method= {}", req.method),
186 path: format!("path= {}", req.path),
187
188 client_content_length: format!(
189 "client content length: {}",
190 req.client_content_length
191 ),
192 server_content_length: format!(
193 "server content length: {}",
194 req.server_content_length
195 ),
196
197 client_pri_hdr: format!(
198 "client pri hdr: {}",
199 req.client_pri_hdr
200 ),
201
202 client_pri_update: format!(
203 "client pri updates: {:?}",
204 req.priority_updates
205 ),
206
207 server_pri_hdr: format!(
208 "server pri hdr: {}",
209 req.server_pri_hdr
210 ),
211
212 duration_tx_hdr_rx_hdr,
213
214 duration_tx_hdr_rx_first_data,
215
216 duration_tx_hdr_rx_last_data,
217
218 duration_tx_first_data_tx_last_data,
219 }
220 }
221
222 fn request_at_server_from_stub(
223 stream_id: u64, req: &HttpRequestStub,
224 ) -> Self {
225 let summary = format!("ID= {}, status= {}", stream_id, req.status);
226 let empty = &"".to_string();
227
228 let duration_rx_hdr_tx_hdr =
229 match req.at_server_deltas.as_ref().unwrap().rx_hdr_tx_hdr.inner {
230 Some(t) => format!("d_rx_hdr_tx_hdr: {:.2}ms", t),
231
232 None => empty.to_owned(),
233 };
234
235 let duration_rx_hdr_tx_first_data = match req
236 .at_server_deltas
237 .as_ref()
238 .unwrap()
239 .rx_hdr_tx_first_data
240 .inner
241 {
242 Some(t) => format!("d_rx_hdr_tx_data_first: {:.2}ms", t),
243
244 None => empty.to_owned(),
245 };
246
247 let duration_rx_hdr_tx_last_data = match req
248 .at_server_deltas
249 .as_ref()
250 .unwrap()
251 .rx_hdr_tx_last_data
252 .inner
253 {
254 Some(t) => format!("d_rx_hdr_tx_data_last: {:.2}ms", t),
255
256 None => empty.to_owned(),
257 };
258
259 let duration_tx_first_data_tx_last_data = match req
260 .at_server_deltas
261 .as_ref()
262 .unwrap()
263 .tx_first_data_tx_last_data
264 .inner
265 {
266 Some(t) => format!("d_tx_data_first_tx_data_last: {:.2}ms", t),
267
268 None => empty.to_owned(),
269 };
270
271 SparkCaption::RequestAtServer {
272 summary,
273 method: format!("method= {}", req.method),
274 path: format!("path= {}", req.path),
275
276 client_content_length: format!(
277 "client content length: {}",
278 req.client_content_length
279 ),
280 server_content_length: format!(
281 "server content length: {}",
282 req.server_content_length
283 ),
284
285 client_pri_hdr: format!(
286 "client pri hdr: {}",
287 req.client_pri_hdr
288 ),
289
290 server_pri_hdr: format!(
291 "server pri hdr: {}",
292 req.server_pri_hdr
293 ),
294
295 duration_rx_hdr_tx_hdr,
296
297 duration_rx_hdr_tx_first_data,
298
299 duration_rx_hdr_tx_last_data,
300
301 duration_tx_first_data_tx_last_data,
302 }
303 }
304
305 fn from_data_store(ds: &Datastore, stream_id: u64) -> Option<Self> {
306 let is_request = match ds.application_proto {
307 ApplicationProto::Http3 => stream_id.is_multiple_of(4),
308 ApplicationProto::Http2 => true,
309 };
310
311 if is_request {
312 if let Some(req) = ds.http_requests.get(&stream_id) {
313 match ds.vantage_point {
314 VantagePoint::Client =>
315 Some(SparkCaption::request_at_client_from_stub(
316 ds.application_proto,
317 req,
318 )),
319 VantagePoint::Server => Some(
320 SparkCaption::request_at_server_from_stub(stream_id, req),
321 ),
322 }
323 } else {
324 None
325 }
326 } else {
327 let summary = format!("ID= {}", stream_id);
328 Some(SparkCaption::UniStream { summary })
329 }
330 }
331
332 fn draw<DB: DrawingBackend>(
333 &self, color: &RGBColor, root: &DrawingArea<DB, plotters::coord::Shift>,
334 x: i32, y: i32,
335 ) {
336 let style = &("monospace", 12).into_text_style(root).color(color);
337
338 let newline_offset = 10;
339 let x = x + 20;
340
341 match self {
342 SparkCaption::UniStream { summary } => {
343 root.draw_text(summary, style, (x, y)).unwrap();
344 },
345
346 SparkCaption::RequestAtServer {
347 summary,
348 method,
349 path,
350 client_content_length,
351 server_content_length,
352 client_pri_hdr,
353 server_pri_hdr,
354 duration_rx_hdr_tx_hdr,
355 duration_rx_hdr_tx_first_data,
356 duration_rx_hdr_tx_last_data,
357 duration_tx_first_data_tx_last_data,
358 ..
359 } => {
360 root.draw_text(summary, style, (x, y)).unwrap();
361
362 root.draw_text(method, style, (x, y + newline_offset))
363 .unwrap();
364
365 root.draw_text(path, style, (x, y + newline_offset * 2))
366 .unwrap();
367
368 root.draw_text(
369 client_content_length,
370 style,
371 (x, y + newline_offset * 3),
372 )
373 .unwrap();
374
375 root.draw_text(
376 server_content_length,
377 style,
378 (x, y + newline_offset * 4),
379 )
380 .unwrap();
381
382 root.draw_text(
383 client_pri_hdr,
384 style,
385 (x, y + newline_offset * 6),
386 )
387 .unwrap();
388
389 root.draw_text(
390 server_pri_hdr,
391 style,
392 (x, y + newline_offset * 7),
393 )
394 .unwrap();
395
396 root.draw_text(
397 duration_rx_hdr_tx_hdr,
398 style,
399 (x, y + newline_offset * 8),
400 )
401 .unwrap();
402
403 root.draw_text(
404 duration_rx_hdr_tx_first_data,
405 style,
406 (x, y + newline_offset * 9),
407 )
408 .unwrap();
409
410 root.draw_text(
411 duration_rx_hdr_tx_last_data,
412 style,
413 (x, y + newline_offset * 10),
414 )
415 .unwrap();
416
417 root.draw_text(
418 duration_tx_first_data_tx_last_data,
419 style,
420 (x, y + newline_offset * 11),
421 )
422 .unwrap();
423 },
424
425 SparkCaption::RequestAtClient {
426 summary,
427 method,
428 path,
429 client_content_length,
430 server_content_length,
431 client_pri_hdr,
432 client_pri_update,
433 server_pri_hdr,
434 duration_tx_hdr_rx_hdr,
435 duration_tx_hdr_rx_first_data,
436 duration_tx_hdr_rx_last_data,
437 duration_tx_first_data_tx_last_data,
438 ..
439 } => {
440 root.draw_text(summary, style, (x, y)).unwrap();
441
442 root.draw_text(method, style, (x, y + newline_offset))
443 .unwrap();
444 root.draw_text(path, style, (x, y + newline_offset * 2))
445 .unwrap();
446
447 root.draw_text(
448 client_content_length,
449 style,
450 (x, y + newline_offset * 3),
451 )
452 .unwrap();
453
454 root.draw_text(
455 server_content_length,
456 style,
457 (x, y + newline_offset * 4),
458 )
459 .unwrap();
460
461 root.draw_text(
462 client_pri_hdr,
463 style,
464 (x, y + newline_offset * 6),
465 )
466 .unwrap();
467
468 root.draw_text(
469 client_pri_update,
470 style,
471 (x, y + newline_offset * 7),
472 )
473 .unwrap();
474
475 root.draw_text(
476 server_pri_hdr,
477 style,
478 (x, y + newline_offset * 8),
479 )
480 .unwrap();
481
482 root.draw_text(
483 duration_tx_hdr_rx_hdr,
484 style,
485 (x, y + newline_offset * 9),
486 )
487 .unwrap();
488
489 root.draw_text(
490 duration_tx_hdr_rx_first_data,
491 style,
492 (x, y + newline_offset * 10),
493 )
494 .unwrap();
495
496 root.draw_text(
497 duration_tx_hdr_rx_last_data,
498 style,
499 (x, y + newline_offset * 11),
500 )
501 .unwrap();
502
503 root.draw_text(
504 duration_tx_first_data_tx_last_data,
505 style,
506 (x, y + newline_offset * 12),
507 )
508 .unwrap();
509 },
510 }
511 }
512}
513
514fn plot_legend<DB: DrawingBackend>(
515 color: &RGBColor, root: &DrawingArea<DB, plotters::coord::Shift>, x: i32,
516 y: i32, params: &SparkPlotsParams, transmission_type: TransmissionType,
517) {
518 let style = &("monospace", 12).into_text_style(root).color(color);
520 let x: i32 = x + params.spark_dimension_x as i32 + 300;
521 let y: i32 = y;
522 let line_len: i32 = 50;
523 let nl_off: i32 = 20;
524
525 root.draw_text("Legend", style, (x, y)).unwrap();
526
527 let label = match transmission_type {
529 TransmissionType::Download => "Stream data read",
530 TransmissionType::Upload => "Stream data sent",
531 };
532 root.draw(&PathElement::new(
533 vec![(x, y + nl_off), (x + line_len, y + nl_off)],
534 PURPLE_500,
535 ))
536 .unwrap();
537 root.draw_text(label, style, (x + line_len + 10, y + (nl_off) - 5))
538 .unwrap();
539
540 root.draw(&PathElement::new(
542 vec![(x, y + nl_off * 2), (x + line_len, y + nl_off * 2)],
543 MAGENTA,
544 ))
545 .unwrap();
546 root.draw_text(
547 "Stream data buffered",
548 style,
549 (x + line_len + 10, y + (nl_off * 2) - 5),
550 )
551 .unwrap();
552
553 let label = match transmission_type {
555 TransmissionType::Download => "Max stream data sent",
556 TransmissionType::Upload => "Max stream data received",
557 };
558
559 root.draw(&PathElement::new(
560 vec![(x, y + nl_off * 3), (x + line_len, y + nl_off * 3)],
561 MUSTARD,
562 ))
563 .unwrap();
564 root.draw_text(label, style, (x + line_len + 10, y + (nl_off * 3) - 5))
565 .unwrap();
566
567 root.draw(&PathElement::new(
569 vec![(x, y + nl_off * 4), (x + line_len, y + nl_off * 4)],
570 CYAN,
571 ))
572 .unwrap();
573 root.draw_text(
574 "Request headers sent",
575 style,
576 (x + line_len + 10, y + (nl_off * 4) - 5),
577 )
578 .unwrap();
579
580 root.draw(&PathElement::new(
582 vec![(x, y + nl_off * 5), (x + line_len, y + nl_off * 5)],
583 ORANGE,
584 ))
585 .unwrap();
586 root.draw_text(
587 "First data frame read",
588 style,
589 (x + line_len + 10, y + (nl_off * 5) - 5),
590 )
591 .unwrap();
592
593 root.draw(&PathElement::new(
595 vec![(x, y + nl_off * 6), (x + line_len, y + nl_off * 6)],
596 BROWN,
597 ))
598 .unwrap();
599 root.draw_text(
600 "Last data frame read",
601 style,
602 (x + line_len + 10, y + (nl_off * 6) - 5),
603 )
604 .unwrap();
605}
606
607#[allow(clippy::too_many_arguments)]
608pub fn plot_sparks(
609 params: &SparkPlotsParams, filename: &str, ss: &SeriesStore, ds: &Datastore,
610 abs_dl_ty: &ChartOutputType, rel_dl_ty: &ChartOutputType,
611 abs_ul_ty: &ChartOutputType, rel_ul_ty: &ChartOutputType,
612) {
613 let (stream_frame_dl_series_to_plot, stream_frame_ul_series_to_plot) =
614 match (ds.vantage_point, ds.application_proto) {
615 (VantagePoint::Client, ApplicationProto::Http3) => (
616 &ss.received_stream_frames_series,
617 &ss.sent_stream_frames_series,
618 ),
619 (VantagePoint::Server, ApplicationProto::Http3) => (
620 &ss.sent_stream_frames_series,
621 &ss.received_stream_frames_series,
622 ),
623 (VantagePoint::Client, ApplicationProto::Http2) =>
624 (&ss.received_data_frames_series, &ss.sent_data_frames_series),
625 _ => unimplemented!(),
626 };
627
628 #[cfg(not(target_arch = "wasm32"))]
629 let (total_dl_sparks, total_ul_sparks) =
630 if params.bidi_only && ds.application_proto == ApplicationProto::Http3 {
631 let total_dl = stream_frame_dl_series_to_plot
632 .iter()
633 .filter(|(k, _)| (*k & 0x2) == 0)
634 .count() as u32;
635
636 let total_ul = stream_frame_ul_series_to_plot
637 .iter()
638 .filter(|(k, _)| (*k & 0x2) == 0)
639 .count() as u32;
640
641 (total_dl, total_ul)
642 } else {
643 (
644 stream_frame_dl_series_to_plot.len() as u32,
645 stream_frame_ul_series_to_plot.len() as u32,
646 )
647 };
648
649 #[cfg(not(target_arch = "wasm32"))]
651 let total_dl_rows: u32 = total_dl_sparks / params.sparks_per_row +
652 u32::from(total_dl_sparks % params.sparks_per_row != 0);
653 #[cfg(not(target_arch = "wasm32"))]
654 let total_ul_rows: u32 = total_ul_sparks / params.sparks_per_row +
655 u32::from(total_ul_sparks % params.sparks_per_row != 0);
656
657 #[cfg(not(target_arch = "wasm32"))]
658 let caption_area_width = if params.captions_on_top {
659 0
660 } else {
661 params.caption_area_width
662 };
663
664 let caption_area_height = if params.captions_on_top {
665 params.caption_area_height
666 } else {
667 0
668 };
669
670 #[cfg(not(target_arch = "wasm32"))]
671 let spark_and_captions_width = params.spark_dimension_x +
672 (params.spark_offset_x as f32 * 1.2) as u32 +
673 caption_area_width;
674 #[cfg(not(target_arch = "wasm32"))]
675 let spark_and_captions_height = params.spark_dimension_y +
676 params.spark_offset_y +
677 (params.label_area_height) +
678 caption_area_height;
679
680 #[cfg(not(target_arch = "wasm32"))]
681 let root_width: u32 = params.sparks_per_row * spark_and_captions_width;
682
683 #[cfg(not(target_arch = "wasm32"))]
684 let dl_root_height: u32 = total_dl_rows * spark_and_captions_height;
685
686 #[cfg(not(target_arch = "wasm32"))]
687 let ul_root_height: u32 = total_ul_rows * spark_and_captions_height;
688
689 let mut x_min = match ds.vantage_point {
690 VantagePoint::Client => ss.received_x_min,
691 VantagePoint::Server => ss.sent_x_min,
692 };
693
694 let mut x_max = match ds.vantage_point {
695 VantagePoint::Client => ss.received_x_max,
696 VantagePoint::Server => ss.sent_x_max,
697 };
698
699 if let Some(s) = params.clamp.start {
701 x_min = s;
702 }
703
704 if let Some(e) = params.clamp.end {
705 x_max = x_max.min(e);
706 }
707
708 let abs_dl_chart_config = ChartConfig {
709 title: "stream-spark-dl-absolute".into(),
710 input_filename: filename.into(),
711 clamp: params.clamp.clone(),
712 app_proto: ds.application_proto,
713 host: ds.host.clone(),
714 session_id: ds.session_id,
715 ty: abs_dl_ty.clone(),
716 };
717 abs_dl_chart_config.init_chart_dir();
718
719 let rel_dl_chart_config = ChartConfig {
720 title: "stream-spark-dl-relative".into(),
721 input_filename: filename.into(),
722 clamp: params.clamp.clone(),
723 app_proto: ds.application_proto,
724 host: ds.host.clone(),
725 session_id: ds.session_id,
726 ty: rel_dl_ty.clone(),
727 };
728 rel_dl_chart_config.init_chart_dir();
729
730 let abs_ul_chart_config = ChartConfig {
731 title: "stream-spark-ul-absolute".into(),
732 input_filename: filename.into(),
733 clamp: params.clamp.clone(),
734 app_proto: ds.application_proto,
735 host: ds.host.clone(),
736 session_id: ds.session_id,
737 ty: abs_ul_ty.clone(),
738 };
739 abs_ul_chart_config.init_chart_dir();
740
741 let rel_ul_chart_config = ChartConfig {
742 title: "stream-spark-ul-relative".into(),
743 input_filename: filename.into(),
744 clamp: params.clamp.clone(),
745 app_proto: ds.application_proto,
746 host: ds.host.clone(),
747 session_id: ds.session_id,
748 ty: rel_ul_ty.clone(),
749 };
750 rel_ul_chart_config.init_chart_dir();
751
752 #[cfg(not(target_arch = "wasm32"))]
753 let dl_size = ChartSize {
754 width: root_width,
755 height: dl_root_height,
756 };
757
758 #[cfg(not(target_arch = "wasm32"))]
759 let ul_size = ChartSize {
760 width: root_width,
761 height: ul_root_height,
762 };
763 let margin = ChartMargin::default();
764
765 #[cfg(not(target_arch = "wasm32"))]
766 let abs_dl_chart_path = abs_dl_chart_config.chart_filepath();
767
768 #[cfg(not(target_arch = "wasm32"))]
769 let rel_dl_chart_path = rel_dl_chart_config.chart_filepath();
770
771 #[cfg(not(target_arch = "wasm32"))]
772 let abs_ul_chart_path = abs_ul_chart_config.chart_filepath();
773
774 #[cfg(not(target_arch = "wasm32"))]
775 let rel_ul_chart_path = rel_ul_chart_config.chart_filepath();
776
777 #[cfg(not(target_arch = "wasm32"))]
778 let abs_dl_root = make_chart_bitmap_area(
779 &abs_dl_chart_path,
780 dl_size,
781 params.colors,
782 margin,
783 );
784
785 #[cfg(not(target_arch = "wasm32"))]
786 let rel_dl_root = make_chart_bitmap_area(
787 &rel_dl_chart_path,
788 dl_size,
789 params.colors,
790 margin,
791 );
792
793 #[cfg(not(target_arch = "wasm32"))]
794 let abs_ul_root = make_chart_bitmap_area(
795 &abs_ul_chart_path,
796 ul_size,
797 params.colors,
798 margin,
799 );
800
801 #[cfg(not(target_arch = "wasm32"))]
802 let rel_ul_root = make_chart_bitmap_area(
803 &rel_ul_chart_path,
804 ul_size,
805 params.colors,
806 margin,
807 );
808
809 #[cfg(target_arch = "wasm32")]
810 let abs_dl_canvas_id = abs_dl_chart_config.canvas_id().unwrap_or_default();
811
812 #[cfg(target_arch = "wasm32")]
813 let rel_dl_canvas_id = rel_dl_chart_config.canvas_id().unwrap_or_default();
814
815 #[cfg(target_arch = "wasm32")]
816 let abs_ul_canvas_id = abs_ul_chart_config.canvas_id().unwrap_or_default();
817
818 #[cfg(target_arch = "wasm32")]
819 let rel_ul_canvas_id = rel_ul_chart_config.canvas_id().unwrap_or_default();
820
821 #[cfg(target_arch = "wasm32")]
822 let abs_dl_root =
823 make_chart_canvas_area(&abs_dl_canvas_id, params.colors, margin);
824 #[cfg(target_arch = "wasm32")]
825 let rel_dl_root =
826 make_chart_canvas_area(&rel_dl_canvas_id, params.colors, margin);
827 #[cfg(target_arch = "wasm32")]
828 let abs_ul_root =
829 make_chart_canvas_area(&abs_ul_canvas_id, params.colors, margin);
830 #[cfg(target_arch = "wasm32")]
831 let rel_ul_root =
832 make_chart_canvas_area(&rel_ul_canvas_id, params.colors, margin);
833
834 let mut upper_x = params.spark_offset_x;
835 let mut dl_upper_y = params.spark_offset_y;
836 let mut ul_upper_y = params.spark_offset_y;
837
838 plot_legend(
839 ¶ms.colors.caption,
840 &abs_dl_root,
841 upper_x as i32,
842 dl_upper_y as i32,
843 params,
844 TransmissionType::Download,
845 );
846 plot_legend(
847 ¶ms.colors.caption,
848 &rel_dl_root,
849 upper_x as i32,
850 dl_upper_y as i32,
851 params,
852 TransmissionType::Download,
853 );
854 plot_legend(
855 ¶ms.colors.caption,
856 &abs_ul_root,
857 upper_x as i32,
858 ul_upper_y as i32,
859 params,
860 TransmissionType::Upload,
861 );
862 plot_legend(
863 ¶ms.colors.caption,
864 &rel_ul_root,
865 upper_x as i32,
866 ul_upper_y as i32,
867 params,
868 TransmissionType::Upload,
869 );
870
871 let mut dl_layout_count = 0;
872 let mut ul_layout_count = 0;
873
874 for (stream_id, dl_stream_frames) in stream_frame_dl_series_to_plot {
875 if ds.application_proto == ApplicationProto::Http3 &&
876 params.bidi_only &&
877 (stream_id & 0x2) != 0
878 {
879 continue;
880 }
881
882 let abs_dl_small_area = abs_dl_root.clone().shrink(
883 (upper_x, dl_upper_y + caption_area_height),
884 (params.spark_dimension_x, params.spark_dimension_y),
885 );
886
887 let rel_dl_small_area = rel_dl_root.clone().shrink(
888 (upper_x, dl_upper_y + caption_area_height),
889 (params.spark_dimension_x, params.spark_dimension_y),
890 );
891
892 let abs_ul_small_area = abs_ul_root.clone().shrink(
893 (upper_x, ul_upper_y + caption_area_height),
894 (params.spark_dimension_x, params.spark_dimension_y),
895 );
896
897 let rel_ul_small_area = rel_ul_root.clone().shrink(
898 (upper_x, ul_upper_y + caption_area_height),
899 (params.spark_dimension_x, params.spark_dimension_y),
900 );
901
902 let dl_zoom_x_min = dl_stream_frames.first().unwrap().0;
903 let dl_zoom_x_max = dl_stream_frames.last().unwrap().0;
904
905 let dl_zoom_y_max = if let Some(y_max) = params.clamp.stream_y_max {
906 y_max
907 } else {
908 dl_stream_frames.last().unwrap().1
909 };
910
911 let mut abs_dl_chart = ChartBuilder::on(&abs_dl_small_area)
912 .set_label_area_size(LabelAreaPosition::Left, params.label_area_width)
913 .set_label_area_size(
914 LabelAreaPosition::Bottom,
915 params.label_area_height,
916 )
917 .build_cartesian_2d(x_min..x_max, 0..dl_zoom_y_max)
918 .unwrap();
919 draw_mesh(¶ms.colors, &mut abs_dl_chart);
920
921 let mut rel_dl_chart = ChartBuilder::on(&rel_dl_small_area)
922 .set_label_area_size(LabelAreaPosition::Left, params.label_area_width)
923 .set_label_area_size(
924 LabelAreaPosition::Bottom,
925 params.label_area_height,
926 )
927 .build_cartesian_2d(dl_zoom_x_min..dl_zoom_x_max, 0..dl_zoom_y_max)
928 .unwrap();
929 draw_mesh(¶ms.colors, &mut rel_dl_chart);
930
931 dl_layout_count += 1;
932
933 let ul_stream_frames = stream_frame_ul_series_to_plot.get(stream_id);
934
935 let ul_h2_send_window =
937 ss.h2_send_window_series_balanced.get(&(*stream_id as u32));
938 let ul_h2_send_window_max =
939 ss.h2_send_window_balanced_max.get(&(*stream_id as u32));
940
941 let (abs_ul_chart, rel_ul_chart) = match ul_stream_frames {
942 Some(frames) => {
943 let ul_zoom_x_min = frames.first().unwrap().0;
944 let ul_zoom_x_max = frames.last().unwrap().0;
945
946 let ul_zoom_y_max = if let Some(y_max) = params.clamp.stream_y_max
952 {
953 y_max
954 } else {
955 frames.last().unwrap().1
956 };
957
958 let ul_h2_send_window_y_max = ul_h2_send_window_max.unwrap_or(&0);
962
963 let mut abs_ul_chart = ChartBuilder::on(&abs_ul_small_area)
964 .set_label_area_size(
965 LabelAreaPosition::Left,
966 params.label_area_width,
967 )
968 .set_label_area_size(
969 LabelAreaPosition::Bottom,
970 params.label_area_height,
971 )
972 .build_cartesian_2d(x_min..x_max, 0..ul_zoom_y_max)
973 .unwrap()
974 .set_secondary_coord(
975 x_min..x_max,
976 0i32..*ul_h2_send_window_y_max,
977 );
978
979 draw_mesh(¶ms.colors, &mut abs_ul_chart);
980
981 if ul_h2_send_window_max.is_some() {
982 abs_ul_chart.configure_secondary_axes().draw().unwrap();
983 }
984
985 let mut rel_ul_chart = ChartBuilder::on(&rel_ul_small_area)
986 .set_label_area_size(
987 LabelAreaPosition::Left,
988 params.label_area_width,
989 )
990 .set_label_area_size(
991 LabelAreaPosition::Bottom,
992 params.label_area_height,
993 )
994 .right_y_label_area_size(params.label_area_width)
995 .build_cartesian_2d(
996 ul_zoom_x_min..ul_zoom_x_max,
997 0..ul_zoom_y_max,
998 )
999 .unwrap()
1000 .set_secondary_coord(
1001 ul_zoom_x_min..ul_zoom_x_max,
1002 0i32..*ul_h2_send_window_y_max,
1003 );
1004 draw_mesh(¶ms.colors, &mut rel_ul_chart);
1005
1006 if ul_h2_send_window_max.is_some() {
1007 rel_ul_chart
1008 .configure_secondary_axes()
1009 .y_labels(5)
1010 .draw()
1011 .unwrap();
1012 }
1013
1014 ul_layout_count += 1;
1015
1016 (Some(abs_ul_chart), Some(rel_ul_chart))
1017 },
1018
1019 None => (None, None),
1020 };
1021
1022 draw_captions(
1023 &abs_dl_root,
1024 &rel_dl_root,
1025 &abs_ul_root,
1026 &rel_ul_root,
1027 params,
1028 ds,
1029 *stream_id,
1030 ul_stream_frames.is_some(),
1031 upper_x,
1032 dl_upper_y,
1033 ul_upper_y,
1034 );
1035
1036 upper_x += params.spark_dimension_x + params.spark_offset_x;
1037
1038 if dl_layout_count == params.sparks_per_row {
1040 upper_x = params.spark_offset_x;
1041
1042 let y_offset = if params.captions_on_top {
1043 params.caption_area_height
1044 } else {
1045 0
1046 };
1047
1048 dl_upper_y +=
1049 params.spark_dimension_y + params.spark_offset_y + y_offset;
1050
1051 dl_layout_count = 0;
1052 }
1053
1054 if ul_layout_count == params.sparks_per_row {
1055 upper_x = params.spark_offset_x;
1056
1057 let y_offset = if params.captions_on_top {
1058 params.caption_area_height
1059 } else {
1060 0
1061 };
1062
1063 ul_upper_y +=
1064 params.spark_dimension_y + params.spark_offset_y + y_offset;
1065
1066 ul_layout_count = 0;
1067 }
1068
1069 draw_stream_frames_line(
1070 dl_stream_frames,
1071 &mut abs_dl_chart,
1072 &mut rel_dl_chart,
1073 );
1074
1075 if let (Some(mut abs), Some(mut rel), Some(frames)) =
1076 (abs_ul_chart, rel_ul_chart, ul_stream_frames)
1077 {
1078 if let Some(v) = ul_h2_send_window {
1079 abs.draw_secondary_series(LineSeries::new(v.to_vec(), ORANGE))
1080 .unwrap();
1081
1082 rel.draw_secondary_series(LineSeries::new(v.to_vec(), ORANGE))
1083 .unwrap();
1084 } else {
1085 draw_stream_max_line(
1086 *stream_id,
1087 ss,
1088 &ds.vantage_point,
1089 TransmissionType::Upload,
1090 &mut abs,
1091 &mut rel,
1092 );
1093 }
1094
1095 draw_stream_frames_line(frames, &mut abs, &mut rel);
1096 }
1097
1098 draw_buffered_data_line(
1099 *stream_id,
1100 ss,
1101 &ds.vantage_point,
1102 &mut abs_dl_chart,
1103 &mut rel_dl_chart,
1104 );
1105
1106 draw_stream_max_line(
1107 *stream_id,
1108 ss,
1109 &ds.vantage_point,
1110 TransmissionType::Download,
1111 &mut abs_dl_chart,
1112 &mut rel_dl_chart,
1113 );
1114
1115 let y_max = match ds.vantage_point {
1116 VantagePoint::Client => ss.y_max_stream_recv_plot,
1117 VantagePoint::Server => ss.y_max_stream_send_plot,
1118 };
1119
1120 draw_request_timing_lines(
1121 ds,
1122 *stream_id,
1123 y_max,
1124 &mut abs_dl_chart,
1125 &mut rel_dl_chart,
1126 );
1127 }
1128}
1129
1130fn draw_mesh<XT, YT, X, Y, DB: DrawingBackend>(
1131 colors: &PlotColors, chart: &mut ChartContext<DB, Cartesian2d<X, Y>>,
1132) where
1133 X: Ranged<ValueType = XT> + ValueFormatter<XT>,
1134 Y: Ranged<ValueType = YT> + ValueFormatter<YT>,
1135{
1136 chart
1137 .configure_mesh()
1138 .disable_mesh()
1139 .x_labels(5)
1140 .y_labels(5)
1141 .axis_style(colors.axis)
1142 .label_style(chart_label_style(&colors.caption))
1143 .draw()
1144 .unwrap();
1145}
1146
1147#[allow(clippy::too_many_arguments)]
1148fn draw_captions<DB: DrawingBackend>(
1149 abs_dl_root: &DrawingArea<DB, plotters::coord::Shift>,
1150 rel_dl_root: &DrawingArea<DB, plotters::coord::Shift>,
1151 abs_ul_root: &DrawingArea<DB, plotters::coord::Shift>,
1152 rel_ul_root: &DrawingArea<DB, plotters::coord::Shift>,
1153 params: &SparkPlotsParams, ds: &Datastore, stream_id: u64, upload_data: bool,
1154 upper_x: u32, dl_upper_y: u32, ul_upper_y: u32,
1155) {
1156 if let Some(captions) = SparkCaption::from_data_store(ds, stream_id) {
1157 let x_offset = if params.captions_on_top {
1158 0
1159 } else if ds.application_proto == ApplicationProto::Http2 {
1160 params.spark_dimension_x + 30
1161 } else {
1162 params.spark_dimension_x
1163 };
1164
1165 captions.draw(
1166 ¶ms.colors.caption,
1167 abs_dl_root,
1168 (upper_x + x_offset) as i32,
1169 dl_upper_y as i32,
1170 );
1171 captions.draw(
1172 ¶ms.colors.caption,
1173 rel_dl_root,
1174 (upper_x + x_offset) as i32,
1175 dl_upper_y as i32,
1176 );
1177
1178 if upload_data {
1179 captions.draw(
1180 ¶ms.colors.caption,
1181 abs_ul_root,
1182 (upper_x + x_offset) as i32,
1183 ul_upper_y as i32,
1184 );
1185 captions.draw(
1186 ¶ms.colors.caption,
1187 rel_ul_root,
1188 (upper_x + x_offset) as i32,
1189 ul_upper_y as i32,
1190 );
1191 }
1192 }
1193}
1194
1195fn draw_stream_frames_line<DB: DrawingBackend>(
1196 stream_frames: &[(f64, u64)],
1197 abs_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,
1198 rel_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,
1199) {
1200 abs_chart
1201 .draw_series(LineSeries::new(stream_frames.to_vec(), PURPLE_500))
1202 .unwrap();
1203
1204 rel_chart
1205 .draw_series(LineSeries::new(stream_frames.to_vec(), PURPLE_500))
1206 .unwrap();
1207
1208 let circles: Vec<Circle<(f64, u64), i32>> = stream_frames
1209 .iter()
1210 .map(|point| Circle::new(*point, 2, PURPLE_500))
1211 .collect();
1212 rel_chart.draw_series(circles).unwrap();
1213}
1214
1215fn draw_buffered_data_line<DB: DrawingBackend>(
1216 stream_id: u64, ss: &SeriesStore, vantage_point: &VantagePoint,
1217 abs_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,
1218 rel_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,
1219) {
1220 let buffered_data_to_plot = match vantage_point {
1221 VantagePoint::Client => ss.stream_buffer_reads.get(&stream_id),
1222 VantagePoint::Server => ss.stream_buffer_writes.get(&stream_id),
1223 };
1224
1225 if let Some(buffered_data) = buffered_data_to_plot {
1226 abs_chart
1227 .draw_series(LineSeries::new(buffered_data.clone(), MAGENTA))
1228 .unwrap();
1229
1230 rel_chart
1231 .draw_series(LineSeries::new(buffered_data.clone(), MAGENTA))
1232 .unwrap();
1233
1234 rel_chart
1235 .draw_series(
1236 buffered_data
1237 .iter()
1238 .map(|point| TriangleMarker::new(*point, 3, MAGENTA)),
1239 )
1240 .unwrap();
1241 }
1242}
1243
1244fn draw_stream_max_line<DB: DrawingBackend>(
1245 stream_id: u64, ss: &SeriesStore, vantage_point: &VantagePoint,
1246 transmission_type: TransmissionType,
1247 abs_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,
1248 rel_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,
1249) {
1250 let stream_max_data_to_plot = match (vantage_point, transmission_type) {
1251 (VantagePoint::Client, TransmissionType::Download) =>
1252 ss.sent_stream_max_data.get(&stream_id),
1253 (VantagePoint::Client, TransmissionType::Upload) =>
1254 ss.received_stream_max_data.get(&stream_id),
1255 (VantagePoint::Server, TransmissionType::Download) =>
1256 ss.received_stream_max_data.get(&stream_id),
1257 (VantagePoint::Server, TransmissionType::Upload) =>
1258 ss.sent_stream_max_data.get(&stream_id),
1259 };
1260
1261 if let Some(stream_max_data) = stream_max_data_to_plot {
1262 abs_chart
1263 .draw_series(LineSeries::new(stream_max_data.clone(), MUSTARD))
1264 .unwrap();
1265
1266 rel_chart
1267 .draw_series(LineSeries::new(stream_max_data.clone(), MUSTARD))
1268 .unwrap();
1269 }
1270}
1271
1272fn draw_request_timing_lines<DB: DrawingBackend>(
1273 ds: &Datastore, stream_id: u64, y_max: u64,
1274 abs_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,
1275 rel_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,
1276) {
1277 if let Some(req) = ds.http_requests.get(&stream_id) {
1278 if let Some(h) = req.time_first_headers_rx {
1279 let points = vec![(h, 0), (h, y_max)];
1280 abs_chart
1281 .draw_series(LineSeries::new(points, GREEN))
1282 .unwrap();
1283
1284 }
1286
1287 if let Some(h) = req.time_first_headers_tx {
1288 let points = vec![(h, 0), (h, y_max)];
1289 abs_chart
1290 .draw_series(LineSeries::new(points.clone(), CYAN))
1291 .unwrap();
1292 rel_chart
1293 .draw_series(LineSeries::new(points, CYAN))
1294 .unwrap();
1295 }
1296
1297 let (first_data_to_plot, last_data_to_plot) = match ds.vantage_point {
1298 VantagePoint::Client =>
1299 (req.time_first_data_rx, req.time_last_data_rx),
1300 VantagePoint::Server =>
1301 (req.time_first_data_tx, req.time_last_data_tx),
1302 };
1303
1304 if let Some(t) = first_data_to_plot {
1305 let points = vec![(t, 0), (t, y_max)];
1306 abs_chart
1307 .draw_series(LineSeries::new(points.clone(), ORANGE))
1308 .unwrap();
1309 rel_chart
1310 .draw_series(LineSeries::new(points, ORANGE))
1311 .unwrap();
1312 }
1313
1314 if let Some(t) = last_data_to_plot {
1315 let points = vec![(t, 0), (t, y_max)];
1316 abs_chart
1317 .draw_series(LineSeries::new(points.clone(), BROWN))
1318 .unwrap();
1319 rel_chart
1320 .draw_series(LineSeries::new(points, BROWN))
1321 .unwrap();
1322 }
1323 }
1324}