mirror of
https://github.com/ilya-zlobintsev/LACT.git
synced 2025-02-25 18:55:26 -06:00
perf: disable plot supersampling, add plot render benchmarks (#467)
* dev: add benchmark for drawing plots * chore: update bench * perf: disable supersampling
This commit is contained in:
parent
8e58d19807
commit
8dde4df4d1
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -1282,6 +1282,7 @@ name = "lact"
|
|||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"divan",
|
||||||
"lact-cli",
|
"lact-cli",
|
||||||
"lact-daemon",
|
"lact-daemon",
|
||||||
"lact-gui",
|
"lact-gui",
|
||||||
@ -1327,7 +1328,6 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"insta",
|
"insta",
|
||||||
"lact-daemon",
|
|
||||||
"lact-schema",
|
"lact-schema",
|
||||||
"libdrm_amdgpu_sys",
|
"libdrm_amdgpu_sys",
|
||||||
"libflate",
|
"libflate",
|
||||||
@ -1358,6 +1358,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"cairo-rs",
|
"cairo-rs",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"divan",
|
||||||
"gtk4",
|
"gtk4",
|
||||||
"itertools",
|
"itertools",
|
||||||
"lact-client",
|
"lact-client",
|
||||||
|
@ -26,6 +26,7 @@ chrono = "0.4.31"
|
|||||||
indexmap = { version = "2.5.0", features = ["serde"] }
|
indexmap = { version = "2.5.0", features = ["serde"] }
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.0"
|
||||||
divan = "0.1"
|
divan = "0.1"
|
||||||
|
gtk = { version = "0.9", package = "gtk4" }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = "symbols"
|
strip = "symbols"
|
||||||
@ -45,3 +46,5 @@ opt-level = 3
|
|||||||
[profile.bench]
|
[profile.bench]
|
||||||
strip = false
|
strip = false
|
||||||
debug = 1
|
debug = 1
|
||||||
|
lto = "thin"
|
||||||
|
codegen-units = 256
|
||||||
|
@ -48,14 +48,8 @@ copes = { git = "https://gitlab.com/corectrl/copes" }
|
|||||||
libloading = "0.8.6"
|
libloading = "0.8.6"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
divan = { workspace = true }
|
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
lact-daemon = { path = ".", features = ["bench"] }
|
|
||||||
insta = { version = "1.41.1", features = ["json", "yaml"] }
|
insta = { version = "1.41.1", features = ["json", "yaml"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bindgen = "0.68"
|
bindgen = "0.68"
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "daemon"
|
|
||||||
harness = false
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
// Include the daemon lib
|
|
||||||
let _ = lact_daemon::MODULE_CONF_PATH;
|
|
||||||
divan::main();
|
|
||||||
}
|
|
@ -8,6 +8,7 @@ edition = "2021"
|
|||||||
default = ["gtk-tests"]
|
default = ["gtk-tests"]
|
||||||
gtk-tests = []
|
gtk-tests = []
|
||||||
adw = ["dep:adw", "relm4/libadwaita"]
|
adw = ["dep:adw", "relm4/libadwaita"]
|
||||||
|
bench = ["dep:divan"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lact-client = { path = "../lact-client" }
|
lact-client = { path = "../lact-client" }
|
||||||
@ -19,8 +20,8 @@ tracing = { workspace = true }
|
|||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
|
gtk = { workspace = true, features = ["v4_6", "blueprint"] }
|
||||||
|
|
||||||
gtk = { version = "0.9", package = "gtk4", features = ["v4_6", "blueprint"] }
|
|
||||||
adw = { package = "libadwaita", version = "0.7.1", features = [
|
adw = { package = "libadwaita", version = "0.7.1", features = [
|
||||||
"v1_4",
|
"v1_4",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
@ -37,5 +38,7 @@ itertools = "0.13.0"
|
|||||||
|
|
||||||
thread-priority = "1.1.0"
|
thread-priority = "1.1.0"
|
||||||
|
|
||||||
|
divan = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.0"
|
||||||
|
@ -38,12 +38,13 @@ impl relm4::Component for GraphsWindow {
|
|||||||
set_margin_all: 10,
|
set_margin_all: 10,
|
||||||
set_row_spacing: 20,
|
set_row_spacing: 20,
|
||||||
set_column_spacing: 20,
|
set_column_spacing: 20,
|
||||||
|
set_column_homogeneous: true,
|
||||||
|
|
||||||
attach[0, 0, 1, 1]: temperature_plot = &Plot {
|
attach[0, 0, 1, 1]: temperature_plot = &Plot {
|
||||||
set_title: "Temperature",
|
set_title: "Temperature",
|
||||||
set_hexpand: true,
|
set_hexpand: true,
|
||||||
set_value_suffix: "°C",
|
set_value_suffix: "°C",
|
||||||
set_y_label_area_relative_size: 0.15,
|
set_y_label_area_relative_size: 0.2,
|
||||||
#[watch]
|
#[watch]
|
||||||
set_time_period_seconds: model.time_period_seconds_adj.value() as i64,
|
set_time_period_seconds: model.time_period_seconds_adj.value() as i64,
|
||||||
},
|
},
|
||||||
@ -52,7 +53,7 @@ impl relm4::Component for GraphsWindow {
|
|||||||
set_title: "Fan speed",
|
set_title: "Fan speed",
|
||||||
set_hexpand: true,
|
set_hexpand: true,
|
||||||
set_value_suffix: "RPM",
|
set_value_suffix: "RPM",
|
||||||
set_y_label_area_relative_size: 0.25,
|
set_y_label_area_relative_size: 0.3,
|
||||||
set_secondary_y_label_area_relative_size: 0.15,
|
set_secondary_y_label_area_relative_size: 0.15,
|
||||||
#[watch]
|
#[watch]
|
||||||
set_time_period_seconds: model.time_period_seconds_adj.value() as i64,
|
set_time_period_seconds: model.time_period_seconds_adj.value() as i64,
|
||||||
|
@ -9,6 +9,8 @@ use std::collections::BTreeMap;
|
|||||||
|
|
||||||
use super::render_thread::{RenderRequest, RenderThread};
|
use super::render_thread::{RenderRequest, RenderThread};
|
||||||
|
|
||||||
|
const SUPERSAMPLE_FACTOR: u32 = 1;
|
||||||
|
|
||||||
#[derive(Properties, Default)]
|
#[derive(Properties, Default)]
|
||||||
#[properties(wrapper_type = super::Plot)]
|
#[properties(wrapper_type = super::Plot)]
|
||||||
pub struct Plot {
|
pub struct Plot {
|
||||||
@ -75,7 +77,7 @@ impl WidgetImpl for Plot {
|
|||||||
secondary_y_label_relative_area_size: self
|
secondary_y_label_relative_area_size: self
|
||||||
.secondary_y_label_area_relative_size
|
.secondary_y_label_area_relative_size
|
||||||
.get(),
|
.get(),
|
||||||
supersample_factor: 4,
|
supersample_factor: SUPERSAMPLE_FACTOR,
|
||||||
time_period_seconds: self.time_period_seconds.get(),
|
time_period_seconds: self.time_period_seconds.get(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -105,14 +107,19 @@ impl PlotData {
|
|||||||
self.push_secondary_line_series_with_time(name, point, chrono::Local::now().naive_local());
|
self.push_secondary_line_series_with_time(name, point, chrono::Local::now().naive_local());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_line_series_with_time(&mut self, name: &str, point: f64, time: NaiveDateTime) {
|
pub(super) fn push_line_series_with_time(
|
||||||
|
&mut self,
|
||||||
|
name: &str,
|
||||||
|
point: f64,
|
||||||
|
time: NaiveDateTime,
|
||||||
|
) {
|
||||||
self.line_series
|
self.line_series
|
||||||
.entry(name.to_owned())
|
.entry(name.to_owned())
|
||||||
.or_default()
|
.or_default()
|
||||||
.push((time.and_utc().timestamp_millis(), point));
|
.push((time.and_utc().timestamp_millis(), point));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_secondary_line_series_with_time(
|
pub(super) fn push_secondary_line_series_with_time(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &str,
|
name: &str,
|
||||||
point: f64,
|
point: f64,
|
||||||
@ -188,3 +195,60 @@ impl PlotData {
|
|||||||
self.line_series.is_empty() && self.secondary_line_series.is_empty()
|
self.line_series.is_empty() && self.secondary_line_series.is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "bench")]
|
||||||
|
mod benches {
|
||||||
|
use crate::app::graphs_window::plot::{
|
||||||
|
render_thread::{process_request, RenderRequest},
|
||||||
|
PlotData,
|
||||||
|
};
|
||||||
|
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
|
||||||
|
use divan::{counter::ItemsCount, Bencher};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use super::SUPERSAMPLE_FACTOR;
|
||||||
|
|
||||||
|
#[divan::bench]
|
||||||
|
fn render_plot(bencher: Bencher) {
|
||||||
|
let last_texture = &Mutex::new(None);
|
||||||
|
|
||||||
|
bencher
|
||||||
|
.with_inputs(sample_plot_data)
|
||||||
|
.input_counter(|_| ItemsCount::new(1usize))
|
||||||
|
.bench_values(|data| {
|
||||||
|
let request = RenderRequest {
|
||||||
|
title: "bench render".into(),
|
||||||
|
value_suffix: "%".into(),
|
||||||
|
secondary_value_suffix: "".into(),
|
||||||
|
y_label_area_relative_size: 1.0,
|
||||||
|
secondary_y_label_relative_area_size: 1.0,
|
||||||
|
data,
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
supersample_factor: SUPERSAMPLE_FACTOR,
|
||||||
|
time_period_seconds: 60,
|
||||||
|
};
|
||||||
|
|
||||||
|
process_request(request, last_texture)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_plot_data() -> PlotData {
|
||||||
|
let mut data = PlotData::default();
|
||||||
|
|
||||||
|
// Simulate 1 minute plot with 4 values per second
|
||||||
|
for sec in 0..60 {
|
||||||
|
for milli in [0, 250, 500, 750] {
|
||||||
|
let datetime = NaiveDateTime::new(
|
||||||
|
NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(),
|
||||||
|
NaiveTime::from_hms_milli_opt(0, 0, sec, milli).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
data.push_line_series_with_time("GPU", 100.0, datetime);
|
||||||
|
data.push_secondary_line_series_with_time("GPU Secondary", 10.0, datetime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -94,62 +94,15 @@ impl RenderThread {
|
|||||||
|
|
||||||
match current_request.take() {
|
match current_request.take() {
|
||||||
Some(Request::Render(render_request)) => {
|
Some(Request::Render(render_request)) => {
|
||||||
// Create a new ImageSurface for Cairo rendering.
|
process_request(render_request, last_texture);
|
||||||
let mut surface = ImageSurface::create(
|
}
|
||||||
cairo::Format::ARgb32,
|
|
||||||
(render_request.width * render_request.supersample_factor) as i32,
|
|
||||||
(render_request.height * render_request.supersample_factor) as i32,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let cairo_context = CairoContext::new(&surface).unwrap();
|
|
||||||
|
|
||||||
// Don't use Cairo's default antialiasing, it makes the lines look too blurry
|
|
||||||
// Supersampling is our 2D anti-aliasing solution.
|
|
||||||
if render_request.supersample_factor > 1 {
|
|
||||||
cairo_context.set_antialias(cairo::Antialias::None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let cairo_backend = CairoBackend::new(
|
|
||||||
&cairo_context,
|
|
||||||
// Supersample the rendering
|
|
||||||
(
|
|
||||||
render_request.width * render_request.supersample_factor,
|
|
||||||
render_request.height * render_request.supersample_factor,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if let Err(err) = render_request.draw(cairo_backend) {
|
|
||||||
error!("Failed to plot chart: {err:?}")
|
|
||||||
}
|
|
||||||
|
|
||||||
match (
|
|
||||||
surface.to_texture(),
|
|
||||||
last_texture.lock().unwrap().deref_mut(),
|
|
||||||
) {
|
|
||||||
// Successfully generated a new texture, but the old texture is also there
|
|
||||||
(Some(texture), Some(last_texture)) => {
|
|
||||||
*last_texture = texture;
|
|
||||||
}
|
|
||||||
// If texture conversion failed, keep the old texture if it's present.
|
|
||||||
(None, None) => {
|
|
||||||
error!("Failed to convert cairo surface to gdk texture, not overwriting old one");
|
|
||||||
}
|
|
||||||
// Update the last texture, if The old texture wasn't ever generated (None),
|
|
||||||
// No matter the result of conversion
|
|
||||||
(result, last_texture) => {
|
|
||||||
*last_texture = result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Terminate the thread if a Terminate request is received.
|
// Terminate the thread if a Terminate request is received.
|
||||||
Some(Request::Terminate) => break,
|
Some(Request::Terminate) => break,
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
state,
|
state,
|
||||||
@ -177,6 +130,60 @@ impl RenderThread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn process_request(
|
||||||
|
render_request: RenderRequest,
|
||||||
|
last_texture: &Mutex<Option<MemoryTexture>>,
|
||||||
|
) {
|
||||||
|
// Create a new ImageSurface for Cairo rendering.
|
||||||
|
let mut surface = ImageSurface::create(
|
||||||
|
cairo::Format::ARgb32,
|
||||||
|
(render_request.width * render_request.supersample_factor) as i32,
|
||||||
|
(render_request.height * render_request.supersample_factor) as i32,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let cairo_context = CairoContext::new(&surface).unwrap();
|
||||||
|
|
||||||
|
// Don't use Cairo's default antialiasing, it makes the lines look too blurry
|
||||||
|
// Supersampling is our 2D anti-aliasing solution.
|
||||||
|
if render_request.supersample_factor > 1 {
|
||||||
|
cairo_context.set_antialias(cairo::Antialias::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cairo_backend = CairoBackend::new(
|
||||||
|
&cairo_context,
|
||||||
|
// Supersample the rendering
|
||||||
|
(
|
||||||
|
render_request.width * render_request.supersample_factor,
|
||||||
|
render_request.height * render_request.supersample_factor,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Err(err) = render_request.draw(cairo_backend) {
|
||||||
|
error!("Failed to plot chart: {err:?}")
|
||||||
|
}
|
||||||
|
|
||||||
|
match (
|
||||||
|
surface.to_texture(),
|
||||||
|
last_texture.lock().unwrap().deref_mut(),
|
||||||
|
) {
|
||||||
|
// Successfully generated a new texture, but the old texture is also there
|
||||||
|
(Some(texture), Some(last_texture)) => {
|
||||||
|
*last_texture = texture;
|
||||||
|
}
|
||||||
|
// If texture conversion failed, keep the old texture if it's present.
|
||||||
|
(None, None) => {
|
||||||
|
error!("Failed to convert cairo surface to gdk texture, not overwriting old one");
|
||||||
|
}
|
||||||
|
// Update the last texture, if The old texture wasn't ever generated (None),
|
||||||
|
// No matter the result of conversion
|
||||||
|
(result, last_texture) => {
|
||||||
|
*last_texture = result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Implement the default constructor for RenderThread using the `new` method.
|
// Implement the default constructor for RenderThread using the `new` method.
|
||||||
impl Default for RenderThread {
|
impl Default for RenderThread {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
@ -336,12 +343,12 @@ impl RenderRequest {
|
|||||||
(current_date, segment.evaluate(current_date))
|
(current_date, segment.evaluate(current_date))
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
Palette99::pick(idx).stroke_width(8),
|
Palette99::pick(idx).stroke_width(2),
|
||||||
))
|
))
|
||||||
.context("Failed to draw series")?
|
.context("Failed to draw series")?
|
||||||
.label(caption)
|
.label(caption)
|
||||||
.legend(move |(x, y)| {
|
.legend(move |(x, y)| {
|
||||||
let offset = self.relative_size(0.04) as i32;
|
let offset = self.relative_size(0.02) as i32;
|
||||||
Rectangle::new(
|
Rectangle::new(
|
||||||
[(x - offset, y - offset), (x + offset, y + offset)],
|
[(x - offset, y - offset), (x + offset, y + offset)],
|
||||||
Palette99::pick(idx).filled(),
|
Palette99::pick(idx).filled(),
|
||||||
@ -360,12 +367,12 @@ impl RenderRequest {
|
|||||||
(current_date, segment.evaluate(current_date))
|
(current_date, segment.evaluate(current_date))
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
Palette99::pick(idx + 10).stroke_width(8),
|
Palette99::pick(idx + 10).stroke_width(2),
|
||||||
))
|
))
|
||||||
.context("Failed to draw series")?
|
.context("Failed to draw series")?
|
||||||
.label(caption)
|
.label(caption)
|
||||||
.legend(move |(x, y)| {
|
.legend(move |(x, y)| {
|
||||||
let offset = self.relative_size(0.04) as i32;
|
let offset = self.relative_size(0.02) as i32;
|
||||||
Rectangle::new(
|
Rectangle::new(
|
||||||
[(x - offset, y - offset), (x + offset, y + offset)],
|
[(x - offset, y - offset), (x + offset, y + offset)],
|
||||||
Palette99::pick(idx + 10).filled(),
|
Palette99::pick(idx + 10).filled(),
|
||||||
|
@ -13,3 +13,12 @@ lact-schema = { path = "../lact-schema", features = ["args"] }
|
|||||||
lact-cli = { path = "../lact-cli" }
|
lact-cli = { path = "../lact-cli" }
|
||||||
lact-gui = { path = "../lact-gui", optional = true }
|
lact-gui = { path = "../lact-gui", optional = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
divan = { workspace = true }
|
||||||
|
lact-daemon = { path = "../lact-daemon", features = ["bench"] }
|
||||||
|
lact-gui = { path = "../lact-gui", features = ["bench"] }
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "bench"
|
||||||
|
harness = false
|
||||||
|
7
lact/benches/bench.rs
Normal file
7
lact/benches/bench.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fn main() {
|
||||||
|
// Include crates in the binary
|
||||||
|
let _ = lact_daemon::run;
|
||||||
|
let _ = lact_gui::run;
|
||||||
|
|
||||||
|
divan::main();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user