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 ispattern_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_rollupalways 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 literalworker[7]on baseline and a literalworker[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 ispcomm[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.