Skip to main content

qemu_command_builder/args/
numa.rs

1use crate::parsers::ARG_NUMA;
2use bon::Builder;
3use proptest_derive::Arbitrary;
4use std::str::FromStr;
5
6use crate::parsers::DELIM_COMMA;
7use crate::to_command::ToCommand;
8
9#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
10pub struct NUMANodeMem {
11    mem_size: Option<usize>,
12    cpu_first: Option<usize>,
13    cpu_last: Option<usize>,
14    node_id: Option<usize>,
15    initiator: Option<usize>,
16}
17
18#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
19pub struct NUMANodeMemDev {
20    mem_id: Option<usize>,
21    cpu_first: Option<usize>,
22    cpu_last: Option<usize>,
23    node_id: Option<usize>,
24    initiator: Option<usize>,
25}
26#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
27pub struct NUMADist {
28    src: usize,
29    dst: usize,
30    val: usize,
31}
32
33#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
34pub struct NUMACPU {
35    node_id: usize,
36    socket_id: Option<usize>,
37    core_id: Option<usize>,
38    thread_id: Option<usize>,
39}
40
41/// HMAT hierarchy selector for `-numa hmat-lb,...`.
42#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
43pub enum NUMAHierarchy {
44    Memory,
45    FirstLevel,
46    SecondLevel,
47    ThirdLevel,
48}
49/// HMAT latency data type for `-numa hmat-lb,...`.
50#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
51pub enum NUMADataType {
52    AccessLatency,
53    ReadLatency,
54    WriteLatency,
55}
56
57#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
58pub struct NUMAHMATLb {
59    initiator: usize,
60    target: usize,
61    hierarchy: NUMAHierarchy,
62    data_type: NUMADataType,
63    latency: Option<usize>,
64    bandwidth: Option<usize>,
65}
66
67#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
68pub enum HMATCacheAssociativity {
69    None,
70    Direct,
71    Complex,
72}
73#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
74pub enum HMATCachePolicy {
75    None,
76    WriteBack,
77    WriteThrough,
78}
79
80#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Builder, Arbitrary)]
81pub struct NUMAHMATCache {
82    node_id: usize,
83    size: usize,
84    level: usize,
85    associativity: Option<HMATCacheAssociativity>,
86    policy: Option<HMATCachePolicy>,
87    line: Option<usize>,
88}
89
90/// A supported `-numa` clause.
91///
92/// The parser accepts the same comma-separated forms that this crate
93/// renders for node, distance, CPU, and HMAT entries.
94#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
95pub enum NUMA {
96    NodeMem(NUMANodeMem),
97    NodeMemDev(NUMANodeMemDev),
98    Dist(NUMADist),
99    Cpu(NUMACPU),
100    HMATLB(NUMAHMATLb),
101    HMATCache(NUMAHMATCache),
102}
103
104impl ToCommand for NUMA {
105    fn command(&self) -> String {
106        ARG_NUMA.to_string()
107    }
108    fn to_args(&self) -> Vec<String> {
109        match self {
110            NUMA::NodeMem(node_mem) => {
111                let mut node_mem_args = "node".to_string();
112                if let Some(mem) = &node_mem.mem_size {
113                    node_mem_args.push_str(format!(",mem={}", mem).as_str());
114                }
115                if let Some(cpu) = &node_mem.cpu_first {
116                    node_mem_args.push_str(format!(",cpu={}", cpu).as_str());
117                }
118                if let Some(cpu) = &node_mem.cpu_last {
119                    node_mem_args.push_str(format!("-{}", cpu).as_str());
120                }
121                if let Some(node_id) = &node_mem.node_id {
122                    node_mem_args.push_str(format!(",nodeid={}", node_id).as_str());
123                }
124                if let Some(initiator) = &node_mem.initiator {
125                    node_mem_args.push_str(format!(",initiator={}", initiator).as_str());
126                }
127                vec![node_mem_args.to_string()]
128            }
129            NUMA::NodeMemDev(node_memdev) => {
130                let mut node_memdev_args = "node".to_string();
131                if let Some(memdev) = &node_memdev.mem_id {
132                    node_memdev_args.push_str(format!(",memdev={}", memdev).as_str());
133                }
134                if let Some(cpu) = &node_memdev.cpu_first {
135                    node_memdev_args.push_str(format!(",cpu={}", cpu).as_str());
136                }
137                if let Some(cpu) = &node_memdev.cpu_last {
138                    node_memdev_args.push_str(format!("-{}", cpu).as_str());
139                }
140                if let Some(node_id) = &node_memdev.node_id {
141                    node_memdev_args.push_str(format!(",nodeid={}", node_id).as_str());
142                }
143                if let Some(initiator) = &node_memdev.initiator {
144                    node_memdev_args.push_str(format!(",initiator={}", initiator).as_str());
145                }
146                vec![node_memdev_args.to_string()]
147            }
148            NUMA::Dist(dist) => {
149                vec![format!("dist,src={},dst={},val={}", dist.src, dist.dst, dist.val)]
150            }
151            NUMA::Cpu(cpu) => {
152                let mut cpu_args = "cpu".to_string();
153
154                cpu_args.push_str(format!(",node-id={}", cpu.node_id).as_str());
155                if let Some(socket_id) = &cpu.socket_id {
156                    cpu_args.push_str(format!(",socket-id={}", socket_id).as_str());
157                }
158                if let Some(core_id) = &cpu.core_id {
159                    cpu_args.push_str(format!(",core-id={}", core_id).as_str());
160                }
161                if let Some(thread_id) = &cpu.thread_id {
162                    cpu_args.push_str(format!(",thread-id={}", thread_id).as_str());
163                }
164                vec![cpu_args.to_string()]
165            }
166            NUMA::HMATLB(hmat_lb) => {
167                let mut hmat_lb_args = "hmat-lb".to_string();
168                hmat_lb_args.push_str(format!(",initiator={},target={},hierarchy=", hmat_lb.initiator, hmat_lb.target).as_str());
169                match hmat_lb.hierarchy {
170                    NUMAHierarchy::Memory => hmat_lb_args.push_str("memory"),
171                    NUMAHierarchy::FirstLevel => hmat_lb_args.push_str("first-level"),
172                    NUMAHierarchy::SecondLevel => hmat_lb_args.push_str("second-level"),
173                    NUMAHierarchy::ThirdLevel => hmat_lb_args.push_str("third-level"),
174                }
175                hmat_lb_args.push_str(",data-type=");
176                match hmat_lb.data_type {
177                    NUMADataType::AccessLatency => hmat_lb_args.push_str("access-latency"),
178                    NUMADataType::ReadLatency => hmat_lb_args.push_str("read-latency"),
179                    NUMADataType::WriteLatency => hmat_lb_args.push_str("write-latency"),
180                }
181                if let Some(lat) = &hmat_lb.latency {
182                    hmat_lb_args.push_str(format!(",latency={}", lat).as_str());
183                }
184                if let Some(bw) = &hmat_lb.bandwidth {
185                    hmat_lb_args.push_str(format!(",bandwidth={}", bw).as_str());
186                }
187                vec![hmat_lb_args]
188            }
189            NUMA::HMATCache(hmat_cache) => {
190                let mut hmat_cache_args = "hmat-cache".to_string();
191                hmat_cache_args.push_str(format!(",node-id={},size={},level={}", hmat_cache.node_id, hmat_cache.size, hmat_cache.level).as_str());
192                if let Some(assoc) = &hmat_cache.associativity {
193                    hmat_cache_args.push_str(",associativity=");
194                    match assoc {
195                        HMATCacheAssociativity::None => hmat_cache_args.push_str("none"),
196                        HMATCacheAssociativity::Direct => hmat_cache_args.push_str("direct"),
197                        HMATCacheAssociativity::Complex => hmat_cache_args.push_str("complex"),
198                    }
199                }
200                if let Some(policy) = &hmat_cache.policy {
201                    hmat_cache_args.push_str(",policy=");
202                    match policy {
203                        HMATCachePolicy::None => hmat_cache_args.push_str("none"),
204                        HMATCachePolicy::WriteBack => hmat_cache_args.push_str("write-back"),
205                        HMATCachePolicy::WriteThrough => hmat_cache_args.push_str("write-through"),
206                    }
207                }
208                if let Some(line) = &hmat_cache.line {
209                    hmat_cache_args.push_str(format!(",line={}", line).as_str());
210                }
211                vec![hmat_cache_args]
212            }
213        }
214    }
215}
216
217impl FromStr for NUMA {
218    type Err = String;
219
220    fn from_str(s: &str) -> Result<Self, Self::Err> {
221        let mut parts = s.split(DELIM_COMMA);
222        let kind = parts.next().ok_or_else(|| "empty numa argument".to_string())?;
223
224        match kind {
225            "node" => {
226                let mut mem_size = None;
227                let mut memdev = None;
228                let mut cpu_first = None;
229                let mut cpu_last = None;
230                let mut node_id = None;
231                let mut initiator = None;
232
233                for part in parts {
234                    let (key, raw) = part.split_once('=').ok_or_else(|| format!("invalid numa node option: {part}"))?;
235                    match key {
236                        "mem" => mem_size = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
237                        "memdev" => memdev = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
238                        "cpu" => {
239                            if let Some((first, last)) = raw.split_once('-') {
240                                cpu_first = Some(first.parse::<usize>().map_err(|e| e.to_string())?);
241                                cpu_last = Some(last.parse::<usize>().map_err(|e| e.to_string())?);
242                            } else {
243                                cpu_first = Some(raw.parse::<usize>().map_err(|e| e.to_string())?);
244                            }
245                        }
246                        "nodeid" => node_id = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
247                        "initiator" => initiator = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
248                        other => return Err(format!("unsupported numa node option: {other}")),
249                    }
250                }
251
252                if let Some(mem_id) = memdev {
253                    Ok(Self::NodeMemDev(NUMANodeMemDev {
254                        mem_id: Some(mem_id),
255                        cpu_first,
256                        cpu_last,
257                        node_id,
258                        initiator,
259                    }))
260                } else {
261                    Ok(Self::NodeMem(NUMANodeMem {
262                        mem_size,
263                        cpu_first,
264                        cpu_last,
265                        node_id,
266                        initiator,
267                    }))
268                }
269            }
270            "dist" => {
271                let mut src = None;
272                let mut dst = None;
273                let mut val = None;
274
275                for part in parts {
276                    let (key, raw) = part.split_once('=').ok_or_else(|| format!("invalid numa dist option: {part}"))?;
277                    match key {
278                        "src" => src = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
279                        "dst" => dst = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
280                        "val" => val = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
281                        other => return Err(format!("unsupported numa dist option: {other}")),
282                    }
283                }
284
285                Ok(Self::Dist(NUMADist {
286                    src: src.ok_or_else(|| "numa dist requires src=".to_string())?,
287                    dst: dst.ok_or_else(|| "numa dist requires dst=".to_string())?,
288                    val: val.ok_or_else(|| "numa dist requires val=".to_string())?,
289                }))
290            }
291            "cpu" => {
292                let mut node_id = None;
293                let mut socket_id = None;
294                let mut core_id = None;
295                let mut thread_id = None;
296
297                for part in parts {
298                    let (key, raw) = part.split_once('=').ok_or_else(|| format!("invalid numa cpu option: {part}"))?;
299                    match key {
300                        "node-id" => node_id = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
301                        "socket-id" => socket_id = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
302                        "core-id" => core_id = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
303                        "thread-id" => thread_id = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
304                        other => return Err(format!("unsupported numa cpu option: {other}")),
305                    }
306                }
307
308                Ok(Self::Cpu(NUMACPU {
309                    node_id: node_id.ok_or_else(|| "numa cpu requires node-id=".to_string())?,
310                    socket_id,
311                    core_id,
312                    thread_id,
313                }))
314            }
315            "hmat-lb" => {
316                let mut initiator = None;
317                let mut target = None;
318                let mut hierarchy = None;
319                let mut data_type = None;
320                let mut latency = None;
321                let mut bandwidth = None;
322
323                for part in parts {
324                    let (key, raw) = part.split_once('=').ok_or_else(|| format!("invalid numa hmat-lb option: {part}"))?;
325                    match key {
326                        "initiator" => initiator = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
327                        "target" => target = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
328                        "hierarchy" => hierarchy = Some(parse_numa_hierarchy(raw)?),
329                        "data-type" => data_type = Some(parse_numa_data_type(raw)?),
330                        "latency" => latency = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
331                        "bandwidth" => bandwidth = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
332                        other => return Err(format!("unsupported numa hmat-lb option: {other}")),
333                    }
334                }
335
336                Ok(Self::HMATLB(NUMAHMATLb {
337                    initiator: initiator.ok_or_else(|| "numa hmat-lb requires initiator=".to_string())?,
338                    target: target.ok_or_else(|| "numa hmat-lb requires target=".to_string())?,
339                    hierarchy: hierarchy.ok_or_else(|| "numa hmat-lb requires hierarchy=".to_string())?,
340                    data_type: data_type.ok_or_else(|| "numa hmat-lb requires data-type=".to_string())?,
341                    latency,
342                    bandwidth,
343                }))
344            }
345            "hmat-cache" => {
346                let mut node_id = None;
347                let mut size = None;
348                let mut level = None;
349                let mut associativity = None;
350                let mut policy = None;
351                let mut line = None;
352
353                for part in parts {
354                    let (key, raw) = part.split_once('=').ok_or_else(|| format!("invalid numa hmat-cache option: {part}"))?;
355                    match key {
356                        "node-id" => node_id = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
357                        "size" => size = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
358                        "level" => level = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
359                        "associativity" => associativity = Some(parse_hmat_cache_associativity(raw)?),
360                        "policy" => policy = Some(parse_hmat_cache_policy(raw)?),
361                        "line" => line = Some(raw.parse::<usize>().map_err(|e| e.to_string())?),
362                        other => return Err(format!("unsupported numa hmat-cache option: {other}")),
363                    }
364                }
365
366                Ok(Self::HMATCache(NUMAHMATCache {
367                    node_id: node_id.ok_or_else(|| "numa hmat-cache requires node-id=".to_string())?,
368                    size: size.ok_or_else(|| "numa hmat-cache requires size=".to_string())?,
369                    level: level.ok_or_else(|| "numa hmat-cache requires level=".to_string())?,
370                    associativity,
371                    policy,
372                    line,
373                }))
374            }
375            other => Err(format!("unsupported numa kind: {other}")),
376        }
377    }
378}
379
380fn parse_numa_hierarchy(value: &str) -> Result<NUMAHierarchy, String> {
381    match value {
382        "memory" => Ok(NUMAHierarchy::Memory),
383        "first-level" => Ok(NUMAHierarchy::FirstLevel),
384        "second-level" => Ok(NUMAHierarchy::SecondLevel),
385        "third-level" => Ok(NUMAHierarchy::ThirdLevel),
386        other => Err(format!("invalid numa hierarchy: {other}")),
387    }
388}
389
390fn parse_numa_data_type(value: &str) -> Result<NUMADataType, String> {
391    match value {
392        "access-latency" => Ok(NUMADataType::AccessLatency),
393        "read-latency" => Ok(NUMADataType::ReadLatency),
394        "write-latency" => Ok(NUMADataType::WriteLatency),
395        other => Err(format!("invalid numa data-type: {other}")),
396    }
397}
398
399fn parse_hmat_cache_associativity(value: &str) -> Result<HMATCacheAssociativity, String> {
400    match value {
401        "none" => Ok(HMATCacheAssociativity::None),
402        "direct" => Ok(HMATCacheAssociativity::Direct),
403        "complex" => Ok(HMATCacheAssociativity::Complex),
404        other => Err(format!("invalid hmat-cache associativity: {other}")),
405    }
406}
407
408fn parse_hmat_cache_policy(value: &str) -> Result<HMATCachePolicy, String> {
409    match value {
410        "none" => Ok(HMATCachePolicy::None),
411        "write-back" => Ok(HMATCachePolicy::WriteBack),
412        "write-through" => Ok(HMATCachePolicy::WriteThrough),
413        other => Err(format!("invalid hmat-cache policy: {other}")),
414    }
415}