Skip to content

Commit bef1147

Browse files
committed
libvirt: Fix memory display unit conversion
I noticed the output was wonky. Assisted-by: Claude Code Signed-off-by: Colin Walters <walters@verbum.org>
1 parent 7b3b1a2 commit bef1147

4 files changed

Lines changed: 95 additions & 4 deletions

File tree

crates/kit/src/domain_list.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,7 @@ impl DomainLister {
199199
.unwrap_or_default();
200200

201201
// Extract memory and vcpu from domain XML
202-
let memory_mb = dom.find("memory").and_then(|node| {
203-
// Memory might have unit attribute, but we'll try to parse the value
204-
node.text_content().parse::<u32>().ok()
205-
});
202+
let memory_mb = dom.find("memory").and_then(|node| node.parse_memory_mb());
206203

207204
let vcpus = dom
208205
.find("vcpu")

crates/kit/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//! bcvk library - exposes internal modules for testing
22
33
pub mod qemu_img;
4+
pub mod utils;
45
pub mod xml_utils;

crates/kit/src/utils.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,60 @@ pub(crate) fn parse_memory_to_mb(memory_str: &str) -> Result<u32> {
173173
Err(eyre!("Memory specification cannot be empty - please provide a value like '2G', '1024M', or '512'"))
174174
}
175175
}
176+
177+
/// Convert memory value with unit to megabytes (MiB)
178+
/// Handles libvirt-style units distinguishing between decimal (KB, MB, GB - powers of 1000)
179+
/// and binary (KiB, MiB, GiB - powers of 1024) units per libvirt specification
180+
pub(crate) fn convert_memory_to_mb(value: u32, unit: &str) -> u32 {
181+
// Use u128 for calculations to prevent overflow with large units like TB
182+
let value_u128 = value as u128;
183+
let mib_u128 = 1024 * 1024;
184+
185+
let mb = match unit {
186+
// Binary prefixes (powers of 1024), converting to MiB
187+
"k" | "K" | "KiB" => value_u128 / 1024,
188+
"M" | "MiB" => value_u128,
189+
"G" | "GiB" => value_u128 * 1024,
190+
"T" | "TiB" => value_u128 * 1024 * 1024,
191+
192+
// Decimal prefixes (powers of 1000), converting to MiB
193+
"B" | "bytes" => value_u128 / mib_u128,
194+
"KB" => (value_u128 * 1_000) / mib_u128,
195+
"MB" => (value_u128 * 1_000_000) / mib_u128,
196+
"GB" => (value_u128 * 1_000_000_000) / mib_u128,
197+
"TB" => (value_u128 * 1_000_000_000_000) / mib_u128,
198+
199+
// Libvirt default is KiB for memory
200+
_ => value_u128 / 1024,
201+
};
202+
mb as u32
203+
}
204+
205+
#[cfg(test)]
206+
mod tests {
207+
use super::*;
208+
209+
#[test]
210+
fn test_convert_memory_to_mb() {
211+
// Test binary units (powers of 1024)
212+
assert_eq!(convert_memory_to_mb(4194304, "KiB"), 4096);
213+
assert_eq!(convert_memory_to_mb(2097152, "KiB"), 2048);
214+
assert_eq!(convert_memory_to_mb(2048, "MiB"), 2048);
215+
assert_eq!(convert_memory_to_mb(4096, "MiB"), 4096);
216+
assert_eq!(convert_memory_to_mb(4, "GiB"), 4096);
217+
assert_eq!(convert_memory_to_mb(2, "GiB"), 2048);
218+
219+
// Test short forms (binary)
220+
assert_eq!(convert_memory_to_mb(4, "G"), 4096);
221+
assert_eq!(convert_memory_to_mb(2048, "M"), 2048);
222+
assert_eq!(convert_memory_to_mb(2097152, "K"), 2048);
223+
224+
// Test decimal units (powers of 1000)
225+
assert_eq!(convert_memory_to_mb(1048576, "KB"), 1000);
226+
assert_eq!(convert_memory_to_mb(1024, "MB"), 976);
227+
assert_eq!(convert_memory_to_mb(4, "GB"), 3814);
228+
229+
// Test default/unknown unit (defaults to KiB)
230+
assert_eq!(convert_memory_to_mb(4194304, "unknown"), 4096);
231+
}
232+
}

crates/kit/src/xml_utils.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,19 @@ impl XmlNode {
140140
pub fn text_content(&self) -> &str {
141141
&self.text
142142
}
143+
144+
/// Parse memory value from an XML node with unit attribute
145+
/// Returns the value in megabytes (MB)
146+
pub fn parse_memory_mb(&self) -> Option<u32> {
147+
let value = self.text_content().parse::<u32>().ok()?;
148+
// Convert to MB based on unit attribute (default is KiB per libvirt spec)
149+
let unit = self
150+
.attributes
151+
.get("unit")
152+
.map(|s| s.as_str())
153+
.unwrap_or("KiB");
154+
Some(crate::utils::convert_memory_to_mb(value, unit))
155+
}
143156
}
144157

145158
/// Parse XML string into a simple DOM structure
@@ -370,4 +383,27 @@ mod tests {
370383
assert!(xml.contains("<custom>raw content</custom>"));
371384
assert!(xml.contains("</root>"));
372385
}
386+
387+
#[test]
388+
fn test_parse_memory_mb() {
389+
// Test KiB (default unit)
390+
let xml = r#"<memory>4194304</memory>"#;
391+
let dom = parse_xml_dom(xml).unwrap();
392+
assert_eq!(dom.parse_memory_mb(), Some(4096));
393+
394+
// Test MiB
395+
let xml = r#"<memory unit='MiB'>2048</memory>"#;
396+
let dom = parse_xml_dom(xml).unwrap();
397+
assert_eq!(dom.parse_memory_mb(), Some(2048));
398+
399+
// Test GiB
400+
let xml = r#"<memory unit='GiB'>4</memory>"#;
401+
let dom = parse_xml_dom(xml).unwrap();
402+
assert_eq!(dom.parse_memory_mb(), Some(4096));
403+
404+
// Test KB (decimal unit: 1000-based)
405+
let xml = r#"<memory unit='KB'>1048576</memory>"#;
406+
let dom = parse_xml_dom(xml).unwrap();
407+
assert_eq!(dom.parse_memory_mb(), Some(1000));
408+
}
373409
}

0 commit comments

Comments
 (0)