use zenoh_protocol::{
core::WhatAmI,
network::timestamp_stack::{interception_point, Interception, TsStackType},
};
use crate::{net::runtime::IRuntime, session::ZenohId};
#[non_exhaustive]
#[zenoh_macros::unstable]
pub struct TimestampContext {
pub zid: ZenohId,
pub whatami: WhatAmI,
}
#[zenoh_macros::unstable]
pub(crate) type GetTimestampCallback = Box<dyn Fn(TimestampContext) -> Vec<u8> + Send + Sync>;
#[zenoh_macros::unstable]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InterceptionPoint {
Send,
Route,
Receive,
}
impl TryFrom<u8> for InterceptionPoint {
type Error = zenoh_result::Error;
fn try_from(value: u8) -> zenoh_result::ZResult<Self> {
match value & !interception_point::IS_CUSTOM_TS {
interception_point::SEND => Ok(Self::Send),
interception_point::RECEIVE => Ok(Self::Receive),
interception_point::ROUTE => Ok(Self::Route),
_ => bail!("Unknown interception point ID '{value}'"),
}
}
}
impl From<InterceptionPoint> for u8 {
fn from(value: InterceptionPoint) -> Self {
match value {
InterceptionPoint::Send => interception_point::SEND,
InterceptionPoint::Route => interception_point::ROUTE,
InterceptionPoint::Receive => interception_point::RECEIVE,
}
}
}
#[zenoh_macros::unstable]
#[derive(Debug, Default, Clone, Copy)]
pub struct TimestampInstrumentationBuilder {
conf_flags: u8,
}
#[zenoh_macros::unstable]
impl TimestampInstrumentationBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn set_send(self, enabled: bool) -> Self {
Self {
conf_flags: if enabled {
self.conf_flags | interception_point::SEND
} else {
self.conf_flags & !interception_point::SEND
},
}
}
pub fn set_route(self, enabled: bool) -> Self {
Self {
conf_flags: if enabled {
self.conf_flags | interception_point::ROUTE
} else {
self.conf_flags & !interception_point::ROUTE
},
}
}
pub fn set_receive(self, enabled: bool) -> Self {
Self {
conf_flags: if enabled {
self.conf_flags | interception_point::RECEIVE
} else {
self.conf_flags & !interception_point::RECEIVE
},
}
}
pub fn build(self) -> zenoh_result::ZResult<TimestampInstrumentation> {
if self.conf_flags == 0 {
bail!("Invalid instrumentation config: at least one point must be active");
}
Ok(TimestampInstrumentation {
conf_flags: self.conf_flags,
})
}
}
#[zenoh_macros::unstable]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct TimestampInstrumentation {
conf_flags: u8,
}
#[zenoh_macros::unstable]
impl TimestampInstrumentation {
#[zenoh_macros::unstable]
pub fn is_instrumented(&self, point: InterceptionPoint) -> bool {
self.conf_flags & u8::from(point) != 0
}
pub(crate) fn conf_flags(&self) -> u8 {
self.conf_flags
}
pub(crate) fn try_from_flags(conf_flags: u8) -> zenoh_result::ZResult<Self> {
if conf_flags == 0 {
bail!("invalid instrumentation flags: at least one point must be active");
}
Ok(Self { conf_flags })
}
}
#[zenoh_macros::unstable]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InstrumentationTimestamp {
UHLC(uhlc::Timestamp),
Custom(Vec<u8>),
}
#[zenoh_macros::unstable]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TimestampStackRecord {
point: InterceptionPoint,
timestamp: InstrumentationTimestamp,
}
impl TimestampStackRecord {
#[zenoh_macros::unstable]
pub fn point(&self) -> InterceptionPoint {
self.point
}
#[zenoh_macros::unstable]
pub fn is_custom(&self) -> bool {
matches!(self.timestamp, InstrumentationTimestamp::Custom(_))
}
#[zenoh_macros::unstable]
pub fn timestamp(&self) -> &InstrumentationTimestamp {
&self.timestamp
}
}
#[zenoh_macros::unstable]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TimestampStack {
instrumentation: TimestampInstrumentation,
records: Vec<TimestampStackRecord>,
}
#[zenoh_macros::unstable]
impl TimestampStack {
pub fn instrumentation(&self) -> TimestampInstrumentation {
self.instrumentation
}
pub fn records(&self) -> &[TimestampStackRecord] {
&self.records
}
pub(crate) fn new(instrumentation: TimestampInstrumentation) -> Self {
Self {
instrumentation,
records: Vec::new(),
}
}
}
#[cfg(feature = "unstable")]
pub(crate) fn push_ts_interception<const ID: u8, T: IRuntime + ?Sized, R, F>(
ext_ts_stack: &mut Option<TsStackType<ID>>,
get_runtime: F,
point: u8,
) where
R: std::ops::Deref<Target = T>,
F: FnOnce() -> Option<R>,
{
let Some(ts_stack) = ext_ts_stack else {
return;
};
if ts_stack.ts_stack.conf_flags & point != 0 {
let Some(runtime) = get_runtime() else {
return;
};
let context = TimestampContext {
zid: runtime.zid(),
whatami: runtime.whatami(),
};
let (timestamp, is_custom) = runtime.get_ts_stack_timestamp(context);
if timestamp.is_empty() {
return;
}
if ts_stack.ts_stack.stack.len() >= zenoh_protocol::network::timestamp_stack::MAX_STACK_SIZE
{
return;
}
ts_stack.ts_stack.stack.push(Interception {
flags: point
| if is_custom {
interception_point::IS_CUSTOM_TS
} else {
0
},
timestamp,
});
}
}
#[cfg(feature = "unstable")]
impl TryFrom<&zenoh_protocol::network::timestamp_stack::TimestampStack> for TimestampStack {
type Error = zenoh_result::Error;
fn try_from(
ts: &zenoh_protocol::network::timestamp_stack::TimestampStack,
) -> zenoh_result::ZResult<Self> {
let mut instance = Self {
instrumentation: TimestampInstrumentation::try_from_flags(ts.conf_flags)?,
records: Vec::new(),
};
for record in &ts.stack {
let point: InterceptionPoint = match record.flags.try_into() {
Ok(p) => p,
Err(_) => {
tracing::warn!(
"Skipping instrumentation measurement with unknown or malformed instrumentation flags '{:b}'",
record.flags
);
continue;
}
};
let is_custom = (record.flags & interception_point::IS_CUSTOM_TS) != 0;
let timestamp = match is_custom {
true => InstrumentationTimestamp::Custom(record.timestamp.clone()),
false => {
use zenoh_buffers::reader::HasReader;
use zenoh_codec::{RCodec, Zenoh080};
let mut reader = (&record.timestamp).reader();
let Ok(ts): Result<uhlc::Timestamp, _> = Zenoh080.read(&mut reader) else {
tracing::warn!(
"Skipping instrumentation measurement with malformed uhlc timestamp"
);
continue;
};
InstrumentationTimestamp::UHLC(ts)
}
};
instance
.records
.push(TimestampStackRecord { point, timestamp });
}
Ok(instance)
}
}
#[cfg(feature = "unstable")]
impl From<&TimestampStack> for zenoh_protocol::network::timestamp_stack::TimestampStack {
fn from(value: &TimestampStack) -> Self {
zenoh_protocol::network::timestamp_stack::TimestampStack {
conf_flags: value.instrumentation.conf_flags,
stack: value
.records
.iter()
.map(|r| Interception {
flags: u8::from(r.point)
| if matches!(r.timestamp, InstrumentationTimestamp::Custom(_)) {
interception_point::IS_CUSTOM_TS
} else {
0
},
timestamp: match &r.timestamp {
InstrumentationTimestamp::UHLC(ts) => {
use zenoh_codec::{WCodec, Zenoh080};
let mut buf = Vec::new();
Zenoh080
.write(&mut buf, ts)
.expect("serializing valid UHLC timestamp should not fail");
buf
}
InstrumentationTimestamp::Custom(ts) => ts.clone(),
},
})
.collect(),
}
}
}