collect_smaps_rollup

Function collect_smaps_rollup 

Source
pub fn collect_smaps_rollup(
    snap: &CtprofSnapshot,
    no_thread_normalize: bool,
) -> BTreeMap<String, BTreeMap<String, u64>>
Expand description

Walk a snapshot’s threads and pull non-empty smaps_rollup maps off the leader threads (tid == tgid; non-leader threads land at empty map per the leader-dedup contract).

Keying:

  • Default normalization (no_thread_normalize: false): key is pattern_key(&t.pcomm) — pcomm only, the [tgid] suffix is DROPPED. The tgid digits would always normalize to {N} and add no discriminating signal to the join key, so omitting them makes smaps keys match the primary-table Pcomm group keys exactly (kworker/{N}:{N}, firefox, worker-{N}, etc.).

    No singleton revert. Unlike build_groups, which reverts a pattern_key to the literal name when only one contributor shares the skeleton, collect_smaps_rollup always normalizes when normalization is enabled regardless of how many PIDs share the bucket. The reason is structural: smaps keys exist to JOIN baseline vs candidate across snapshots, and PIDs are per-snapshot ephemeral. A singleton-revert path would emit a literal worker[7] on baseline and a literal worker[1234] on candidate — two never-matching keys — orphaning every cross-snapshot row. The build_groups invariant (“don’t advertise a pattern that no peer shares”) doesn’t apply on the smaps axis because the bucket’s role isn’t intra- snapshot fleet aggregation; it’s cross-snapshot memory diffing.

  • Literal mode (no_thread_normalize: true): key is pcomm[tgid] so each PID stays attributable to its specific instance. The tradeoff is that rows only join across snapshots when the same process instance ran on both sides — the [tgid] is preserved precisely so two distinct PIDs sharing a pcomm don’t collide within a snapshot.

Aggregation: multiple leader threads mapping to the same key (default mode: a fleet of worker-{N} parents) SUM their per-field byte counts. Rss, Pss, Private_*, Shared_* etc. each accumulate via saturating_add — memory quantities are additive across the merged bucket. saturating_add mirrors the cumulative-counter merge policy elsewhere in ctprof_compare (cgroup_merge.rs: usage_usec, throttled_usec); a u64 byte-count overflow implies more than 16 EiB of resident memory across the bucket, well past any realistic host.

Caveat on Shared_* aggregation: when multiple PIDs in a merged bucket share physical pages (the COW case for forked children, mmap’d shared libraries, etc.), summing each PID’s per-process Shared_* reading double-counts the overlapping physical residency. The same double-count exists in the un-aggregated display — the operator already sees Shared_Clean = 500MiB listed against two distinct PID rows that happen to share the same library mapping — so the merge introduces no new information loss, just preserves the pre-existing kernel- emission characteristic. Pss stays the precise read for a merged bucket’s resident footprint because the kernel proportionally divides shared pages across mappers (fs/proc/task_mmu.c::smaps_account); operators tracking actual memory pressure should prefer Pss over Rss + Shared_* arithmetic on collapsed buckets.

Values are converted from kB to bytes via ThreadState::smaps_rollup_bytes up-front, so the downstream renderer can pass cell values directly into the auto_scale “B” ladder without further unit math.