use {
solana_metrics::{datapoint_info, poh_timing_point::PohTimingPoint},
solana_sdk::clock::Slot,
std::{collections::HashMap, fmt},
};
#[derive(Debug, Clone, Copy, Default)]
pub struct SlotPohTimestamp {
pub start_time: u64,
pub end_time: u64,
pub full_time: u64,
}
impl fmt::Display for SlotPohTimestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"SlotPohTimestamp: start={} end={} full={}",
self.start_time, self.end_time, self.full_time
)
}
}
impl SlotPohTimestamp {
pub fn is_complete(&self) -> bool {
self.start_time != 0 && self.end_time != 0 && self.full_time != 0
}
pub fn update(&mut self, timing_point: PohTimingPoint) {
match timing_point {
PohTimingPoint::PohSlotStart(ts) => self.start_time = ts,
PohTimingPoint::PohSlotEnd(ts) => self.end_time = ts,
PohTimingPoint::FullSlotReceived(ts) => self.full_time = ts,
}
}
fn slot_start_to_full_time(&self) -> i64 {
(self.full_time as i64).saturating_sub(self.start_time as i64)
}
fn slot_full_to_end_time(&self) -> i64 {
(self.end_time as i64).saturating_sub(self.full_time as i64)
}
pub fn report(&self, slot: Slot) {
datapoint_info!(
"poh_slot_timing",
("slot", slot as i64, i64),
("start_time", self.start_time as i64, i64),
("end_time", self.end_time as i64, i64),
("full_time", self.full_time as i64, i64),
(
"start_to_full_time_diff",
self.slot_start_to_full_time(),
i64
),
("full_to_end_time_diff", self.slot_full_to_end_time(), i64),
);
}
}
#[derive(Default)]
pub struct PohTimingReporter {
slot_timestamps: HashMap<Slot, SlotPohTimestamp>,
last_root_slot: Slot,
}
impl PohTimingReporter {
pub fn is_complete(&self, slot: Slot) -> bool {
if let Some(slot_timestamp) = self.slot_timestamps.get(&slot) {
slot_timestamp.is_complete()
} else {
false
}
}
pub fn process(&mut self, slot: Slot, root_slot: Option<Slot>, t: PohTimingPoint) -> bool {
let slot_timestamp = self.slot_timestamps.entry(slot).or_default();
slot_timestamp.update(t);
let is_completed = slot_timestamp.is_complete();
if is_completed {
slot_timestamp.report(slot);
}
if let Some(root_slot) = root_slot {
if root_slot > self.last_root_slot {
self.slot_timestamps.retain(|&k, _| k >= root_slot);
self.last_root_slot = root_slot;
}
}
is_completed
}
pub fn slot_count(&self) -> usize {
self.slot_timestamps.len()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_poh_timing_reporter() {
let mut reporter = PohTimingReporter::default();
let complete = reporter.process(42, None, PohTimingPoint::PohSlotStart(100));
assert!(!complete);
let complete = reporter.process(42, None, PohTimingPoint::PohSlotEnd(200));
assert!(!complete);
let complete = reporter.process(42, None, PohTimingPoint::FullSlotReceived(150));
assert!(complete);
let root = Some(43);
let complete = reporter.process(45, None, PohTimingPoint::PohSlotStart(100));
assert!(!complete);
let complete = reporter.process(45, None, PohTimingPoint::PohSlotEnd(200));
assert!(!complete);
let complete = reporter.process(45, root, PohTimingPoint::FullSlotReceived(150));
assert!(complete);
assert_eq!(reporter.slot_count(), 1)
}
#[test]
fn test_poh_timing_reporter_out_of_order() {
let mut reporter = PohTimingReporter::default();
let mut c = 0;
c += reporter.process(42, None, PohTimingPoint::PohSlotStart(100)) as i32;
c += reporter.process(42, None, PohTimingPoint::FullSlotReceived(120)) as i32;
c += reporter.process(43, None, PohTimingPoint::FullSlotReceived(140)) as i32;
c += reporter.process(42, None, PohTimingPoint::PohSlotEnd(200)) as i32;
c += reporter.process(43, None, PohTimingPoint::PohSlotStart(100)) as i32;
c += reporter.process(43, None, PohTimingPoint::PohSlotEnd(200)) as i32;
assert_eq!(c, 2);
assert_eq!(reporter.slot_count(), 2)
}
#[test]
fn test_poh_timing_reporter_never_complete() {
let mut reporter = PohTimingReporter::default();
let mut c = 0;
c += reporter.process(42, None, PohTimingPoint::PohSlotStart(100)) as i32;
c += reporter.process(42, None, PohTimingPoint::FullSlotReceived(120)) as i32;
c += reporter.process(43, None, PohTimingPoint::FullSlotReceived(140)) as i32;
c += reporter.process(43, None, PohTimingPoint::PohSlotStart(100)) as i32;
c += reporter.process(43, None, PohTimingPoint::PohSlotEnd(200)) as i32;
assert_eq!(c, 1);
assert_eq!(reporter.slot_count(), 2)
}
#[test]
fn test_poh_timing_reporter_overflow() {
let mut reporter = PohTimingReporter::default();
let complete = reporter.process(42, None, PohTimingPoint::PohSlotStart(1647624609896));
assert!(!complete);
let complete = reporter.process(42, None, PohTimingPoint::PohSlotEnd(1647624610286));
assert!(!complete);
let complete = reporter.process(42, None, PohTimingPoint::FullSlotReceived(1647624610281));
assert!(complete);
}
#[test]
fn test_slot_poh_timestamp_fmt() {
let t = SlotPohTimestamp::default();
assert_eq!(format!("{t}"), "SlotPohTimestamp: start=0 end=0 full=0");
}
}