Merge pull request #873 from qitechgmbh/872-add-cycle-target-time
872 add cycle target time
This commit is contained in:
4 files changed
+120
-63
No files matched your search
Generated
+30
-5
@@ -476,7 +476,7 @@ dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2688,6 +2688,7 @@ dependencies = [
|
||||
"serialport",
|
||||
"smol",
|
||||
"socketioxide",
|
||||
"spin_sleep",
|
||||
"textplots",
|
||||
"tokio",
|
||||
"tonic",
|
||||
@@ -2884,6 +2885,15 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
|
||||
|
||||
[[package]]
|
||||
name = "spin_sleep"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c07347b7c0301b9adba4350bdcf09c039d0e7160922050db0439b3c6723c8ab"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
@@ -3591,7 +3601,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
"windows-result",
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
@@ -3624,6 +3634,12 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.4.0"
|
||||
@@ -3641,7 +3657,7 @@ version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3650,7 +3666,7 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3659,7 +3675,7 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3693,6 +3709,15 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
|
||||
@@ -54,6 +54,7 @@ opentelemetry-otlp = { version = "0.30.0", features = [
|
||||
"grpc-tonic",
|
||||
], optional = true }
|
||||
tonic = { version = "0.13.1", optional = true }
|
||||
spin_sleep = "1.3.3"
|
||||
|
||||
[dev-dependencies]
|
||||
approx = "0.5.1"
|
||||
|
||||
+84
-53
@@ -1,20 +1,29 @@
|
||||
use crate::app_state::AppState;
|
||||
use bitvec::prelude::*;
|
||||
use control_core::machines::connection::MachineConnection;
|
||||
use control_core::machines::manager::MachineManager;
|
||||
use control_core::realtime::set_core_affinity;
|
||||
#[cfg(not(feature = "development-build"))]
|
||||
use control_core::realtime::set_realtime_priority;
|
||||
use std::sync::Arc;
|
||||
use smol::lock::RwLockReadGuard;
|
||||
use spin_sleep::SpinSleeper;
|
||||
use std::time::Instant;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tracing::{instrument, trace_span};
|
||||
|
||||
// 300 us loop cycle target
|
||||
pub fn start_loop_thread(
|
||||
app_state: Arc<AppState>,
|
||||
cycle_target: Duration,
|
||||
) -> Result<std::thread::JoinHandle<()>, std::io::Error> {
|
||||
// Start control loop
|
||||
let res = std::thread::Builder::new()
|
||||
.name("loop".to_owned())
|
||||
.spawn(move || {
|
||||
let sleeper =
|
||||
SpinSleeper::new(3_333_333) // frequency in Hz ~ 1 / 300µs, Basically specifies the accuracy of our sleep
|
||||
.with_spin_strategy(spin_sleep::SpinStrategy::YieldThread);
|
||||
|
||||
let _ = set_core_affinity(2);
|
||||
#[cfg(not(feature = "development-build"))]
|
||||
if let Err(e) = set_realtime_priority() {
|
||||
@@ -35,23 +44,15 @@ pub fn start_loop_thread(
|
||||
smol::block_on(async {
|
||||
rt.run(async {
|
||||
loop {
|
||||
if let Err(e) = loop_once(app_state.clone()).await {
|
||||
if let Err(e) = loop_once(app_state.clone(), &sleeper, cycle_target).await {
|
||||
tracing::error!("Loop failed\n{:?}", e);
|
||||
std::process::exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
});
|
||||
|
||||
/*
|
||||
// If timeouts keep happenning do a spin_sleep, better then thread_sleep and allows for more deterministic timing
|
||||
let elapsed = start.elapsed();
|
||||
if elapsed < target_cycle {
|
||||
spin_sleep::sleep(target_cycle - elapsed);
|
||||
}
|
||||
*/
|
||||
|
||||
if let Some(last_loop_start) = app_state
|
||||
.performance_metrics
|
||||
.read_arc_blocking()
|
||||
@@ -66,36 +67,21 @@ pub fn start_loop_thread(
|
||||
return res;
|
||||
}
|
||||
|
||||
#[instrument(skip(app_state))]
|
||||
pub async fn loop_once<'maindevice>(app_state: Arc<AppState>) -> Result<(), anyhow::Error> {
|
||||
// Record cycle start for performance metrics
|
||||
{
|
||||
let mut metrics = app_state.performance_metrics.write().await;
|
||||
metrics.cycle_start();
|
||||
}
|
||||
|
||||
let ethercat_setup_guard = app_state.ethercat_setup.read().await;
|
||||
|
||||
pub async fn copy_ethercat_inputs(app_state: Arc<AppState>) -> Result<(), anyhow::Error> {
|
||||
//println!("copy_ethercat_intputs");
|
||||
// only if we have an ethercat setup
|
||||
// - tx/rx cycle
|
||||
// - copy inputs to devices
|
||||
let ethercat_setup_guard = app_state.ethercat_setup.read().await;
|
||||
//tracing::info!("EtherCAT setup available: {}", ethercat_setup.is_some());
|
||||
if let Some(ethercat_setup) = ethercat_setup_guard.as_ref() {
|
||||
let span = trace_span!("loop_once_inputs");
|
||||
let _enter = span.enter();
|
||||
|
||||
// Measure tx_rx performance
|
||||
let txrx_start = Instant::now();
|
||||
ethercat_setup
|
||||
.group
|
||||
.tx_rx(ðercat_setup.maindevice)
|
||||
.await?;
|
||||
let txrx_duration = txrx_start.elapsed();
|
||||
|
||||
// Record tx_rx performance metrics
|
||||
{
|
||||
let mut metrics = app_state.performance_metrics.write().await;
|
||||
metrics.record_txrx_time(txrx_duration);
|
||||
}
|
||||
|
||||
// copy inputs to devices
|
||||
for (i, subdevice) in ethercat_setup
|
||||
@@ -137,32 +123,14 @@ pub async fn loop_once<'maindevice>(app_state: Arc<AppState>) -> Result<(), anyh
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// execute machines
|
||||
{
|
||||
let span = trace_span!("loop_once_act");
|
||||
let _enter = span.enter();
|
||||
|
||||
let machine_guard = app_state.machines.read().await;
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
for machine in machine_guard.iter() {
|
||||
let connection = &machine.1.lock_blocking().machine_connection;
|
||||
if let MachineConnection::Connected(machine) = connection {
|
||||
// if the machine is currenlty locked (likely processing API call)
|
||||
// we skip the machine
|
||||
if let Some(mut machine_guard) = machine.try_lock() {
|
||||
let span = trace_span!("loop_once_act_machine",);
|
||||
let _enter = span.enter();
|
||||
// execute machine
|
||||
machine_guard.act(now);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn copy_ethercat_outputs(app_state: Arc<AppState>) -> Result<(), anyhow::Error> {
|
||||
// only if we have an ethercat setup
|
||||
// - copy outputs from devices
|
||||
let ethercat_setup_guard = app_state.ethercat_setup.read().await;
|
||||
|
||||
if let Some(ethercat_setup) = ethercat_setup_guard.as_ref() {
|
||||
let span = trace_span!("loop_once_outputs");
|
||||
let _enter = span.enter();
|
||||
@@ -207,6 +175,69 @@ pub async fn loop_once<'maindevice>(app_state: Arc<AppState>) -> Result<(), anyh
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn execute_machines(machine_guard: &RwLockReadGuard<'_, MachineManager>) {
|
||||
let now = std::time::Instant::now();
|
||||
for machine in machine_guard.iter() {
|
||||
let connection = &machine.1.lock_blocking().machine_connection;
|
||||
if let MachineConnection::Connected(machine) = connection {
|
||||
// if the machine is currenlty locked (likely processing API call)
|
||||
// we skip the machine
|
||||
if let Some(mut machine_guard) = machine.try_lock() {
|
||||
let span = trace_span!("loop_once_act_machine",);
|
||||
let _enter = span.enter();
|
||||
// execute machine
|
||||
machine_guard.act(now);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(app_state))]
|
||||
pub async fn loop_once<'maindevice>(
|
||||
app_state: Arc<AppState>,
|
||||
sleeper: &SpinSleeper,
|
||||
cycle_target: Duration,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let loop_once_start = std::time::Instant::now();
|
||||
// Record cycle start for performance metrics
|
||||
{
|
||||
let mut metrics = app_state.performance_metrics.write().await;
|
||||
metrics.cycle_start();
|
||||
}
|
||||
{
|
||||
let txrx_start = Instant::now();
|
||||
copy_ethercat_inputs(app_state.clone()).await?;
|
||||
let txrx_duration = txrx_start.elapsed();
|
||||
let mut metrics = app_state.performance_metrics.write().await;
|
||||
metrics.record_txrx_time(txrx_duration);
|
||||
}
|
||||
|
||||
// execute machines
|
||||
{
|
||||
let span = trace_span!("loop_once_act");
|
||||
let _enter = span.enter();
|
||||
let machine_guard = app_state.machines.read().await;
|
||||
execute_machines(&machine_guard).await;
|
||||
}
|
||||
|
||||
{
|
||||
copy_ethercat_outputs(app_state.clone()).await?;
|
||||
}
|
||||
|
||||
if app_state.ethercat_setup.read().await.is_some() {
|
||||
// spin_sleep so we have a cycle time of ~300us
|
||||
// This does push usage to 100% if completely busy, but provides much better accuracy then thread sleep or async sleep
|
||||
sleeper.sleep_until(loop_once_start + cycle_target);
|
||||
} else {
|
||||
// if we dont have an ethercat setup or other rt relevant stuff do the "worse" async sleep or later if we get rid of async thread::sleep or yielding
|
||||
// We do this, so that when no rt relevant code runs the cpu doesnt spin at 100% for no reason
|
||||
let loop_duration = loop_once_start.elapsed();
|
||||
if cycle_target > loop_once_start.elapsed() {
|
||||
smol::Timer::after(cycle_target - loop_duration).await;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
+5
-5
@@ -5,17 +5,18 @@ use mock::init::init_mock;
|
||||
#[cfg(feature = "mock-machine")]
|
||||
pub mod mock;
|
||||
|
||||
use crate::{ethercat::setup::setup_loop, panic::init_panic_handling};
|
||||
use app_state::AppState;
|
||||
use ethercat::{
|
||||
ethercat_discovery_info::{send_ethercat_discovering, send_ethercat_found},
|
||||
init::start_interface_discovery,
|
||||
setup::setup_loop,
|
||||
};
|
||||
use r#loop::start_loop_thread;
|
||||
use panic::init_panic_handling;
|
||||
use rest::init::start_api_thread;
|
||||
use serial::init::{handle_serial_device_hotplug, start_serial_discovery};
|
||||
use socketio::queue::start_socketio_queue;
|
||||
use std::sync::Arc;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
pub mod app_state;
|
||||
pub mod ethercat;
|
||||
pub mod logging;
|
||||
@@ -31,20 +32,19 @@ fn main() {
|
||||
logging::init_tracing();
|
||||
tracing::info!("Tracing initialized successfully");
|
||||
init_panic_handling();
|
||||
|
||||
const CYCLE_TARGET_TIME: Duration = Duration::from_micros(300);
|
||||
let app_state = Arc::new(AppState::new());
|
||||
|
||||
#[cfg(feature = "mock-machine")]
|
||||
init_mock(app_state.clone()).expect("Failed to initialize mock machines");
|
||||
|
||||
let _ = start_api_thread(app_state.clone());
|
||||
let _ = start_loop_thread(app_state.clone());
|
||||
let mut socketio_fut = start_socketio_queue(app_state.clone()).fuse();
|
||||
let mut ethercat_fut = start_interface_discovery().fuse();
|
||||
let mut serial_fut = start_serial_discovery().fuse();
|
||||
let _loop_thread = start_loop_thread(app_state.clone(), CYCLE_TARGET_TIME);
|
||||
|
||||
smol::block_on(async { send_ethercat_discovering(app_state.clone()).await });
|
||||
|
||||
smol::block_on(async {
|
||||
loop {
|
||||
// lets the async runtime decide which future to run next
|
||||
|
||||
Reference in new issue
Block a user