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:

  1. Enumerates all running containers via client.list(...)
  2. Invokes collectStats() to gather an initial sample (stats1)
  3. Pauses for 2 seconds
  4. Gathers a second sample (stats2)
  5. Calculates CPU percentage via calculateCPUPercent() using the delta of cpuUsageUsec between snapshots
  6. 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 cpuUsageUsec between 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

Summary

  • The container stats command 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-stream delivers snapshot data in table or JSON format for scripting.
  • CPU utilization is calculated from the delta of cpuUsageUsec across two 2-second samples.
  • All metrics—including memory, network I/O, block I/O, and process counts—originate from RuntimeService.swift and propagate through the ContainerClient API.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →