Repository navigation
feat(stats): add --all-projects and --aggregate flags - #14337
Conversation
docker-agent
left a comment
There was a problem hiding this comment.
Assessment: 🟢 APPROVE
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
`docker compose stats` only reports on a single project. Add two
opt-in flags, the default behavior being unchanged:
--all-projects one line per container for every project, with a
PROJECT column
--aggregate one line per project, summing the resource usage of
its containers; alone, it reports on the current
project
Both support the `table` and `json` formats, the latter printing one
object per line like `docker stats`. Unless `--no-stream` is set, they
refresh 2s after each sample. They cannot be combined with a SERVICE,
and `--all-projects` cannot be combined with `--project-name`.
docker/cli does not export its stats collection, so containers are
sampled through the Engine API and the figures computed the same way
`docker stats` does.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
0059b5a to
25e882a
Compare
docker-agent
left a comment
There was a problem hiding this comment.
Assessment: 🟡 NEEDS ATTENTION
ndeloof
left a comment
There was a problem hiding this comment.
Nice feature, and the code is clean: up-front validation (flags, format), deterministic ordering, JSON one object per line like docker stats. The unit tests pass locally and go vet is clean. I did not run it against a daemon. A few points to address:
- CPU on older engines. CPU% relies on the daemon returning the previous sample (
IncludePreviousSample). If an engine ignores the option,PreCPUStatsstays at zero,cpuDeltaandsystemDeltaare both positive, and the result is a since-start average rather than an instantaneous value. Could you check this on the engine versions Compose supports, or document it? - Unbounded concurrency.
collectContainerStatsstarts one goroutine per container on every refresh (2s). With--all-projectson a busy host that can be hundreds of concurrent calls;eg.SetLimit(...)would bound it. - One failure stops the whole loop. Any error other than
NotFoundon a single container cancels the other samples and exits. Rendering that row without usage (as for stopped containers) would be more robust for a refresh loop.
Minor:
- With
--all, a stopped container shows0B / 0B; as far as I rememberdocker statsshows--. - The main loop of
runStatsProjects(--no-stream, screen clear, refresh) isn't covered by tests, only its building blocks. - The formulas follow
docker statsfor Linux only. A link to the upstream functions in a comment would help limit drift, and Windows hosts get no warning.
Drafted with Claude Code; read and validated before posting.
Sampling a container takes about a second on the daemon side, and one goroutine was started per container on every refresh: with all the projects of a busy host that is hundreds of concurrent calls. Bound them to 32. A container that cannot be sampled is now shown without usage, like a stopped one, instead of aborting the refresh of all the others. An interrupted command is still reported as such, not rendered as containers without usage. Point containerUsage to the docker/cli helpers it follows, and cover the --no-stream path of runStatsProjects. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
|
Thanks for the review. 1. CPU on older engines Compose doesn't send anything new here: 2. Unbounded concurrency Fixed: at most 32 samples at a time ( 3. One failure stops the loop Fixed: a container that can't be sampled is listed without usage and the others are still refreshed. An interrupted command is still reported as such rather than rendered as containers without usage. Minor
Pushed in 71bb455. |
docker-agent
left a comment
There was a problem hiding this comment.
This review covers only the commits pushed since 25e882ada344.
Assessment: 🟢 APPROVE
The concurrency-bounding refactor (SetLimit(32), silent-continue on sampling failures, ctx.Err() cancellation check) is correct and well-tested. The four new tests cover the key scenarios cleanly.
| rows, err := collectContainerStats(t.Context(), apiClient, false, client.Filters{}) | ||
| assert.NilError(t, err) | ||
| assert.Equal(t, len(rows), containers) | ||
| assert.Equal(t, peak.Load(), int32(statsConcurrency)) |
There was a problem hiding this comment.
[low] Exact peak-concurrency assertion may fail spuriously under extreme CPU starvation
TestCollectContainerStatsBoundsConcurrency asserts peak.Load() == int32(statsConcurrency) — i.e., exactly 32 goroutines must have been running simultaneously. The test creates 40 tasks (32 + 8) each sleeping 20 ms, relying on the Go scheduler filling all 32 errgroup slots before any of the first batch finishes.
In practice the 20 ms sleep provides a large margin, but on an extremely CPU-starved CI runner the scheduler could delay goroutine startup long enough that some of the first batch complete before all 32 slots are filled, causing peak to land below 32 and the test to fail spuriously.
The meaningful invariant is the upper bound (≤ statsConcurrency), not the exact value. If also testing that real concurrency occurred, a lower-bound check (> 1) is sufficient:
| assert.Equal(t, peak.Load(), int32(statsConcurrency)) | |
| assert.Assert(t, peak.Load() <= int32(statsConcurrency), "peak concurrency %d exceeded limit %d", peak.Load(), statsConcurrency) |
| Confidence | Score |
|---|---|
| 🟡 moderate | 62/100 |
ndeloof
left a comment
There was a problem hiding this comment.
LGTM, thanks for the fixes. I checked the points in the reply:
- CPU on older engines: confirmed in moby.
noStreamFirstFrameis indaemon/stats.goin v19.03.15 (unconditional), v20.10.0 and v25.0.0 (!config.OneShot), and onmaster. Compose doesn't sendone-shot, soprecpu_statsis populated. I did not run an older engine either. - Bounded concurrency and rows kept when a sample fails (cancellation still reported): correct, and covered by the new tests.
0B / 0Bfor a stopped container matchesdocker stats -a --no-stream, fair enough.
Two non-blocking suggestions:
sampleStatserrors are dropped silently, so a container that failed to sample looks like a stopped one. Alogrus.Debugfwould help diagnose it.TestCollectContainerStatsBoundsConcurrencyassertspeak == statsConcurrency, which depends on scheduling (docker-agent flagged it too). Having each call wait untilstatsConcurrencyare in flight (with a timeout) would make it deterministic.
Drafted with Claude Code; read and validated before posting.
What this PR does
Adds two opt-in flags to
docker compose stats. Without them the command is unchanged: current project, one line per container.--all-projects: one line per container for every project, with aPROJECTcolumn--aggregate: one line per project, summing CPU, memory, network, block I/O and PIDs of its containers. Combined with--all-projectsit covers all projects; alone it reports on the current projectBoth support
tableandjson(one object per line, likedocker stats) and refresh until interrupted unless--no-streamis set. They can't be combined with aSERVICE, and--all-projectscan't be combined with--project-name.docker/cli doesn't export its stats collection, so containers are sampled through the Engine API and the figures are computed the way
docker statsdoes.Related issue
Closes #12321
(not mandatory) A picture of a cute animal, if possible in relation to what you did
