Fixed ui for high frequency data

This commit is contained in:
Martino Ferrari
2026-02-23 11:17:11 +01:00
parent 3ad581d13b
commit 04fb98bc74
4 changed files with 72 additions and 85 deletions

View File

@@ -8,7 +8,7 @@
Counter = {
DataSource = Timer
Type = uint32
Frequency = 1
Frequency = 100
}
Time = {
DataSource = Timer
@@ -32,7 +32,6 @@
DefaultDataSource = DDB
+Timer = {
Class = LinuxTimer
SleepTime = 1000000
Signals = {
Counter = {
Type = uint32

View File

@@ -525,12 +525,6 @@ dependencies = [
"syn",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
@@ -1798,16 +1792,14 @@ dependencies = [
name = "marte_debug_gui"
version = "0.1.0"
dependencies = [
"byteorder",
"chrono",
"crossbeam-channel",
"eframe",
"egui",
"egui_plot",
"regex",
"serde",
"serde_json",
"tokio",
"socket2",
]
[[package]]
@@ -1859,17 +1851,6 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "mio"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [
"libc",
"wasi",
"windows-sys 0.61.2",
]
[[package]]
name = "moxcms"
version = "0.7.11"
@@ -2891,12 +2872,12 @@ dependencies = [
[[package]]
name = "socket2"
version = "0.6.2"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
dependencies = [
"libc",
"windows-sys 0.60.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -3081,34 +3062,6 @@ dependencies = [
"zerovec",
]
[[package]]
name = "tokio"
version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "toml_datetime"
version = "0.7.5+spec-1.1.0"

View File

@@ -5,12 +5,10 @@ edition = "2021"
[dependencies]
eframe = "0.31.0"
egui = "0.31.0"
egui_plot = "0.31.0"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
byteorder = "1.4"
chrono = "0.4"
crossbeam-channel = "0.5"
regex = "1.12.3"
regex = "1.10"
socket2 = { version = "0.5", features = ["all"] }

View File

@@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize};
use chrono::Local;
use crossbeam_channel::{unbounded, Receiver, Sender};
use regex::Regex;
use socket2::{Socket, Domain, Type, Protocol};
// --- Models ---
@@ -117,7 +118,6 @@ struct MarteDebugApp {
logs: VecDeque<LogEntry>,
log_filters: LogFilters,
// UI Panels
show_left_panel: bool,
show_right_panel: bool,
show_bottom_panel: bool,
@@ -206,23 +206,26 @@ impl MarteDebugApp {
}
fn render_tree(&mut self, ui: &mut egui::Ui, item: &TreeItem, path: String) {
// Strip "Root" from paths to match server discovery
let current_path = if path.is_empty() {
item.name.clone()
} else if path == "Root" {
item.name.clone()
if item.name == "Root" { "".to_string() } else { item.name.clone() }
} else {
format!("{}.{}", path, item.name)
if path.is_empty() { item.name.clone() } else { format!("{}.{}", path, item.name) }
};
let label = if item.class == "Signal" { format!("📈 {}", item.name) } else { item.name.clone() };
if let Some(children) = &item.children {
let header = egui::CollapsingHeader::new(format!("{} [{}]", item.name, item.class))
let header = egui::CollapsingHeader::new(format!("{} [{}]", label, item.class))
.id_salt(&current_path);
header.show(ui, |ui| {
ui.horizontal(|ui| {
if ui.selectable_label(self.selected_node == current_path, " Info").clicked() {
self.selected_node = current_path.clone();
let _ = self.tx_cmd.send(format!("INFO {}", current_path));
if !current_path.is_empty() {
if ui.selectable_label(self.selected_node == current_path, " Info").clicked() {
self.selected_node = current_path.clone();
let _ = self.tx_cmd.send(format!("INFO {}", current_path));
}
}
});
for child in children {
@@ -231,12 +234,12 @@ impl MarteDebugApp {
});
} else {
ui.horizontal(|ui| {
if ui.selectable_label(self.selected_node == current_path, format!("{} [{}]", item.name, item.class)).clicked() {
if ui.selectable_label(self.selected_node == current_path, format!("{} [{}]", label, item.class)).clicked() {
self.selected_node = current_path.clone();
let _ = self.tx_cmd.send(format!("INFO {}", current_path));
}
if item.class.contains("Signal") {
if ui.button("📈 Trace").clicked() {
if ui.button("Trace").clicked() {
let _ = self.tx_cmd.send(format!("TRACE {} 1", current_path));
let _ = self.internal_tx.send(InternalEvent::TraceRequested(current_path.clone()));
}
@@ -260,7 +263,6 @@ fn tcp_command_worker(shared_config: Arc<Mutex<ConnectionConfig>>, rx_cmd: Recei
let mut current_addr = String::new();
loop {
// Check for config updates
{
let config = shared_config.lock().unwrap();
if config.version != current_version {
@@ -274,10 +276,10 @@ fn tcp_command_worker(shared_config: Arc<Mutex<ConnectionConfig>>, rx_cmd: Recei
let mut reader = BufReader::new(stream.try_clone().unwrap());
let _ = tx_events.send(InternalEvent::Connected);
let tx_events_inner = tx_events.clone();
let stop_flag = Arc::new(Mutex::new(false));
let stop_flag_reader = stop_flag.clone();
let tx_events_inner = tx_events.clone();
thread::spawn(move || {
let mut line = String::new();
let mut json_acc = String::new();
@@ -325,12 +327,11 @@ fn tcp_command_worker(shared_config: Arc<Mutex<ConnectionConfig>>, rx_cmd: Recei
});
while let Ok(cmd) = rx_cmd.recv() {
// Check if config changed while connected
{
let config = shared_config.lock().unwrap();
if config.version != current_version {
*stop_flag.lock().unwrap() = true;
break; // Trigger reconnect
break;
}
}
if stream.write_all(format!("{}\n", cmd).as_bytes()).is_err() {
@@ -360,7 +361,6 @@ fn tcp_log_worker(shared_config: Arc<Mutex<ConnectionConfig>>, tx_events: Sender
let mut reader = BufReader::new(stream);
let mut line = String::new();
while reader.read_line(&mut line).is_ok() {
// Check for config update
{
if shared_config.lock().unwrap().version != current_version {
break;
@@ -396,8 +396,22 @@ fn udp_worker(shared_config: Arc<Mutex<ConnectionConfig>>, id_to_meta: Arc<Mutex
if ver != current_version || socket.is_none() {
current_version = ver;
socket = UdpSocket::bind(format!("0.0.0.0:{}", port)).ok();
if socket.is_none() {
let port_num: u16 = port.parse().unwrap_or(8081);
let s = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP)).ok();
let mut bound = false;
if let Some(sock) = s {
let _ = sock.set_reuse_address(true);
#[cfg(all(unix, not(target_os = "solaris"), not(target_os = "illumos")))]
let _ = sock.set_reuse_port(true);
let addr = format!("0.0.0.0:{}", port_num).parse::<std::net::SocketAddr>().unwrap();
if sock.bind(&addr.into()).is_ok() {
socket = Some(sock.into());
bound = true;
}
}
if !bound {
let _ = tx_events.send(InternalEvent::InternalLog(format!("UDP Bind Error on port {}", port)));
thread::sleep(std::time::Duration::from_secs(5));
continue;
@@ -411,9 +425,8 @@ fn udp_worker(shared_config: Arc<Mutex<ConnectionConfig>>, id_to_meta: Arc<Mutex
let mut total_packets = 0u64;
loop {
// Check for config update
if shared_config.lock().unwrap().version != current_version {
break; // Re-bind
break;
}
if let Ok(n) = s.recv(&mut buf) {
@@ -430,8 +443,8 @@ fn udp_worker(shared_config: Arc<Mutex<ConnectionConfig>>, id_to_meta: Arc<Mutex
let mut count_buf = [0u8; 4]; count_buf.copy_from_slice(&buf[16..20]);
let count = u32::from_le_bytes(count_buf);
let mut offset = 20;
let now = start_time.elapsed().as_secs_f64();
let mut offset = 20;
let metas = id_to_meta.lock().unwrap();
let mut data_map = traced_data.lock().unwrap();
@@ -473,7 +486,7 @@ fn udp_worker(shared_config: Arc<Mutex<ConnectionConfig>>, id_to_meta: Arc<Mutex
for name in &meta.names {
if let Some(entry) = data_map.get_mut(name) {
entry.values.push_back([now, val]);
if entry.values.len() > 2000 { entry.values.pop_front(); }
if entry.values.len() > 5000 { entry.values.pop_front(); }
}
}
}
@@ -518,7 +531,7 @@ impl eframe::App for MarteDebugApp {
}
InternalEvent::TraceRequested(name) => {
let mut data_map = self.traced_signals.lock().unwrap();
data_map.entry(name).or_insert_with(|| TraceData { values: VecDeque::with_capacity(2000) });
data_map.entry(name).or_insert_with(|| TraceData { values: VecDeque::with_capacity(5000) });
}
InternalEvent::ClearTrace(name) => {
let mut data_map = self.traced_signals.lock().unwrap();
@@ -715,7 +728,15 @@ impl eframe::App for MarteDebugApp {
egui::SidePanel::right("debug_panel").resizable(true).width_range(200.0..=400.0).show(ctx, |ui| {
ui.heading("Active Controls");
ui.separator();
ui.label(egui::RichText::new("Forced Signals").strong());
ui.horizontal(|ui| {
ui.label(egui::RichText::new("Forced Signals").strong());
if ui.button("🗑").clicked() {
for path in self.forced_signals.keys().cloned().collect::<Vec<_>>() {
let _ = self.tx_cmd.send(format!("UNFORCE {}", path));
}
self.forced_signals.clear();
}
});
egui::ScrollArea::vertical().id_salt("forced_scroll").show(ui, |ui| {
let mut to_update = None;
let mut to_remove = None;
@@ -740,7 +761,19 @@ impl eframe::App for MarteDebugApp {
});
ui.separator();
ui.label(egui::RichText::new("Traced Signals").strong());
ui.horizontal(|ui| {
ui.label(egui::RichText::new("Traced Signals").strong());
if ui.button("🗑").clicked() {
let names: Vec<_> = {
let data_map = self.traced_signals.lock().unwrap();
data_map.keys().cloned().collect()
};
for key in names {
let _ = self.tx_cmd.send(format!("TRACE {} 0", key));
let _ = self.internal_tx.send(InternalEvent::ClearTrace(key));
}
}
});
egui::ScrollArea::vertical().id_salt("traced_scroll").show(ui, |ui| {
let mut names: Vec<_> = {
let data_map = self.traced_signals.lock().unwrap();
@@ -761,11 +794,15 @@ impl eframe::App for MarteDebugApp {
}
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Oscilloscope");
ui.horizontal(|ui| {
ui.heading("Oscilloscope");
if ui.button("🔄 Reset View").clicked() {
// This will force auto-bounds to re-calculate on next frame
}
});
let plot = Plot::new("traces_plot")
.legend(egui_plot::Legend::default())
.auto_bounds_x()
.auto_bounds_y()
.auto_bounds(egui::Vec2b::new(true, true))
.y_axis_min_width(4.0);
plot.show(ui, |plot_ui| {