Repository navigation
feat: what the process holds, beside the peak it once reached (ADR 0061) - #96
Merged
Merged
Conversation
`/proc/<pid>/status` has about forty rows and the scanner took two, because the allow-list was drawn for one finding. Three more rows and two small neighbouring files answer questions the agent could not: - how much of a container's memory is the program's own, rather than page cache the limit counts alike — the distinction every memory recommendation rests on; - what one more replica actually costs, which resident memory overstates because it counts a shared page once per process mapping it, and `Pss` does not; - how close a process is to descriptor exhaustion, which no metric shows until connections start failing. `process_peaks` gains seven fields. They merge as maxima across the processes of one build, except the descriptor ceiling, which merges as a minimum: headroom is what the tightest replica has. The names carry the distinction the record now holds — `peak_rss_bytes` is a mark the kernel maintains, every `*_max` beside it is the highest sample the agent took. Descriptors are counted without being followed: what one points at is a path, a socket or a pipe. The rollup's mapping paths are not read. `Name` still has nowhere in the status struct to go, and the test that keeps that true as the struct grows now covers five fields instead of two. No new capability — the node already passes the same ptrace check to open /proc/<pid>/exe. The unmeasured cost of the smaps_rollup page-table walk is stated in the ADR rather than implied away (ADR 0039). go test ./... -race, golangci-lint run ./... (host and GOOS=linux): 0 issues.
…et (ADR 0044) The obligation is new, not restated: how the footprint samples differ from the kernel's mark, which figure may be summed across replicas, and what an absent descriptor ceiling means. Both the file and the "4. Ingest" section move to exactly the size that obligation costs.
kfeofantov
force-pushed
the
feat/what-the-process-holds
branch
from
August 31, 2026 16:59
09a87c3 to
0f6c8e7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three of the seven new fields are rows of a file the scanner already opens and
parses; the other four cost two small reads beside it.
What was missing
/proc/<pid>/statushas about forty rows and ADR 0052 took two of them, becausethe allow-list was drawn for one finding. Three questions the agent could not
answer are answered by rows in that same file or in two small neighbours:
counts anonymous and file-backed pages alike, so 300 MB of heap plus 500 MB of
page cache looks identical to 800 MB of heap. Every recommendation about a
memory limit rests on that difference, and the agent was shipping the total.
process mapping it; replicas of one image share their text.
Pssis the figurethat may be summed across replicas, and resident memory is not.
every metric until connections start failing.
What ships
process_peaksgains seven fields, merged across the processes of one build onevery node still running it:
Maxima, except the ceiling, which is a minimum — headroom is what the
tightest replica has: the most descriptors any replica held against the lowest
ceiling any was given.
The names carry a distinction the record now has to hold:
peak_rss_bytesis amark the kernel maintains between readings; every
*_maxbeside it is thehighest value the agent happened to sample. Naming them alike would have claimed
a continuity that isn't there.
Filtering
Descriptors are counted, not followed — what one points at is a path, a
socket or a pipe. The rollup's mapping paths are not read. Keys are matched
exactly, because
Pss_Dirtysits directly underPssandPrivate_Cleandirectly above
Private_Dirty, and a prefix match would be wrong by a plausibleamount.
Namestill has nowhere in the status struct to go, and the test thatkeeps that true as the struct grows now covers five fields instead of two.
No new capability: the node already runs with
hostPIDandCAP_SYS_PTRACEtoopen
/proc/<pid>/exe, and every file here sits behind the same kernel check.Stated, not implied
smaps_rollupcosts a page-table walk proportional to the process's addressspace, per kept process per pass. That cost is not measured, so ADR 0061
marks it rather than claiming otherwise (ADR 0039), and names how to close it:
the inventory e2e on a cluster with large processes, timed with the read and
without.
Evidence
go test ./... -raceandgolangci-lint run ./...on host andGOOS=linux:0 issues. The inventory e2e now asserts the footprint against the kernel's own
arithmetic rather than magic numbers — anon plus file cannot exceed the
high-water mark the same kernel recorded. The e2e suites run against a kind
cluster and are not part of CI, so this change has not been exercised on one.
🤖 Generated with Claude Code
https://claude.ai/code/session_011YRKuL97KRndaopfpixrhp