Skip to main content

qlog_dancer/plots/
congestion_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 full_palette::PURPLE_500;
28use plotters::coord::types::RangedCoordf64;
29use plotters::coord::types::RangedCoordu64;
30use plotters::coord::Shift;
31
32use crate::plots::colors::*;
33use crate::plots::*;
34
35use crate::datastore::Datastore;
36use crate::seriesstore::SeriesStore;
37
38use super::minmax::XYMinMax;
39
40pub fn draw_congestion_plot<'a, DB: DrawingBackend + 'a>(
41    params: &PlotParameters, axis: &XYMinMax<u64>, ss: &SeriesStore,
42    ds: &Datastore, plot: &plotters::drawing::DrawingArea<DB, Shift>,
43) -> ChartContext<'a, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>> {
44    let mut builder = ChartBuilder::on(plot);
45
46    builder
47        .x_label_area_size(params.area_margin.x)
48        .y_label_area_size(params.area_margin.y);
49
50    if params.display_chart_title {
51        builder
52            .caption("Congestion", chart_subtitle_style(&params.colors.caption));
53    }
54
55    let extended_y =
56        axis.y_range.start..axis.y_range.end + (axis.y_range.end / 10) * 5;
57
58    let mut plot = builder
59        .build_cartesian_2d(axis.x.range(), extended_y.clone())
60        .unwrap();
61
62    draw_mesh(
63        &params.colors,
64        "Relative time (ms)",
65        "Data (bytes)",
66        false,
67        &mut plot,
68    );
69
70    draw_cc_updates(
71        &ds.congestion_state_updates,
72        axis.y_range.clone(),
73        extended_y,
74        &mut plot,
75    );
76    draw_bytes_in_flight(&ss.local_bytes_in_flight, &mut plot);
77    draw_cwnd(&ss.local_cwnd, &mut plot);
78    draw_ssthresh(&ss.local_ssthresh, &mut plot);
79
80    if params.display_legend {
81        plot.configure_series_labels()
82            .label_font(chart_label_style(&params.colors.caption))
83            .background_style(params.colors.fill.mix(0.8))
84            .border_style(params.colors.axis)
85            .position(SeriesLabelPosition::UpperLeft)
86            .draw()
87            .unwrap();
88    }
89
90    plot
91}
92
93#[cfg(target_arch = "wasm32")]
94pub fn plot_cc_plot<'a>(
95    params: &PlotParameters, ss: &SeriesStore, ds: &Datastore, canvas_id: &str,
96) -> ChartContext<'a, CanvasBackend, Cartesian2d<RangedCoordf64, RangedCoordu64>>
97{
98    let root =
99        make_chart_canvas_area(&canvas_id, params.colors, params.chart_margin);
100
101    let cwnd_y_max = if let Some(y_max) = params.cwnd_y_max {
102        y_max
103    } else {
104        // add a bit of margin
105        ss.y_max_congestion_plot + ss.y_max_congestion_plot / 10
106    };
107
108    // TODO set minimum
109    let axis = super::minmax::XYMinMax::init(
110        ss.sent_x_min..ss.sent_x_max,
111        params.clamp.start,
112        params.clamp.end,
113        0..cwnd_y_max,
114    );
115
116    draw_congestion_plot(params, &axis, ss, ds, &root)
117}
118
119fn draw_cc_updates<DB: DrawingBackend>(
120    data: &[(f64, u64, String)], y_range: std::ops::Range<u64>,
121    y_range_extended: std::ops::Range<u64>,
122    congestion_chart: &mut ChartContext<
123        DB,
124        Cartesian2d<RangedCoordf64, RangedCoordu64>,
125    >,
126) {
127    let my_label = |x: f64, y: u64, name: &str| {
128        let color = cc_state_to_color(name);
129        let text_width = name.len() as i32 * 6; // Rough estimate
130        let text_height = 12;
131
132        return EmptyElement::at((x, y))
133            //+ Circle::new((0, 0), 1, ShapeStyle::from(color).filled())
134            + Rectangle::new([(1, -2), (1 + text_width, text_height)],
135                        WHITE.mix(0.7).filled()) // 0.7 alpha for transparency
136            + Text::new(
137                name.to_owned(),
138                (1, 0),
139                ("sans-serif", 12.0).into_font().color(&color),
140            );
141    };
142
143    let mut woggle = vec![0];
144
145    for i in 1..5 {
146        woggle.push((y_range.end / 10) * i);
147    }
148
149    // Add labels from right to left, to ensure the z-ordering doesn't add lines
150    // over the labels
151    let iter = data.iter().rev();
152    for cc_state in iter {
153        let color = cc_state_to_color(&cc_state.2);
154        let x = cc_state.0;
155
156        let line_coords =
157            [(x, y_range_extended.start), (x, y_range_extended.end)];
158
159        congestion_chart
160            .draw_series(LineSeries::new(line_coords, color))
161            .unwrap();
162
163        let area = congestion_chart.plotting_area();
164
165        // control the vertical position of the text label to avoid overlaps.
166        let y = y_range.end + woggle[0];
167        area.draw(&my_label(x, y, &cc_state.2)).unwrap();
168
169        woggle.rotate_left(1);
170    }
171}
172
173fn draw_bytes_in_flight<DB: DrawingBackend>(
174    data: &[(f64, u64)],
175    congestion_chart: &mut ChartContext<
176        DB,
177        Cartesian2d<RangedCoordf64, RangedCoordu64>,
178    >,
179) {
180    draw_line(data, Some("bytes in flight"), TAUPE, congestion_chart);
181}
182
183fn draw_cwnd<DB: DrawingBackend>(
184    data: &[(f64, u64)],
185    congestion_chart: &mut ChartContext<
186        DB,
187        Cartesian2d<RangedCoordf64, RangedCoordu64>,
188    >,
189) {
190    draw_line(data, Some("cwnd"), PURPLE_500, congestion_chart);
191}
192
193fn draw_ssthresh<DB: DrawingBackend>(
194    data: &[(f64, u64)],
195    congestion_chart: &mut ChartContext<
196        DB,
197        Cartesian2d<RangedCoordf64, RangedCoordu64>,
198    >,
199) {
200    draw_line(data, Some("ssthresh"), ORANGE, congestion_chart);
201}
202
203// Colors from ColorCycle list
204fn cc_state_to_color(cc_state: &str) -> RGBColor {
205    match cc_state {
206        // Cubic and Reno
207        "slow_start" => RGBColor(204, 81, 81),
208        "recovery" => RGBColor(127, 51, 51),
209        "congestion_avoidance" => RGBColor(81, 204, 204),
210
211        // BBR
212        "bbr_startup" => RGBColor(204, 81, 81),
213        "bbr_drain" => RGBColor(127, 51, 51),
214        "bbr_probe_bw" => RGBColor(81, 204, 204),
215        "bbr_probe_rtt" => RGBColor(51, 127, 127),
216
217        _ => BLACK,
218    }
219}