pipecircuit 0.0.12

Correct. Robust. Fast. Portable.
Documentation
use core::fmt;

#[cfg(feature = "alloc")]
mod attribution;
mod counts;

#[cfg(feature = "alloc")]
pub use attribution::{
    AttributingFlowObserver, AttributingFlowObserverCreationError, FlowAttribution,
    FlowAttributionFamily, FlowAttributionPhase,
};
pub use counts::{CountingFlowObserver, ExecutionCounts, TransitionCounts};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use = "flow observations must be recorded or explicitly discarded"]
pub enum FlowObservation {
    // the static str design is a holdover. Long-term we want better type-safe visibility for the consumer
    CircuitTurnBegan {
        circuit: &'static str,
        input: &'static str,
        input_representation_bytes: usize,
    },
    CircuitTurnCompleted {
        circuit: &'static str,
        output: &'static str,
        output_representation_bytes: usize,
    },
    PipeSendBegan {
        participant: &'static str,
        input: &'static str,
        transport: &'static str,
        input_representation_bytes: usize,
    },
    PipeSendCompleted {
        participant: &'static str,
        transport: &'static str,
        outcome: &'static str,
        output_representation_bytes: usize,
    },
    ParticipantStepBegan {
        participant: &'static str,
        input: &'static str,
        input_representation_bytes: usize,
    },
    ParticipantStepCompleted {
        participant: &'static str,
        outcome: &'static str,
        output_representation_bytes: usize,
    },
    SwitchboardRouteBegan {
        switchboard: &'static str,
        input: &'static str,
        input_representation_bytes: usize,
    },
    SwitchboardRouteCompleted {
        switchboard: &'static str,
        output: &'static str,
        output_representation_bytes: usize,
    },
}

pub trait FlowObserver {
    fn observe(&mut self, observation: FlowObservation);
}

impl<Observer: FlowObserver + ?Sized> FlowObserver for &mut Observer {
    #[inline(always)]
    fn observe(&mut self, observation: FlowObservation) {
        (**self).observe(observation);
    }
}

impl<First: FlowObserver, Second: FlowObserver> FlowObserver for (First, Second) {
    fn observe(&mut self, observation: FlowObservation) {
        self.0.observe(observation);
        self.1.observe(observation);
    }
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct NoopFlowObserver;

impl FlowObserver for NoopFlowObserver {
    #[inline(always)]
    fn observe(&mut self, _observation: FlowObservation) {}
}

impl fmt::Display for FlowObservation {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::CircuitTurnBegan {
                circuit,
                input,
                input_representation_bytes,
            } => write!(
                formatter,
                "circuit-turn-began circuit={circuit} input={input} input-representation-bytes={input_representation_bytes}"
            ),
            Self::CircuitTurnCompleted {
                circuit,
                output,
                output_representation_bytes,
            } => write!(
                formatter,
                "circuit-turn-completed circuit={circuit} output={output} output-representation-bytes={output_representation_bytes}"
            ),
            Self::PipeSendBegan {
                participant,
                input,
                transport,
                input_representation_bytes,
            } => write!(
                formatter,
                "pipe-send-began participant={participant} input={input} transport={transport} input-representation-bytes={input_representation_bytes}"
            ),
            Self::PipeSendCompleted {
                participant,
                transport,
                outcome,
                output_representation_bytes,
            } => write!(
                formatter,
                "pipe-send-completed participant={participant} transport={transport} outcome={outcome} output-representation-bytes={output_representation_bytes}"
            ),
            Self::ParticipantStepBegan {
                participant,
                input,
                input_representation_bytes,
            } => write!(
                formatter,
                "participant-step-began participant={participant} input={input} input-representation-bytes={input_representation_bytes}"
            ),
            Self::ParticipantStepCompleted {
                participant,
                outcome,
                output_representation_bytes,
            } => write!(
                formatter,
                "participant-step-completed participant={participant} outcome={outcome} output-representation-bytes={output_representation_bytes}"
            ),
            Self::SwitchboardRouteBegan {
                switchboard,
                input,
                input_representation_bytes,
            } => write!(
                formatter,
                "switchboard-route-began switchboard={switchboard} input={input} input-representation-bytes={input_representation_bytes}"
            ),
            Self::SwitchboardRouteCompleted {
                switchboard,
                output,
                output_representation_bytes,
            } => write!(
                formatter,
                "switchboard-route-completed switchboard={switchboard} output={output} output-representation-bytes={output_representation_bytes}"
            ),
        }
    }
}

