Skip the synthetic swap disk entry when collecting filesystem stats on macOS. The swap row is not a real mountpoint, so calling statvfs() on it fails and leaves the async disk-stat future in an error path.
Also release IOKit properties only when IORegistryEntryCreateCFProperties() succeeds and returns a valid object, avoiding an unsafe CFRelease() after failed property creation.
Auto-collapse processes with N or more direct children when entering
tree mode. Root-level processes (depth 0 and 1) are never collapsed.
Useful for multi-process apps like Chromium, Firefox, or Electron apps
that spawn dozens of subprocesses and clutter the tree view.
Default is 0 (disabled). Example: proc_tree_auto_collapse = 4
Avoid running the full terminal resize path from the signal handler so resize notifications cannot reenter term_resize() while it already holds its guard.
Change atomic_waiting_lock::wait_for() so its timeout is measured with
an uptime clock that pauses during system suspend, while still using the
condition variable for wakeups.
Previously, std::condition_variable::wait_for() owned the timeout. If
the system suspended while the main thread was waiting for the runner
to finish, the condition-variable timeout clock could include the
suspend duration (this definitely happens on macOS). On resume, the
wait could immediately time out even though the runner had only had
milliseconds of awake time to make progress.
The new uptime_micros() helper uses the best available clock on each
platform:
- macOS: CLOCK_UPTIME_RAW (pauses during Sleep/Deep Idle)
- FreeBSD: CLOCK_UPTIME (explicitly excludes suspend time)
- Linux/OpenBSD/NetBSD: CLOCK_MONOTONIC (excludes suspend on Linux;
best available on other platforms)
This preserves the condition-variable locking model, but makes the
stall timeout count *awake* time rather than *wall clock* or *suspend*
time.
Co-authored-by: ItsMeSamey <sameychain5041@gmail.com>
Replace synchronous IOKit HID sensor reads with std::async futures
and remove all pthread_cancel() calls from the runner stall-recovery
path. This addresses the macOS/NetBSD deadlocks reported in #1349.
The root cause of the deadlocks is that pthread_cancel() does not
reliably invoke C++ destructors on macOS (libc++) or NetBSD
(libstdc++), leaving mutexes and semaphore internals wedged when the
runner thread is cancelled mid-collection.
Sensor collection via futures:
Wrap the IOKit HID thermal sensor reads (getSensors) and SMC
temperature reads in a std::async future, following the same pattern
already used for statvfs in the Linux disk collector. Each collection
cycle checks if the previous cycle's future is ready (non-blocking),
grabs the result if so, and launches a new async. Temperature
readings are delayed by at most one collection cycle (~2s), which is
imperceptible to users.
This moves the dominant cost (~130ms mean, ~700ms worst case from
77 Mach IPC roundtrips to IOKit HID sensors) off the runner's
critical path, reducing the runner cycle from ~180ms to ~50ms.
Remove pthread_cancel from stall recovery:
Replace the 5-second timeout + pthread_cancel + thread recreation
logic with a 10-second warning + 30-second clean exit. If the runner
is still stalled after 30 seconds (which should be nearly impossible
with sensor reads off the critical path), btop exits cleanly rather
than risking undefined behavior from pthread_cancel.
This follows the maintainer's recommendation: remove pthread_cancel
for BSD-derived platforms and exit with an error if the thread
stalls, with an extended timeout to allow recovery from transient
OS-level delays (sleep/wake transitions, IOKit contention).
All three timeout sites are updated:
- Runner::run(): 10s warning, 30s exit (was 5s + pthread_cancel)
- Runner::stop(): 30s exit (was 5s)
- Runner thread loop: 30s (was 5s)
- clean_quit(): remove pthread_cancel fallback from thread join
Fixes#1349
Change log timestamps from the custom format to ISO 8601 with UTC
indicator:
Before: 2026-05-14 (10:31:47) | DEBUG: ...
After: 2026-05-14T10:31:47Z | DEBUG: ...
The previous format used parentheses around the time and omitted any
timezone indicator, making timestamps ambiguous. The logger uses
std::chrono::system_clock which measures time since Unix epoch (UTC
on all platforms), but the old format did not communicate this.
ISO 8601 with the Z suffix is an unambiguous, universally-recognized
format parseable by standard functions across languages:
- C++ (C++20): std::chrono::from_stream(ss, "%FT%TZ", tp)
- C: strptime(s, "%Y-%m-%dT%H:%M:%SZ", &tm)
- Python: datetime.fromisoformat("2026-05-14T10:31:47Z")
This matters for correlating btop log events with system logs (e.g.,
macOS pmset power events) that use local time with explicit timezone
offsets.
This PR is AI-assisted (per CONTRIBUTING.md disclosure requirement).
Reviewed and tested by the author; independently confirmed by @rnk.
Adds Gpu::Asysfs, a pure-sysfs fallback that activates only when
rocm-smi enumerates 0 AMD devices. Covers consumer hardware where
ROCm libraries are absent or /dev/kfd isn't loaded — typical for
APUs (Phoenix/Strix-class iGPUs) and older discrete cards no longer
supported by ROCm.
Closes#1643. Related: #1169, #1045, #956.
Scope is deliberately additive:
* No change of behavior on hardware where rocm-smi already works.
* Not a migration to amd-smi (#1196 remains open and warranted
for the discrete-GPU case the sysfs path may not fully cover).
* @rnk's parallel dlopen(libamd_smi.so) draft lives at
https://github.com/rnk/btop/compare/main...amd-smi-sysfs as a
complementary direction for discrete GPUs.
Reads only standard amdgpu DRM sysfs nodes — no library dependency,
no dlopen, pure file I/O:
device/gpu_busy_percent -> utilization
device/mem_info_vram_* -> VRAM total / used
hwmon*/temp1_input -> temperature (millidegrees -> degrees)
hwmon*/freq1_input -> core clock (Hz -> MHz)
hwmon*/power1_average -> power (microwatts -> milliwatts),
falls back to power1_input
Filters by PCI vendor 0x1002 and driver=amdgpu, so does not collide
with nvidia/intel backends. Skips connector children (card1-DP-1)
and render nodes via filename pattern. Cards exposing no readable
signals (virtual GPUs, freshly-bound devices) are dropped at
enumeration rather than rendered as empty entries every tick.
Code follows CONTRIBUTING.md style: tabs of 4, alternative operators
(and/or/not), opening brace at line end, RAII (no raw resources;
std::filesystem::path / std::string / std::vector throughout),
std::ranges algorithms (all_of), fmt::format for the device-id
label, descriptive names, comments on non-obvious logic only.
Macros/files touched:
src/btop.cpp +1 (Asysfs::shutdown in clean_quit)
src/btop_shared.hpp +3 (forward declare Asysfs::shutdown)
src/linux/btop_collect.cpp +218 (the backend)
src/osx/btop_collect.cpp +1 (no-op shutdown stub for the
GPU_SUPPORT path used by Apple
Silicon, so the macOS link works)
Tested on Strix-class iGPU (PCI 1002:150e, Radeon 780M variant):
- btop displays "AMD GPU (1002:150e)" as gpu0
- utilization, temperature, power, clock, VRAM used/total live-update
- matches sysfs values within sampling jitter
Independently confirmed by @rnk on AMD APU Framework laptop.
Warning sweep clean under -Wall -Wextra -pedantic -Wconversion
-Wshadow -Wfloat-conversion (zero new warnings introduced).
This fixes#443 and a downstream issue in nixpkgs:
https://github.com/NixOS/nixpkgs/issues/460344
Short description of the latter issue: I nixpkgs, different versions
of btop are installed under different paths. If a user selects a
theme and it is saved to the config by its absolute path, that path
will be broken after an update.
To solve this, we allow writing the theme name only into the config,
and we save the theme by name if possible.
When selecting a theme, we check if it is the first (with respect to
the load oder priority: custom > user > system) match for this
filename in the list of available themes. If it is, we save it by
filename instead of using the full path. If there is another theme
with the same name and higher priority, we save using the full path.
This should also be compatible with the previous behavior of being
able to load themes by name from $XDG_CONFIG_PATH/btop/themes.
Signed-off-by: Paul Meyer <katexochen0@gmail.com>
The original code used relaxed ordering which does not fence reordering.
Relaxed atomics infact are identical to volatile variables [on x86
atleast]
compare&swap was wrong too and not resetting the variable to false
correctly.
Stores were using strict ordering which is not actually required.