1use std::cell::UnsafeCell;
12use std::collections::{BTreeMap, BTreeSet, VecDeque};
13use std::ffi::c_void;
14use std::panic::{catch_unwind, AssertUnwindSafe};
15use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, AtomicUsize, Ordering};
16use std::sync::{Arc, Condvar, Mutex};
17use std::time::Instant;
18
19use crate::config::{GraphConfig, StatsLevel};
20use crate::context::Context;
21use crate::executor::Executor;
22use crate::kernel::{KernelInstance, PortTable};
23use crate::packet::Packet;
24use crate::runtime::{self, GraphShared};
25use crate::status::{Error, Result};
26use crate::timestamp::Timestamp;
27
28mod api;
29mod backpressure;
30mod build;
31mod dot;
32mod introspect;
33mod lifecycle;
34mod node;
35mod poller;
36mod scheduler;
37
38use backpressure::*;
39use node::*;
40pub use node::{DotView, State};
41use poller::PollerInner;
42pub use poller::{
43 OutputEvent, Poller, PollerBackpressureStatsSnapshot, PollerOptions, PollerOverflow,
44};
45
46pub type NodeId = usize;
47pub type EdgeId = usize;
48
49#[derive(Debug, Default)]
62struct Activity {
63 gen: u64,
64 waiters: usize,
65}
66
67type WakeupCallback = Arc<dyn Fn() + Send + Sync>;
68
69pub struct GraphInner {
70 pub shared: Arc<GraphShared>,
71 nodes: Vec<Node>,
72 edges: Vec<Edge>,
73 graph_inputs: Vec<EdgeId>,
74 graph_outputs: Vec<EdgeId>,
75 input_by_name: BTreeMap<String, EdgeId>,
76 output_by_name: BTreeMap<String, EdgeId>,
77 edge_by_name: BTreeMap<String, EdgeId>,
78 state: Mutex<State>,
79 executors: Vec<Executor>,
81 in_flight: AtomicUsize,
83 delegated_cursor: AtomicUsize,
85 delegated_running: AtomicBool,
87 activity: (Mutex<Activity>, Condvar),
89 wakeup_callback: Mutex<Option<WakeupCallback>>,
91 wakeup_pending: AtomicBool,
93 wakeup_generation: AtomicU64,
95 paused: AtomicBool,
97 blocked_flush_nodes: Mutex<BTreeSet<NodeId>>,
99 side_packets: Mutex<BTreeMap<String, Packet>>,
100 required_side_packets: Vec<(String, String)>,
102 epoch: Instant,
104 run_started_us: AtomicI64,
106 dot_intervals: Mutex<dot::DotIntervalBaselines>,
108 stats_level: StatsLevel,
110 e2e_stats: E2eStats,
111}
112
113impl GraphInner {
114 #[inline]
115 fn basic_stats(&self) -> bool {
116 self.stats_level != StatsLevel::Off
117 }
118
119 #[inline]
120 fn full_stats(&self) -> bool {
121 self.stats_level == StatsLevel::Full
122 }
123
124 #[inline]
125 pub(super) fn record_e2e(&self, pkt: &Packet) {
126 if !self.full_stats() || !pkt.mark_e2e_observed() {
127 return;
128 }
129 let ingress = pkt.ingress_us();
130 if ingress == 0 {
131 return;
132 }
133 self.e2e_stats
134 .record(self.epoch_us().saturating_sub(ingress).max(0) as u64);
135 }
136}
137
138pub struct Graph {
151 inner: Arc<GraphInner>,
152}
153
154impl Graph {
155 pub fn from_yaml(text: &str) -> Result<Self> {
156 let cfg = GraphConfig::from_yaml(text)?;
157 Self::from_config(cfg)
158 }
159
160 pub fn from_yaml_file(path: &str) -> Result<Self> {
161 let cfg = GraphConfig::from_yaml_file(path)?;
162 Self::from_config(cfg)
163 }
164
165 pub fn from_config(cfg: GraphConfig) -> Result<Self> {
166 let inner = GraphInner::build(cfg)?;
167 Ok(Self {
168 inner: Arc::new(inner),
169 })
170 }
171
172 pub fn state(&self) -> State {
173 *self.inner.state.lock().expect("state lock poisoned")
174 }
175
176 pub fn inner(&self) -> &Arc<GraphInner> {
177 &self.inner
178 }
179
180 pub fn set_side_packet(&self, name: &str, pkt: Packet) -> Result<()> {
182 if self.state() != State::Initialized {
183 return Err(Error::State(
184 "side packets must be injected before start".into(),
185 ));
186 }
187 crate::packet::validate_type_id(pkt.type_id()).map_err(|error| {
188 Error::InvalidArg(format!(
189 "side packet `{name}` carries an invalid type: {error}"
190 ))
191 })?;
192 self.inner
193 .side_packets
194 .lock()
195 .expect("side packet lock poisoned")
196 .insert(name.to_string(), pkt);
197 Ok(())
198 }
199
200 pub fn input(&self, port: &str) -> Result<Input> {
201 let edge =
202 *self.inner.input_by_name.get(port).ok_or_else(|| {
203 Error::NotFound(format!("graph input port `{port}` does not exist"))
204 })?;
205 Ok(Input {
206 graph: self.inner.clone(),
207 edge,
208 })
209 }
210
211 pub fn close_input(&self, port: &str) -> Result<()> {
212 let edge =
213 *self.inner.input_by_name.get(port).ok_or_else(|| {
214 Error::NotFound(format!("graph input port `{port}` does not exist"))
215 })?;
216 self.inner.close_edge(edge);
217 self.inner.set_state_draining_if_all_inputs_closed();
218 Ok(())
219 }
220
221 pub fn close_all_inputs(&self) {
222 for &e in &self.inner.graph_inputs {
223 self.inner.close_edge(e);
224 }
225 self.inner.set_state_draining_if_all_inputs_closed();
226 }
227
228 pub fn cancel(&self) {
229 self.inner.shared.cancel();
230 for node in &self.inner.nodes {
231 node.source_waiting.store(false, Ordering::SeqCst);
232 node.source_wait_reason.store(0, Ordering::Relaxed);
233 node.source_wake_deadline_us.store(0, Ordering::Relaxed);
234 node.source_wake_generation.fetch_add(1, Ordering::SeqCst);
235 }
236 for executor in &self.inner.executors {
237 executor.clear_delayed();
238 }
239 self.inner.resume_blocked_flushes();
240 self.inner.finish_all_backpressure_blocks();
241 self.inner.notify_activity();
242 self.inner.request_wakeup();
243 }
244
245 pub fn wait_done_timeout(&self, timeout: std::time::Duration) -> Result<()> {
249 self.inner
250 .wait_done(Some(std::time::Instant::now() + timeout))
251 }
252
253 pub fn wait_until_idle_timeout(&self, timeout: std::time::Duration) -> Result<()> {
256 self.inner
257 .wait_until_idle(Some(std::time::Instant::now() + timeout))
258 }
259
260 pub fn pause(&self) {
261 self.inner.pause();
262 }
263
264 pub fn resume(&self) {
265 self.inner.resume();
266 }
267
268 pub fn is_paused(&self) -> bool {
269 self.inner.is_paused()
270 }
271
272 pub fn executor_names(&self) -> Vec<&str> {
274 self.inner.executor_names()
275 }
276
277 pub fn is_idle(&self) -> bool {
279 self.inner.is_idle_pub()
280 }
281
282 pub fn pump_step(&self) -> bool {
286 self.inner.pump_step_pub()
287 }
288
289 pub fn pump_steps(&self, max_steps: usize) -> usize {
291 (0..max_steps).take_while(|_| self.pump_step()).count()
292 }
293
294 pub fn set_wakeup_callback<F>(&self, callback: F)
311 where
312 F: Fn() + Send + Sync + 'static,
313 {
314 self.inner.set_wakeup_callback(Some(Arc::new(callback)));
315 }
316
317 pub fn clear_wakeup_callback(&self) {
319 self.inner.set_wakeup_callback(None);
320 }
321
322 pub fn observe<F>(&self, port: &str, f: F) -> Result<()>
326 where
327 F: Fn(&Packet) + Send + Sync + 'static,
328 {
329 self.observe_inner(port, f, false)
330 }
331
332 pub fn observe_with_timestamp_bounds<F>(&self, port: &str, f: F) -> Result<()>
334 where
335 F: Fn(&Packet) + Send + Sync + 'static,
336 {
337 self.observe_inner(port, f, true)
338 }
339
340 fn observe_inner<F>(&self, port: &str, f: F, observe_timestamp_bounds: bool) -> Result<()>
341 where
342 F: Fn(&Packet) + Send + Sync + 'static,
343 {
344 if self.state() != State::Initialized {
345 return Err(Error::State(
346 "observe must be called before start, otherwise already-produced packets are missed".into(),
347 ));
348 }
349 self.inner
350 .add_observer_fn(port, Arc::new(f), observe_timestamp_bounds)
351 }
352
353 pub fn counter_value(&self, name: &str) -> i64 {
355 self.inner.shared.counter_value(name)
356 }
357
358 pub fn counter_names(&self) -> Vec<String> {
360 self.inner.shared.counter_names()
361 }
362
363 pub fn shared_for_inspection(&self) -> Arc<crate::runtime::GraphShared> {
366 self.inner.shared.clone()
367 }
368
369 pub fn node_count(&self) -> usize {
370 self.inner.nodes.len()
371 }
372 pub fn node_name(&self, i: usize) -> Option<&str> {
373 self.inner.nodes.get(i).map(|n| n.name.as_str())
374 }
375 pub fn input_port_names(&self) -> Vec<&str> {
376 self.inner
377 .graph_inputs
378 .iter()
379 .map(|&e| self.inner.edges[e].name.as_str())
380 .collect()
381 }
382 pub fn output_port_names(&self) -> Vec<&str> {
383 self.inner
384 .graph_outputs
385 .iter()
386 .map(|&e| self.inner.edges[e].name.as_str())
387 .collect()
388 }
389
390 pub fn queue_depth(&self, port: &str) -> Option<usize> {
392 let e = *self.inner.edge_by_name.get(port)?;
393 Some(self.inner.queue_depth(e))
394 }
395
396 pub fn dropped_count(&self, port: &str) -> Option<u64> {
397 let e = *self.inner.edge_by_name.get(port)?;
398 Some(self.inner.edges[e].dropped_count())
399 }
400
401 pub fn dump(&self) -> String {
402 self.inner.dump()
403 }
404
405 pub fn to_dot(&self) -> String {
407 self.to_dot_with_view(DotView::Topology)
408 }
409
410 pub fn to_dot_with_view(&self, view: DotView) -> String {
412 self.inner.to_dot(view)
413 }
414
415 pub fn to_dot_compact(&self) -> String {
417 self.to_dot_with_view(DotView::Compact)
418 }
419
420 pub fn to_dot_with_stats(&self) -> String {
427 self.to_dot_with_view(DotView::Diagnostics)
428 }
429
430 pub fn to_chrome_trace(&self) -> String {
434 self.inner.to_chrome_trace()
435 }
436
437 pub fn node_stats(&self, i: usize) -> Option<NodeStatsSnapshot> {
438 self.inner.node_stats(i)
439 }
440
441 pub fn input_queue_stats(&self, node: usize, port: usize) -> Option<InputQueueStatsSnapshot> {
442 self.inner.input_queue_stats(node, port)
443 }
444}
445
446impl Drop for Graph {
447 fn drop(&mut self) {
454 if self.state() != State::Terminated {
458 self.cancel();
459 let _ = self.wait_done();
460 }
461 self.inner.shutdown_executors_pub();
462 }
463}
464
465impl std::fmt::Debug for Graph {
466 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
467 write!(
468 f,
469 "Graph{{state:{:?}, nodes:{}, inputs:{:?}, outputs:{:?}}}",
470 self.state(),
471 self.inner.nodes.len(),
472 self.input_port_names(),
473 self.output_port_names()
474 )
475 }
476}
477
478pub struct Input {
480 graph: Arc<GraphInner>,
481 edge: EdgeId,
482}
483
484impl Input {
485 pub fn send(&self, pkt: Packet) -> Result<()> {
488 self.graph.send(self.edge, pkt, true)
489 }
490 pub fn try_send(&self, pkt: Packet) -> Result<()> {
492 self.graph.send(self.edge, pkt, false)
493 }
494 pub fn backpressure_stats(&self) -> WatermarkBackpressureStatsSnapshot {
495 self.graph.watermark_backpressure_stats(self.edge)
496 }
497 pub fn close(&self) {
500 self.graph.close_edge(self.edge);
501 self.graph.set_state_draining_if_all_inputs_closed();
502 }
503}
504
505impl std::fmt::Debug for Input {
506 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
507 write!(f, "Input{{port:`{}`}}", self.graph.edges[self.edge].name)
508 }
509}
510
511#[derive(Debug, Clone)]
513pub struct NodeStatsSnapshot {
514 pub node_name: String,
515 pub kernel_name: String,
516 pub running: bool,
517 pub running_for_us: i64,
518 pub processed: u64,
519 pub errors: u64,
520 pub total_process_us: i64,
521 pub max_process_us: i64,
522 pub packets_in: u64,
524 pub packets_out: u64,
526 pub peak_queue_depth: usize,
528 pub queued: usize,
529}
530
531#[derive(Debug, Clone)]
532pub(super) struct ExecutorStatsSnapshot {
533 pub queued: usize,
534 pub running: usize,
535 pub peak_queued: usize,
536 pub completed: u64,
537 pub total_wait_us: u64,
538 pub total_execution_us: u64,
539 pub queued_for_us: u64,
540 pub saturated: bool,
541 pub queued_nodes: Vec<String>,
542}
543
544#[derive(Debug, Clone)]
546pub struct InputQueueStatsSnapshot {
547 pub node_name: String,
548 pub port_name: String,
549 pub producer_name: Option<String>,
550 pub packet_capacity: Option<usize>,
551 pub queued_packets: usize,
552 pub queued_bytes: u64,
553 pub reserved_packets: usize,
554 pub peak_queued_packets: usize,
555 pub peak_queued_bytes: u64,
556 pub blocked: bool,
557 pub blocked_for_us: u64,
558 pub block_events: u64,
559 pub total_blocked_us: u64,
560}
561#[derive(Debug, Clone)]
562pub struct WatermarkBackpressureStatsSnapshot {
563 pub port_name: String,
564 pub packet_limit: usize,
565 pub total_queued_packets: usize,
566 pub blocked: bool,
567 pub active_waiters: usize,
568 pub blocked_for_us: u64,
569 pub block_events: u64,
570 pub total_blocked_us: u64,
571}