#[cfg(test)]
mod tests {
    extern crate alloc;

    use alloc::string::ToString as _;
    use core::mem::size_of;

    use super::*;
    use crate::PipeFlow;

    #[test]
    fn every_observation_has_a_flat_display_form() {
        let observations = [
            FlowObservation::CircuitTurnBegan {
                circuit: "circuit",
                input: "input",
                input_representation_bytes: 1,
            },
            FlowObservation::CircuitTurnCompleted {
                circuit: "circuit",
                output: "output",
                output_representation_bytes: 2,
            },
            FlowObservation::PipeSendBegan {
                participant: "machine",
                input: "input",
                transport: "inline",
                input_representation_bytes: 3,
            },
            FlowObservation::PipeSendCompleted {
                participant: "machine",
                transport: "inline",
                outcome: "output",
                output_representation_bytes: 4,
            },
            FlowObservation::ParticipantStepBegan {
                participant: "machine",
                input: "input",
                input_representation_bytes: 5,
            },
            FlowObservation::ParticipantStepCompleted {
                participant: "machine",
                outcome: "output",
                output_representation_bytes: 6,
            },
            FlowObservation::SwitchboardRouteBegan {
                switchboard: "switchboard",
                input: "input",
                input_representation_bytes: 7,
            },
            FlowObservation::SwitchboardRouteCompleted {
                switchboard: "switchboard",
                output: "output",
                output_representation_bytes: 8,
            },
        ];
        let displayed = observations.map(|observation| observation.to_string());

        assert_eq!(
            [
                "circuit-turn-began circuit=circuit input=input input-representation-bytes=1",
                "circuit-turn-completed circuit=circuit output=output output-representation-bytes=2",
                "pipe-send-began participant=machine input=input transport=inline input-representation-bytes=3",
                "pipe-send-completed participant=machine transport=inline outcome=output output-representation-bytes=4",
                "participant-step-began participant=machine input=input input-representation-bytes=5",
                "participant-step-completed participant=machine outcome=output output-representation-bytes=6",
                "switchboard-route-began switchboard=switchboard input=input input-representation-bytes=7",
                "switchboard-route-completed switchboard=switchboard output=output output-representation-bytes=8",
            ],
            displayed
        );
    }

    #[test]
    fn borrowed_and_noop_observers_accept_observations() {
        let observation = FlowObservation::ParticipantStepCompleted {
            participant: "machine",
            outcome: "output",
            output_representation_bytes: 0,
        };
        let mut noop = NoopFlowObserver;
        let borrowed = &mut noop;

        borrowed.observe(observation);
    }

    #[test]
    fn observer_pairs_receive_the_same_observation() {
        let observation = FlowObservation::ParticipantStepCompleted {
            participant: "machine",
            outcome: "output",
            output_representation_bytes: 7,
        };
        let mut observers = (CountingFlowObserver::new(), CountingFlowObserver::new());

        observers.observe(observation);

        assert_eq!(observers.0.counts(), observers.1.counts());
    }

    #[test]
    fn noop_flow_observation_carries_no_runtime_state() {
        assert_eq!(size_of::<NoopFlowObserver>(), 0);
        assert_eq!(size_of::<PipeFlow<NoopFlowObserver>>(), 0);
    }
}