# JSON output

Canonical: https://plottypus.com/docs/json

Last updated: 2026-09-23

> plottypus --json and --json-stream, schema 1: every field, its unit, when it is null, and the names it shares with macmon.

```sh
plottypus --json                          # one sample, then exit
plottypus --json-stream                   # one line per interval (NDJSON) until you stop it
plottypus --json-stream --count 10 -i 1s  # ten lines, one a second
plottypus --json --procs 25               # include the top 25 processes (default 10)
```

The field most scripts want is `cpu_power`, in watts. On the M5 Pro that is the SMC rails. On other chips it is a held IOReport value, or `null` once the value is older than 10 seconds. [Where the watts come from](https://plottypus.com/blog/mac-cpu-watts-without-sudo).

Both modes sample once a second unless `-i` says otherwise. The first sample only primes the rates, so `--json` takes one interval before it prints. It then waits, for at most 2.5 s in total, until the slow collectors (sensors, battery, IOReport) have a first reading, so a one-shot sample is complete. `--json-stream` does not wait: its first lines may still carry `null` for readings that arrive later.

Nothing needs sudo, and nothing is sent anywhere. Output goes to stdout; errors go to stderr.

## Rules

- `schema_version` is `1`. Schema 1 only ever **gains** fields; nothing is renamed or removed. A test pins the field set.
- snake_case everywhere. Names follow [macmon](https://github.com/vladkens/macmon) where macmon has one: `cpu_power`, `gpu_power`, `ane_power`, `ram_power`, `sys_power`, `all_power`, `gpu_freq_mhz`, `memory.ram_total`, `memory.ram_usage`, `memory.swap_total`, `memory.swap_usage`, `temp.cpu_temp_avg`, `temp.gpu_temp_avg`.
- **`null` means not measured**, on this Mac or not yet. A measured zero is `0`. Nothing is ever reported as `0` to fill a gap.
- Non-finite numbers are never printed: `NaN` becomes `null` (or `0` for a usage share).
- Units are in the name or in the table: `_bytes`, `_mhz`, `_c` (°C), `_power` (watts), `usage` (a share from 0 to 1).
- **`_bps` is a rate per second, and the unit differs:** `network.rx_bps` and `tx_bps` are **bits** per second; `disk.read_bps`, `disk.write_bps`, `memory.swap_in_bps` and `swap_out_bps` are **bytes** per second.

## An example

From `plottypus --demo --json --procs 2`, the canned demo session, so you can reproduce it on any Mac. `cpu.cores` is cut to two entries here; a real sample lists every logical core.

```json
{
  "schema_version": 1,
  "unix_ms": 1790190625473,
  "interval_ms": 1000,
  "primed": true,
  "soc": {
    "name": "M5 Pro",
    "e_cores": 0,
    "p_cores": 12,
    "s_cores": 6,
    "gpu_cores": 20,
    "memory_bytes": 25769803776
  },
  "cpu": {
    "usage": 0.101194374,
    "scaled": 0.025834152,
    "freq_mhz": 1260,
    "temp_c": 43.437275,
    "clusters": [
      {
        "kind": "performance",
        "cores": 12,
        "usage": 0.10454839,
        "scaled": 0.025833176,
        "freq_mhz": 1020
      },
      {
        "kind": "super",
        "cores": 6,
        "usage": 0.094486326,
        "scaled": 0.025836105,
        "freq_mhz": 1260
      }
    ],
    "cores": [
      {
        "kind": "performance",
        "index": 0,
        "usage": 0.15696236
      },
      {
        "kind": "performance",
        "index": 1,
        "usage": 0.048249885
      }
    ]
  },
  "gpu": {
    "usage": 0.060781993,
    "scaled": 0.017491708,
    "freq_mhz": 466,
    "temp_c": 38.0,
    "mem_in_use_bytes": 943718400,
    "mem_alloc_bytes": 1468006400
  },
  "memory": {
    "ram_total": 25769803776,
    "ram_usage": 13779774464,
    "swap_total": 2147483648,
    "swap_usage": 859832320,
    "wired_bytes": 3328599552,
    "compressed_bytes": 1288490240,
    "cache_bytes": 6871947776,
    "pressure": "nominal",
    "swap_in_bps": 0,
    "swap_out_bps": 0,
    "free_percent": 47
  },
  "network": {
    "iface": "en0",
    "rx_bps": 931840,
    "tx_bps": 147756,
    "primary": "en0"
  },
  "disk": {
    "read_bps": 2057865,
    "write_bps": 427981,
    "volumes": [
      {
        "name": "Macintosh HD",
        "mount": "/",
        "used_bytes": 512400000000,
        "total_bytes": 994662584320
      }
    ]
  },
  "temp": {
    "cpu_temp_avg": 43.437275,
    "gpu_temp_avg": 38.0,
    "hotspot_c": 46.437275,
    "e_cluster_c": null,
    "p_cluster_c": 41.437275,
    "s_cluster_c": 43.437275
  },
  "fans": [
    {
      "name": "Fan 1",
      "rpm": 0,
      "max_rpm": 6550
    },
    {
      "name": "Fan 2",
      "rpm": 0,
      "max_rpm": 7150
    }
  ],
  "thermal_pressure": "nominal",
  "sys_power": 6.8392706,
  "all_power": 1.3792578,
  "cpu_power": 1.0824988,
  "gpu_power": 0.29675904,
  "ane_power": null,
  "ram_power": 0.6202551,
  "gpu_freq_mhz": 466,
  "battery": {
    "percent": 79.0,
    "charging": false,
    "on_ac": false,
    "watts": -6.8392706,
    "minutes_remaining": 501
  },
  "processes": [
    {
      "pid": 412,
      "ppid": 1,
      "name": "WindowServer",
      "user": "_windowserver",
      "cpu_pct": 11.229062,
      "cpu_cores": 0.11229062,
      "mem_bytes": 432013312,
      "threads": 24,
      "energy_w": 0.15720686,
      "restricted": false,
      "status": ""
    },
    {
      "pid": 2240,
      "ppid": 2210,
      "name": "Safari Web Content",
      "user": "dev",
      "cpu_pct": 6.6470704,
      "cpu_cores": 0.066470705,
      "mem_bytes": 421527552,
      "threads": 22,
      "energy_w": 0.09305899,
      "restricted": false,
      "status": ""
    }
  ],
  "verdict": {
    "kind": "normal",
    "headline": "All normal",
    "evidence": []
  },
  "health": {
    "battery": "ok",
    "cpu": "ok",
    "disk": "ok",
    "fans": "ok",
    "gpu": "ok",
    "memory": "ok",
    "network": "ok",
    "processes": "ok",
    "sensors": "ok",
    "thermal": "ok"
  }
}
```

## Fields

### Envelope

| Field | Type | Meaning |
| --- | --- | --- |
| `schema_version` | int | `1` |
| `unix_ms` | int | wall-clock time the object was built |
| `interval_ms` | int | sampling interval (`-i`; 1000 by default in headless modes) |
| `primed` | bool | rates were measured over a real interval; always `true` in printed samples |

### Chip

| Field | Type | Meaning |
| --- | --- | --- |
| `soc.name` | string | chip name, e.g. `Apple M5 Pro` |
| `soc.e_cores`, `soc.p_cores`, `soc.s_cores` | int | Efficiency, Performance and Super core counts |
| `soc.gpu_cores` | int | GPU core count |
| `soc.memory_bytes` | int | installed memory |

### CPU

| Field | Type | Meaning |
| --- | --- | --- |
| `cpu.usage` | 0–1 | non-idle share across all cores (what Activity Monitor calls CPU) |
| `cpu.scaled` | 0–1 | usage weighted by frequency: Mach busy × the IOReport clock ratio per core. Equals `usage` where IOReport residency is unavailable |
| `cpu.freq_mhz` | int or null | package frequency |
| `cpu.temp_c` | float or null | CPU temperature |
| `cpu.clusters[]` | object | `{kind, cores, usage, scaled, freq_mhz}` per cluster. `kind` is `efficiency`, `performance` or `super`. `freq_mhz` is the residency-weighted average of the DVFS states, `null` until the first residency window lands or where IOReport is unavailable |
| `cpu.cores[]` | object | `{kind, index, usage}` per logical core, in Mach order |

### GPU

| Field | Type | Meaning |
| --- | --- | --- |
| `gpu` | object or null | `{usage, scaled, freq_mhz, temp_c, mem_in_use_bytes, mem_alloc_bytes}`; `null` without a GPU reading |
| `gpu_freq_mhz` | int or null | macmon name; the same value as `gpu.freq_mhz` |

### Memory

| Field | Type | Meaning |
| --- | --- | --- |
| `memory.ram_total`, `memory.ram_usage` | bytes | installed and used memory (used as Activity Monitor counts it) |
| `memory.swap_total`, `memory.swap_usage` | bytes | swap size and swap in use |
| `memory.wired_bytes`, `memory.compressed_bytes` | bytes | wired and compressed memory |
| `memory.cache_bytes` | bytes | file-backed plus purgeable: Activity Monitor's "Cached Files" |
| `memory.pressure` | string | `nominal`, `warn` or `critical` |
| `memory.swap_in_bps`, `memory.swap_out_bps` | bytes/s or null | swap activity; `null` until two samples exist |
| `memory.free_percent` | 0–100 or null | `kern.memorystatus_level`, the figure `memory_pressure` prints |

### Network and disk

| Field | Type | Meaning |
| --- | --- | --- |
| `network.iface` | string | headline interface: the default-route one, else the busiest physical `enN` |
| `network.rx_bps`, `network.tx_bps` | bits/s | from the exact 64-bit `ifmib` counters |
| `network.primary` | string or null | interface carrying the default route (`en0`, a VPN's `utunN`); `null` without one |
| `disk.read_bps`, `disk.write_bps` | bytes/s | disk throughput |
| `disk.volumes[]` | object | `{name, mount, used_bytes, total_bytes}` |

### Temperature and fans

| Field | Type | Meaning |
| --- | --- | --- |
| `temp.cpu_temp_avg`, `temp.gpu_temp_avg` | °C or null | macmon names |
| `temp.hotspot_c` | °C or null | the hottest die sensor |
| `temp.e_cluster_c`, `temp.p_cluster_c`, `temp.s_cluster_c` | °C or null | per-cluster zones; `null` for a cluster the chip does not have |
| `fans[]` | object | `{name, rpm, max_rpm}`; an **empty array** on fanless Macs, never a fan at 0 |
| `thermal_pressure` | string | `nominal`, `fair`, `serious` or `critical` |

### Power

| Field | Type | Meaning |
| --- | --- | --- |
| `sys_power` | W or null | whole-system input power (SMC `PSTR`); `null` when the SMC has no `PSTR` |
| `cpu_power` | W or null | SMC CPU rails on the M5 Pro (Mac17,8), else the IOReport CPU energy counter |
| `gpu_power` | W or null | IOReport GPU energy |
| `ane_power`, `ram_power` | W or null | IOReport ANE and DRAM energy |
| `all_power` | W or null | sum of the known `cpu_power`, `gpu_power` and `ane_power`; `null` when none is known |

IOReport rails are held between publications and turn `null` until two publications have landed, or once the held value is more than 10 s old. On macOS 27 the CPU, ANE and DRAM counters publish in batches seconds to minutes apart, so expect those three to be `null` often on Macs without SMC CPU rails. See [Power and battery](https://plottypus.com/docs/panels#power-and-battery).

### Battery

| Field | Type | Meaning |
| --- | --- | --- |
| `battery` | object or null | `{percent, charging, on_ac, watts, minutes_remaining}`; `null` on Macs without a battery |
| `battery.watts` | W or null | power through the battery: positive in, negative out |
| `battery.minutes_remaining` | int or null | macOS's own estimate; `null` when it has none (for example on AC) |

### Processes

`processes[]` holds the top `--procs N` processes by CPU (default 10):

| Field | Type | Meaning |
| --- | --- | --- |
| `pid`, `ppid` | int | process and parent id |
| `name`, `user` | string | display name and owner |
| `cpu_pct` | float or null | **per-core percent**, like Activity Monitor and `top`: 100 is one fully busy core, so it can exceed 100 |
| `cpu_cores` | float or null | `cpu_pct / 100` |
| `mem_bytes` | int or null | physical footprint |
| `threads` | int or null | thread count |
| `energy_w` | float or null | the process's energy use, in watts |
| `restricted` | bool | `true` when the kernel withholds this process's details (another user's process, without sudo) |
| `status` | string | `stopped`, `zombie`, or empty for everything else |

When `restricted` is `true`, `cpu_pct`, `cpu_cores`, `mem_bytes`, `threads` and `energy_w` are `null`, never an invented `0`.

### Verdict and health

| Field | Type | Meaning |
| --- | --- | --- |
| `verdict.kind` | string | `normal`, `busy`, `memory_tight`, `throttling` or `battery_drain` |
| `verdict.headline` | string | `All normal`, `Busy: rustc using 9.1 cores`, … |
| `verdict.evidence[]` | string | the readings behind it |
| `health` | object | per collector: `ok`, `pending`, `stale`, `failed` or `absent` |

`battery_drain` is part of the schema but the 1.0.0 verdict engine never produces it. The rules are on [Diagnose and doctor](https://plottypus.com/docs/diagnose-doctor#the-verdict).

## Recipes

```sh
# CPU watts, or "n/a" when the Mac has not published one
plottypus --json | jq -r '.cpu_power // "n/a"'

# Per-cluster load and clock
plottypus --json | jq -r '.cpu.clusters[] | "\(.kind)\t\(.usage * 100 | round)%\t\(.freq_mhz // "-") MHz"'

# The three hungriest processes, per-core percent
plottypus --json --procs 3 | jq -r '.processes[] | "\(.cpu_pct // "-")\t\(.name)"'

# Log system watts every 5 s as CSV
plottypus --json-stream -i 5s | jq -r '[.unix_ms, .sys_power] | @csv' >> watts.csv

# Test a script without a busy Mac
plottypus --demo --json | jq .verdict
```

Keep the `// "n/a"` style defaults: a `null` is information, not an error.

## Coming in 1.1

CSV export and a status line for tmux, SketchyBar, Starship and SwiftBar are planned for 1.1. They are not in 1.0.0.
