use serde::{Deserialize, Serialize};
use super::super::PlacementTypeError;
use super::spec::Execution;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Constraint {
Requirements,
Provenance,
Memory,
Trust,
Liveness,
FactsExpired,
NodeRevoked,
WorkloadRevoked,
CoResidency,
EmulationNotAllowed,
Gate,
PerfBudget,
}
impl Constraint {
pub fn as_str(self) -> &'static str {
match self {
Constraint::Requirements => "requirements",
Constraint::Provenance => "provenance",
Constraint::Memory => "memory",
Constraint::Trust => "trust",
Constraint::Liveness => "liveness",
Constraint::FactsExpired => "facts_expired",
Constraint::NodeRevoked => "node_revoked",
Constraint::WorkloadRevoked => "workload_revoked",
Constraint::CoResidency => "co_residency",
Constraint::EmulationNotAllowed => "emulation_not_allowed",
Constraint::Gate => "gate",
Constraint::PerfBudget => "perf_budget",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Rejection {
pub constraint: Constraint,
pub detail: String,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ScoreBreakdown {
pub execution: f64,
pub locality: f64,
pub accel_fit: f64,
pub perf: f64,
pub load: f64,
pub stickiness: f64,
}
impl ScoreBreakdown {
pub fn total(&self) -> f64 {
self.execution + self.locality + self.accel_fit + self.perf + self.load + self.stickiness
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Tier {
Native,
DevFallback,
Emulated,
}
impl Tier {
pub fn as_str(self) -> &'static str {
match self {
Tier::Native => "native",
Tier::DevFallback => "dev_fallback",
Tier::Emulated => "emulated",
}
}
pub fn rank(self) -> u8 {
match self {
Tier::Native => 0,
Tier::Emulated => 1,
Tier::DevFallback => 2,
}
}
pub const ORDER: [Tier; 3] = [Tier::Native, Tier::Emulated, Tier::DevFallback];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Flag {
Unmeasured,
Emulated,
Pinned,
Affinity,
LoadUnknown,
}
impl Flag {
pub fn as_str(self) -> &'static str {
match self {
Flag::Unmeasured => "unmeasured",
Flag::Emulated => "emulated",
Flag::Pinned => "pinned",
Flag::Affinity => "affinity",
Flag::LoadUnknown => "load_unknown",
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NodeReport {
pub node_id: String,
pub rejections: Vec<Rejection>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub variant: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tier: Option<Tier>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub score: Option<ScoreBreakdown>,
#[serde(default)]
pub flags: Vec<Flag>,
#[serde(default)]
pub assigned: Vec<Vec<String>>,
}
impl NodeReport {
pub fn eligible(&self) -> bool {
self.rejections.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Placement {
pub node_id: String,
pub variant: String,
pub execution: Execution,
pub tier: Tier,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub accelerator: Option<String>,
pub score: ScoreBreakdown,
pub flags: Vec<Flag>,
}
impl Placement {
pub fn emulated(&self) -> bool {
self.execution == Execution::Emulated
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Decision {
pub kind: String,
pub name: String,
pub allow_emulated: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pin: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub placement: Option<Placement>,
pub candidates: Vec<NodeReport>,
}
impl Decision {
pub fn is_unplaceable(&self) -> bool {
self.placement.is_none()
}
pub fn reasons(&self) -> impl Iterator<Item = &NodeReport> {
self.candidates.iter().filter(|c| !c.eligible())
}
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum PlacementError {
#[error("invalid placement request: {0}")]
Invalid(#[from] PlacementTypeError),
#[error("duplicate node id {0:?} in facts")]
DuplicateNode(String),
#[error("pinned node {0:?} is not a known node")]
UnknownPin(String),
#[error("pinned node {node:?} is ineligible: {}", describe(.rejections))]
PinIneligible {
node: String,
rejections: Vec<Rejection>,
},
}
impl PlacementError {
pub fn constraints(&self) -> Vec<Constraint> {
match self {
PlacementError::PinIneligible { rejections, .. } => {
rejections.iter().map(|r| r.constraint).collect()
}
_ => vec![],
}
}
}
fn describe(r: &[Rejection]) -> String {
r.iter()
.map(|r| format!("constraint `{}`: {}", r.constraint.as_str(), r.detail))
.collect::<Vec<_>>()
.join("; ")
}