Skip to main content

qlog_dancer/plots/
conn_flow_control.rs

1// Copyright (C) 2025, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27use minmax::XMinMax;
28use plotters::chart::ChartContext;
29use plotters::coord::types::RangedCoordf64;
30use plotters::coord::types::RangedCoordu64;
31use plotters::prelude::Cartesian2d;
32
33use crate::plots::colors::*;
34use crate::plots::*;
35
36use crate::datastore::Datastore;
37use crate::seriesstore::SeriesStore;
38
39const Y_WIGGLE: f64 = 1.2;
40
41struct XYMinMax {
42    pub x: XMinMax,
43    pub y_min: u64,
44    pub y_max: u64,
45}
46
47impl XYMinMax {
48    fn init(params: &PlotParameters, ss: &SeriesStore, ds: &Datastore) -> Self {
49        let x = XMinMax::new(
50            ss.received_x_min,
51            ss.received_x_max,
52            params.clamp.start,
53            params.clamp.end,
54        );
55
56        Self {
57            x,
58            y_min: 0,
59            y_max: Self::y_max(ss, &ds.application_proto),
60        }
61    }
62
63    fn y_range(&self) -> std::ops::Range<u64> {
64        self.y_min..self.y_max
65    }
66
67    fn y_max(ss: &SeriesStore, proto: &ApplicationProto) -> u64 {
68        let y = match proto {
69            ApplicationProto::Http2 =>
70                *(ss.h2_send_window_absolute_max.get(&0).unwrap_or(&0)),
71            ApplicationProto::Http3 =>
72                ss.netlog_quic_stream_received_connection_cumulative
73                    .last()
74                    .unwrap_or(&(0.0, 0))
75                    .1,
76        };
77
78        (y as f64 * Y_WIGGLE) as u64
79    }
80}
81
82fn blocked_lines<DB: DrawingBackend>(
83    ds: &Datastore, y_max: u64,
84    chart: &mut ChartContext<'_, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,
85) {
86    if let Some(blocked) = ds.netlog_quic_server_window_blocked.get(&-1).cloned()
87    {
88        let blocked_lines = blocked
89            .iter()
90            .map(|t| PathElement::new([(*t, 0), (*t, y_max)], GREEN));
91
92        chart
93            .draw_series(blocked_lines)
94            .unwrap()
95            .label("server conn blocked")
96            .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], GREEN));
97    }
98}
99
100fn received_data<DB: DrawingBackend>(
101    ss: &SeriesStore, proto: &ApplicationProto,
102    chart: &mut ChartContext<'_, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,
103) {
104    let points = match proto {
105        ApplicationProto::Http2 =>
106            ss.netlog_h2_stream_received_connection_cumulative.clone(),
107        ApplicationProto::Http3 =>
108            ss.netlog_quic_stream_received_connection_cumulative.clone(),
109    };
110
111    chart
112        .draw_series(LineSeries::new(points, ORANGE))
113        .unwrap()
114        .label("cumulative received stream data")
115        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], ORANGE));
116}
117
118fn window_updates<DB: DrawingBackend>(
119    ss: &SeriesStore, ds: &Datastore,
120    chart: &mut ChartContext<'_, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,
121) {
122    let points = match ds.application_proto {
123        ApplicationProto::Http2 => {
124            if let Some(conn_win_updates) =
125                ss.h2_send_window_series_absolute.get(&0).cloned()
126            {
127                conn_win_updates
128            } else {
129                return;
130            }
131        },
132
133        ApplicationProto::Http3 => {
134            if let Some(conn_win_updates) =
135                ss.netlog_quic_client_side_window_updates.get(&-1).cloned()
136            {
137                conn_win_updates
138            } else {
139                return;
140            }
141        },
142    };
143
144    // Help us see the exact time a window update happened.
145    let circles: Vec<Circle<(f64, u64), i32>> = points
146        .iter()
147        .map(|point| Circle::new(*point, 2, BLUE))
148        .collect();
149
150    chart
151        .draw_series(LineSeries::new(points, BLUE))
152        .unwrap()
153        .label("sent window updates")
154        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], BLUE));
155
156    chart.draw_series(circles).unwrap();
157}
158
159fn draw_series<'a, DB: DrawingBackend + 'a>(
160    chart: &mut ChartContext<'a, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,
161    params: &PlotParameters, ss: &SeriesStore, ds: &Datastore, axis: XYMinMax,
162) {
163    draw_mesh(
164        &params.colors,
165        "Relative time (ms)",
166        "Bytes",
167        params.display_minor_lines,
168        chart,
169    );
170
171    // Draw the series
172    blocked_lines(ds, axis.y_max, chart);
173    received_data(ss, &ds.application_proto, chart);
174    window_updates(ss, ds, chart);
175
176    if params.display_legend {
177        // Draw the series legend
178        chart
179            .configure_series_labels()
180            .label_font(chart_label_style(&params.colors.caption))
181            .background_style(params.colors.fill.mix(0.8))
182            .border_style(params.colors.axis)
183            .position(SeriesLabelPosition::LowerRight)
184            .draw()
185            .unwrap();
186    }
187}
188
189#[cfg(target_arch = "wasm32")]
190pub fn plot_conn_flow_control_canvas<'a>(
191    params: &PlotParameters, filename: &str, ss: &SeriesStore, ds: &Datastore,
192    ty: &ChartOutputType,
193) -> ChartContext<'a, CanvasBackend, Cartesian2d<RangedCoordf64, RangedCoordu64>>
194{
195    let chart_config =
196        make_chart_config("flow_control", params, filename, ds, ty);
197
198    let canvas_id: String = chart_config.canvas_id().unwrap_or_default();
199    let root =
200        make_chart_canvas_area(&canvas_id, params.colors, params.chart_margin);
201
202    let axis = XYMinMax::init(params, ss, ds);
203
204    let mut builder = ChartBuilder::on(&root);
205    builder
206        .x_label_area_size(params.area_margin.x)
207        .y_label_area_size(params.area_margin.y);
208
209    if params.display_chart_title {
210        let caption = format!("{} Connection Flow Control timeline", filename);
211        builder.caption(caption, chart_title_style(&params.colors.caption));
212    }
213
214    let mut fc_chart = builder
215        .build_cartesian_2d(axis.x.range(), axis.y_range())
216        .unwrap();
217
218    draw_series(&mut fc_chart, params, ss, ds, axis);
219
220    fc_chart
221}
222
223#[cfg(not(target_arch = "wasm32"))]
224pub fn plot_conn_flow_control(
225    params: &PlotParameters, filename: &str, ss: &SeriesStore, ds: &Datastore,
226    ty: &ChartOutputType,
227) {
228    let chart_config =
229        make_chart_config("flow_control", params, filename, ds, ty);
230    let chart_path = chart_config.chart_filepath();
231
232    let root = make_chart_bitmap_area(
233        &chart_path,
234        params.chart_size,
235        params.colors,
236        params.chart_margin,
237    );
238
239    let axis = XYMinMax::init(params, ss, ds);
240
241    let mut builder = ChartBuilder::on(&root);
242    builder
243        .x_label_area_size(params.area_margin.x)
244        .y_label_area_size(params.area_margin.y);
245
246    if params.display_chart_title {
247        let caption = format!("{} Connection Flow Control timeline", filename);
248        builder.caption(caption, chart_title_style(&params.colors.caption));
249    }
250
251    let mut fc_chart = builder
252        .build_cartesian_2d(axis.x.range(), axis.y_range())
253        .unwrap();
254
255    draw_mesh(
256        &params.colors,
257        "Relative time (ms)",
258        "Bytes",
259        params.display_minor_lines,
260        &mut fc_chart,
261    );
262
263    draw_series(&mut fc_chart, params, ss, ds, axis);
264}