feat(stats): report cpuPercent in the machine-readable formats - #2146
feat(stats): report cpuPercent in the machine-readable formats#2146guanchzhou wants to merge 2 commits into
Conversation
container stats already samples twice, sleeping two seconds between samples, and computes a CPU percentage from the pair for its table. Only the second sample was rendered for json, yaml and toml, so a consumer paid that latency and received neither the percentage nor the earlier sample needed to derive it. Getting a CPU percentage from the CLI meant invoking the command twice and diffing cpuUsageUsec by hand. Adds a StatsReport payload for the non-table formats: the latest sample plus the derived percentage. ContainerResource.ContainerStats models a single sample and a rate is not a property of one sample, so the derived value lives in the command rather than widening that type. The two-second interval is now a single constant. collectStats sleeps for it and the percentage divides by it, so the two must agree; they were separate literals before.
kavix
left a comment
There was a problem hiding this comment.
Thanks for working on this, @guanchzhou! Adding cpuPercent to the machine-readable output is a useful improvement, and consolidating the hardcoded 2-second sleep into sampleInterval also helps prevent sampling drift.
A few small suggestions that could improve maintainability
- Deduplicate CPU calculation
Consider moving cpuPercent into a computed property on StatsSnapshot. This would allow both StatsReport and statsTable to share the same calculation logic and avoid duplication. - Avoid field duplication in StatsReport
Rather than manually mirroring all properties from ContainerResource.ContainerStats (and potentially missing fields added in the future), consider wrapping stats2 directly and implementing a custom encode(to:). This would make the implementation more resilient to future changes. - Add test coverage
Please consider adding or updating unit/snapshot tests to verify that --format json includes the expected cpuPercent field.
|
Thanks @kavix — took all three.
|
Review asked for one calculation, no field mirroring on StatsReport, and tests that json carries cpuPercent. TOMLEncoder cannot merge two keyed containers, so encode flattens the wrapped sample in one pass.
9746cab to
0a12f00
Compare
Fixes #2144
container statsalready samples twice, sleeping two seconds between samples, and computes a CPU percentage from the pair for its table.Output.renderreceived onlystats2, sojson,yamlandtomlconsumers paid that latency and got back neither the percentage nor the earlier sample needed to derive it. Obtaining a CPU percentage from the CLI meant invoking the command twice and diffingcpuUsageUsecby hand — four seconds of sleeping for a number the tool already had after two.Change
A
StatsReportpayload for the non-table formats: the latest sample plus the derived percentage.ContainerResource.ContainerStatsis documented as modelling a single sample, and a rate is not a property of one sample, so the derived value lives in the command rather than widening that type. That also keeps the wire shape of the existing fields unchanged —cpuPercentis additive.The two-second interval becomes a single
sampleIntervalconstant.collectStatssleeps for it andcalculateCPUPercentdivides by it, so the two have to agree; they were separate.seconds(2)literals before, one incollectStatsand one instatsTable.Verification, end to end with this build
Table (unchanged) and json side by side, same runtime, four containers — one of them a
while :; do :; doneloop allocated a single CPU:The percentages agree with the table across independent samplings, and the single-CPU spin loop reads ~100% as expected.
yamlandtomlcarry the field too, including through TOML's[[items]]wrapper for top-level arrays:swift build --product containersucceeds;swift test --filter ContainerCommandsTests→ 29 tests in 10 suites passed.Two things I left for your call
calculateCPUPercentdocuments "100% = one fully utilized core", matchingtopanddocker stats. I noted that on the field, because I made the opposite mistake against this API before checkingcontainer statsas ground truth — dividing by the container's CPU allocation, which understates a 4-CPU container fourfold. Happy to rename to something likecpuPercentOfOneCoreif you would prefer the convention in the name.stats1is still dropped. Emitting both samples would let a consumer compute other rates (network and block I/O per second) the same way. I kept this PR to the one number the table already shows; say the word if the fuller shape is preferable.Environment: macOS 27.0 (Tahoe), Apple silicon, Swift 6.4.
Per CONTRIBUTING: I used AI assistance while investigating and drafting this, and I can explain and justify every line — the change moves an already-computed value into the rendered payload and de-duplicates one interval literal.