use std::collections::HashMap;
use std::sync::Arc;
use numaperf_core::{CpuSet, NodeId, NumaError};
use crate::discovery;
use crate::node::NumaNode;
#[derive(Debug)]
pub struct Topology {
nodes: Vec<NumaNode>,
cpu_to_node: HashMap<u32, NodeId>,
total_cpus: usize,
}
impl Topology {
pub fn discover() -> Result<Self, NumaError> {
discovery::discover()
}
pub fn from_nodes(nodes: Vec<NumaNode>) -> Self {
let mut cpu_to_node = HashMap::new();
let mut total_cpus = 0;
for node in &nodes {
for cpu in node.cpus().iter() {
cpu_to_node.insert(cpu, node.id());
total_cpus += 1;
}
}
Self {
nodes,
cpu_to_node,
total_cpus,
}
}
pub fn single_node(cpus: CpuSet) -> Self {
let total_cpus = cpus.count();
let node = NumaNode::new(NodeId::new(0), cpus);
let mut cpu_to_node = HashMap::new();
for cpu in node.cpus().iter() {
cpu_to_node.insert(cpu, NodeId::new(0));
}
Self {
nodes: vec![node],
cpu_to_node,
total_cpus,
}
}
#[inline]
pub fn numa_nodes(&self) -> &[NumaNode] {
&self.nodes
}
#[inline]
pub fn node_count(&self) -> usize {
self.nodes.len()
}
#[inline]
pub fn cpu_count(&self) -> usize {
self.total_cpus
}
#[inline]
pub fn is_single_node(&self) -> bool {
self.nodes.len() == 1
}
pub fn node(&self, id: NodeId) -> Option<&NumaNode> {
self.nodes.iter().find(|n| n.id() == id)
}
pub fn cpu_set(&self, node: NodeId) -> CpuSet {
self.node(node)
.map(|n| n.cpus().clone())
.unwrap_or_default()
}
pub fn node_for_cpu(&self, cpu: u32) -> Option<NodeId> {
self.cpu_to_node.get(&cpu).copied()
}
pub fn iter(&self) -> impl Iterator<Item = (NodeId, &NumaNode)> {
self.nodes.iter().map(|n| (n.id(), n))
}
pub fn summary(&self) -> String {
let mut s = String::new();
s.push_str(&format!(
"NUMA Topology: {} nodes, {} CPUs\n",
self.node_count(),
self.cpu_count()
));
for node in &self.nodes {
s.push_str(&format!(" {}\n", node));
}
s
}
}
unsafe impl Send for Topology {}
unsafe impl Sync for Topology {}
impl std::fmt::Display for Topology {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Topology({} nodes, {} CPUs)",
self.node_count(),
self.cpu_count()
)
}
}
pub fn shared_topology() -> Result<Arc<Topology>, NumaError> {
Ok(Arc::new(Topology::discover()?))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_single_node_topology() {
let cpus = CpuSet::parse("0-7").unwrap();
let topo = Topology::single_node(cpus);
assert_eq!(topo.node_count(), 1);
assert_eq!(topo.cpu_count(), 8);
assert!(topo.is_single_node());
assert_eq!(topo.node_for_cpu(0), Some(NodeId::new(0)));
assert_eq!(topo.node_for_cpu(7), Some(NodeId::new(0)));
assert_eq!(topo.node_for_cpu(8), None);
}
#[test]
fn test_multi_node_topology() {
let cpus0 = CpuSet::parse("0-3").unwrap();
let cpus1 = CpuSet::parse("4-7").unwrap();
let nodes = vec![
NumaNode::new(NodeId::new(0), cpus0),
NumaNode::new(NodeId::new(1), cpus1),
];
let topo = Topology::from_nodes(nodes);
assert_eq!(topo.node_count(), 2);
assert_eq!(topo.cpu_count(), 8);
assert!(!topo.is_single_node());
assert_eq!(topo.node_for_cpu(0), Some(NodeId::new(0)));
assert_eq!(topo.node_for_cpu(4), Some(NodeId::new(1)));
}
}