pub mod attributes;
pub mod depth;
pub mod distance;
pub(crate) mod hierarchy;
pub(crate) mod lists;
pub mod search;
pub mod types;
use self::{
attributes::{DownstreamAttributes, ObjectAttributes, PCIDomain},
depth::{Depth, NormalDepth},
types::ObjectType,
};
#[cfg(doc)]
use crate::topology::{Topology, builder::BuildFlags, support::DiscoverySupport};
use crate::{
bitmap::BitmapRef,
cpu::cpuset::CpuSet,
ffi::{
self, int,
transparent::{AsNewtype, TransparentNewtype},
},
info::TextualInfo,
memory::nodeset::NodeSet,
};
#[cfg(feature = "hwloc-2_3_0")]
use crate::{
errors::{self, HybridError, NulError},
ffi::string::LibcString,
};
use hwlocality_sys::{HWLOC_UNKNOWN_INDEX, hwloc_obj};
#[allow(unused)]
#[cfg(test)]
use similar_asserts::assert_eq;
use std::{
ffi::CStr,
fmt::{self, Debug, Display},
iter::FusedIterator,
ops::Deref,
ptr,
};
#[allow(clippy::non_send_fields_in_send_ty, missing_copy_implementations)]
#[doc(alias = "hwloc_obj")]
#[doc(alias = "hwloc_obj_t")]
#[repr(transparent)]
pub struct TopologyObject(hwloc_obj);
impl TopologyObject {
#[doc(alias = "hwloc_obj::type")]
pub fn object_type(&self) -> ObjectType {
unsafe { ObjectType::from_hwloc(self.0.ty) }
}
#[doc(alias = "hwloc_obj::subtype")]
pub fn subtype(&self) -> Option<&CStr> {
unsafe { ffi::deref_str(&self.0.subtype) }
}
#[allow(clippy::missing_errors_doc, deprecated)]
#[cfg(all(feature = "hwloc-2_3_0", not(windows), not(tarpaulin_include)))]
#[deprecated = "Use the subtype parameter to TopologyEditor::insert_group_object()"]
pub fn set_subtype(&mut self, subtype: &str) -> Result<(), NulError> {
unsafe { self.set_subtype_unchecked(subtype) }
}
#[cfg(all(feature = "hwloc-2_3_0", not(tarpaulin_include)))]
#[deprecated = "Use the subtype parameter to TopologyEditor::insert_group_object()"]
pub unsafe fn set_subtype_unchecked(&mut self, subtype: &str) -> Result<(), NulError> {
self.0.subtype = unsafe { LibcString::new(subtype)?.into_raw() };
Ok(())
}
#[doc(alias = "hwloc_obj::name")]
pub fn name(&self) -> Option<&CStr> {
unsafe { ffi::deref_str(&self.0.name) }
}
#[doc(alias = "hwloc_obj::attr")]
pub fn attributes(&self) -> Option<ObjectAttributes<'_>> {
unsafe { ObjectAttributes::new(self.object_type(), &self.0.attr) }
}
#[doc(alias = "hwloc_obj::os_index")]
pub fn os_index(&self) -> Option<usize> {
(self.0.os_index != HWLOC_UNKNOWN_INDEX).then(|| int::expect_usize(self.0.os_index))
}
#[doc(alias = "hwloc_obj::gp_index")]
pub fn global_persistent_index(&self) -> TopologyObjectID {
self.0.gp_index
}
}
pub type TopologyObjectID = u64;
impl TopologyObject {
#[doc(alias = "hwloc_obj::depth")]
pub fn depth(&self) -> Depth {
unsafe { Depth::from_hwloc(self.0.depth) }.expect("Got unexpected depth value")
}
#[doc(alias = "hwloc_obj::parent")]
pub fn parent(&self) -> Option<&Self> {
unsafe { ffi::deref_ptr_mut(&self.0.parent).map(|raw| raw.as_newtype()) }
}
pub fn ancestors(&self) -> impl FusedIterator<Item = &Self> + Clone {
Ancestors(self)
}
#[doc(alias = "hwloc_get_ancestor_obj_by_depth")]
pub fn ancestor_at_depth<DepthLike>(&self, depth: DepthLike) -> Option<&Self>
where
DepthLike: TryInto<Depth>,
<DepthLike as TryInto<Depth>>::Error: Debug,
{
fn polymorphized(self_: &TopologyObject, depth: Depth) -> Option<&TopologyObject> {
let self_depth = self_.depth();
if let (Ok(self_depth), Ok(depth)) = (
NormalDepth::try_from(self_depth),
NormalDepth::try_from(depth),
) {
if self_depth <= depth {
return None;
}
}
self_.ancestors().find(|ancestor| ancestor.depth() == depth)
}
let depth = depth.try_into().ok()?;
polymorphized(self, depth)
}
#[doc(alias = "hwloc_get_ancestor_obj_by_type")]
pub fn first_ancestor_with_type(&self, ty: ObjectType) -> Option<&Self> {
self.ancestors()
.find(|ancestor| ancestor.object_type() == ty)
}
#[doc(alias = "hwloc_get_common_ancestor_obj")]
pub fn first_common_ancestor(&self, other: &Self) -> Option<&Self> {
if ptr::eq(self, other) {
return self.parent();
}
fn collect_virtual_ancestors(
obj: &TopologyObject,
) -> (Vec<&TopologyObject>, Option<&TopologyObject>) {
let mut ancestors = Vec::new();
let mut current = obj;
loop {
if let Some(parent) = current.parent() {
if let Depth::Normal(_) = parent.depth() {
return (ancestors, Some(parent));
} else {
ancestors.push(parent);
current = parent;
}
} else {
return (ancestors, None);
}
}
}
let (virtual_ancestors_1, parent1) = collect_virtual_ancestors(self);
let (virtual_ancestors_2, parent2) = collect_virtual_ancestors(other);
for ancestor1 in virtual_ancestors_1 {
for ancestor2 in &virtual_ancestors_2 {
if ptr::eq(ancestor1, *ancestor2) {
return Some(ancestor1);
}
}
}
let mut parent1 = parent1?;
let mut parent2 = parent2?;
loop {
let normal_depth = |obj: &Self| {
NormalDepth::try_from(obj.depth()).expect("Should only observe normal depth here")
};
let depth2 = normal_depth(parent2);
while normal_depth(parent1) > depth2 {
parent1 = parent1.parent()?;
}
let depth1 = normal_depth(parent1);
while normal_depth(parent2) > depth1 {
parent2 = parent2.parent()?;
}
if ptr::eq(parent1, parent2) {
return Some(parent1);
}
if parent1.depth() == parent2.depth() {
parent1 = parent1.parent()?;
parent2 = parent2.parent()?;
}
}
}
#[doc(alias = "hwloc_obj_is_in_subtree")]
pub fn is_in_subtree(&self, subtree_root: &Self) -> bool {
self.ancestors()
.any(|ancestor| ptr::eq(ancestor, subtree_root))
}
#[doc(alias = "hwloc_get_shared_cache_covering_obj")]
pub fn first_shared_cache(&self) -> Option<&Self> {
let cpuset = self.cpuset()?;
self.ancestors()
.skip_while(|ancestor| ancestor.cpuset() == Some(cpuset))
.find(|ancestor| ancestor.object_type().is_cpu_data_cache())
}
#[doc(alias = "hwloc_get_non_io_ancestor_obj")]
pub fn first_non_io_ancestor(&self) -> Option<&Self> {
self.ancestors().find(|obj| obj.cpuset().is_some())
}
}
#[derive(Copy, Clone, Debug)]
struct Ancestors<'object>(&'object TopologyObject);
impl<'object> Iterator for Ancestors<'object> {
type Item = &'object TopologyObject;
fn next(&mut self) -> Option<Self::Item> {
self.0 = self.0.parent()?;
Some(self.0)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let depth_res = usize::try_from(self.0.depth());
(depth_res.unwrap_or(0), depth_res.ok())
}
}
impl FusedIterator for Ancestors<'_> {}
impl TopologyObject {
#[doc(alias = "hwloc_obj::logical_index")]
pub fn logical_index(&self) -> usize {
int::expect_usize(self.0.logical_index)
}
#[doc(alias = "hwloc_obj::next_cousin")]
pub fn next_cousin(&self) -> Option<&Self> {
unsafe { ffi::deref_ptr_mut(&self.0.next_cousin).map(|raw| raw.as_newtype()) }
}
#[doc(alias = "hwloc_obj::prev_cousin")]
pub fn prev_cousin(&self) -> Option<&Self> {
unsafe { ffi::deref_ptr_mut(&self.0.prev_cousin).map(|raw| raw.as_newtype()) }
}
#[doc(alias = "hwloc_obj::sibling_rank")]
pub fn sibling_rank(&self) -> usize {
int::expect_usize(self.0.sibling_rank)
}
#[doc(alias = "hwloc_obj::next_sibling")]
pub fn next_sibling(&self) -> Option<&Self> {
unsafe { ffi::deref_ptr_mut(&self.0.next_sibling).map(|raw| raw.as_newtype()) }
}
#[doc(alias = "hwloc_obj::prev_sibling")]
pub fn prev_sibling(&self) -> Option<&Self> {
unsafe { ffi::deref_ptr_mut(&self.0.prev_sibling).map(|raw| raw.as_newtype()) }
}
}
impl TopologyObject {
#[doc(alias = "hwloc_obj::arity")]
pub fn normal_arity(&self) -> usize {
int::expect_usize(self.0.arity)
}
#[doc(alias = "hwloc_obj::children")]
#[doc(alias = "hwloc_obj::first_child")]
#[doc(alias = "hwloc_obj::last_child")]
pub fn normal_children(
&self,
) -> impl DoubleEndedIterator<Item = &Self> + Clone + ExactSizeIterator + FusedIterator {
if self.0.children.is_null() {
#[cfg(not(tarpaulin_include))]
assert_eq!(
self.normal_arity(),
0,
"Got null children pointer with nonzero arity"
);
}
(0..self.normal_arity()).map(move |offset| {
let child = unsafe { *self.0.children.add(offset) };
#[cfg(not(tarpaulin_include))]
assert!(!child.is_null(), "Got null child pointer");
unsafe { (&*child).as_newtype() }
})
}
#[doc(alias = "hwloc_obj::symmetric_subtree")]
pub fn is_symmetric_subtree(&self) -> bool {
self.0.symmetric_subtree != 0
}
#[doc(alias = "hwloc_get_child_covering_cpuset")]
pub fn normal_child_covering_cpuset(&self, set: impl Deref<Target = CpuSet>) -> Option<&Self> {
let set: &CpuSet = &set;
self.normal_children()
.find(|child| child.covers_cpuset(set))
}
#[doc(alias = "hwloc_obj::memory_arity")]
pub fn memory_arity(&self) -> usize {
int::expect_usize(self.0.memory_arity)
}
#[doc(alias = "hwloc_obj::memory_first_child")]
pub fn memory_children(&self) -> impl ExactSizeIterator<Item = &Self> + Clone + FusedIterator {
unsafe { self.singly_linked_children(self.0.memory_first_child, self.memory_arity()) }
}
#[doc(alias = "hwloc_obj::total_memory")]
pub fn total_memory(&self) -> u64 {
self.0.total_memory
}
#[doc(alias = "hwloc_obj::io_arity")]
pub fn io_arity(&self) -> usize {
int::expect_usize(self.0.io_arity)
}
#[doc(alias = "hwloc_obj::io_first_child")]
pub fn io_children(&self) -> impl ExactSizeIterator<Item = &Self> + Clone + FusedIterator {
unsafe { self.singly_linked_children(self.0.io_first_child, self.io_arity()) }
}
#[doc(alias = "hwloc_bridge_covers_pcibus")]
pub fn is_bridge_covering_pci_bus(&self, domain: PCIDomain, bus_id: u8) -> bool {
let Some(ObjectAttributes::Bridge(bridge)) = self.attributes() else {
return false;
};
let Some(DownstreamAttributes::PCI(pci)) = bridge.downstream_attributes() else {
return false;
};
pci.domain() == domain && pci.secondary_bus() <= bus_id && pci.subordinate_bus() >= bus_id
}
#[doc(alias = "hwloc_obj::misc_arity")]
pub fn misc_arity(&self) -> usize {
int::expect_usize(self.0.misc_arity)
}
#[doc(alias = "hwloc_obj::misc_first_child")]
pub fn misc_children(&self) -> impl ExactSizeIterator<Item = &Self> + Clone + FusedIterator {
unsafe { self.singly_linked_children(self.0.misc_first_child, self.misc_arity()) }
}
#[doc(alias = "hwloc_get_next_child")]
pub fn all_children(&self) -> impl FusedIterator<Item = &Self> + Clone {
self.normal_children()
.chain(self.memory_children())
.chain(self.io_children())
.chain(self.misc_children())
}
unsafe fn singly_linked_children(
&self,
first: *mut hwloc_obj,
arity: usize,
) -> impl ExactSizeIterator<Item = &Self> + Clone + FusedIterator {
let mut current = first;
(0..arity).map(move |_| {
#[cfg(not(tarpaulin_include))]
assert!(!current.is_null(), "Got null child before expected arity");
let result: &Self = unsafe { (&*current).as_newtype() };
current = result.0.next_sibling;
result
})
}
}
impl TopologyObject {
#[doc(alias = "hwloc_obj::cpuset")]
pub fn cpuset(&self) -> Option<BitmapRef<'_, CpuSet>> {
unsafe { CpuSet::borrow_from_raw_mut(self.0.cpuset) }
}
pub fn is_inside_cpuset(&self, set: impl Deref<Target = CpuSet>) -> bool {
let Some(object_cpuset) = self.cpuset() else {
return false;
};
set.includes(object_cpuset) && !object_cpuset.is_empty()
}
pub fn covers_cpuset(&self, set: impl Deref<Target = CpuSet>) -> bool {
let Some(object_cpuset) = self.cpuset() else {
return false;
};
let set: &CpuSet = &set;
object_cpuset.includes(set) && !set.is_empty()
}
#[doc(alias = "hwloc_obj::complete_cpuset")]
pub fn complete_cpuset(&self) -> Option<BitmapRef<'_, CpuSet>> {
unsafe { CpuSet::borrow_from_raw_mut(self.0.complete_cpuset) }
}
}
impl TopologyObject {
#[cfg_attr(
feature = "hwloc-2_3_0",
doc = "With hwloc 2.3+, [`Topology::local_numa_nodes()`] may be used to"
)]
#[cfg_attr(feature = "hwloc-2_3_0", doc = "list those NUMA nodes more precisely.")]
#[doc(alias = "hwloc_obj::nodeset")]
pub fn nodeset(&self) -> Option<BitmapRef<'_, NodeSet>> {
unsafe { NodeSet::borrow_from_raw_mut(self.0.nodeset) }
}
#[doc(alias = "hwloc_obj::complete_nodeset")]
pub fn complete_nodeset(&self) -> Option<BitmapRef<'_, NodeSet>> {
unsafe { NodeSet::borrow_from_raw_mut(self.0.complete_nodeset) }
}
}
impl TopologyObject {
#[doc(alias = "hwloc_obj::infos")]
pub fn infos(&self) -> &[TextualInfo] {
if self.0.infos.is_null() {
#[cfg(not(tarpaulin_include))]
assert_eq!(
self.0.infos_count, 0,
"Got null infos pointer with nonzero info count"
);
return &[];
}
let infos_len = int::expect_usize(self.0.infos_count);
#[allow(clippy::missing_docs_in_private_items)]
type Element = TextualInfo;
int::assert_slice_len::<Element>(infos_len);
unsafe { std::slice::from_raw_parts::<Element>(self.0.infos.as_newtype(), infos_len) }
}
#[doc(alias = "hwloc_obj_get_info_by_name")]
pub fn info(&self, key: &str) -> Option<&CStr> {
self.infos().iter().find_map(|info| {
let Ok(info_name) = info.name().to_str() else {
return None;
};
(info_name == key).then_some(info.value())
})
}
#[cfg(feature = "hwloc-2_3_0")]
#[doc(alias = "hwloc_obj_add_info")]
pub fn add_info(&mut self, name: &str, value: &str) -> Result<(), HybridError<NulError>> {
let name = LibcString::new(name)?;
let value = LibcString::new(value)?;
errors::call_hwloc_zero_or_minus1("hwloc_obj_add_info", || unsafe {
hwlocality_sys::hwloc_obj_add_info(&raw mut self.0, name.borrow(), value.borrow())
})
.map_err(HybridError::Hwloc)
}
}
impl TopologyObject {
fn display(&self, f: &mut fmt::Formatter<'_>, verbose: bool) -> fmt::Result {
let (type_chars, attr_chars) = unsafe {
let type_chars = ffi::call_snprintf(|buf, len| {
hwlocality_sys::hwloc_obj_type_snprintf(buf, len, &raw const self.0, verbose.into())
});
let separator = if f.alternate() { c",\n " } else { c", " };
let attr_chars = ffi::call_snprintf(|buf, len| {
hwlocality_sys::hwloc_obj_attr_snprintf(
buf,
len,
&raw const self.0,
separator.as_ptr(),
verbose.into(),
)
});
(type_chars, attr_chars)
};
let cpuset_str = self
.cpuset()
.map_or_else(String::new, |cpuset| format!(" with {cpuset}"));
unsafe {
let type_str = CStr::from_ptr(type_chars.as_ptr()).to_string_lossy();
let attr_str = CStr::from_ptr(attr_chars.as_ptr()).to_string_lossy();
let type_and_cpuset = format!("{type_str}{cpuset_str}");
if attr_str.is_empty() {
f.pad(&type_and_cpuset)
} else if f.alternate() {
let s = format!("{type_and_cpuset} (\n {attr_str}\n)");
f.pad(&s)
} else {
let s = format!("{type_and_cpuset} ({attr_str})");
f.pad(&s)
}
}
}
#[cfg(all(feature = "hwloc-2_3_0", not(feature = "hwloc-2_10_0")))]
pub(crate) unsafe fn delete_all_sets(self_: ptr::NonNull<Self>) {
let self_ = self_.as_ptr();
debug_assert_eq!(
unsafe { (*self_).0.ty },
hwlocality_sys::HWLOC_OBJ_GROUP,
"this method should only be called on Group objects"
);
unsafe {
for set_ptr in [
ptr::addr_of_mut!((*self_).0.cpuset),
ptr::addr_of_mut!((*self_).0.nodeset),
ptr::addr_of_mut!((*self_).0.complete_cpuset),
ptr::addr_of_mut!((*self_).0.complete_nodeset),
] {
let set = set_ptr.read();
if !set.is_null() {
hwlocality_sys::hwloc_bitmap_free(set);
set_ptr.write(ptr::null_mut())
}
}
}
}
}
impl Debug for TopologyObject {
#[doc(alias = "hwloc_obj_attr_snprintf")]
#[doc(alias = "hwloc_obj_type_snprintf")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, true)
}
}
impl Display for TopologyObject {
#[allow(clippy::doc_markdown)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, false)
}
}
unsafe impl Send for TopologyObject {}
unsafe impl Sync for TopologyObject {}
unsafe impl TransparentNewtype for TopologyObject {
type Inner = hwloc_obj;
}
#[allow(clippy::cognitive_complexity)]
#[cfg(test)]
pub(crate) mod tests {
use super::hierarchy::tests::{any_hwloc_depth, any_normal_depth, any_usize_depth};
use super::*;
use crate::{
strategies::{any_object, any_string, set_with_reference, test_object},
topology::Topology,
};
use proptest::prelude::*;
use similar_asserts::assert_eq;
use std::{collections::HashMap, ffi::CString, ops::RangeInclusive};
#[test]
fn check_all_objects() -> Result<(), TestCaseError> {
let topology = Topology::test_instance();
for obj in topology.objects() {
check_any_object(obj)?;
}
Ok(())
}
fn check_any_object(obj: &TopologyObject) -> Result<(), TestCaseError> {
check_sets(obj)?;
check_parent(obj)?;
check_first_shared_cache(obj)?;
check_cousins_and_siblings(obj)?;
check_children(obj)?;
check_infos(obj)?;
check_displays(obj)?;
Ok(())
}
fn check_sets(obj: &TopologyObject) -> Result<(), TestCaseError> {
let has_sets = obj.object_type().has_sets();
prop_assert_eq!(obj.cpuset().is_some(), has_sets);
prop_assert_eq!(obj.complete_cpuset().is_some(), has_sets);
if let (Some(complete), Some(normal)) = (obj.complete_cpuset(), obj.cpuset()) {
prop_assert!(complete.includes(normal));
prop_assert!(obj.is_inside_cpuset(complete));
prop_assert!(obj.covers_cpuset(normal));
}
prop_assert_eq!(obj.nodeset().is_some(), has_sets);
prop_assert_eq!(obj.complete_nodeset().is_some(), has_sets);
if let (Some(complete), Some(normal)) = (obj.complete_nodeset(), obj.nodeset()) {
prop_assert!(complete.includes(normal));
}
Ok(())
}
fn check_parent(obj: &TopologyObject) -> Result<(), TestCaseError> {
let Some(parent) = obj.parent() else {
prop_assert_eq!(obj.object_type(), ObjectType::Machine);
return Ok(());
};
let first_ancestor = obj.ancestors().next().unwrap();
prop_assert!(ptr::eq(parent, first_ancestor));
if let (Depth::Normal(parent_depth), Depth::Normal(obj_depth)) =
(parent.depth(), obj.depth())
{
prop_assert!(parent_depth < obj_depth);
}
if obj.object_type().has_sets() {
prop_assert!(parent.object_type().has_sets());
prop_assert!(parent.cpuset().unwrap().includes(obj.cpuset().unwrap()));
prop_assert!(parent.nodeset().unwrap().includes(obj.nodeset().unwrap()));
prop_assert!(
parent
.complete_cpuset()
.unwrap()
.includes(obj.complete_cpuset().unwrap())
);
prop_assert!(
parent
.complete_nodeset()
.unwrap()
.includes(obj.complete_nodeset().unwrap())
);
}
Ok(())
}
fn check_first_shared_cache(obj: &TopologyObject) -> Result<(), TestCaseError> {
let result = obj.first_shared_cache();
if obj.cpuset().is_none() {
prop_assert!(result.is_none());
return Ok(());
}
let expected = obj
.ancestors()
.skip_while(|ancestor| ancestor.normal_arity() == 1)
.find(|ancestor| ancestor.object_type().is_cpu_data_cache());
if let (Some(result), Some(expected)) = (result, expected) {
prop_assert!(ptr::eq(result, expected));
} else {
prop_assert!(result.is_none() && expected.is_none());
}
Ok(())
}
fn check_cousins_and_siblings(obj: &TopologyObject) -> Result<(), TestCaseError> {
let siblings_len = if let Some(parent) = obj.parent() {
let ty = obj.object_type();
if ty.is_normal() {
parent.normal_arity()
} else if ty.is_memory() {
parent.memory_arity()
} else if ty.is_io() {
parent.io_arity()
} else {
prop_assert_eq!(ty, ObjectType::Misc);
parent.misc_arity()
}
} else {
1
};
let topology = Topology::test_instance();
let cousins_len = topology.num_objects_at_depth(obj.depth());
if let Some(prev_cousin) = obj.prev_cousin() {
check_cousin(obj, prev_cousin)?;
prop_assert_eq!(prev_cousin.logical_index(), obj.logical_index() - 1);
prop_assert!(ptr::eq(prev_cousin.next_cousin().unwrap(), obj));
} else {
prop_assert_eq!(obj.logical_index(), 0);
}
if let Some(next_cousin) = obj.next_cousin() {
check_cousin(obj, next_cousin)?;
prop_assert_eq!(next_cousin.logical_index(), obj.logical_index() + 1);
prop_assert!(ptr::eq(next_cousin.prev_cousin().unwrap(), obj));
} else {
prop_assert_eq!(obj.logical_index(), cousins_len - 1);
}
if let Some(prev_sibling) = obj.prev_sibling() {
check_sibling(obj, prev_sibling)?;
prop_assert_eq!(prev_sibling.sibling_rank(), obj.sibling_rank() - 1);
prop_assert!(ptr::eq(prev_sibling.next_sibling().unwrap(), obj));
} else {
prop_assert_eq!(obj.sibling_rank(), 0);
}
if let Some(next_sibling) = obj.next_sibling() {
check_sibling(obj, next_sibling)?;
prop_assert_eq!(next_sibling.sibling_rank(), obj.sibling_rank() + 1);
prop_assert!(ptr::eq(next_sibling.prev_sibling().unwrap(), obj));
} else {
prop_assert_eq!(obj.sibling_rank(), siblings_len - 1);
}
Ok(())
}
fn check_cousin(obj: &TopologyObject, cousin: &TopologyObject) -> Result<(), TestCaseError> {
prop_assert_eq!(cousin.object_type(), obj.object_type());
prop_assert_eq!(cousin.depth(), obj.depth());
if obj.object_type().has_sets() {
prop_assert!(!obj.cpuset().unwrap().intersects(cousin.cpuset().unwrap()));
prop_assert!(
!obj.complete_cpuset()
.unwrap()
.intersects(cousin.complete_cpuset().unwrap())
);
}
Ok(())
}
fn check_sibling(obj: &TopologyObject, sibling: &TopologyObject) -> Result<(), TestCaseError> {
prop_assert_eq!(sibling.0.parent, obj.0.parent);
Ok(())
}
fn check_children(obj: &TopologyObject) -> Result<(), TestCaseError> {
prop_assert_eq!(obj.normal_arity(), obj.normal_children().count());
prop_assert_eq!(obj.memory_arity(), obj.memory_children().count());
prop_assert_eq!(obj.io_arity(), obj.io_children().count());
prop_assert_eq!(obj.misc_arity(), obj.misc_children().count());
prop_assert_eq!(
obj.all_children().count(),
obj.normal_arity() + obj.memory_arity() + obj.io_arity() + obj.misc_arity()
);
for (idx, normal_child) in obj.normal_children().enumerate() {
prop_assert_eq!(normal_child.sibling_rank(), idx);
prop_assert!(normal_child.object_type().is_normal());
}
for (idx, memory_child) in obj.memory_children().enumerate() {
prop_assert_eq!(memory_child.sibling_rank(), idx);
prop_assert!(memory_child.object_type().is_memory());
}
for (idx, io_child) in obj.io_children().enumerate() {
prop_assert_eq!(io_child.sibling_rank(), idx);
prop_assert!(io_child.object_type().is_io());
}
for (idx, misc_child) in obj.misc_children().enumerate() {
prop_assert_eq!(misc_child.sibling_rank(), idx);
prop_assert_eq!(misc_child.object_type(), ObjectType::Misc);
}
Ok(())
}
fn check_infos(obj: &TopologyObject) -> Result<(), TestCaseError> {
for info in obj.infos() {
if let Ok(name) = info.name().to_str() {
prop_assert_eq!(
obj.info(name),
obj.infos()
.iter()
.find(|other_info| other_info.name() == info.name())
.map(TextualInfo::value)
);
}
}
Ok(())
}
fn check_displays(obj: &TopologyObject) -> Result<(), TestCaseError> {
let display = format!("{obj}");
let display_alternate = format!("{obj:#}");
let debug = format!("{obj:?}");
let debug_alternate = format!("{obj:#?}");
for non_alternate in [&display, &debug] {
prop_assert!(!non_alternate.contains('\n'));
}
prop_assert!(debug.len() >= display.len());
prop_assert!(debug_alternate.len() >= display_alternate.len());
prop_assert!(debug_alternate.len() >= debug.len());
prop_assert!(display_alternate.len() >= display.len());
Ok(())
}
#[test]
fn is_symmetric_subtree() {
let topology = Topology::test_instance();
for depth in NormalDepth::iter_range(NormalDepth::MIN, topology.depth()).rev() {
'objs: for obj in topology.objects_at_depth(depth) {
let Some(first_child) = obj.normal_children().next() else {
assert!(obj.is_symmetric_subtree());
continue 'objs;
};
let should_be_symmetric = obj.normal_children().all(|child| {
child.is_symmetric_subtree()
&& child.object_type() == first_child.object_type()
&& child.subtype() == first_child.subtype()
&& child.attributes() == first_child.attributes()
&& child.depth() == first_child.depth()
&& child.normal_arity() == first_child.normal_arity()
});
assert_eq!(obj.is_symmetric_subtree(), should_be_symmetric);
}
}
assert!(
topology
.virtual_objects()
.all(|obj| !obj.is_symmetric_subtree())
);
}
#[test]
fn total_memory() {
let topology = Topology::test_instance();
let mut expected_total_memory = HashMap::new();
let mut curr_objects = HashMap::new();
let mut next_objects = HashMap::new();
for numa in topology.objects_with_type(ObjectType::NUMANode) {
let Some(ObjectAttributes::NUMANode(attrs)) = numa.attributes() else {
unreachable!()
};
let gp_index = numa.global_persistent_index();
let local_memory = attrs.local_memory().map_or(0, u64::from);
assert!(
expected_total_memory
.insert(gp_index, local_memory)
.is_none()
);
assert!(curr_objects.insert(gp_index, numa).is_none());
}
while !curr_objects.is_empty() {
for (gp_index, obj) in curr_objects.drain() {
let obj_memory = expected_total_memory[&gp_index];
if let Some(parent) = obj.parent() {
let parent_gp_index = parent.global_persistent_index();
*expected_total_memory.entry(parent_gp_index).or_default() += obj_memory;
next_objects.insert(parent_gp_index, parent);
}
}
std::mem::swap(&mut curr_objects, &mut next_objects);
}
for obj in topology.objects() {
assert_eq!(
obj.total_memory(),
expected_total_memory
.remove(&obj.global_persistent_index())
.unwrap_or(0)
);
}
}
fn check_ancestor_at_depth<DepthLike>(
obj: &TopologyObject,
depth: DepthLike,
) -> Result<(), TestCaseError>
where
DepthLike: TryInto<Depth> + Copy + Debug + Eq,
Depth: PartialEq<DepthLike>,
<DepthLike as TryInto<Depth>>::Error: Debug,
{
let actual = obj.ancestor_at_depth(depth);
let expected = obj.ancestors().find(|obj| obj.depth() == depth);
if let (Some(actual), Some(expected)) = (actual, expected) {
prop_assert!(ptr::eq(actual, expected));
} else {
prop_assert!(actual.is_none() && expected.is_none());
}
Ok(())
}
proptest! {
#[test]
fn ancestor_at_hwloc_depth(obj in test_object(),
depth in any_hwloc_depth()) {
check_ancestor_at_depth(obj, depth)?;
}
#[test]
fn ancestor_at_normal_depth(obj in test_object(),
depth in any_normal_depth()) {
check_ancestor_at_depth(obj, depth)?;
}
#[test]
fn ancestor_at_usize_depth(obj in test_object(),
depth in any_usize_depth()) {
check_ancestor_at_depth(obj, depth)?;
}
}
pub(crate) fn object_and_related_cpuset()
-> impl Strategy<Value = (&'static TopologyObject, CpuSet)> {
let topology = Topology::test_instance();
let mut with_cpuset = Vec::new();
let mut without_cpuset = Vec::new();
for obj in topology.objects() {
if obj.object_type().has_sets() {
with_cpuset.push(obj);
} else {
without_cpuset.push(obj);
}
}
fn with_reference(
objects: Vec<&'static TopologyObject>,
ref_cpuset: impl Fn(&TopologyObject) -> BitmapRef<'_, CpuSet>,
) -> Option<impl Strategy<Value = (&'static TopologyObject, CpuSet)>> {
(!objects.is_empty()).then(move || {
prop::sample::select(objects)
.prop_flat_map(move |obj| (Just(obj), set_with_reference(ref_cpuset(obj))))
})
}
let with_cpuset = with_reference(with_cpuset, |obj| obj.cpuset().unwrap());
let without_cpuset = with_reference(without_cpuset, |_obj| topology.cpuset());
match (with_cpuset, without_cpuset) {
(Some(with), Some(without)) => prop_oneof![with, without].boxed(),
(Some(with), None) => with.boxed(),
(None, Some(without)) => without.boxed(),
(None, None) => unreachable!(),
}
}
proptest! {
#[test]
fn normal_child_covering_cpuset((obj, set) in object_and_related_cpuset()) {
if let Some(result) = obj.normal_child_covering_cpuset(&set) {
prop_assert!(result.covers_cpuset(&set));
} else {
prop_assert!(obj.normal_children().all(|obj| !obj.covers_cpuset(&set)));
}
}
#[test]
fn is_inside_cpuset((obj, set) in object_and_related_cpuset()) {
let result = obj.is_inside_cpuset(&set);
let Some(obj_set) = obj.cpuset() else {
prop_assert!(!result);
return Ok(());
};
if obj_set.is_empty() {
prop_assert!(!result);
return Ok(());
}
prop_assert_eq!(result, set.includes(obj_set));
}
#[test]
fn covers_cpuset((obj, set) in object_and_related_cpuset()) {
let result = obj.covers_cpuset(&set);
let Some(obj_set) = obj.cpuset() else {
prop_assert!(!result);
return Ok(());
};
if set.is_empty() {
prop_assert!(!result);
return Ok(());
}
prop_assert_eq!(result, obj_set.includes(&set));
}
}
fn bridge_coverage() -> impl Strategy<Value = (&'static TopologyObject, PCIDomain, u8)> {
#[derive(Clone, Debug)]
struct BridgeCoverage {
bridge: &'static TopologyObject,
domain: PCIDomain,
bus_id_range: RangeInclusive<u8>,
}
let topology = Topology::test_instance();
let bridge_coverages = topology
.objects_with_type(ObjectType::Bridge)
.filter_map(|bridge| {
let Some(ObjectAttributes::Bridge(attrs)) = bridge.attributes() else {
unreachable!()
};
let Some(DownstreamAttributes::PCI(pci)) = attrs.downstream_attributes() else {
return None;
};
Some(BridgeCoverage {
bridge,
domain: pci.domain(),
bus_id_range: pci.secondary_bus()..=pci.subordinate_bus(),
})
})
.collect::<Vec<_>>();
if bridge_coverages.is_empty() {
(any_object(), any::<PCIDomain>(), any::<u8>()).boxed()
} else {
prop::sample::select(bridge_coverages)
.prop_flat_map(|bridge_coverage| {
let obj = prop_oneof![
3 => Just(bridge_coverage.bridge),
2 => any_object()
];
let domain = prop_oneof![
3 => Just(bridge_coverage.domain),
2 => any::<PCIDomain>()
];
let bus_id = prop_oneof![
3 => bridge_coverage.bus_id_range,
2 => any::<u8>()
];
(obj, domain, bus_id)
})
.boxed()
}
}
proptest! {
#[test]
fn is_bridge_covering_pci_bus((obj, domain, bus_id) in bridge_coverage()) {
let result = obj.is_bridge_covering_pci_bus(domain, bus_id);
let Some(ObjectAttributes::Bridge(attrs)) = obj.attributes() else {
prop_assert!(!result);
return Ok(());
};
let Some(DownstreamAttributes::PCI(pci)) = attrs.downstream_attributes() else {
prop_assert!(!result);
return Ok(());
};
prop_assert_eq!(
result,
domain == pci.domain() && bus_id >= pci.secondary_bus() && bus_id <= pci.subordinate_bus()
);
}
}
proptest! {
#[test]
fn info(obj in any_object(), key in any_string()) {
let result = obj.info(&key);
let Ok(ckey) = CString::new(key.clone()) else {
assert_eq!(result, None);
return Ok(());
};
assert_eq!(
obj.info(&key),
obj.infos().iter().find_map(|info|{
(info.name() == ckey.as_c_str()).then_some(info.value())
})
);
}
}
proptest! {
#[test]
fn first_common_ancestor(obj in test_object(), other in any_object()) {
let result = obj.first_common_ancestor(other);
if obj.object_type() == ObjectType::Machine || other.object_type() == ObjectType::Machine
{
prop_assert!(result.is_none());
return Ok(());
}
let topology = Topology::test_instance();
if !topology.contains(other) {
prop_assert!(result.is_none());
return Ok(());
}
let common_ancestor = result.unwrap();
fn prev_ancestor_candidate<'obj>(
obj: &'obj TopologyObject,
common_ancestor: &TopologyObject,
) -> Option<&'obj TopologyObject> {
obj.ancestors().take_while(|&ancestor| !ptr::eq(ancestor, common_ancestor)).last()
}
let obj_ancestor = prev_ancestor_candidate(obj, common_ancestor);
let other_ancestor = prev_ancestor_candidate(other, common_ancestor);
if let (Some(obj_ancestor), Some(other_ancestor)) = (obj_ancestor, other_ancestor) {
prop_assert!(!ptr::eq(obj_ancestor, other_ancestor));
}
}
}
#[cfg(feature = "hwloc-2_3_0")]
mod editing {
use super::*;
use std::panic::UnwindSafe;
fn test_object_editing<R>(check: impl FnOnce(&mut TopologyObject) -> R + UnwindSafe) -> R {
let mut topology = Topology::test_instance().clone();
topology.edit(|editor| {
let misc = editor
.insert_misc_object("This is a modifiable test object trololol", |topology| {
topology.root_object()
})
.unwrap();
check(misc)
})
}
proptest! {
#[test]
fn add_info(name in any_string(), value in any_string()) {
test_object_editing(|obj| {
let res = obj.add_info(&name, &value);
if name.contains('\0') || value.contains('\0') {
prop_assert_eq!(res, Err(NulError.into()));
return Ok(());
}
res.unwrap();
prop_assert_eq!(obj.info(&name).unwrap().to_str().unwrap(), value);
Ok(())
})?;
}
}
}
}