use query_planning::{
admission::{AttemptCapabilityEvidence, ExecutionCapability},
error::Result,
};
use super::{DeviceAdmissionHealth, DeviceLedger, DeviceResourceProfile};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub(crate) struct PlacementRequest {
pub(crate) requested_query_cap_bytes: Option<u64>,
pub(crate) attempt_requirement: Option<AttemptCapabilityEvidence>,
pub(crate) required_capabilities: Vec<ExecutionCapability>,
}
impl PlacementRequest {
pub(crate) fn new(query_cap_bytes: u64) -> Self {
Self {
requested_query_cap_bytes: (query_cap_bytes > 0).then_some(query_cap_bytes),
attempt_requirement: None,
required_capabilities: Vec::new(),
}
}
pub(crate) fn with_attempt_requirement(mut self, attempt_requirement: AttemptCapabilityEvidence) -> Self {
self.attempt_requirement = Some(attempt_requirement);
self
}
pub(crate) fn with_required_capabilities(
mut self,
required_capabilities: impl IntoIterator<Item = ExecutionCapability>,
) -> Self {
self.required_capabilities = required_capabilities.into_iter().collect();
self
}
pub(crate) fn with_requested_query_cap_bytes(mut self, requested_query_cap_bytes: Option<u64>) -> Self {
self.requested_query_cap_bytes = requested_query_cap_bytes;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct PlacementCandidate {
pub(crate) ordinal: usize,
pub(crate) query_cap_bytes: u64,
pub(crate) spare_capacity_bytes: u64,
pub(crate) active_attempts: usize,
pub(crate) live_grants_bytes: u64,
pub(crate) worker_depth: usize,
pub(crate) stream_slots_available: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct PlacementCandidateSnapshot {
pub(crate) candidate: PlacementCandidate,
pub(crate) can_admit_now: bool,
pub(crate) can_ever_fit_query_cap: bool,
}
impl PlacementCandidate {
fn spare_after(&self) -> u64 {
self.spare_capacity_bytes
}
}
pub(crate) fn compare_placement_candidates(
left: &PlacementCandidate,
right: &PlacementCandidate,
) -> std::cmp::Ordering {
right
.spare_after()
.cmp(&left.spare_after())
.then_with(|| left.active_attempts.cmp(&right.active_attempts))
.then_with(|| left.live_grants_bytes.cmp(&right.live_grants_bytes))
.then_with(|| left.worker_depth.cmp(&right.worker_depth))
.then_with(|| right.stream_slots_available.cmp(&left.stream_slots_available))
.then_with(|| left.ordinal.cmp(&right.ordinal))
}
#[cfg(test)]
pub(crate) fn rank_placement_candidates(mut candidates: Vec<PlacementCandidate>, query_cap_bytes: u64) -> Vec<usize> {
for candidate in &mut candidates {
candidate.query_cap_bytes = query_cap_bytes;
}
candidates.sort_by(compare_placement_candidates);
candidates.into_iter().map(|candidate| candidate.ordinal).collect()
}
pub(crate) fn placement_candidate_snapshot(
ledger: &DeviceLedger,
request: &PlacementRequest,
) -> Result<Option<PlacementCandidateSnapshot>> {
if !profile_supports_placement_request(ledger.profile(), &request.required_capabilities) {
return Ok(None);
}
let snapshot = ledger.snapshot()?;
if !matches!(snapshot.health, DeviceAdmissionHealth::Open) {
return Ok(None);
}
let query_cap_bytes = request.requested_query_cap_bytes.unwrap_or_else(|| {
snapshot
.default_query_limit_bytes
.min(snapshot.query_share_bytes.saturating_sub(snapshot.frozen_resident_bytes))
});
let can_ever_fit_query_cap = snapshot.can_ever_fit_query_cap(request.requested_query_cap_bytes);
let admitted_cap = snapshot.admissible_query_cap_bytes(request.requested_query_cap_bytes);
let spare_capacity_bytes = admitted_cap.map_or(0, |cap| {
snapshot
.query_share_bytes
.saturating_sub(snapshot.frozen_resident_bytes)
.saturating_sub(snapshot.live_grants_bytes)
.saturating_sub(cap)
});
Ok(Some(PlacementCandidateSnapshot {
candidate: PlacementCandidate {
ordinal: ledger.device_ordinal(),
query_cap_bytes,
spare_capacity_bytes,
active_attempts: snapshot.active_attempts,
live_grants_bytes: snapshot.live_grants_bytes,
worker_depth: snapshot.worker_depth,
stream_slots_available: snapshot.stream_slots_available(),
},
can_admit_now: admitted_cap.is_some(),
can_ever_fit_query_cap,
}))
}
pub(crate) fn compare_placement_candidate_snapshots(
left: &PlacementCandidateSnapshot,
right: &PlacementCandidateSnapshot,
) -> std::cmp::Ordering {
right
.can_admit_now
.cmp(&left.can_admit_now)
.then_with(|| compare_placement_candidates(&left.candidate, &right.candidate))
}
fn profile_supports_placement_request(
profile: &DeviceResourceProfile,
required_capabilities: &[ExecutionCapability],
) -> bool {
profile.device_capabilities.supports_all(required_capabilities)
}
#[cfg(test)]
mod tests {
use query_planning::admission::ExecutionCapability;
use super::*;
use crate::runtime::{DeviceCapabilities, DeviceCapability, gpu::DeviceResourceEnvelope};
fn candidate(
ordinal: usize,
max_new_grant: u64,
active: usize,
live: u64,
depth: usize,
streams: usize,
) -> PlacementCandidate {
PlacementCandidate {
ordinal,
query_cap_bytes: 0,
spare_capacity_bytes: max_new_grant,
active_attempts: active,
live_grants_bytes: live,
worker_depth: depth,
stream_slots_available: streams,
}
}
#[test]
fn placement_ranking_prefers_spare_then_active_then_grants_then_depth_then_streams_then_ordinal() {
let ranked = rank_placement_candidates(
vec![
candidate(2, 100, 0, 0, 0, 10),
candidate(1, 200, 2, 10, 0, 10),
candidate(0, 200, 1, 50, 0, 10),
candidate(3, 200, 1, 50, 0, 20),
candidate(4, 200, 1, 40, 1, 30),
],
50,
);
assert_eq!(ranked, vec![4, 3, 0, 1, 2]);
}
#[test]
fn capability_gate_excludes_incapable_profiles_before_placement_ranking() {
let requirement = ExecutionCapability::new("test_operator");
let incapable = DeviceResourceProfile::resolve(DeviceResourceEnvelope {
device_ordinal: 0,
configured_limit_bytes: 1_000,
headroom_bytes: 0,
backend_reserve_bytes: 0,
default_query_limit_bytes: 100,
max_active_attempts: 1,
source_work_per_attempt: 1,
stream_pool_size: 2,
worker_job_capacity: 2,
device_capabilities: DeviceCapabilities::only(std::iter::empty::<DeviceCapability>()),
cache_cap_bytes: 0,
})
.unwrap();
let capable = DeviceResourceProfile::resolve(DeviceResourceEnvelope {
device_ordinal: 1,
configured_limit_bytes: 1_000,
headroom_bytes: 0,
backend_reserve_bytes: 0,
default_query_limit_bytes: 100,
max_active_attempts: 1,
source_work_per_attempt: 1,
stream_pool_size: 2,
worker_job_capacity: 2,
device_capabilities: DeviceCapabilities::only([DeviceCapability::new("test_operator")]),
cache_cap_bytes: 0,
})
.unwrap();
assert!(!profile_supports_placement_request(&incapable, std::slice::from_ref(&requirement)));
assert!(profile_supports_placement_request(&capable, &[requirement]));
}
}