How to Monitor Real-Time Resource Usage of Containers with Apple Container
Use the container stats command to stream live CPU, memory, network, and block I/O metrics from running containers, or add --no-stream for a single JSON snapshot.
The Apple container toolset provides native support for observing live resource consumption through the container stats command. This interface delivers per-container metrics by integrating CLI parsing, XPC-based client-server communication, and runtime statistics collection. Whether you need continuous monitoring or programmatic access to resource data, the tool exposes detailed usage statistics directly from the underlying daemon.
Architecture of the container stats Command
The implementation spans three architectural layers that handle everything from user input to kernel-level data retrieval.
CLI Front-End (ContainerStats)
The entry point resides in Sources/ContainerCommands/Container/ContainerStats.swift. The ContainerStats command parses user flags such as --format (accepting table or json) and --no-stream to determine execution mode. It then routes to either streaming or static logic based on these options.
Client-Server API Layer
Communication flows through ContainerClient, which dispatches XPC calls to the daemon. The critical method is stats(id:), defined in the client interface, which returns a ContainerResource.ContainerStats value. This data structure—modeled in Sources/ContainerResource/Container/ContainerStats.swift—encapsulates raw metrics including memory bytes, CPU microseconds, network I/O, block I/O, and process counts.
Runtime Service Layer
On the daemon side, Sources/Services/RuntimeLinux/Server/RuntimeService.swift implements the RuntimeService class. This service invokes container.statistics() against the underlying container runtime, then returns a ContainerStatistics-derived struct that the client decodes and formats for display.
How Resource Monitoring Works
The command distinguishes between two operational modes, each handled by distinct internal methods.
Streaming Mode (Real-Time)
When invoked without --no-stream, the command executes runStreaming() from ContainerStats.swift. This method enters an alternate screen buffer and repeatedly fetches fresh snapshots via client.stats(id:). It clears the screen between iterations and renders an updated table using statsTable(_:), producing a top-like interface that continues until you press Ctrl-C.
Static Mode (Single Snapshot)
When you pass --no-stream, the command calls runStatic(). This routine:
- Enumerates all running containers via
client.list(...) - Invokes
collectStats()to gather an initial sample (stats1) - Pauses for 2 seconds
- Gathers a second sample (
stats2) - Calculates CPU percentage via
calculateCPUPercent()using the delta ofcpuUsageUsecbetween snapshots - Outputs results using
formatBytes(_:)for human-readable sizes (KiB/MiB/GiB) and the appropriate formatter for the chosen--format
Container Metrics Explained
Each stats payload contains the following fields, directly mapped from the ContainerStats struct:
- CPU %: Percentage of a full CPU core consumed, computed from the difference in
cpuUsageUsecbetween two 2-second samples. - Memory Usage / Limit: Current resident memory in bytes versus the container-configured memory limit.
- Network Rx/Tx: Cumulative bytes received and transmitted across all network interfaces.
- Block I/O: Total bytes read from and written to block devices.
- PIDs: Current number of processes running inside the container namespace.
Practical Examples
Stream Live Statistics for All Containers
container stats
This opens an alternate screen buffer and continuously refreshes a table showing all running containers until interrupted.
Export a Single Snapshot as JSON
container stats --format json --no-stream
This invokes runStatic(), producing a parseable JSON array suitable for scripting and automation.
Monitor a Specific Container
container stats my-web-app
Replace my-web-app with a container ID or name. Without --no-stream, this streams updates for only that container.
JSON Output Structure
When using --format json, the output matches the Swift model defined in Sources/ContainerResource/Container/ContainerStats.swift:
[
{
"id": "e5f9a7c3b2d1",
"memoryUsageBytes": 104857600,
"memoryLimitBytes": 2147483648,
"cpuUsageUsec": 12345678,
"networkRxBytes": 5242880,
"networkTxBytes": 1048576,
"blockReadBytes": 262144,
"blockWriteBytes": 131072,
"numProcesses": 12
}
]
Key Source Files
Sources/ContainerCommands/Container/ContainerStats.swift– Implementsrun(),runStatic(),runStreaming(),collectStats(), and formatting helpers.Sources/ContainerResource/Container/ContainerStats.swift– Defines theContainerStatsdata model with properties for memory, CPU, network, block I/O, and PIDs.Sources/Services/RuntimeLinux/Server/RuntimeService.swift– Daemon-side service that fetches raw statistics from the runtime viacontainer.statistics().Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift– Exposes thestats(id:)RPC endpoint over XPC.
Summary
- The
container statscommand provides real-time visibility into container resource usage through a three-tier architecture spanning CLI, XPC client, and runtime service. - Streaming mode (
runStreaming()) offers a continuously updating TUI for interactive monitoring. - Static mode (
runStatic()) with--no-streamdelivers snapshot data in table or JSON format for scripting. - CPU utilization is calculated from the delta of
cpuUsageUsecacross two 2-second samples. - All metrics—including memory, network I/O, block I/O, and process counts—originate from
RuntimeService.swiftand propagate through theContainerClientAPI.
Frequently Asked Questions
How does the container stats command calculate CPU percentage?
The command calls collectStats() to retrieve two samples approximately 2 seconds apart, then executes calculateCPUPercent(). This function computes the delta between the first sample's cpuUsageUsec and the second sample's cpuUsageUsec, converting that time difference into a percentage of total CPU capacity.
Can I monitor resource usage programmatically without the TUI?
Yes. Append --format json --no-stream to any container stats invocation. This triggers runStatic() and outputs a JSON array containing the current metrics for all specified containers, bypassing the alternate screen buffer used by streaming mode.
What is the difference between streaming and static mode?
Streaming mode (default) invokes runStreaming(), which enters an alternate screen buffer and refreshes the display indefinitely until interrupted. Static mode (enabled with --no-stream) invokes runStatic(), which collects a single set of statistics, calculates CPU percentages from two temporal samples, prints the results, and exits immediately.
Where does the daemon retrieve underlying container metrics?
According to the source in Sources/Services/RuntimeLinux/Server/RuntimeService.swift, the RuntimeService calls container.statistics() on the runtime instance. This method returns a ContainerStatistics object containing raw cgroup and namespace data that the service wraps into the ContainerStats struct returned to clients.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →