Skip to content

Commit 8dde4df

Browse files
perf: disable plot supersampling, add plot render benchmarks (#467)
* dev: add benchmark for drawing plots * chore: update bench * perf: disable supersampling
1 parent 8e58d19 commit 8dde4df

File tree

10 files changed

+157
-73
lines changed

10 files changed

+157
-73
lines changed

Cargo.lock

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ chrono = "0.4.31"
2626
indexmap = { version = "2.5.0", features = ["serde"] }
2727
pretty_assertions = "1.4.0"
2828
divan = "0.1"
29+
gtk = { version = "0.9", package = "gtk4" }
2930

3031
[profile.release]
3132
strip = "symbols"
@@ -45,3 +46,5 @@ opt-level = 3
4546
[profile.bench]
4647
strip = false
4748
debug = 1
49+
lto = "thin"
50+
codegen-units = 256

lact-daemon/Cargo.toml

-6
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,8 @@ copes = { git = "https://gitlab.com/corectrl/copes" }
4848
libloading = "0.8.6"
4949

5050
[dev-dependencies]
51-
divan = { workspace = true }
5251
pretty_assertions = { workspace = true }
53-
lact-daemon = { path = ".", features = ["bench"] }
5452
insta = { version = "1.41.1", features = ["json", "yaml"] }
5553

5654
[build-dependencies]
5755
bindgen = "0.68"
58-
59-
[[bench]]
60-
name = "daemon"
61-
harness = false

lact-daemon/benches/daemon.rs

-5
This file was deleted.

lact-gui/Cargo.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ edition = "2021"
88
default = ["gtk-tests"]
99
gtk-tests = []
1010
adw = ["dep:adw", "relm4/libadwaita"]
11+
bench = ["dep:divan"]
1112

1213
[dependencies]
1314
lact-client = { path = "../lact-client" }
@@ -19,8 +20,8 @@ tracing = { workspace = true }
1920
anyhow = { workspace = true }
2021
tracing-subscriber = { workspace = true }
2122
chrono = { workspace = true }
23+
gtk = { workspace = true, features = ["v4_6", "blueprint"] }
2224

23-
gtk = { version = "0.9", package = "gtk4", features = ["v4_6", "blueprint"] }
2425
adw = { package = "libadwaita", version = "0.7.1", features = [
2526
"v1_4",
2627
], optional = true }
@@ -37,5 +38,7 @@ itertools = "0.13.0"
3738

3839
thread-priority = "1.1.0"
3940

41+
divan = { workspace = true, optional = true }
42+
4043
[dev-dependencies]
4144
pretty_assertions = "1.4.0"

lact-gui/src/app/graphs_window/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,13 @@ impl relm4::Component for GraphsWindow {
3838
set_margin_all: 10,
3939
set_row_spacing: 20,
4040
set_column_spacing: 20,
41+
set_column_homogeneous: true,
4142

4243
attach[0, 0, 1, 1]: temperature_plot = &Plot {
4344
set_title: "Temperature",
4445
set_hexpand: true,
4546
set_value_suffix: "°C",
46-
set_y_label_area_relative_size: 0.15,
47+
set_y_label_area_relative_size: 0.2,
4748
#[watch]
4849
set_time_period_seconds: model.time_period_seconds_adj.value() as i64,
4950
},
@@ -52,7 +53,7 @@ impl relm4::Component for GraphsWindow {
5253
set_title: "Fan speed",
5354
set_hexpand: true,
5455
set_value_suffix: "RPM",
55-
set_y_label_area_relative_size: 0.25,
56+
set_y_label_area_relative_size: 0.3,
5657
set_secondary_y_label_area_relative_size: 0.15,
5758
#[watch]
5859
set_time_period_seconds: model.time_period_seconds_adj.value() as i64,

lact-gui/src/app/graphs_window/plot/imp.rs

+67-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use std::collections::BTreeMap;
99

1010
use super::render_thread::{RenderRequest, RenderThread};
1111

12+
const SUPERSAMPLE_FACTOR: u32 = 1;
13+
1214
#[derive(Properties, Default)]
1315
#[properties(wrapper_type = super::Plot)]
1416
pub struct Plot {
@@ -75,7 +77,7 @@ impl WidgetImpl for Plot {
7577
secondary_y_label_relative_area_size: self
7678
.secondary_y_label_area_relative_size
7779
.get(),
78-
supersample_factor: 4,
80+
supersample_factor: SUPERSAMPLE_FACTOR,
7981
time_period_seconds: self.time_period_seconds.get(),
8082
});
8183
}
@@ -105,14 +107,19 @@ impl PlotData {
105107
self.push_secondary_line_series_with_time(name, point, chrono::Local::now().naive_local());
106108
}
107109

108-
fn push_line_series_with_time(&mut self, name: &str, point: f64, time: NaiveDateTime) {
110+
pub(super) fn push_line_series_with_time(
111+
&mut self,
112+
name: &str,
113+
point: f64,
114+
time: NaiveDateTime,
115+
) {
109116
self.line_series
110117
.entry(name.to_owned())
111118
.or_default()
112119
.push((time.and_utc().timestamp_millis(), point));
113120
}
114121

115-
pub fn push_secondary_line_series_with_time(
122+
pub(super) fn push_secondary_line_series_with_time(
116123
&mut self,
117124
name: &str,
118125
point: f64,
@@ -188,3 +195,60 @@ impl PlotData {
188195
self.line_series.is_empty() && self.secondary_line_series.is_empty()
189196
}
190197
}
198+
199+
#[cfg(feature = "bench")]
200+
mod benches {
201+
use crate::app::graphs_window::plot::{
202+
render_thread::{process_request, RenderRequest},
203+
PlotData,
204+
};
205+
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
206+
use divan::{counter::ItemsCount, Bencher};
207+
use std::sync::Mutex;
208+
209+
use super::SUPERSAMPLE_FACTOR;
210+
211+
#[divan::bench]
212+
fn render_plot(bencher: Bencher) {
213+
let last_texture = &Mutex::new(None);
214+
215+
bencher
216+
.with_inputs(sample_plot_data)
217+
.input_counter(|_| ItemsCount::new(1usize))
218+
.bench_values(|data| {
219+
let request = RenderRequest {
220+
title: "bench render".into(),
221+
value_suffix: "%".into(),
222+
secondary_value_suffix: "".into(),
223+
y_label_area_relative_size: 1.0,
224+
secondary_y_label_relative_area_size: 1.0,
225+
data,
226+
width: 1920,
227+
height: 1080,
228+
supersample_factor: SUPERSAMPLE_FACTOR,
229+
time_period_seconds: 60,
230+
};
231+
232+
process_request(request, last_texture)
233+
});
234+
}
235+
236+
fn sample_plot_data() -> PlotData {
237+
let mut data = PlotData::default();
238+
239+
// Simulate 1 minute plot with 4 values per second
240+
for sec in 0..60 {
241+
for milli in [0, 250, 500, 750] {
242+
let datetime = NaiveDateTime::new(
243+
NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(),
244+
NaiveTime::from_hms_milli_opt(0, 0, sec, milli).unwrap(),
245+
);
246+
247+
data.push_line_series_with_time("GPU", 100.0, datetime);
248+
data.push_secondary_line_series_with_time("GPU Secondary", 10.0, datetime);
249+
}
250+
}
251+
252+
data
253+
}
254+
}

lact-gui/src/app/graphs_window/plot/render_thread.rs

+62-55
Original file line numberDiff line numberDiff line change
@@ -94,62 +94,15 @@ impl RenderThread {
9494

9595
match current_request.take() {
9696
Some(Request::Render(render_request)) => {
97-
// Create a new ImageSurface for Cairo rendering.
98-
let mut surface = ImageSurface::create(
99-
cairo::Format::ARgb32,
100-
(render_request.width * render_request.supersample_factor) as i32,
101-
(render_request.height * render_request.supersample_factor) as i32,
102-
)
103-
.unwrap();
104-
105-
let cairo_context = CairoContext::new(&surface).unwrap();
106-
107-
// Don't use Cairo's default antialiasing, it makes the lines look too blurry
108-
// Supersampling is our 2D anti-aliasing solution.
109-
if render_request.supersample_factor > 1 {
110-
cairo_context.set_antialias(cairo::Antialias::None);
111-
}
112-
113-
let cairo_backend = CairoBackend::new(
114-
&cairo_context,
115-
// Supersample the rendering
116-
(
117-
render_request.width * render_request.supersample_factor,
118-
render_request.height * render_request.supersample_factor,
119-
),
120-
)
121-
.unwrap();
122-
123-
if let Err(err) = render_request.draw(cairo_backend) {
124-
error!("Failed to plot chart: {err:?}")
125-
}
126-
127-
match (
128-
surface.to_texture(),
129-
last_texture.lock().unwrap().deref_mut(),
130-
) {
131-
// Successfully generated a new texture, but the old texture is also there
132-
(Some(texture), Some(last_texture)) => {
133-
*last_texture = texture;
134-
}
135-
// If texture conversion failed, keep the old texture if it's present.
136-
(None, None) => {
137-
error!("Failed to convert cairo surface to gdk texture, not overwriting old one");
138-
}
139-
// Update the last texture, if The old texture wasn't ever generated (None),
140-
// No matter the result of conversion
141-
(result, last_texture) => {
142-
*last_texture = result;
143-
}
144-
};
145-
}
97+
process_request(render_request, last_texture);
98+
}
14699
// Terminate the thread if a Terminate request is received.
147100
Some(Request::Terminate) => break,
148101
None => {}
149102
}
150103
}
151-
})
152-
.unwrap();
104+
})
105+
.unwrap();
153106

