windows-topology-sys 0.2.0

Safe enumeration of Windows processor, cache, and memory topology, with a JSON-describable schema for discovered or synthetic topologies.
Documentation

windows-topology-sys

A refined view of the processor, cache, and memory topology Windows publishes.

Windows 11 / Windows Server 2025 and later. That floor is what is tested, not the oldest version the APIs might work on: discover calls GetSystemCpuSetInformation, which is documented only from Windows 10 / Server 2016 and is imported statically, and nothing below Windows 11 is exercised here. (Server 2025 is the server release built on the Windows 11 codebase; Server 2022 is not, despite the adjacent version numbers.)

Windows only. The crate does not build on other platforms, and is not intended to: an earlier version of this line claimed it degraded to an empty shell elsewhere, which was never true and was never built in CI (raised in PR #56 review). That correction reached the prose but not the code -- two modules stayed ungated, so the crate really did build for a Linux target and hand back a two-type API -- and a compile_error! now enforces what this paragraph says (raised in PR #61 review). Depend on it under [target.'cfg(windows)'.dependencies].

Example

use windows_topology_sys::MachineMemoryTopology;

let topology = MachineMemoryTopology::discover()?;
println!(
    "{} logical processor(s), {} domain(s)",
    topology.processors.len(),
    topology.domains.len()
);
# Ok::<(), std::io::Error>(())

examples/print_topology.rs prints the host's full topology as JSON (cargo run --example print_topology --features serde), in the same shape a hand-written or fed-in description takes.

Why this crate exists

GetLogicalProcessorInformationEx is the Win32 entry point for topology, and the windows crate exposes it as typed but still unsafe FFI: a raw output pointer, two-call sizing, and records that must be walked by their own self-reported Size rather than indexed as a slice. Several of the records also declare a trailing array as length 1 (PROCESSOR_RELATIONSHIP::GroupMask: [GROUP_AFFINITY; 1]) while actually holding as many entries as GroupCount reports -- reading past element 0 is exactly what correct use of the API requires, and exactly what Rust calls undefined behavior if done through the declared type. None of that is solved by a thin unsafe fn wrapper; it has to be solved by walking the buffer correctly.

This crate does that walk once, safely, and hands back owned records.

What the model is

Observed connectivity, not a ladder of levels with optional rungs. The difference matters to a consumer: a level-shaped model has to invent a value for a rung the platform did not report, and the invented value is indistinguishable from a measured one.

  • Relations are held as a set, and never reduced on insert. Windows describes processors through two APIs -- GetLogicalProcessorInformationEx and GetSystemCpuSetInformation -- and they are not the same API twice. Each relation carries an Observation naming its Source, so where the two disagree the disagreement survives rather than being silently resolved in favour of whichever was read last. MachineMemoryTopology::cpu_sets also keeps the CPU-set view verbatim.
  • Observed<T> distinguishes three facts that a plain Option collapses into two: Known, Absent ("asked, and there is none"), and NotObserved ("nobody asked"). With the serde feature these are three distinct encodings, so a description round-trips the distinction instead of losing it.
  • Granularity orders the domain kinds, and minimal_shared is the meet -- so "how close are these two processors?" is answered over every kind and any cache depth, rather than over one nominated level. proximity derives the pairwise answer from an inclusion-ordered partitioning rather than restating the rule.
  • MachineMemoryTopology::enumeration_anomalies records what could not be decoded. Empty on every healthy machine. A malformed record is neither a panic nor a silent truncation, so a short list is distinguishable from a small machine.
  • Provenance records how the object was obtained -- discovered, restored from a description, or hand-built -- which is a fact about the construction, orthogonal to which source reported any given relation.

Scope

What this is: safe enumeration (MachineMemoryTopology::discover), plus a plain-data description (MachineMemoryTopology, Domain) that needs no Windows API call to construct -- build one by hand, or (with the serde feature) deserialize one from JSON written for a machine you do not have. (A machine you do not have, not a platform you are not on.)

What this is not: a partitioning policy. It answers what the machine looks like, never which boundary you should shard on -- that depends on the workload, so it is the consumer's call. It is also not a device topology: no NVMe controller, NIC, or GPU is a participant here.

It does not go below the Win32 topology APIs. If Windows does not report a fact, this crate does not have it. That is a scope boundary rather than a judgement about the fact: ACPI carries SLIT distances, no Win32 API surfaces them, and reading firmware directly would be going below the boundary -- so there is no attributed-distance model, and none is planned here.

See DESIGN-NOTES.md for the full reasoning, including a cross-check against Linux's topology model, D-9's list of what was declined and why, D-20's scope ruling above, and D-8's note that the JSON schema is not covered by this crate's semver contract.

License

MIT. Copyright (c) Mike Grier.