Table of Contents

Debug Properties Drill-Down (IDebugPropertiesProvider)

An ad-hoc, allocation-tolerant key/value dictionary for per-resource diagnostic drill-down.

Status: โœ… Implemented ยท Visibility: Internal ยท Category: Resources

๐ŸŽฏ What it solves

IMetricSource is deliberately narrow โ€” five fixed kinds (Memory, Capacity, DiskIO, Throughput, Duration), zero-allocation, read on every snapshot. That shape is right for dashboards and alerts, but wrong for "why is this specific ComponentTable behaving oddly?" questions: per-segment chunk counts, page-cache state histograms, schema names, transaction-chain TSNs โ€” detail that's too verbose, too shaped-per-type, or too rarely needed to justify a dedicated metric field. IDebugPropertiesProvider gives any resource a second, looser channel for exactly that detail, without growing the structured metric surface or the resource tree itself.

โš™๏ธ How it works (in brief)

Any IResource may additionally implement IDebugPropertiesProvider with one method, GetDebugProperties(), returning IReadOnlyDictionary<string, object>. Unlike ReadMetrics, this call is allowed to allocate โ€” it's invoked on demand for diagnostics, not on every snapshot tick. Keys follow a Container.Field dot-convention (e.g. ComponentSegment.AllocatedChunks, Contention.MaxWaitUs); values are primitives or types with meaningful ToString(). It is also the documented surface for the "Owner Aggregates" pattern: fine-grained primitives that don't get their own graph node (segments, latches, banks) report their per-instance breakdown here, while their owner reports only the aggregate via IMetricSource.

๐Ÿ’ป Usage

var ct = dbe.GetComponentTable<Player>();

// Direct call on a typed reference
foreach (var (key, value) in ct.GetDebugProperties())
{
    Console.WriteLine($"{key} = {value}");
}
// StorageMode = Versioned
// ComponentSegment.AllocatedChunks = 4096
// ComponentSegment.Capacity = 8192
// CompRevTableSegment.AllocatedChunks = 4096
// DefaultIndexSegment.AllocatedChunks = 256
// ...

// Or drill into any node reached via the resource tree, without holding a typed reference
var node = resourceGraph.FindByPath("Storage/ManagedPagedMMF");
if (node is IDebugPropertiesProvider provider)
{
    var props = provider.GetDebugProperties();
    // PageCache.FreeCount, PageCache.DirtyCount, ClockSweep.MinCounter, Segments.Count, ...
}

โš ๏ธ Guarantees & limits

  • Optional interface โ€” most IResource types don't implement it; check with is IDebugPropertiesProvider before calling on an arbitrary node, or call directly on a known type.
  • May allocate โ€” a new Dictionary<string, object> per call, including boxing for value types. Not for hot paths or per-tick polling; call on demand for debugging/diagnostics.
  • No cross-key snapshot consistency โ€” unlike IMetricSource.ReadMetrics (one coherent call per node during a tree walk), GetDebugProperties() reads live state directly; individual entries may be momentarily inconsistent with each other under concurrent mutation.
  • Lock-free by convention, not enforced โ€” implementations are expected to avoid taking locks so a debug read can't itself become a contention source, but the interface doesn't require it.
  • No tree-wide snapshot helper ships today โ€” there is no built-in walker that collects GetDebugProperties() across every node; call it per node yourself (directly, or after an is IDebugPropertiesProvider check while walking IResource.Children).
  • Currently implemented on ComponentTable, ManagedPagedMMF, DatabaseEngine, TransactionChain, MemoryAllocator, ConcurrentBitmapL3All, PinnedMemoryBlock, MemoryBlockArray, MemoryBlockBase, and StagingBufferPool.

๐Ÿงช Tests

  • OwnerAggregatesTests โ€” ComponentTable.GetDebugProperties() per-segment breakdown (ComponentSegment.*, CompRevTableSegment.*, DefaultIndexSegment.*, String64IndexSegment.* when present), values match live segment state after entity creation