use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::{Arc, LazyLock};
use std::time::Instant;
use near_time::Clock;
use parking_lot::RwLock;
use crate::instrumentation::instrumented_window::InstrumentedWindow;
use crate::instrumentation::queue::InstrumentedQueue;
use crate::instrumentation::reader::InstrumentedThreadsView;
use crate::instrumentation::{NUM_WINDOWS, WINDOW_SIZE_NS};
const WINDOW_ARRAY_SIZE: usize = NUM_WINDOWS + 4;
pub struct AllActorInstrumentations {
pub reference_instant: Instant,
pub threads: RwLock<HashMap<usize, Arc<InstrumentedThread>>>,
}
impl AllActorInstrumentations {
pub fn to_view(&self, clock: &Clock) -> InstrumentedThreadsView {
#[allow(clippy::needless_collect)] let threads = self.threads.read().values().cloned().collect::<Vec<_>>();
let current_time_ns = clock.now().duration_since(self.reference_instant).as_nanos() as u64;
let current_time_unix_ms = (clock.now_utc().unix_timestamp_nanos() / 1000000) as u64;
let mut threads =
threads.into_iter().map(|thread| thread.to_view(current_time_ns)).collect::<Vec<_>>();
threads.sort_by_key(|thread| -(thread.active_time_ns as i128));
InstrumentedThreadsView {
current_time_unix_ms,
current_time_relative_ms: current_time_ns / 1_000_000,
threads,
}
}
}
pub static ALL_ACTOR_INSTRUMENTATIONS: LazyLock<AllActorInstrumentations> =
LazyLock::new(|| AllActorInstrumentations {
reference_instant: Instant::now(),
threads: RwLock::new(HashMap::new()),
});
pub struct InstrumentedThread {
pub thread_name: String,
pub actor_name: String,
pub queue: Arc<InstrumentedQueue>,
pub started_time_ns: u64,
pub message_type_registry: MessageTypeRegistry,
pub windows: Vec<RwLock<InstrumentedWindow>>,
pub current_window_index: AtomicUsize,
pub active_event: AtomicU64,
pub active_event_start_ns: AtomicU64,
}
impl InstrumentedThread {
pub fn new(
thread_name: String,
actor_name: String,
queue: Arc<InstrumentedQueue>,
start_time: u64,
) -> Self {
Self {
thread_name,
actor_name,
queue,
started_time_ns: start_time,
message_type_registry: MessageTypeRegistry::default(),
windows: (0..WINDOW_ARRAY_SIZE)
.map(|_| RwLock::new(InstrumentedWindow::new()))
.collect(),
current_window_index: AtomicUsize::new(0),
active_event: AtomicU64::new(0),
active_event_start_ns: AtomicU64::new(0),
}
}
pub fn start_event(&self, message_type_id: u32, timestamp_ns: u64, dequeue_time_ns: u64) {
let encoded_event = encode_message_event(message_type_id, true);
self.active_event_start_ns.store(timestamp_ns, Ordering::Relaxed);
self.active_event.store(encoded_event, Ordering::Release);
let current_window_index = self.current_window_index.load(Ordering::Relaxed);
let window = &self.windows[current_window_index % WINDOW_ARRAY_SIZE];
let window = window.read();
window.events.push(encoded_event, timestamp_ns.saturating_sub(window.start_time_ns));
window.dequeue_summary.add_message_time(
current_window_index,
message_type_id,
dequeue_time_ns,
);
}
pub fn end_event(&self, timestamp_ns: u64) -> u64 {
let active_event = self.active_event.load(Ordering::Relaxed);
let message_type_id = active_event as u32;
let start_timestamp = self.active_event_start_ns.load(Ordering::Relaxed);
let encoded_event = encode_message_event(message_type_id, false);
self.active_event.store(0, Ordering::Relaxed);
let current_window_index = self.current_window_index.load(Ordering::Relaxed);
let window = &self.windows[current_window_index % WINDOW_ARRAY_SIZE];
let window = window.read();
window.events.push(encoded_event, timestamp_ns.saturating_sub(window.start_time_ns));
let elapsed_ns = timestamp_ns.saturating_sub(start_timestamp.max(window.start_time_ns));
window.summary.add_message_time(current_window_index, message_type_id, elapsed_ns);
let total_elapsed_ns = timestamp_ns.saturating_sub(start_timestamp);
total_elapsed_ns
}
pub fn advance_window(&self, window_end_time_ns: u64) {
let current_window_index = self.current_window_index.load(Ordering::Relaxed);
let active_event = self.active_event.load(Ordering::Relaxed);
if active_event != 0 {
let active_event = active_event as u32;
let elapsed_in_window = window_end_time_ns
.saturating_sub(self.active_event_start_ns.load(Ordering::Relaxed))
.min(WINDOW_SIZE_NS);
self.windows[current_window_index % WINDOW_ARRAY_SIZE].read().summary.add_message_time(
current_window_index,
active_event,
elapsed_in_window,
);
}
let next_window_index = current_window_index + 1;
let next_window = &self.windows[next_window_index % WINDOW_ARRAY_SIZE];
let num_types = self.message_type_registry.types.read().len();
next_window.write().reinitialize(next_window_index, window_end_time_ns, num_types);
self.current_window_index.store(next_window_index, Ordering::Release);
}
}
#[derive(Default)]
pub struct MessageTypeRegistry {
pub types: RwLock<Vec<String>>,
}
impl MessageTypeRegistry {
pub fn push_type(&self, type_name: String) {
let mut types = self.types.write();
types.push(type_name);
}
}
fn encode_message_event(message_type_id: u32, is_start: bool) -> u64 {
let mut event = message_type_id as u64;
if is_start {
event |= 1 << 32;
}
event
}