use std::sync::Arc;
use time::Duration;
use near_time::FakeClock;
use crate::instrumentation::NUM_WINDOWS;
use crate::instrumentation::queue::InstrumentedQueue;
use crate::instrumentation::writer::InstrumentedThreadWriterSharedPart;
use crate::instrumentation::{
WINDOW_SIZE_NS, data::InstrumentedThread, writer::InstrumentedThreadWriter,
};
const TEST_WINDOW_SIZE_MS: i64 = WINDOW_SIZE_NS as i64 / 1_000_000;
struct TestSetup {
clock: FakeClock,
reference_instant: near_time::Instant,
target: Arc<InstrumentedThread>,
writer: InstrumentedThreadWriter,
}
impl Default for TestSetup {
fn default() -> Self {
let fake_clock = FakeClock::default();
let clock = fake_clock.clock();
let reference_instant = fake_clock.now();
let queue = InstrumentedQueue::new("test_actor");
let target = Arc::new(InstrumentedThread::new(
"test_thread".to_string(),
"test_actor".to_string(),
queue.clone(),
0,
));
let shared = InstrumentedThreadWriterSharedPart::new_for_test(
"test_actor".to_string(),
clock,
reference_instant,
queue,
);
let writer = shared.new_writer_for_test(target.clone());
Self { clock: fake_clock, reference_instant, target, writer }
}
}
#[test]
fn test_basic_event_tracking() {
let TestSetup { clock, reference_instant, target, mut writer } = Default::default();
writer.start_event("TestMessage", 1_000_000); clock.advance(Duration::milliseconds(50)); writer.end_event("TestMessage");
let view = target.to_view(clock.now().duration_since(reference_instant).as_nanos() as u64);
assert_eq!(view.thread_name, "test_thread");
assert_eq!(view.message_types.len(), 1);
assert_eq!(view.message_types[0], "TestMessage");
assert_eq!(view.windows.len(), 1);
let window = &view.windows[0];
assert_eq!(window.events.len(), 2); assert!(!window.events_overfilled);
assert!(!window.summary.message_stats_by_type.is_empty());
let message_stats = &window.summary.message_stats_by_type[0];
assert_eq!(message_stats.message_type, 0); assert_eq!(message_stats.count, 1);
assert!(message_stats.total_time_ns >= 50_000_000);
assert!(!window.dequeue_summary.message_stats_by_type.is_empty());
let dequeue_stats = &window.dequeue_summary.message_stats_by_type[0];
assert_eq!(dequeue_stats.message_type, 0);
assert_eq!(dequeue_stats.count, 1);
assert_eq!(dequeue_stats.total_time_ns, 1_000_000); }
#[test]
fn test_multiple_message_types() {
let TestSetup { clock, reference_instant, target, mut writer } = Default::default();
writer.start_event("MessageA", 500_000);
clock.advance(Duration::milliseconds(10));
writer.end_event("MessageA");
writer.start_event("MessageB", 750_000);
clock.advance(Duration::milliseconds(20));
writer.end_event("MessageB");
writer.start_event("MessageA", 1_000_000);
clock.advance(Duration::milliseconds(15));
writer.end_event("MessageA");
let view = target.to_view(clock.now().duration_since(reference_instant).as_nanos() as u64);
assert_eq!(view.message_types.len(), 2);
assert!(view.message_types.contains(&"MessageA".to_string()));
assert!(view.message_types.contains(&"MessageB".to_string()));
let window = &view.windows[0];
assert_eq!(window.events.len(), 6);
assert!(window.summary.message_stats_by_type.len() >= 2);
let message_a_stats = window
.summary
.message_stats_by_type
.iter()
.find(|s| s.count == 2)
.expect("MessageA should have 2 events");
assert!(message_a_stats.total_time_ns >= 25_000_000);
let message_b_stats = window
.summary
.message_stats_by_type
.iter()
.find(|s| s.count == 1)
.expect("MessageB should have 1 event");
assert!(message_b_stats.total_time_ns >= 20_000_000); }
#[test]
fn test_window_wrapping() {
let TestSetup { clock, reference_instant, target, mut writer } = Default::default();
for _i in 0..NUM_WINDOWS + 5 {
writer.start_event("TestMessage", 100_000);
clock.advance(Duration::milliseconds(10));
writer.end_event("TestMessage");
clock.advance(Duration::milliseconds(TEST_WINDOW_SIZE_MS - 10));
writer.advance_window_if_needed();
}
let view = target.to_view(clock.now().duration_since(reference_instant).as_nanos() as u64);
assert!(
view.windows.len() <= NUM_WINDOWS,
"Should not exceed NUM_WINDOWS due to buffer wrapping"
);
for window in &view.windows {
if !window.summary.message_stats_by_type.is_empty() {
assert_eq!(window.summary.message_stats_by_type[0].count, 1);
assert!(window.summary.message_stats_by_type[0].total_time_ns >= 10_000_000); }
}
}
#[test]
fn test_cross_window_event() {
let TestSetup { clock, reference_instant, target, mut writer } = Default::default();
clock.advance(Duration::milliseconds(TEST_WINDOW_SIZE_MS - 50)); writer.start_event("CrossWindowMessage", 200_000);
clock.advance(Duration::milliseconds(100)); writer.end_event("CrossWindowMessage");
let view = target.to_view(clock.now().duration_since(reference_instant).as_nanos() as u64);
assert_eq!(view.windows.len(), 2);
let first_window = &view.windows[1]; if !first_window.summary.message_stats_by_type.is_empty() {
assert!(first_window.summary.message_stats_by_type[0].total_time_ns <= 50_000_000); }
let second_window = &view.windows[0];
if !second_window.summary.message_stats_by_type.is_empty() {
assert!(second_window.summary.message_stats_by_type[0].total_time_ns >= 50_000_000); }
}
#[test]
fn test_active_event_tracking() {
let TestSetup { clock, reference_instant, target, mut writer } = Default::default();
writer.start_event("ActiveMessage", 300_000);
clock.advance(Duration::milliseconds(75));
let view = target.to_view(clock.now().duration_since(reference_instant).as_nanos() as u64);
assert!(view.active_event.is_some());
let active_event = view.active_event.unwrap();
assert_eq!(active_event.message_type, 0); assert!(active_event.active_for_ns >= 75_000_000);
writer.end_event("ActiveMessage");
let view = target.to_view(clock.now().duration_since(reference_instant).as_nanos() as u64);
assert!(view.active_event.is_none());
let window = &view.windows[0];
assert_eq!(window.summary.message_stats_by_type[0].count, 1);
assert!(window.summary.message_stats_by_type[0].total_time_ns >= 75_000_000);
}