#[cfg(doc)]
use crate::topology::builder::TopologyBuilder;
use crate::{
errors::{self, RawHwlocError},
ffi::int,
topology::Topology,
};
use bitflags::bitflags;
use hwlocality_sys::{
HWLOC_TOPOLOGY_EXPORT_SYNTHETIC_FLAG_IGNORE_MEMORY,
HWLOC_TOPOLOGY_EXPORT_SYNTHETIC_FLAG_NO_ATTRS,
HWLOC_TOPOLOGY_EXPORT_SYNTHETIC_FLAG_NO_EXTENDED_TYPES,
HWLOC_TOPOLOGY_EXPORT_SYNTHETIC_FLAG_V1, hwloc_topology_export_synthetic_flags_e,
};
#[allow(unused)]
#[cfg(test)]
use similar_asserts::assert_eq;
use std::ffi::{CString, c_char};
impl Topology {
#[allow(clippy::missing_errors_doc, clippy::needless_continue)]
#[doc(alias = "hwloc_topology_export_synthetic")]
pub fn export_synthetic(&self, flags: SyntheticExportFlags) -> Result<String, RawHwlocError> {
let mut buf = vec![0u8; 1024]; loop {
let len =
errors::call_hwloc_positive_or_minus1("hwloc_topology_export_synthetic", || unsafe {
hwlocality_sys::hwloc_topology_export_synthetic(
self.as_ptr(),
buf.as_mut_ptr().cast::<c_char>(),
buf.len(),
flags.bits(),
)
})?;
if int::expect_usize(len) == buf.len() - 1 {
buf.resize(2 * buf.len(), 0);
continue;
} else {
buf.truncate(len as usize + 1);
return Ok(CString::from_vec_with_nul(buf)
.expect("Missing NUL from hwloc")
.into_string()
.expect("Synthetic export should yield an ASCII string"));
}
}
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
#[doc(alias = "hwloc_topology_export_synthetic_flags_e")]
pub struct SyntheticExportFlags: hwloc_topology_export_synthetic_flags_e {
#[doc(alias = "HWLOC_TOPOLOGY_EXPORT_SYNTHETIC_FLAG_NO_EXTENDED_TYPES")]
const NO_EXTENDED_TYPES = HWLOC_TOPOLOGY_EXPORT_SYNTHETIC_FLAG_NO_EXTENDED_TYPES;
#[doc(alias = "HWLOC_TOPOLOGY_EXPORT_SYNTHETIC_FLAG_NO_ATTRS")]
const NO_ATTRIBUTES = HWLOC_TOPOLOGY_EXPORT_SYNTHETIC_FLAG_NO_ATTRS;
#[doc(alias = "HWLOC_TOPOLOGY_EXPORT_SYNTHETIC_FLAG_V1")]
const V1 = HWLOC_TOPOLOGY_EXPORT_SYNTHETIC_FLAG_V1;
#[doc(alias = "HWLOC_TOPOLOGY_EXPORT_SYNTHETIC_FLAG_IGNORE_MEMORY")]
const IGNORE_MEMORY = HWLOC_TOPOLOGY_EXPORT_SYNTHETIC_FLAG_IGNORE_MEMORY;
}
}
crate::impl_arbitrary_for_bitflags!(
SyntheticExportFlags,
hwloc_topology_export_synthetic_flags_e
);
#[cfg(test)]
mod tests {
use super::*;
use crate::{
object::types::ObjectType,
topology::{TopologyObject, builder::TypeFilter},
};
use proptest::prelude::*;
#[allow(unused)]
use similar_asserts::assert_eq;
use strum::IntoEnumIterator;
proptest! {
#[test]
fn export_synthetic(flags: SyntheticExportFlags) {
let topology = Topology::test_instance();
let result = topology.export_synthetic(flags);
if !topology.root_object().is_symmetric_subtree() {
prop_assert!(result.is_err());
}
let Ok(synthetic) = result else { return Ok(()); };
let imported = Topology::builder().from_synthetic(&synthetic);
if imported.is_err() {
prop_assert!(flags.contains(SyntheticExportFlags::NO_EXTENDED_TYPES));
return Ok(());
}
let imported = imported
.expect("Synthetic strings from hwloc should be valid")
.with_common_type_filter(TypeFilter::KeepAll)
.unwrap()
.build()
.expect("Building a topology from an hwloc synthetic string should be valid");
prop_assert_eq!(imported.cpuset().weight(), topology.cpuset().weight());
if !flags.contains(SyntheticExportFlags::NO_EXTENDED_TYPES) {
let flags_affecting_memobjs = SyntheticExportFlags::IGNORE_MEMORY | SyntheticExportFlags::V1;
for ty in ObjectType::iter() {
if ty.is_normal()
|| (ty.is_memory() && !flags.intersects(flags_affecting_memobjs))
{
prop_assert_eq!(
imported.objects_with_type(ty).count(),
topology.objects_with_type(ty).count(),
"count of objects of type {} doesn't match", ty
);
}
if ty.is_memory() && flags.contains(SyntheticExportFlags::IGNORE_MEMORY) {
let expected_count = usize::from(ty == ObjectType::NUMANode);
prop_assert_eq!(
imported.objects_with_type(ty).count(),
expected_count
);
}
}
}
let total_memory = |topology: &Topology| topology.objects().map(TopologyObject::total_memory).sum::<u64>();
if !flags.intersects(SyntheticExportFlags::NO_ATTRIBUTES | SyntheticExportFlags::IGNORE_MEMORY) {
prop_assert_eq!(total_memory(&imported), total_memory(topology));
};
}
}
}