Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ This project follows a Keep a Changelog-style format and uses version tags for r

## [Unreleased]

## [1.6.3] - 2026-08-08

### Added
- **GPU% column in the TUI process table** — `gpu_time_share` from the Monitor
was always collected but never rendered. The process table now shows a `GPU%`
column (between CPU% and PWR) displaying the per-process share of total GPU
time as a percent. `–` when the first GPU delta is still pending. The `s` sort
cycle now includes GPU% (CPU% → GPU% → PWR → RSS → PID).

## [1.6.2] - 2026-08-08

### Fixed
Expand Down
26 changes: 21 additions & 5 deletions actop/tui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,19 @@
_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]

SORT_CPU = "cpu"
SORT_GPU = "gpu"
SORT_POWER = "power"
SORT_MEMORY = "memory"
SORT_PID = "pid"
SORT_LABELS = {
SORT_CPU: "CPU%",
SORT_GPU: "GPU%",
SORT_POWER: "PWR",
SORT_MEMORY: "RSS",
SORT_PID: "PID",
}

_SORT_CYCLE = [SORT_CPU, SORT_POWER, SORT_MEMORY, SORT_PID]
_SORT_CYCLE = [SORT_CPU, SORT_GPU, SORT_POWER, SORT_MEMORY, SORT_PID]


def sort_processes(processes, sort_mode, limit):
Expand All @@ -49,6 +51,12 @@ def sort_processes(processes, sort_mode, limit):
return sorted(processes, key=lambda p: p.attributed_w or 0.0, reverse=True)[
:limit
]
elif sort_mode == SORT_GPU:
return sorted(
processes,
key=lambda p: p.gpu_time_share or 0.0,
reverse=True,
)[:limit]
else:
# Default: CPU sort (snapshot list already CPU-sorted from L2).
return list(processes)[:limit]
Expand Down Expand Up @@ -84,7 +92,7 @@ def _process_display_name(command, max_len=24):

q Quit
p Pause / resume sampling
s Cycle process sort (CPU% → PWR → RSS → PID)
s Cycle process sort (CPU% → GPU% → PWR → RSS → PID)
g Toggle chart glyph (braille dots / blocks)
l Cycle layout preset (grid ⇄ stack)
c Toggle per-core panels (hidden by default)
Expand Down Expand Up @@ -122,6 +130,9 @@ def _process_display_name(command, max_len=24):
[b]Process table[/b]

CPU% Per-process CPU utilization (Δ CPU-time over the interval)
GPU% Per-process GPU utilization (share of total GPU time over the
interval, from Metal command-queue usage via AGXDeviceUserClient).
"–" means no GPU reading yet (first sample after launch or resume).
PWR Estimated per-process CPU+GPU power: the process's share of
total CPU-time × package CPU watts, plus its share of total
GPU-time (from Metal command-queue usage) × package GPU watts.
Expand Down Expand Up @@ -475,15 +486,17 @@ def _refresh_process_table(self) -> None:
if self._sort_mode != self._last_sort_mode:
self._last_sort_mode = self._sort_mode
table.clear(columns=True)
cols = ["PID", "Command", "CPU%", "PWR", "MEM (MiB)", "Threads"]
cols = ["PID", "Command", "CPU%", "GPU%", "PWR", "MEM (MiB)", "Threads"]
if self._sort_mode == SORT_PID:
cols[0] = "*PID"
elif self._sort_mode == SORT_CPU:
cols[2] = "*CPU%"
elif self._sort_mode == SORT_GPU:
cols[3] = "*GPU%"
elif self._sort_mode == SORT_POWER:
cols[3] = "*PWR"
cols[4] = "*PWR"
elif self._sort_mode == SORT_MEMORY:
cols[4] = "*MEM (MiB)"
cols[5] = "*MEM (MiB)"
table.add_columns(*cols)
else:
table.clear()
Expand All @@ -510,10 +523,13 @@ def _refresh_process_table(self) -> None:
else:
shown_pwr += attributed_w
pwr_cell = f"{attributed_w:.2f}W"
gts = proc.gpu_time_share
gpu_cell = "–" if gts is None else f"{gts * 100:.1f}"
table.add_row(
str(proc.pid),
_process_display_name(proc.command, max_len=28),
f"{proc.cpu_percent or 0.0:.1f}",
gpu_cell,
pwr_cell,
# MiB from the exact byte count; the column header says MiB.
f"{(proc.rss_bytes or 0) / 1024 / 1024:.1f}",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "actop"
version = "1.6.2"
version = "1.6.3"
description = "Apple Silicon (M1–M4) power, GPU, ANE & memory-bandwidth monitor — sudoless TUI + Python API for profiling local LLM / MLX / CoreML inference"
readme = "README.md"
license = { text = "MIT" }
Expand Down