#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FlowState {
Running,
Throttled,
Paused,
Draining,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum FlowPriority {
Low = 0,
Normal = 1,
High = 2,
Critical = 3,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FlowItem {
pub item_id: u64,
pub tensor_id: u64,
pub priority: FlowPriority,
pub size_bytes: u64,
pub enqueued_at_tick: u64,
}
#[derive(Clone, Debug)]
pub struct FlowControllerConfig {
pub max_queue_size: usize,
pub max_bytes_per_tick: u64,
pub backpressure_threshold: f64,
}
impl Default for FlowControllerConfig {
fn default() -> Self {
Self {
max_queue_size: 256,
max_bytes_per_tick: 1_048_576,
backpressure_threshold: 0.8,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct FlowStats {
pub total_admitted: u64,
pub total_dropped: u64,
pub total_processed: u64,
pub total_bytes_processed: u64,
}
impl FlowStats {
pub fn drop_rate(&self) -> f64 {
let total = self.total_admitted + self.total_dropped;
if total == 0 {
0.0
} else {
self.total_dropped as f64 / total as f64
}
}
}
pub struct TensorFlowController {
queue: Vec<FlowItem>,
state: FlowState,
config: FlowControllerConfig,
stats: FlowStats,
}
impl TensorFlowController {
pub fn new(config: FlowControllerConfig) -> Self {
Self {
queue: Vec::new(),
state: FlowState::Running,
config,
stats: FlowStats::default(),
}
}
pub fn admit(&mut self, item: FlowItem) -> bool {
if self.state == FlowState::Paused || self.state == FlowState::Draining {
self.stats.total_dropped += 1;
return false;
}
if self.queue.len() >= self.config.max_queue_size {
self.stats.total_dropped += 1;
return false;
}
let pos = self.queue.partition_point(|existing| {
existing.priority > item.priority
|| (existing.priority == item.priority
&& existing.enqueued_at_tick <= item.enqueued_at_tick)
});
self.queue.insert(pos, item);
self.stats.total_admitted += 1;
self.state = self.compute_state_after_admit();
true
}
pub fn process_tick(&mut self) -> Vec<FlowItem> {
if self.state == FlowState::Paused {
return Vec::new();
}
let mut processed = Vec::new();
let mut bytes_remaining = self.config.max_bytes_per_tick;
while let Some(front) = self.queue.first() {
if front.size_bytes > bytes_remaining {
break;
}
bytes_remaining -= front.size_bytes;
let item = self.queue.remove(0);
self.stats.total_processed += 1;
self.stats.total_bytes_processed += item.size_bytes;
processed.push(item);
}
self.state = self.compute_state_after_tick();
processed
}
pub fn pause(&mut self) {
self.state = FlowState::Paused;
}
pub fn resume(&mut self) {
if self.state == FlowState::Paused {
self.state = self.fill_based_state();
}
}
pub fn drain(&mut self) {
self.state = FlowState::Draining;
}
pub fn queue_len(&self) -> usize {
self.queue.len()
}
pub fn stats(&self) -> &FlowStats {
&self.stats
}
fn compute_state_after_admit(&self) -> FlowState {
let fill = self.queue.len() as f64 / self.config.max_queue_size as f64;
if fill >= self.config.backpressure_threshold {
FlowState::Throttled
} else {
FlowState::Running
}
}
fn compute_state_after_tick(&self) -> FlowState {
if self.state == FlowState::Draining && self.queue.is_empty() {
return FlowState::Paused;
}
self.fill_based_state()
}
fn fill_based_state(&self) -> FlowState {
let fill = self.queue.len() as f64 / self.config.max_queue_size as f64;
if fill >= self.config.backpressure_threshold {
FlowState::Throttled
} else {
FlowState::Running
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn default_controller() -> TensorFlowController {
TensorFlowController::new(FlowControllerConfig::default())
}
fn make_item(item_id: u64, priority: FlowPriority, size_bytes: u64, tick: u64) -> FlowItem {
FlowItem {
item_id,
tensor_id: item_id * 10,
priority,
size_bytes,
enqueued_at_tick: tick,
}
}
#[test]
fn test_new_starts_running() {
let ctrl = default_controller();
assert_eq!(ctrl.state, FlowState::Running);
assert_eq!(ctrl.queue_len(), 0);
}
#[test]
fn test_admit_running_succeeds() {
let mut ctrl = default_controller();
let admitted = ctrl.admit(make_item(1, FlowPriority::Normal, 100, 0));
assert!(admitted);
assert_eq!(ctrl.queue_len(), 1);
}
#[test]
fn test_admit_paused_drops() {
let mut ctrl = default_controller();
ctrl.pause();
let admitted = ctrl.admit(make_item(1, FlowPriority::Normal, 100, 0));
assert!(!admitted);
assert_eq!(ctrl.stats().total_dropped, 1);
assert_eq!(ctrl.queue_len(), 0);
}
#[test]
fn test_admit_draining_drops() {
let mut ctrl = default_controller();
ctrl.drain();
let admitted = ctrl.admit(make_item(1, FlowPriority::Normal, 100, 0));
assert!(!admitted);
assert_eq!(ctrl.stats().total_dropped, 1);
assert_eq!(ctrl.queue_len(), 0);
}
#[test]
fn test_admit_at_capacity_drops() {
let config = FlowControllerConfig {
max_queue_size: 2,
max_bytes_per_tick: 1_048_576,
backpressure_threshold: 0.99,
};
let mut ctrl = TensorFlowController::new(config);
assert!(ctrl.admit(make_item(1, FlowPriority::Normal, 100, 0)));
assert!(ctrl.admit(make_item(2, FlowPriority::Normal, 100, 1)));
let admitted = ctrl.admit(make_item(3, FlowPriority::Normal, 100, 2));
assert!(!admitted);
assert_eq!(ctrl.stats().total_dropped, 1);
}
#[test]
fn test_backpressure_threshold_throttled() {
let config = FlowControllerConfig {
max_queue_size: 10,
max_bytes_per_tick: 1_048_576,
backpressure_threshold: 0.8,
};
let mut ctrl = TensorFlowController::new(config);
for i in 0..8 {
ctrl.admit(make_item(i, FlowPriority::Normal, 100, i));
}
assert_eq!(ctrl.state, FlowState::Throttled);
}
#[test]
fn test_below_threshold_stays_running() {
let config = FlowControllerConfig {
max_queue_size: 10,
max_bytes_per_tick: 1_048_576,
backpressure_threshold: 0.8,
};
let mut ctrl = TensorFlowController::new(config);
for i in 0..7 {
ctrl.admit(make_item(i, FlowPriority::Normal, 100, i));
}
assert_eq!(ctrl.state, FlowState::Running);
}
#[test]
fn test_process_tick_respects_byte_budget() {
let config = FlowControllerConfig {
max_queue_size: 256,
max_bytes_per_tick: 300,
backpressure_threshold: 0.8,
};
let mut ctrl = TensorFlowController::new(config);
ctrl.admit(make_item(1, FlowPriority::Normal, 100, 0));
ctrl.admit(make_item(2, FlowPriority::Normal, 100, 1));
ctrl.admit(make_item(3, FlowPriority::Normal, 100, 2));
ctrl.admit(make_item(4, FlowPriority::Normal, 100, 3));
let processed = ctrl.process_tick();
assert_eq!(processed.len(), 3);
assert_eq!(ctrl.queue_len(), 1);
}
#[test]
fn test_process_tick_paused_returns_empty() {
let mut ctrl = default_controller();
ctrl.admit(make_item(1, FlowPriority::Normal, 100, 0));
ctrl.pause();
let processed = ctrl.process_tick();
assert!(processed.is_empty());
assert_eq!(ctrl.queue_len(), 1);
}
#[test]
fn test_process_tick_priority_order() {
let config = FlowControllerConfig {
max_queue_size: 256,
max_bytes_per_tick: 200,
backpressure_threshold: 0.8,
};
let mut ctrl = TensorFlowController::new(config);
ctrl.admit(make_item(1, FlowPriority::Low, 100, 0));
ctrl.admit(make_item(2, FlowPriority::Critical, 100, 1));
let processed = ctrl.process_tick();
assert_eq!(processed.len(), 2);
assert_eq!(processed[0].priority, FlowPriority::Critical);
assert_eq!(processed[1].priority, FlowPriority::Low);
}
#[test]
fn test_process_tick_fifo_within_priority() {
let config = FlowControllerConfig {
max_queue_size: 256,
max_bytes_per_tick: 300,
backpressure_threshold: 0.8,
};
let mut ctrl = TensorFlowController::new(config);
ctrl.admit(make_item(10, FlowPriority::Normal, 100, 5));
ctrl.admit(make_item(20, FlowPriority::Normal, 100, 3));
ctrl.admit(make_item(30, FlowPriority::Normal, 100, 7));
let processed = ctrl.process_tick();
assert_eq!(processed.len(), 3);
assert_eq!(processed[0].enqueued_at_tick, 3);
assert_eq!(processed[1].enqueued_at_tick, 5);
assert_eq!(processed[2].enqueued_at_tick, 7);
}
#[test]
fn test_process_tick_updates_total_processed() {
let mut ctrl = default_controller();
ctrl.admit(make_item(1, FlowPriority::Normal, 100, 0));
ctrl.admit(make_item(2, FlowPriority::Normal, 100, 1));
ctrl.process_tick();
assert_eq!(ctrl.stats().total_processed, 2);
}
#[test]
fn test_process_tick_updates_total_bytes() {
let mut ctrl = default_controller();
ctrl.admit(make_item(1, FlowPriority::Normal, 400, 0));
ctrl.admit(make_item(2, FlowPriority::Normal, 600, 1));
ctrl.process_tick();
assert_eq!(ctrl.stats().total_bytes_processed, 1000);
}
#[test]
fn test_draining_empty_queue_becomes_paused() {
let mut ctrl = default_controller();
ctrl.admit(make_item(1, FlowPriority::Normal, 100, 0));
ctrl.drain();
assert_eq!(ctrl.state, FlowState::Draining);
ctrl.process_tick();
assert_eq!(ctrl.state, FlowState::Paused);
}
#[test]
fn test_pause_sets_paused() {
let mut ctrl = default_controller();
ctrl.pause();
assert_eq!(ctrl.state, FlowState::Paused);
}
#[test]
fn test_resume_from_paused_sets_running() {
let mut ctrl = default_controller();
ctrl.pause();
ctrl.resume();
assert_eq!(ctrl.state, FlowState::Running);
}
#[test]
fn test_resume_from_paused_heavy_queue_sets_throttled() {
let config = FlowControllerConfig {
max_queue_size: 10,
max_bytes_per_tick: 1_048_576,
backpressure_threshold: 0.5,
};
let mut ctrl = TensorFlowController::new(config);
for i in 0..5 {
ctrl.admit(make_item(i, FlowPriority::Normal, 100, i));
}
ctrl.pause();
ctrl.resume();
assert_eq!(ctrl.state, FlowState::Throttled);
}
#[test]
fn test_drain_sets_draining() {
let mut ctrl = default_controller();
ctrl.drain();
assert_eq!(ctrl.state, FlowState::Draining);
}
#[test]
fn test_drop_rate_zero_when_nothing_dropped() {
let ctrl = default_controller();
assert_eq!(ctrl.stats().drop_rate(), 0.0);
}
#[test]
fn test_drop_rate_correct() {
let config = FlowControllerConfig {
max_queue_size: 1,
max_bytes_per_tick: 1_048_576,
backpressure_threshold: 0.99,
};
let mut ctrl = TensorFlowController::new(config);
ctrl.admit(make_item(1, FlowPriority::Normal, 100, 0)); ctrl.admit(make_item(2, FlowPriority::Normal, 100, 1)); let rate = ctrl.stats().drop_rate();
assert!((rate - 0.5).abs() < f64::EPSILON);
}
#[test]
fn test_total_admitted_increments() {
let mut ctrl = default_controller();
ctrl.admit(make_item(1, FlowPriority::Normal, 100, 0));
ctrl.admit(make_item(2, FlowPriority::Normal, 100, 1));
ctrl.admit(make_item(3, FlowPriority::Normal, 100, 2));
assert_eq!(ctrl.stats().total_admitted, 3);
}
#[test]
fn test_total_dropped_increments() {
let mut ctrl = default_controller();
ctrl.pause();
ctrl.admit(make_item(1, FlowPriority::Normal, 100, 0));
ctrl.admit(make_item(2, FlowPriority::Normal, 100, 1));
assert_eq!(ctrl.stats().total_dropped, 2);
}
#[test]
fn test_queue_len_reflects_size() {
let mut ctrl = default_controller();
assert_eq!(ctrl.queue_len(), 0);
ctrl.admit(make_item(1, FlowPriority::Normal, 100, 0));
assert_eq!(ctrl.queue_len(), 1);
ctrl.admit(make_item(2, FlowPriority::Normal, 100, 1));
assert_eq!(ctrl.queue_len(), 2);
ctrl.process_tick();
assert_eq!(ctrl.queue_len(), 0);
}
#[test]
fn test_mixed_priority_ordering() {
let config = FlowControllerConfig {
max_queue_size: 256,
max_bytes_per_tick: 1_048_576,
backpressure_threshold: 0.8,
};
let mut ctrl = TensorFlowController::new(config);
ctrl.admit(make_item(1, FlowPriority::Low, 10, 0));
ctrl.admit(make_item(2, FlowPriority::High, 10, 1));
ctrl.admit(make_item(3, FlowPriority::Normal, 10, 2));
ctrl.admit(make_item(4, FlowPriority::Critical, 10, 3));
let processed = ctrl.process_tick();
assert_eq!(processed[0].priority, FlowPriority::Critical);
assert_eq!(processed[1].priority, FlowPriority::High);
assert_eq!(processed[2].priority, FlowPriority::Normal);
assert_eq!(processed[3].priority, FlowPriority::Low);
}
#[test]
fn test_draining_processes_existing_items() {
let mut ctrl = default_controller();
ctrl.admit(make_item(1, FlowPriority::Normal, 100, 0));
ctrl.admit(make_item(2, FlowPriority::Normal, 100, 1));
ctrl.drain();
let processed = ctrl.process_tick();
assert_eq!(processed.len(), 2);
assert_eq!(ctrl.stats().total_processed, 2);
}
}