use ipfrs_core::Cid;
use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SchedulingPolicy {
Fifo,
ShortestJobFirst,
EarliestDeadlineFirst,
WeightedFairQueueing,
MultilevelFeedback,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum SchedulePriority {
Low = 0,
Normal = 1,
High = 2,
Urgent = 3,
Critical = 4,
}
#[derive(Debug, Clone)]
pub struct ScheduledRequest {
pub cid: Cid,
pub priority: SchedulePriority,
pub estimated_size: Option<usize>,
pub deadline: Option<Instant>,
pub submitted_at: Instant,
pub queue_level: usize,
pub reschedule_count: usize,
}
impl ScheduledRequest {
pub fn new(cid: Cid, priority: SchedulePriority) -> Self {
Self {
cid,
priority,
estimated_size: None,
deadline: None,
submitted_at: Instant::now(),
queue_level: 0,
reschedule_count: 0,
}
}
pub fn with_size(mut self, size: usize) -> Self {
self.estimated_size = Some(size);
self
}
pub fn with_deadline(mut self, deadline: Instant) -> Self {
self.deadline = Some(deadline);
self
}
pub fn urgency_score(&self) -> f64 {
match self.deadline {
Some(deadline) => {
let time_until_deadline = deadline
.duration_since(Instant::now())
.as_secs_f64()
.max(0.0);
1.0 / (1.0 + time_until_deadline)
}
None => 0.0,
}
}
pub fn wait_time(&self) -> Duration {
self.submitted_at.elapsed()
}
pub fn aging_bonus(&self) -> f64 {
let wait_secs = self.wait_time().as_secs_f64();
(wait_secs / 60.0).min(5.0) }
}
struct OrderedRequest {
request: ScheduledRequest,
score: f64,
}
impl PartialEq for OrderedRequest {
fn eq(&self, other: &Self) -> bool {
self.score == other.score
}
}
impl Eq for OrderedRequest {}
impl PartialOrd for OrderedRequest {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for OrderedRequest {
fn cmp(&self, other: &Self) -> Ordering {
self.score
.partial_cmp(&other.score)
.unwrap_or(Ordering::Equal)
}
}
pub struct AdvancedScheduler {
policy: SchedulingPolicy,
queue: Arc<RwLock<BinaryHeap<OrderedRequest>>>,
stats: Arc<RwLock<SchedulerStats>>,
}
#[derive(Debug, Clone, Default)]
pub struct SchedulerStats {
pub total_scheduled: u64,
pub total_completed: u64,
pub avg_wait_time: Duration,
pub avg_completion_time: Duration,
pub deadline_misses: u64,
}
impl SchedulerStats {
pub fn completion_rate(&self) -> f64 {
if self.total_scheduled == 0 {
return 0.0;
}
self.total_completed as f64 / self.total_scheduled as f64
}
pub fn deadline_miss_rate(&self) -> f64 {
if self.total_completed == 0 {
return 0.0;
}
self.deadline_misses as f64 / self.total_completed as f64
}
}
impl AdvancedScheduler {
pub fn new(policy: SchedulingPolicy) -> Self {
Self {
policy,
queue: Arc::new(RwLock::new(BinaryHeap::new())),
stats: Arc::new(RwLock::new(SchedulerStats::default())),
}
}
pub async fn schedule(&self, request: ScheduledRequest) {
let score = self.calculate_score(&request);
let mut queue = self.queue.write().await;
queue.push(OrderedRequest { request, score });
let mut stats = self.stats.write().await;
stats.total_scheduled += 1;
}
pub async fn next(&self) -> Option<ScheduledRequest> {
let mut queue = self.queue.write().await;
queue.pop().map(|ordered| ordered.request)
}
pub async fn peek(&self) -> Option<ScheduledRequest> {
let queue = self.queue.read().await;
queue.peek().map(|ordered| ordered.request.clone())
}
pub async fn mark_completed(&self, request: &ScheduledRequest, completion_time: Duration) {
let mut stats = self.stats.write().await;
stats.total_completed += 1;
let wait_time = request.wait_time();
let total_wait = stats.avg_wait_time.as_millis() as u64 * (stats.total_completed - 1)
+ wait_time.as_millis() as u64;
stats.avg_wait_time = Duration::from_millis(total_wait / stats.total_completed);
let total_completion = stats.avg_completion_time.as_millis() as u64
* (stats.total_completed - 1)
+ completion_time.as_millis() as u64;
stats.avg_completion_time = Duration::from_millis(total_completion / stats.total_completed);
if let Some(deadline) = request.deadline {
if Instant::now() > deadline {
stats.deadline_misses += 1;
}
}
}
fn calculate_score(&self, request: &ScheduledRequest) -> f64 {
match self.policy {
SchedulingPolicy::Fifo => {
-(request.submitted_at.elapsed().as_secs_f64())
}
SchedulingPolicy::ShortestJobFirst => {
match request.estimated_size {
Some(size) => -(size as f64),
None => 0.0, }
}
SchedulingPolicy::EarliestDeadlineFirst => {
request.urgency_score() * 1000.0 + request.priority as u8 as f64
}
SchedulingPolicy::WeightedFairQueueing => {
let priority_score = request.priority as u8 as f64 * 10.0;
let urgency_score = request.urgency_score() * 50.0;
let aging_bonus = request.aging_bonus() * 5.0;
priority_score + urgency_score + aging_bonus
}
SchedulingPolicy::MultilevelFeedback => {
let level_boost = request.queue_level as f64 * 10.0;
let priority_score = request.priority as u8 as f64 * 5.0;
let aging_bonus = request.aging_bonus() * 3.0;
level_boost + priority_score + aging_bonus
}
}
}
pub async fn queue_size(&self) -> usize {
let queue = self.queue.read().await;
queue.len()
}
pub async fn stats(&self) -> SchedulerStats {
self.stats.read().await.clone()
}
pub async fn reset_stats(&self) {
let mut stats = self.stats.write().await;
*stats = SchedulerStats::default();
}
pub async fn clear(&self) {
let mut queue = self.queue.write().await;
queue.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
use multihash::Multihash;
fn test_cid(seed: u64) -> Cid {
let data = seed.to_le_bytes();
let hash = Multihash::wrap(0x12, &data).expect("test: create multihash");
Cid::new_v1(0x55, hash)
}
#[tokio::test]
async fn test_scheduler_creation() {
let scheduler = AdvancedScheduler::new(SchedulingPolicy::Fifo);
assert_eq!(scheduler.queue_size().await, 0);
}
#[tokio::test]
async fn test_fifo_scheduling() {
let scheduler = AdvancedScheduler::new(SchedulingPolicy::Fifo);
let req1 = ScheduledRequest::new(test_cid(1), SchedulePriority::Normal);
let req2 = ScheduledRequest::new(test_cid(2), SchedulePriority::Normal);
scheduler.schedule(req1).await;
tokio::time::sleep(Duration::from_millis(10)).await;
scheduler.schedule(req2).await;
let next = scheduler
.next()
.await
.expect("test: get next scheduled item");
assert_eq!(next.cid, test_cid(1));
}
#[tokio::test]
async fn test_shortest_job_first() {
let scheduler = AdvancedScheduler::new(SchedulingPolicy::ShortestJobFirst);
let req1 = ScheduledRequest::new(test_cid(1), SchedulePriority::Normal).with_size(1000);
let req2 = ScheduledRequest::new(test_cid(2), SchedulePriority::Normal).with_size(500);
scheduler.schedule(req1).await;
scheduler.schedule(req2).await;
let next = scheduler
.next()
.await
.expect("test: get next scheduled item");
assert_eq!(next.cid, test_cid(2));
}
#[tokio::test]
async fn test_earliest_deadline_first() {
let scheduler = AdvancedScheduler::new(SchedulingPolicy::EarliestDeadlineFirst);
let far_deadline = Instant::now() + Duration::from_secs(100);
let near_deadline = Instant::now() + Duration::from_secs(10);
let req1 = ScheduledRequest::new(test_cid(1), SchedulePriority::Normal)
.with_deadline(far_deadline);
let req2 = ScheduledRequest::new(test_cid(2), SchedulePriority::Normal)
.with_deadline(near_deadline);
scheduler.schedule(req1).await;
scheduler.schedule(req2).await;
let next = scheduler
.next()
.await
.expect("test: get next scheduled item");
assert_eq!(next.cid, test_cid(2));
}
#[tokio::test]
async fn test_priority_ordering() {
let scheduler = AdvancedScheduler::new(SchedulingPolicy::WeightedFairQueueing);
let req_low = ScheduledRequest::new(test_cid(1), SchedulePriority::Low);
let req_high = ScheduledRequest::new(test_cid(2), SchedulePriority::Critical);
scheduler.schedule(req_low).await;
scheduler.schedule(req_high).await;
let next = scheduler
.next()
.await
.expect("test: get next scheduled item");
assert_eq!(next.cid, test_cid(2));
}
#[tokio::test]
async fn test_urgency_score() {
let near_deadline = Instant::now() + Duration::from_secs(5);
let far_deadline = Instant::now() + Duration::from_secs(100);
let req1 = ScheduledRequest::new(test_cid(1), SchedulePriority::Normal)
.with_deadline(near_deadline);
let req2 = ScheduledRequest::new(test_cid(2), SchedulePriority::Normal)
.with_deadline(far_deadline);
assert!(req1.urgency_score() > req2.urgency_score());
}
#[tokio::test]
async fn test_aging_bonus() {
let mut req = ScheduledRequest::new(test_cid(1), SchedulePriority::Normal);
req.submitted_at = Instant::now() - Duration::from_secs(120);
let bonus = req.aging_bonus();
assert!(bonus > 0.0);
}
#[tokio::test]
async fn test_mark_completed() {
let scheduler = AdvancedScheduler::new(SchedulingPolicy::Fifo);
let req = ScheduledRequest::new(test_cid(1), SchedulePriority::Normal);
scheduler.schedule(req.clone()).await;
let next = scheduler
.next()
.await
.expect("test: get next scheduled item");
scheduler
.mark_completed(&next, Duration::from_millis(100))
.await;
let stats = scheduler.stats().await;
assert_eq!(stats.total_completed, 1);
assert_eq!(stats.completion_rate(), 1.0);
}
#[tokio::test]
async fn test_deadline_miss_tracking() {
let scheduler = AdvancedScheduler::new(SchedulingPolicy::EarliestDeadlineFirst);
let past_deadline = Instant::now() - Duration::from_secs(1);
let req = ScheduledRequest::new(test_cid(1), SchedulePriority::Normal)
.with_deadline(past_deadline);
scheduler.schedule(req.clone()).await;
let next = scheduler
.next()
.await
.expect("test: get next scheduled item");
scheduler
.mark_completed(&next, Duration::from_millis(100))
.await;
let stats = scheduler.stats().await;
assert_eq!(stats.deadline_misses, 1);
assert_eq!(stats.deadline_miss_rate(), 1.0);
}
#[tokio::test]
async fn test_queue_size() {
let scheduler = AdvancedScheduler::new(SchedulingPolicy::Fifo);
assert_eq!(scheduler.queue_size().await, 0);
scheduler
.schedule(ScheduledRequest::new(test_cid(1), SchedulePriority::Normal))
.await;
assert_eq!(scheduler.queue_size().await, 1);
scheduler
.schedule(ScheduledRequest::new(test_cid(2), SchedulePriority::Normal))
.await;
assert_eq!(scheduler.queue_size().await, 2);
scheduler.next().await;
assert_eq!(scheduler.queue_size().await, 1);
}
#[tokio::test]
async fn test_clear_queue() {
let scheduler = AdvancedScheduler::new(SchedulingPolicy::Fifo);
scheduler
.schedule(ScheduledRequest::new(test_cid(1), SchedulePriority::Normal))
.await;
scheduler
.schedule(ScheduledRequest::new(test_cid(2), SchedulePriority::Normal))
.await;
assert_eq!(scheduler.queue_size().await, 2);
scheduler.clear().await;
assert_eq!(scheduler.queue_size().await, 0);
}
#[tokio::test]
async fn test_peek() {
let scheduler = AdvancedScheduler::new(SchedulingPolicy::Fifo);
let req = ScheduledRequest::new(test_cid(1), SchedulePriority::Normal);
scheduler.schedule(req.clone()).await;
let peeked = scheduler.peek().await.expect("test: peek scheduler");
assert_eq!(peeked.cid, test_cid(1));
assert_eq!(scheduler.queue_size().await, 1);
}
#[tokio::test]
async fn test_stats_reset() {
let scheduler = AdvancedScheduler::new(SchedulingPolicy::Fifo);
let req = ScheduledRequest::new(test_cid(1), SchedulePriority::Normal);
scheduler.schedule(req.clone()).await;
let next = scheduler
.next()
.await
.expect("test: get next scheduled item");
scheduler
.mark_completed(&next, Duration::from_millis(100))
.await;
let stats = scheduler.stats().await;
assert!(stats.total_completed > 0);
scheduler.reset_stats().await;
let stats = scheduler.stats().await;
assert_eq!(stats.total_completed, 0);
}
#[tokio::test]
async fn test_multilevel_feedback() {
let scheduler = AdvancedScheduler::new(SchedulingPolicy::MultilevelFeedback);
let mut req1 = ScheduledRequest::new(test_cid(1), SchedulePriority::Normal);
req1.queue_level = 2;
let req2 = ScheduledRequest::new(test_cid(2), SchedulePriority::High);
scheduler.schedule(req1).await;
scheduler.schedule(req2).await;
let next = scheduler
.next()
.await
.expect("test: get next scheduled item");
assert!(next.cid == test_cid(1) || next.cid == test_cid(2));
}
}