/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 | | } |