nodo 0.18.5

A realtime framework for robotics
Documentation
// Copyright 2023 David Weikersdorfer

use crate::{codelet::TransitionMap, prelude::Pubtime};
use core::time::Duration;
use serde::{Deserialize, Serialize};

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Statistics {
    /// Node lifetime statistics
    pub transitions: TransitionMap<TransitionStatistics>,

    /// Number of messages currently available for each RX channel
    pub rx_available_messages_count: Vec<usize>,

    /// Total number of messages published on TX channel (since start)
    pub tx_published_message_count: Vec<usize>,

    /// Last time a message was published on this channel
    pub tx_last_pubtime: Vec<Option<Pubtime>>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct TransitionStatistics {
    pub duration: CountTotal,
    pub period: CountTotal,
    pub skipped_count: u64,

    #[serde(skip)]
    last_exec_begin: Option<Pubtime>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct CountTotal {
    count: u64,
    total: Duration,
    limits: (Duration, Duration),
}

impl Statistics {
    pub fn new() -> Self {
        Default::default()
    }

    /// Copies other statistics into this statistics creating a clone but potentially reusing
    /// existing memory allocation.
    pub fn copy_from(&mut self, other: &Statistics) {
        self.transitions = other.transitions.clone(); // just an array

        fn overwrite_vec<T: Copy>(src: &[T], dst: &mut Vec<T>) {
            if src.len() == dst.len() {
                dst.copy_from_slice(src);
            } else {
                dst.clear();
                dst.extend(src.iter());
            }
        }

        overwrite_vec(
            &other.rx_available_messages_count,
            &mut self.rx_available_messages_count,
        );

        overwrite_vec(
            &other.tx_published_message_count,
            &mut self.tx_published_message_count,
        );

        overwrite_vec(&other.tx_last_pubtime, &mut self.tx_last_pubtime);
    }
}

impl TransitionStatistics {
    pub fn new() -> Self {
        Self {
            duration: CountTotal::default(),
            period: CountTotal::default(),
            skipped_count: 0,
            last_exec_begin: None,
        }
    }

    /// Percentage of steps which were skipped
    pub fn skip_percent(&self) -> f32 {
        let total = self.skipped_count + self.duration.count;
        if total == 0 {
            0.
        } else {
            self.skipped_count as f32 / total as f32
        }
    }

    pub fn begin(&mut self, now: Pubtime) {
        if let Some(last_exec) = self.last_exec_begin {
            self.period.push(*now - *last_exec);
        }

        self.last_exec_begin = Some(now);
    }

    pub fn end(&mut self, now: Pubtime, skipped: bool) {
        if skipped {
            self.skipped_count += 1;
        } else {
            self.duration.push(
                *now - *self
                    .last_exec_begin
                    .expect("end() must be called after begin()"),
            );
        }
    }
}

impl CountTotal {
    pub fn push(&mut self, dt: Duration) {
        self.count += 1;
        self.total += dt;
        self.limits = if self.count == 1 {
            (dt, dt)
        } else {
            (self.limits.0.min(dt), self.limits.1.max(dt))
        };
    }

    pub fn count(&self) -> u64 {
        self.count
    }

    pub fn total(&self) -> Duration {
        self.total
    }

    pub fn average_ms(&self) -> Option<f32> {
        if self.count <= 0 {
            None
        } else {
            Some(self.total.as_secs_f32() * 1000.0 / (self.count as f32))
        }
    }

    pub fn min_ms(&self) -> Option<f32> {
        if self.count <= 0 {
            None
        } else {
            Some(self.limits.0.as_secs_f32() * 1000.0)
        }
    }

    pub fn max_ms(&self) -> Option<f32> {
        if self.count <= 0 {
            None
        } else {
            Some(self.limits.1.as_secs_f32() * 1000.0)
        }
    }
}