## Coverage Report

#### Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /home/noah/src/realizar/src/gpu/batch_scheduling.rs

    Line
    Count
    Source
    1
    //! Batch Scheduling & Async Processing (PMAT-802)
    2
    //!
    3
    //! M25: Token Batching & Speculative Decoding
    4
    //! M26: Async I/O & Event-Driven Processing
    5
    //! M27: Request Scheduling & Resource Management
    6
    7
    8
    9
    // =============================================================================
    10
    // M25: Token Batching & Speculative Decoding (Phase 16)
    11
    // =============================================================================
    12
    13
    /// Token batch accumulator for batched processing (M25 - IMP-058)
    14
    ///
    15
    /// Accumulates tokens until batch is full, then returns for processing.
    16
    /// Improves throughput by processing multiple tokens together.
    17
    #[derive(Debug)]
    18
    pub struct TokenBatch {
    19
        tokens: Vec<usize>,
    20
        capacity: usize,
    21
    }
    22
    23
    impl TokenBatch {
    24
        /// Create a new token batch with given capacity
    25
        #[must_use]
    26
    7
        pub fn new(capacity: usize) -> Self {
    27
    7
            Self {
    28
    7
                tokens: Vec::with_capacity(capacity),
    29
    7
                capacity,
    30
    7
            }
    31
    7
        }
    32
    33
        /// Get the batch capacity
    34
        #[must_use]
    35
    3
        pub fn capacity(&self) -> usize {
    36
    3
            self.capacity
    37
    3
        }
    38
    39
        /// Get current number of tokens in batch
    40
        #[must_use]
    41
    7
        pub fn len(&self) -> usize {
    42
    7
            self.tokens.len()
    43
    7
        }
    44
    45
        /// Check if batch is empty
    46
        #[must_use]
    47
    4
        pub fn is_empty(&self) -> bool {
    48
    4
            self.tokens.is_empty()
    49
    4
        }
    50
    51
        /// Check if batch is full
    52
        #[must_use]
    53
    21
        pub fn is_full(&self) -> bool {
    54
    21
            self.tokens.len() >= self.capacity
    55
    21
        }
    56
    57
        /// Push a token to the batch
    58
        ///
    59
        /// Returns `Some(tokens)` when batch becomes full, `None` otherwise.
    60
    18
        pub fn push(&mut self, token: usize) -> Option<Vec<usize>> {
    61
    18
            self.tokens.push(token);
    62
    18
            if self.is_full() {
    63
    3
                Some(self.flush())
    64
            } else {
    65
    15
                None
    66
            }
    67
    18
        }
    68
    69
        /// Flush and return all tokens, clearing the batch
    70
    6
        pub fn flush(&mut self) -> Vec<usize> {
    71
    6
            std::mem::take(&mut self.tokens)
    72
    6
        }
    73
    }
    74
    75
    /// Candidate token for speculative decoding
    76
    #[derive(Debug, Clone)]
    77
    struct SpeculativeCandidate {
    78
        token: usize,
    79
        /// Confidence score (stored for future use in acceptance thresholds)
    80
        #[allow(dead_code)]
    81
        confidence: f32,
    82
    }
    83
    84
    /// Speculative token buffer for speculative decoding (M25 - IMP-059)
    85
    ///
    86
    /// Manages candidate tokens generated speculatively, allowing verification
    87
    /// against actual model outputs for acceptance or rejection.
    88
    #[derive(Debug)]
    89
    pub struct SpeculativeBuffer {
    90
        candidates: Vec<SpeculativeCandidate>,
    91
        capacity: usize,
    92
    }
    93
    94
    impl SpeculativeBuffer {
    95
        /// Create a new speculative buffer with given capacity
    96
        #[must_use]
    97
    5
        pub fn new(capacity: usize) -> Self {
    98
    5
            Self {
    99
    5
                candidates: Vec::with_capacity(capacity),
    100
    5
                capacity,
    101
    5
            }
    102
    5
        }
    103
    104
        /// Get the buffer capacity
    105
        #[must_use]
    106
    1
        pub fn capacity(&self) -> usize {
    107
    1
            self.capacity
    108
    1
        }
    109
    110
        /// Get current number of candidates
    111
        #[must_use]
    112
    5
        pub fn len(&self) -> usize {
    113
    5
            self.candidates.len()
    114
    5
        }
    115
    116
        /// Check if buffer is empty
    117
        #[must_use]
    118
    1
        pub fn is_empty(&self) -> bool {
    119
    1
            self.candidates.is_empty()
    120
    1
        }
    121
    122
        /// Add a candidate token with confidence score
    123
    17
        pub fn add_candidate(&mut self, token: usize, confidence: f32) {
    124
    17
            if self.candidates.len() < self.capacity {
    125
    17
                self.candidates
    126
    17
                    .push(SpeculativeCandidate { token, confidence });
    127
    17
            }0
    128
    17
        }
    129
    130
        /// Verify candidates against actual tokens
    131
        ///
    132
        /// Returns (num_accepted, rejection_index) where rejection_index is
    133
        /// the first index where mismatch occurred, or None if all matched.
    134
        #[must_use]
    135
    4
        pub fn verify(&self, actual_tokens: &[usize]) -> (usize, Option<usize>) {
    136
    4
            let mut accepted = 0;
    137
    12
            for (i, candidate) in self.candidates.iter()4.enumerate4() {
    138
    12
                if i < actual_tokens.len() && candidate.token == actual_tokens[i] {
    139
    10
                    accepted += 1;
    140
    10
                } else {
    141
    2
                    return (accepted, Some(i));
    142
                }
    143
            }
    144
    2
            (accepted, None)
    145
    4
        }
    146
    147
        /// Accept first n candidates, removing them from buffer
    148
    2
        pub fn accept(&mut self, n: usize) {
    149
    2
            if n >= self.candidates.len() {
    150
    0
                self.candidates.clear();
    151
    2
            } else {
    152
    2
                self.candidates.drain(0..n);
    153
    2
            }
    154
    2
        }
    155
    156
        /// Reject all remaining candidates
    157
    4
        pub fn reject(&mut self) {
    158
    4
            self.candidates.clear();
    159
    4
        }
    160
    }
    161
    162
    /// Batch ID for tracking inference batches
    163
    pub type BatchId = u64;
    164
    165
    /// Inference batch scheduler for coordinating batched processing (M25 - IMP-060)
    166
    ///
    167
    /// Manages pending and completed batches, allowing asynchronous batch
    168
    /// submission and result retrieval.
    169
    #[derive(Debug)]
    170
    pub struct InferenceBatchScheduler {
    171
        next_id: BatchId,
    172
        pending: std::collections::HashMap<BatchId, Vec<usize>>,
    173
        completed: std::collections::VecDeque<(BatchId, Vec<usize>)>,
    174
    }
    175
    176
    impl InferenceBatchScheduler {
    177
        /// Create a new inference batch scheduler
    178
        #[must_use]
    179
    3
        pub fn new() -> Self {
    180
    3
            Self {
    181
    3
                next_id: 0,
    182
    3
                pending: std::collections::HashMap::new(),
    183
    3
                completed: std::collections::VecDeque::new(),
    184
    3
            }
    185
    3
        }
    186
    187
        /// Get count of pending batches
    188
        #[must_use]
    189
    5
        pub fn pending_count(&self) -> usize {
    190
    5
            self.pending.len()
    191
    5
        }
    192
    193
        /// Get count of completed batches
    194
        #[must_use]
    195
    5
        pub fn completed_count(&self) -> usize {
    196
    5
            self.completed.len()
    197
    5
        }
    198
    199
        /// Submit a batch for processing
    200
        ///
    201
        /// Returns a unique batch ID for tracking.
    202
    4
        pub fn submit(&mut self, tokens: Vec<usize>) -> BatchId {
    203
    4
            let id = self.next_id;
    204
    4
            self.next_id += 1;
    205
    4
            self.pending.insert(id, tokens);
    206
    4
            id
    207
    4
        }
    208
    209
        /// Mark a batch as complete with results
    210
    4
        pub fn complete(&mut self, batch_id: BatchId, results: Vec<usize>) {
    211
    4
            self.pending.remove(&batch_id);
    212
    4
            self.completed.push_back((batch_id, results));
    213
    4
        }
    214
    215
        /// Poll for a completed batch
    216
        ///
    217
        /// Returns `Some((batch_id, results))` if a batch is ready, `None` otherwise.
    218
    3
        pub fn poll(&mut self) -> Option<(BatchId, Vec<usize>)> {
    219
    3
            self.completed.pop_front()
    220
    3
        }
    221
    222
        /// Drain all completed batches
    223
    2
        pub fn drain(&mut self) -> Vec<(BatchId, Vec<usize>)> {
    224
    2
            self.completed.drain(..).collect()
    225
    2
        }
    226
    }
    227
    228
    impl Default for InferenceBatchScheduler {
    229
    0
        fn default() -> Self {
    230
    0
            Self::new()
    231
    0
        }
    232
    }
    233
    234
    // =============================================================================
    235
    // M26: Async I/O & Event-Driven Processing (Phase 17)
    236
    // =============================================================================
    237
    238
    /// Async request queue for non-blocking request handling (M26 - IMP-061)
    239
    ///
    240
    /// Provides a bounded FIFO queue for inference requests with backpressure
    241
    /// support via try-based operations.
    242
    #[derive(Debug)]
    243
    pub struct AsyncRequestQueue<T> {
    244
        items: std::collections::VecDeque<T>,
    245
        capacity: usize,
    246
    }
    247
    248
    impl<T> AsyncRequestQueue<T> {
    249
        /// Create a new async request queue with specified capacity
    250
        #[must_use]
    251
    3
        pub fn new(capacity: usize) -> Self {
    252
    3
            Self {
    253
    3
                items: std::collections::VecDeque::with_capacity(capacity),
    254
    3
                capacity,
    255
    3
            }
    256
    3
        }
    257
    258
        /// Get queue capacity
    259
        #[must_use]
    260
    1
        pub fn capacity(&self) -> usize {
    261
    1
            self.capacity
    262
    1
        }
    263
    264
        /// Get current queue length
    265
        #[must_use]
    266
    2
        pub fn len(&self) -> usize {
    267
    2
            self.items.len()
    268
    2
        }
    269
    270
        /// Check if queue is empty
    271
        #[must_use]
    272
    3
        pub fn is_empty(&self) -> bool {
    273
    3
            self.items.is_empty()
    274
    3
        }
    275
    276
        /// Check if queue is full
    277
        #[must_use]
    278
    13
        pub fn is_full(&self) -> bool {
    279
    13
            self.items.len() >= self.capacity
    280
    13
        }
    281
    282
        /// Try to push an item to the queue
    283
        ///
    284
        /// Returns `true` if successful, `false` if queue is full (backpressure).
    285
    9
        pub fn try_push(&mut self, item: T) -> bool {
    286
    9
            if self.is_full() {
    287
    2
                false
    288
            } else {
    289
    7
                self.items.push_back(item);
    290
    7
                true
    291
            }
    292
    9
        }
    293
    294
        /// Try to pop an item from the queue
    295
        ///
    296
        /// Returns `Some(item)` if available, `None` if queue is empty.
    297
    5
        pub fn try_pop(&mut self) -> Option<T> {
    298
    5
            self.items.pop_front()
    299
    5
        }
    300
    }
    301
    302
    /// Type alias for inference completion handler
    303
    pub type InferenceCompletionHandler = Box<dyn Fn(u64, &[usize]) + Send + Sync>;
    304
    305
    /// Event notifier for inference completion (M26 - IMP-062)
    306
    ///
    307
    /// Allows registration of handlers that are called when inference completes.
    308
    pub struct InferenceEventNotifier {
    309
        handlers: Vec<InferenceCompletionHandler>,
    310
    }
    311
    312
    impl std::fmt::Debug for InferenceEventNotifier {
    313
    1
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    314
    1
            f.debug_struct("InferenceEventNotifier")
    315
    1
                .field("handler_count", &self.handlers.len())
    316
    1
                .finish()
    317
    1
        }
    318
    }
    319
    320
    impl InferenceEventNotifier {
    321
        /// Create a new event notifier
    322
        #[must_use]
    323
    3
        pub fn new() -> Self {
    324
    3
            Self {
    325
    3
                handlers: Vec::new(),
    326
    3
            }
    327
    3
        }
    328
    329
        /// Get count of registered handlers
    330
        #[must_use]
    331
    6
        pub fn handler_count(&self) -> usize {
    332
    6
            self.handlers.len()
    333
    6
        }
    334
    335
        /// Register a completion handler
    336
        ///
    337
        /// Handler receives (request_id, output_tokens) when inference completes.
    338
    5
        pub fn register(&mut self, handler: InferenceCompletionHandler) {
    339
    5
            self.handlers.push(handler);
    340
    5
        }
    341
    342
        /// Notify all handlers of completion
    343
        ///
    344
        /// Calls each registered handler with the request ID and output tokens.
    345
    4
        pub fn notify(&self, request_id: u64, tokens: &[usize]) {
    346
    11
            for handler7 in &self.handlers {
    347
    7
                handler(request_id, tokens);
    348
    7
            }
    349
    4
        }
    350
    351
        /// Clear all registered handlers
    352
    2
        pub fn clear(&mut self) {
    353
    2
            self.handlers.clear();
    354
    2
        }
    355
    }
    356
    357
    impl Default for InferenceEventNotifier {
    358
    0
        fn default() -> Self {
    359
    0
            Self::new()
    360
    0
        }
    361
    }
    362
    363
    /// Request ID type for timeout tracking
    364
    pub type RequestId = u64;
    365
    366
    /// Timeout manager for request deadline tracking (M26 - IMP-063)
    367
    ///
    368
    /// Tracks request deadlines and identifies expired requests.
    369
    #[derive(Debug)]
    370
    pub struct TimeoutManager {
    371
        deadlines: std::collections::HashMap<RequestId, std::time::Instant>,
    372
    }
    373
    374
    impl TimeoutManager {
    375
        /// Create a new timeout manager
    376
        #[must_use]
    377
    3
        pub fn new() -> Self {
    378
    3
            Self {
    379
    3
                deadlines: std::collections::HashMap::new(),
    380
    3
            }
    381
    3
        }
    382
    383
        /// Get count of active timeout registrations
    384
        #[must_use]
    385
    7
        pub fn active_count(&self) -> usize {
    386
    7
            self.deadlines.len()
    387
    7
        }
    388
    389
        /// Register a timeout for a request
    390
        ///
    391
        /// The deadline is the absolute time at which the request should timeout.
    392
    4
        pub fn register(&mut self, request_id: RequestId, deadline: std::time::Instant) {
    393
    4
            self.deadlines.insert(request_id, deadline);
    394
    4
        }
    395
    396
        /// Remove timeout registration for a request
    397
        ///
    398
        /// Use when request completes before timeout.
    399
    2
        pub fn remove(&mut self, request_id: RequestId) {
    400
    2
            self.deadlines.remove(&request_id);
    401
    2
        }
    402
    403
        /// Check for expired requests and remove them
    404
        ///
    405
        /// Returns list of request IDs that have timed out.
    406
    3
        pub fn check_expired(&mut self) -> Vec<RequestId> {
    407
    3
            let now = std::time::Instant::now();
    408
    3
            let expired: Vec<RequestId> = self
    409
    3
                .deadlines
    410
    3
                .iter()
    411
    4
                .filter3(|(_, &deadline)| now >= deadline)
    412
    3
                .map(|(&id, _)| id)
    413
    3
                .collect();
    414
    415
    5
            for id2 in &expired {
    416
    2
                self.deadlines.remove(id);
    417
    2
            }
    418
    419
    3
            expired
    420
    3
        }
    421
    }
    422
    423
    impl Default for TimeoutManager {
    424
    0
        fn default() -> Self {
    425
    0
            Self::new()
    426
    0
        }
    427
    }
    428
    429
    // =============================================================================
    430
    // M27: Request Scheduling & Resource Management (Phase 18)
    431
    // =============================================================================
    432
    433
    /// Priority level type (higher = more important)
    434
    pub type Priority = u32;
    435
    436
    /// Priority request wrapper for priority queue (M27 - IMP-064)
    437
    #[derive(Debug, Clone)]
    438
    pub struct PriorityRequest<T> {
    439
        priority: Priority,
    440
        sequence: u64, // For FIFO ordering within same priority
    441
        data: T,
    442
    }
    443
    444
    impl<T> PriorityRequest<T> {
    445
        /// Create a new priority request
    446
        #[must_use]
    447
    13
        pub fn new(priority: Priority, data: T) -> Self {
    448
    13
            Self {
    449
    13
                priority,
    450
    13
                sequence: 0, // Will be set by queue
    451
    13
                data,
    452
    13
            }
    453
    13
        }
    454
    455
        /// Get the priority level
    456
        #[must_use]
    457
    0
        pub fn priority(&self) -> Priority {
    458
    0
            self.priority
    459
    0
        }
    460
    461
        /// Get reference to request data
    462
        #[must_use]
    463
    6
        pub fn data(&self) -> &T {
    464
    6
            &self.data
    465
    6
        }
    466
    467
        /// Consume and return the data
    468
        #[must_use]
    469
    6
        pub fn into_data(self) -> T {
    470
    6
            self.data
    471
    6
        }
    472
    }
    473
    474
    /// Priority request queue for request scheduling (M27 - IMP-064)
    475
    ///
    476
    /// Implements priority-based scheduling with FIFO ordering for same-priority requests.
    477
    #[derive(Debug)]
    478
    pub struct PriorityRequestQueue<T> {
    479
        items: Vec<PriorityRequest<T>>,
    480
        next_sequence: u64,
    481
    }
    482
    483
    impl<T> PriorityRequestQueue<T> {
    484
        /// Create a new priority request queue
    485
        #[must_use]
    486
    4
        pub fn new() -> Self {
    487
    4
            Self {
    488
    4
                items: Vec::new(),
    489
    4
                next_sequence: 0,
    490
    4
            }
    491
    4
        }
    492
    493
        /// Get number of items in queue
    494
        #[must_use]
    495
    2
        pub fn len(&self) -> usize {
    496
    2
            self.items.len()
    497
    2
        }
    498
    499
        /// Check if queue is empty
    500
        #[must_use]
    501
    3
        pub fn is_empty(&self) -> bool {
    502
    3
            self.items.is_empty()
    503
    3
        }
    504
    505
        /// Enqueue a request with priority
    506
    12
        pub fn enqueue(&mut self, mut request: PriorityRequest<T>) {
    507
    12
            request.sequence = self.next_sequence;
    508
    12
            self.next_sequence += 1;
    509
    12
            self.items.push(request);
    510
    12
        }
    511
    512
        /// Dequeue the highest priority request
    513
        ///
    514
        /// Returns the request with highest priority. For equal priorities,
    515
        /// returns the earliest enqueued (FIFO).
    516
    13
        pub fn dequeue_highest(&mut self) -> Option<PriorityRequest<T>> {
    517
    13
            if self.items.is_empty() {
    518
    1
                return None;
    519
    12
            }
    520
    521
            // Find index of highest priority (and earliest sequence for ties)
    522
    12
            let mut best_idx = 0;
    523
    12
            for (i, item) in self.items.iter().enumerate().skip(1) {
    524
    12
                let best = &self.items[best_idx];
    525
    12
                if item.priority > best.priority
    526
    8
                    || (item.priority == best.priority && item.sequence < best.sequence6)
    527
    6
                {
    528
    6
                    best_idx = i;
    529
    6
                }
    530
            }
    531
    532
    12
            Some(self.items.swap_remove(best_idx))
    533
    13
        }
    534
    }
    535
    536
    impl<T> Default for PriorityRequestQueue<T> {
    537
    0
        fn default() -> Self {
    538
    0
            Self::new()
    539
    0
        }
    540
    }
    541
    542
    /// Token bucket rate limiter for throughput control (M27 - IMP-065)
    543
    ///
    544
    /// Implements token bucket algorithm with configurable rate and burst capacity.
    545
    #[derive(Debug)]
    546
    pub struct TokenRateLimiter {
    547
        tokens: u32,
    548
        capacity: u32,
    549
        rate: f64, // tokens per second
    550
        last_refill: std::time::Instant,
    551
    }
    552
    553
    impl TokenRateLimiter {
    554
        /// Create a new rate limiter
    555
        ///
    556
        /// # Arguments
    557
        /// * `rate` - Tokens per second to refill
    558
        /// * `burst_capacity` - Maximum tokens that can accumulate
    559
        #[must_use]
    560
    3
        pub fn new(rate: f64, burst_capacity: u32) -> Self {
    561
    3
            Self {
    562
    3
                tokens: burst_capacity, // Start full
    563
    3
                capacity: burst_capacity,
    564
    3
                rate,
    565
    3
                last_refill: std::time::Instant::now(),
    566
    3
            }
    567
    3
        }
    568
    569
        /// Get current available tokens
    570
        #[must_use]
    571
    9
        pub fn tokens_available(&self) -> u32 {
    572
    9
            self.tokens
    573
    9
        }
    574
    575
        /// Try to acquire tokens
    576
        ///
    577
        /// Returns `true` if tokens were acquired, `false` if insufficient tokens.
    578
    6
        pub fn try_acquire(&mut self, count: u32) -> bool {
    579
    6
            if self.tokens >= count {
    580
    4
                self.tokens -= count;
    581
    4
                true
    582
            } else {
    583
    2
                false
    584
            }
    585
    6
        }
    586
    587
        /// Refill tokens based on elapsed time
    588
        ///
    589
        /// Call periodically to add tokens at the configured rate.
    590
    1
        pub fn refill(&mut self) {
    591
    1
            let now = std::time::Instant::now();
    592
    1
            let elapsed = now.duration_since(self.last_refill).as_secs_f64();
    593
    1
            let new_tokens = (elapsed * self.rate) as u32;
    594
    595
    1
            if new_tokens > 0 {
    596
    1
                self.tokens = (self.tokens + new_tokens).min(self.capacity);
    597
    1
                self.last_refill = now;
    598
    1
            }0
    599
    1
        }
    600
    }
    601
    602
    /// Allocation ID for resource tracking
    603
    pub type AllocationId = u64;
    604
    605
    /// Resource allocation record
    606
    #[derive(Debug, Clone)]
    607
    struct ResourceAllocation {
    608
        memory: u64,
    609
        compute: u32,
    610
    }
    611
    612
    /// Resource usage tracker for memory and compute (M27 - IMP-066)
    613
    ///
    614
    /// Tracks resource allocations and provides utilization metrics.
    615
    #[derive(Debug)]
    616
    pub struct ResourceTracker {
    617
        memory_capacity: u64,
    618
        compute_capacity: u32,
    619
        memory_used: u64,
    620
        compute_used: u32,
    621
        allocations: std::collections::HashMap<AllocationId, ResourceAllocation>,
    622
        next_id: AllocationId,
    623
    }
    624
    625
    impl ResourceTracker {
    626
        /// Create a new resource tracker
    627
        ///
    628
        /// # Arguments
    629
        /// * `memory_capacity` - Total memory capacity in bytes
    630
        /// * `compute_capacity` - Total compute capacity (0-100 percentage)
    631
        #[must_use]
    632
    5
        pub fn new(memory_capacity: u64, compute_capacity: u32) -> Self {
    633
    5
            Self {
    634
    5
                memory_capacity,
    635
    5
                compute_capacity,
    636
    5
                memory_used: 0,
    637
    5
                compute_used: 0,
    638
    5
                allocations: std::collections::HashMap::new(),
    639
    5
                next_id: 0,
    640
    5
            }
    641
    5
        }
    642
    643
        /// Get current memory usage in bytes
    644
        #[must_use]
    645
    6
        pub fn memory_usage(&self) -> u64 {
    646
    6
            self.memory_used
    647
    6
        }
    648
    649
        /// Get current compute usage (0-100)
    650
        #[must_use]
    651
    6
        pub fn compute_usage(&self) -> u32 {
    652
    6
            self.compute_used
    653
    6
        }
    654
    655
        /// Check if allocation is possible
    656
        #[must_use]
    657
    10
        pub fn can_allocate(&self, memory: u64, compute: u32) -> bool {
    658
    10
            self.memory_used + memory <= self.memory_capacity
    659
    7
                && self.compute_used + compute <= self.compute_capacity
    660
    10
        }
    661
    662
        /// Allocate resources
    663
        ///
    664
        /// Returns allocation ID if successful, None if insufficient resources.
    665
    5
        pub fn allocate(&mut self, memory: u64, compute: u32) -> Option<AllocationId> {
    666
    5
            if !self.can_allocate(memory, compute) {
    667
    1
                return None;
    668
    4
            }
    669
    670
    4
            let id = self.next_id;
    671
    4
            self.next_id += 1;
    672
    673
    4
            self.memory_used += memory;
    674
    4
            self.compute_used += compute;
    675
    4
            self.allocations
    676
    4
                .insert(id, ResourceAllocation { memory, compute });
    677
    678
    4
            Some(id)
    679
    5
        }
    680
    681
        /// Release allocated resources
    682
    2
        pub fn release(&mut self, id: AllocationId) {
    683
    2
            if let Some(alloc) = self.allocations.remove(&id) {
    684
    2
                self.memory_used = self.memory_used.saturating_sub(alloc.memory);
    685
    2
                self.compute_used = self.compute_used.saturating_sub(alloc.compute);
    686
    2
            }0
    687
    2
        }
    688
    689
        /// Get usage as percentages
    690
        ///
    691
        /// Returns (memory_percentage, compute_percentage)
    692
        #[must_use]
    693
    3
        pub fn usage_percentage(&self) -> (f64, f64) {
    694
    3
            let mem_pct = if self.memory_capacity > 0 {
    695
    2
                (self.memory_used as f64 / self.memory_capacity as f64) * 100.0
    696
            } else {
    697
    1
                0.0
    698
            };
    699
    3
            let compute_pct = if self.compute_capacity > 0 {
    700
    2
                (self.compute_used as f64 / self.compute_capacity as f64) * 100.0
    701
            } else {
    702
    1
                0.0
    703
            };
    704
    3
            (mem_pct, compute_pct)
    705
    3
        }
    706
    }
    707
    708
    impl Default for ResourceTracker {
    709
    0
        fn default() -> Self {
    710
            // Default: 8GB memory, 100% compute
    711
    0
            Self::new(8 * 1024 * 1024 * 1024, 100)
    712
    0
        }
    713
    }