154107
Self {
155108
state,
@@ -177,6 +130,60 @@ impl RenderThread {
177130
}
178131
}
179132

133+
pub(super) fn process_request(
134+
render_request: RenderRequest,
135+
last_texture: &Mutex<Option<MemoryTexture>>,
136+
) {
137+
// Create a new ImageSurface for Cairo rendering.
138+
let mut surface = ImageSurface::create(
139+
cairo::Format::ARgb32,
140+
(render_request.width * render_request.supersample_factor) as i32,
141+
(render_request.height * render_request.supersample_factor) as i32,
142+
)
143+
.unwrap();
144+
145+
let cairo_context = CairoContext::new(&surface).unwrap();
146+
147+
// Don't use Cairo's default antialiasing, it makes the lines look too blurry
148+
// Supersampling is our 2D anti-aliasing solution.
149+
if render_request.supersample_factor > 1 {
150+
cairo_context.set_antialias(cairo::Antialias::None);
151+
}
152+
153+
let cairo_backend = CairoBackend::new(
154+
&cairo_context,
155+
// Supersample the rendering
156+
(
157+
render_request.width * render_request.supersample_factor,
158+
render_request.height * render_request.supersample_factor,
159+
),
160+
)
161+
.unwrap();
162+
163+
if let Err(err) = render_request.draw(cairo_backend) {
164+
error!("Failed to plot chart: {err:?}")
165+
}
166+
167+
match (
168+
surface.to_texture(),
169+
last_texture.lock().unwrap().deref_mut(),
170+
) {
171+
// Successfully generated a new texture, but the old texture is also there
172+
(Some(texture), Some(last_texture)) => {
173+
*last_texture = texture;
174+
}
175+
// If texture conversion failed, keep the old texture if it's present.
176+
(None, None) => {
177+
error!("Failed to convert cairo surface to gdk texture, not overwriting old one");
178+
}
179+
// Update the last texture, if The old texture wasn't ever generated (None),
180+
// No matter the result of conversion
181+
(result, last_texture) => {
182+
*last_texture = result;
183+
}
184+
};
185+
}
186+
180187
// Implement the default constructor for RenderThread using the `new` method.
181188
impl Default for RenderThread {
182189
fn default() -> Self {
@@ -336,12 +343,12 @@ impl RenderRequest {
336343
(current_date, segment.evaluate(current_date))
337344
})
338345
}),
339-
Palette99::pick(idx).stroke_width(8),
346+
Palette99::pick(idx).stroke_width(2),
340347
))
341348
.context("Failed to draw series")?
342349
.label(caption)
343350
.legend(move |(x, y)| {
344-
let offset = self.relative_size(0.04) as i32;
351+
let offset = self.relative_size(0.02) as i32;
345352
Rectangle::new(
346353
[(x - offset, y - offset), (x + offset, y + offset)],
347354
Palette99::pick(idx).filled(),
@@ -360,12 +367,12 @@ impl RenderRequest {
360367
(current_date, segment.evaluate(current_date))
361368
})
362369
}),
363-
Palette99::pick(idx + 10).stroke_width(8),
370+
Palette99::pick(idx + 10).stroke_width(2),
364371
))
365372
.context("Failed to draw series")?
366373
.label(caption)
367374
.legend(move |(x, y)| {
368-
let offset = self.relative_size(0.04) as i32;
375+
let offset = self.relative_size(0.02) as i32;
369376
Rectangle::new(
370377
[(x - offset, y - offset), (x + offset, y + offset)],
371378
Palette99::pick(idx + 10).filled(),

lact/Cargo.toml

+9
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,12 @@ lact-schema = { path = "../lact-schema", features = ["args"] }
1313
lact-cli = { path = "../lact-cli" }
1414
lact-gui = { path = "../lact-gui", optional = true }
1515
anyhow = { workspace = true }
16+
17+
[dev-dependencies]
18+
divan = { workspace = true }
19+
lact-daemon = { path = "../lact-daemon", features = ["bench"] }
20+
lact-gui = { path = "../lact-gui", features = ["bench"] }
21+
22+
[[bench]]
23+
name = "bench"
24+
harness = false

lact/benches/bench.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
fn main() {
2+
// Include crates in the binary
3+
let _ = lact_daemon::run;
4+
let _ = lact_gui::run;
5+
6+
divan::main();
7+
}

0 commit comments

Comments
 (0)