themis/topology/cpu/
mod.rs1mod cache;
4#[cfg(all(feature = "std", target_os = "linux"))]
5mod cpulist;
6mod detect;
7mod tables;
8
9use super::types::{CacheLevel, NumaNode};
10use crate::law::{MemoryTier, NumaNodeId, TopologyEpoch};
11
12#[cfg(not(feature = "std"))]
13extern crate alloc;
14
15#[cfg(not(feature = "std"))]
16use alloc::boxed::Box;
17
18#[cfg(not(feature = "std"))]
19use alloc::vec;
20
21pub(crate) use cache::detect_cache_levels;
22#[cfg(all(feature = "std", target_os = "linux"))]
23pub(crate) use cpulist::parse_cpu_list;
24pub use tables::{build_adjacent_nodes, build_node_to_index};
25#[cfg(any(test, feature = "std"))]
26pub use tables::{build_default_distance_row, build_processor_to_node};
27pub use tables::{LOCAL_DISTANCE, REMOTE_DISTANCE};
28
29#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct CpuTopology {
32 pub(crate) epoch: TopologyEpoch,
34 pub(crate) numa_nodes: Box<[NumaNode]>,
35 pub(crate) processor_to_node: Box<[NumaNodeId]>,
36 pub(crate) node_to_index: Box<[usize]>,
37 pub(crate) adjacent_nodes: Box<[NumaNodeId]>,
38 pub(crate) logical_processors: usize,
39 pub(crate) cache_levels: Option<Box<[CacheLevel]>>,
40}
41
42impl CpuTopology {
43 #[must_use]
45 pub fn single_node(logical_processors: usize) -> Self {
46 let logical_processors = logical_processors.max(1);
47 let processors: Box<[u32]> = (0..logical_processors as u32).collect();
48 let node_id = NumaNodeId::ZERO;
49 let numa_nodes: Box<[NumaNode]> = Box::new([NumaNode {
50 id: node_id,
51 processors,
52 distances: Box::new([LOCAL_DISTANCE]),
53 memory_tier: MemoryTier::Dram,
54 }]);
55
56 Self {
57 epoch: TopologyEpoch::INITIAL,
58 processor_to_node: vec![node_id; logical_processors].into_boxed_slice(),
59 node_to_index: build_node_to_index(&numa_nodes),
60 adjacent_nodes: build_adjacent_nodes(&numa_nodes),
61 numa_nodes,
62 logical_processors,
63 cache_levels: None,
64 }
65 }
66
67 #[cfg(any(test, feature = "testing"))]
75 #[must_use]
76 pub fn new_for_test(
77 epoch: TopologyEpoch,
78 numa_nodes: Box<[NumaNode]>,
79 processor_to_node: Box<[NumaNodeId]>,
80 logical_processors: usize,
81 cache_levels: Option<Box<[CacheLevel]>>,
82 ) -> Self {
83 let node_to_index = build_node_to_index(&numa_nodes);
84 let adjacent_nodes = build_adjacent_nodes(&numa_nodes);
85 Self {
86 epoch,
87 numa_nodes,
88 processor_to_node,
89 node_to_index,
90 adjacent_nodes,
91 logical_processors,
92 cache_levels,
93 }
94 }
95
96 #[must_use]
98 pub const fn epoch(&self) -> TopologyEpoch {
99 self.epoch
100 }
101
102 #[must_use]
104 pub fn numa_nodes(&self) -> &[NumaNode] {
105 &self.numa_nodes
106 }
107
108 #[must_use]
120 pub fn cache_levels(&self) -> Option<&[CacheLevel]> {
121 self.cache_levels.as_deref()
122 }
123
124 #[must_use]
126 pub const fn logical_processors(&self) -> usize {
127 self.logical_processors
128 }
129
130 #[must_use]
132 pub fn processor_to_numa_node(&self, processor: u32) -> Option<NumaNodeId> {
133 self.processor_to_node
134 .get(processor as usize)
135 .copied()
136 .filter(|&node_id| node_id != NumaNodeId::INVALID)
137 }
138
139 #[must_use = "iterators are lazy; consume the returned mapping iterator"]
141 pub fn processor_node_pairs(&self) -> impl Iterator<Item = (u32, NumaNodeId)> + '_ {
142 self.processor_to_node
143 .iter()
144 .enumerate()
145 .filter_map(|(processor, &node)| {
146 if node != NumaNodeId::INVALID {
147 Some((processor as u32, node))
148 } else {
149 None
150 }
151 })
152 }
153
154 #[must_use]
168 pub fn distance(&self, from: NumaNodeId, to: NumaNodeId) -> u32 {
169 match (self.node_index(from), self.node_index(to)) {
170 (Some(from_index), Some(to_index)) => self
171 .numa_nodes
172 .get(from_index)
173 .and_then(|node| {
174 let max_node_id = self.node_to_index.len().saturating_sub(1);
175 let idx = if node.distances.len() > max_node_id {
176 to.index()
177 } else {
178 to_index
179 };
180 node.distances.get(idx).copied()
181 })
182 .unwrap_or_else(|| tables::default_distance(from_index, to_index)),
183 _ => {
184 if from == to {
185 LOCAL_DISTANCE
186 } else {
187 REMOTE_DISTANCE
188 }
189 }
190 }
191 }
192
193 #[must_use]
195 pub fn node_index(&self, node_id: NumaNodeId) -> Option<usize> {
196 self.node_to_index
197 .get(node_id.index())
198 .copied()
199 .filter(|&index| index != usize::MAX)
200 }
201
202 #[must_use]
204 pub fn adjacent_nodes(&self, node_id: NumaNodeId) -> &[NumaNodeId] {
205 if let Some(index) = self.node_index(node_id) {
206 let node_count = self.numa_nodes.len();
207 if node_count <= 1 {
208 return &[];
209 }
210 let stride = node_count - 1;
211 let start = index * stride;
212 let end = start + stride;
213 self.adjacent_nodes.get(start..end).unwrap_or(&[])
214 } else {
215 &[]
216 }
217 }
218}
219
220fn logical_processor_count() -> usize {
221 #[cfg(feature = "std")]
222 {
223 std::thread::available_parallelism()
224 .map(usize::from)
225 .unwrap_or(1)
226 }
227
228 #[cfg(not(feature = "std"))]
229 {
230 1
231 }
232}