feat(fps): show process CPU and memory in a System counter #562

Open
mooki wants to merge 4 commits from mooki/Longshot:feat/system-readings-92 into main
Member

Type: feat · Risk: low · Tested on: Linux, RTX 4090 (one windowless game run and the game-free checks); Windows built, not run · Needs: Windows; AMD on Linux or Windows, and Intel; a quiet PC for the cost of System (T-3CTT, T-5ZH5, T-W43A)

Why

The FPS counter shows the renderer's work, but not how much processor time the game uses or whether the PC is running short of RAM, which is what a tester asks first about a stutter (#92). This adds a System choice to the FPS counter and an optional local readings file. It is the first slice of that issue; GPU utilization, temperatures and the busiest thread are not in it.

What is measured: one game run on Linux with an RTX 4090 (title screen, no window): the System counter drawn on screen, and a readings file of 48 rows whose values equal what the counter printed, picture by picture (12 of 12), with no fault in the log or the kernel. Game-free checks of the parsing, formatting, unknown values, the file and the menu's enable boundary, and a one-off native program that compared the Linux readers with second readings of the same quantities. Not measured: speed or the cost of sampling, Windows, AMD, Intel, the Steam Deck, a real window, longer runs, and the game run's own CPU, resident RAM and available RAM against an independent reader.

What changed

This adds a System level to the FPS counter, value 6 after Dev, that keeps FPS and frame time and replaces the graph and the renderer details with five lines: process CPU, resident RAM, available RAM, GPU heap used and GPU heap budget. It also adds an optional local readings file, a new versioned CSV written when RX_VK_SYSTEM_LOG names a path. The readings come from unprivileged Linux or Windows interfaces in gpu/vk/system_readings.cpp, an unavailable one shows n/a on screen and na in the file, never zero, and values 0 through 5 keep their meaning with the default Off.

  • Counter: gpu/vk/fps_stats.h appends System as value 6 after Dev. Values 0 through 5 keep their meaning and the default stays Off. System keeps FPS and frame time and replaces the graph and the renderer details with five lines: process CPU, resident RAM, available RAM, GPU heap used and GPU heap budget. It does not switch on Breakdown's extra timing.
  • Readings: gpu/vk/system_readings.cpp reads process CPU, resident memory and available physical RAM through unprivileged Linux or Windows interfaces. system_tick() in gpu/vk/vk_menu.cpp adds the existing selected-device Vulkan usage and budget. A reading that is unavailable shows n/a on screen and na in the file, never zero.
  • File: RX_VK_SYSTEM_LOG=<path> creates a new versioned CSV and refuses an existing file. Its columns are monotonic_ns,cpu_process_pct,rss_bytes,ram_available_bytes,vram_used_bytes,vram_budget_bytes; its header is # longshot_system,1. There are no frame identifiers and no frame statistics in it.
  • Reuse: strict procfs parsing, the CPU-delta rules, locale-independent numbers and exclusive UTF-8 file creation come from the earlier unpublished monitor, trimmed. Vendor probes, thread lists, battery readings and frame-statistic queues are not carried over.
  • Menu text: labels and help follow the existing sorted translation tables for all 13 languages. README.md and docs/development.md explain sources, units, unavailable readings and the cost of enabling it; host/hos_main.c keeps RX_VK_SYSTEM_LOG out of the shared environment file, like the other switches that name a file to write.
  • Tests: the real parser, formatting, interval choice, unknowns, CPU warm-up and reset, exclusive UTF-8 file creation and the actual menu enable boundary run without a game (tests/test_system_readings.py, tests/test_system_readings.cpp). The FPS and environment-file fixtures keep their assertions; the latter also refuses a file-output path from an ordinary shared environment file.
  • docs/tested/2026-11-14-system-readings-mooki-run01.md: the run below, as one row.

Reviewer notes: Start with system_readings::Sampler::tick, the strict proc_kib and cpu_percent routines and the system_tick integration in gpu/vk/vk_menu.cpp. The compact schema deliberately does not pretend to be the larger proposed perf.csv protocol. Vendor sensors, per-field choices, busiest-thread readings, a background file writer and automatic report attachment are left for separate review on purpose.

Related: #65 is a WIP memory and cache counter sampler with its own host callback; this change touches neither that interface nor the cache counters. Both use the driver's memory budget, so how they sample and write should be agreed before both land. The Dev level from #8 is kept as it is. The original request and plan were on the earlier tracker: https://github.com/NotRexed/Hookshot/issues/2915 and https://github.com/NotRexed/Hookshot/issues/2915#issuecomment-6834139903.

Overlaps (the forge's public pull request list, read at 12:24 on 2026-11-14, each diff applied to this head and to main with its context cut to one line): of the 103 open pull requests that change a file this one changes, three apply to main but not to this head. #62 has names_file in host/hos_main.c as context, and this change adds RX_VK_SYSTEM_LOG to that line. #236 deletes the README sections, among them the FPS counter paragraph this change edits, so the System paragraph has to move with them. #244 inserts after the frame_generation row of the options table in docs/development.md, where this change adds the fps_counter row. All three are small; the second to land redoes its lines. Any pull request that adds menu text changes the same 13 language-count lines in gpu/vk/menu_i18n.h as this one (two conflicts in the rebase were exactly there).

Measurements

Verdict: the System counter and the readings file work in one Linux game run: 12 of 12 System pictures print exactly one file row each, in time order, from 48 rows with no fault in the log or the kernel, and the game-free checks pass (1064 assertions) and fail on main as expected (FAIL: FPS counter value 6 is not System). This is not a speed comparison: with a level other than System and no readings file the new code does no clock read, no system read and no file write (counted: 0, 0 and 0); what System costs when switched on is not measured (T-W43A), and the run's CPU, resident RAM and available RAM were not compared with an independent reader.

Branch state: on main 543846ebb08ded4ce75d364403bc4dcf24e83e85; head 99904f1d8dfb2088b374560f1d19348f74ea0e17; whole game-free suite on this head: 555 passed, 1 failed, 16 skipped. The 1 failing test fails on main the same way: tests/test_walk_key.py (a link error in read_input; fixed by #497).

main: 543846ebb0 (2026-11-14)
this PR: feat/system-readings-92-m543846eb @ 99904f1d8
Hardware: RTX 4090 24 GB, NVIDIA 615.78.08, Core i9-13900K, 96 GB, Arch Linux 7.2.15
Scene: the title screen of a fresh profile (no save, no input), no window, output 1920x1080, pictures 1280x720; the counter set over the control bridge to Dev for 8 s, System for 12 s, then Off for 5 s with the file still on. Not a speed comparison
Method: one 53 s game run, built at f7b4b4391 (this PR's head 99904f1d8 differs only by the docs/tested file); the numbers come from the readings file the game wrote, its log and the pictures it captured; the other rows are game-free tests compiled with GCC 16.2.1 and Clang 23.1.3. An ordinary run: other work shared the PC (load average 8.0 before, 5.5 after), so no frame rate here is a measurement
How to reproduce: start the game with RX_VK_SYSTEM_LOG=system.csv in the environment (the file must not exist yet), open the menu, Gameplay, FPS counter, System; the title screen is enough. For the tests, python -I -B tests/test_system_readings.py, and with --source <a checkout of main> for the control that fails on main

Before: main @ 543846ebb After: this PR @ 99904f1d8
Compiled check that FPS counter value 6 is System, GCC and Clang FAIL: FPS counter value 6 is not System PASS: System follows the existing Dev level
Parser, formatting, CSV fixture the new reader does not exist 1064 assertions pass, including 1000 repeated disabled calls
Existing levels 0 to 5, production menu integration no System monitor zero monitor clock, native-reader and GPU-budget calls while file output is off
Game run: System counter on screen no System choice, not run five lines drawn under FPS and frame time (pictures below)
Game run: readings file no file 48 rows, 1000.0 to 1076.3 ms apart, first CPU value na, then 67.79 to 191.00% of one logical CPU
Game run: resident RAM, available RAM not run 2191 to 3779 MiB resident, 72959 to 74320 MiB available
Game run: GPU heap used, budget not run 352 to 3796 MiB used, of a budget of 22761 to 23000 MiB
Game run: counter against file not run 12 of 12 System pictures print exactly one file row each, in time order, rows 30 to 41
Game run: faults not run fatal=0 crash=0 devlost=0 kernel=0 nvrm=0, no core dump

The off-path result counts operations, not elapsed time. No zero-cost or speed claim follows from it. Sampling and the optional flush happen on the menu tick's thread; enabling them can delay a frame, especially with slow storage.

How the counter was checked against the file: each of the 12 System pictures (one every 60 presents, about a second apart) was read by eye, its five values were written down, and each set of five equals one row of the file formatted the way the game formats it for the screen (whole MiB, CPU with two decimals). The first picture was taken 21 presents after the switch and shows the last row written in the Dev phase; the other 11 show System-phase rows. The four vk: memory heaps lines the game already logs (3732 of 22761 MB, then 3796 of 22762 MB three times) equal the file's heap pairs in whole MB. The game run's process CPU, resident RAM and available RAM were not compared with an independent reader such as top. Separately, a throwaway native program (not part of this change) called the branch's read_process and cpu_percent on Linux while one or two threads burned CPU and 300 MiB was held, and compared them with second readings in the same process: CPU against utime plus stime of /proc/self/stat (within 0.22 points: 99.65% against 99.50%, 199.55% against 199.49%, with GCC and with Clang), resident memory against the rss field of /proc/self/stat (within 1.6 MiB), and MemAvailable against a second parse of /proc/meminfo (equal). That covers the readers on Linux, not the numbers of the game run.

The game folder and renderer were built natively with the project's updater (compiled in 376s, 0 failed, version v3.0.0-rc.3-80-gf7b4b4391, source stamp 679585dd, a content fingerprint and not a commit). That is build success, not a build-speed comparison. No Windows game build is claimed.

Screenshots

Picture 1 (hookshot-system-readings-counter.jpg), this PR @ 99904f1d8, title screen, RTX 4090: left, the existing Dev level (the graph, the timing lines, VRAM 3.7 of 22.2 GB and the renderer counts); right, System in the same corner: FPS and frame time as before, then Process CPU 137.35%, Resident RAM 3750 MiB, Available RAM 73032 MiB, GPU heap used 3796 MiB and GPU heap budget 22762 MiB. Look at the right side: five short lines instead of the graph and the renderer counts.

Picture 1: the existing Dev level (left) and the new System level (right), title screen, RTX 4090

Picture 2 (hookshot-system-readings-over-time.jpg), this PR @ 99904f1d8: four System frames from the same run, each captioned with the line of the readings file it prints (lines 34, 38, 42 and 44, for example 439301588217910,139.34,3952521216,76636049408,3980525568,23867686912 for the first: 139.34%, 3769 MiB, 73085 MiB, 3796 MiB). Look at the values: they change from frame to frame and equal the file's rows.

Picture 2: four System frames, each labelled with the line of the readings file it prints

There is no main picture: main has no System choice.

Flags and settings

This PR adds a System level to the FPS counter (fps_counter=6) and two switches, RX_VK_SYSTEM_LOG and RX_VK_SYSTEM_INTERVAL_MS; the counter stays Off by default and both switches are unset by default.

Flag What it is Before this PR After this PR Effect Helps / doesn't help / untested Recommendation
FPS counter: System (fps_counter=6) FPS counter level (menu choice and fps_counter=6) Off by default; no System level Counter stays Off; values 0 to 5 unchanged Shows process CPU, resident RAM, available RAM and GPU heap use against its budget, under FPS and frame time Helps: Linux and Windows where the named interfaces answer. Doesn't help: Does not name a bottleneck; no GPU utilization or temperature. Untested: Windows run, AMD, Intel, translated layouts on screen. Off by default; choose System when looking into resource use
RX_VK_SYSTEM_LOG=<csv> New switch Absent (new) Unset or empty: no file Writes the same samples to a new local file, even with the counter Off; refuses an existing path Helps: Keeping samples for a problem report. Doesn't help: No upload, no automatic report attachment, no frame-clock join. Untested: Windows run, full disks, slow or network storage. Off by default; use a local absolute path or system.csv in the working directory
RX_VK_SYSTEM_INTERVAL_MS=<250..60000> New switch Absent (new) 1000 Sets the interval in milliseconds for the counter and the file; an invalid value switches the readings off with a log line Helps: Choosing the cadence. Doesn't help: Not a real-time guarantee; no catch-up after a pause. Untested: Other hardware, cost of sampling. Keep 1000

Log lines the numbers come from

PASS: System follows the existing Dev level
PASS: production menu integration, six existing levels do no monitor work; file-only capture works
Native availability: cpu=1 rss=1 ram=1
PASS: 1064 system-reading checks; disabled path clocks=0 reads=0 writes=0
PASS: CSV units, unknowns, decimal dot, UTF-8 path and no overwrite

windowless run: exit 0 after 52.8 s, 2882 frames, mean 59.5 fps, 48 pictures at 1280x720, no fatal, crash, device-lost or kernel lines (RTX 4090)
[hos MainThread] vk: system: CSV enabled, interval 1000 ms; CPU 100% is one logical CPU; bytes are not private commit

# longshot_system,1
monotonic_ns,cpu_process_pct,rss_bytes,ram_available_bytes,vram_used_bytes,vram_budget_bytes
439270210143320,na,2298019840,77930774528,369295360,24117379072
439271212098120,100.61,3178713088,77147054080,453181440,24111087616
...
439301588217910,139.34,3952521216,76636049408,3980525568,23867686912

Testing

Tested: the new game-free fixture with GCC and Clang, the same compiled level check against main's source (it fails there, above), the existing FPS statistics fixture with both compilers, translation keys, counts and format conversions, README menu rows and the switch policy. The whole game-free suite on this head (tests/run_all.py --jobs 6 --strict, Linux): 555 passed, 1 failed, 16 skipped in 158 s; the one failure, tests/test_walk_key.py, fails the same way on main (the same link error in read_input), and the 16 skips need Windows, a Vulkan device or the game's files. Seven Windows-target compilation calls pass on this head (syntax only: no Windows execution). A syntax-only compile of the merged gpu/vk/vk_menu.cpp passes. In the game on Linux (RTX 4090, NVIDIA 615.78.08, no window): one run of the title screen with the counter at Dev, System and Off and the file on, described above. Two failures on the way were fixed before this head: a deprecated C++ filesystem conversion in the new reader (now the C++20 form), and the touched C environment fixture, which now defines the project's CRT-warning macro before its includes.

Built for Windows and Linux at the current head, not run: At 99904f1d8dfb2088b374560f1d19348f74ea0e17, the Windows x86-64 Vulkan renderer DLL and all 50 selected host translation units were cross-built on Linux with llvm-mingw 20261104 (Clang 23.1.5), using the project's production flags and pinned dependencies (renderer: 36 newly compiled / 170 reused; host: 1 newly compiled / 49 reused; lifted C: 0 newly compiled / 80 reused; all Windows units: 37 newly compiled / 613 reused from matching object-cache entries), the renderer DLL linked and the full game executable linked; nothing was run on Windows or under Wine, and this is neither the project's hosted builder run nor a Windows UTF-8 run-time path test.

The exact same head 99904f1d8dfb2088b374560f1d19348f74ea0e17 also compiled and linked its full native Linux executable and renderer with Clang 23.1.3 through the documented resume path (323 newly compiled units, 169 matching Linux-cache units, source stamp 679585dd), using the verified target-neutral C from lift 5c175a4727d82f85e425e85400dca17fea0750fb rather than carrying the patch onto another commit; neither Linux binary was run.

Not tested: Windows execution of the new C and C++ fixtures or of the game; AMD, Intel and the Steam Deck; a real window (swapchain presentation); low-memory conditions; captures longer than 53 s; translated layouts on screen (only English was looked at); full disks and slow or network storage; the game run's process CPU, resident RAM and available RAM against an independent reader (only the Linux readers were compared, in a separate program); the cost of enabling the sampler (no paired runs); the Dev picture against a main build (it was looked at on this build only). GPU utilization, temperatures and the busiest thread are not in this slice.

Must be tested:

  • Windows: run the fixtures, and System with an accented output file name, expecting real process, working-set and available-RAM readings and no replaced file (test request T-3CTT; by hand).
  • AMD on Linux or Windows, and Intel: check the GPU heap lines, expecting n/a where the driver has no memory budget and no shared-memory heap described as dedicated VRAM (test request T-5ZH5; by hand).
  • Cost: two interleaved runs each of main, this PR with the counter Off and this PR with System on, same scene and settings, on a quiet PC, expecting a median for each of the three, so that what System costs is known (test request T-W43A; by hand).

Risks

What could break: CPU 100% is one logical CPU, so the process can exceed 100% (the run's maximum was 191.00%); it is not whole-PC use. Resident memory includes shared pages and is not private commit. The Vulkan budget is a moving allowance, not capacity, and an integrated GPU's device-local heaps can be system RAM. A sampled reading is context, not proof of a bottleneck.

While it is enabled the sampler reads a few small OS files and counters and flushes its optional file on the menu tick's thread, so it can add latency, and a pause delays the next sample.

Turning it off: Choose another counter level and leave RX_VK_SYSTEM_LOG unset to stop sampling: the existing levels then do no clock read, OS read, formatting or file work, only an enable check. Reverting the change removes it. No synchronization or safety switch has to be turned off.

Compatibility:

  • longshot_options.ini: fps_counter gains the value 6 (System); values 0 through 5 keep their meaning and the default stays Off. RX_VK_SYSTEM_LOG is kept out of the shared environment file (host/hos_main.c), like the other switches that name a file to write.
  • Saves: the text says nothing; the diff touches no save code.
  • Shader and pipeline caches: the text says nothing; the diff adds no shader file and does not touch the caches' code.
  • The vk ABI: the text says nothing; the diff does not touch gpu/vk/vk_api.h, so it is unchanged.
  • The builder's tools: the text says nothing; the diff touches none.

Issues

See #92

Checklist

Everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description) of the status line's type.
  • Why gives the reason plainly and links the issues that fit (the tracker searched).
  • Measurements has a verdict, or one line saying why there is nothing to measure. No speed claim is made: with the counter on any level other than System and no readings file, the new code does no clock read, no system read and no file write (counted by the new test: 0, 0 and 0), so there is no cost to measure there. What System itself costs when switched on is the cost test request under Needs other testers (T-W43A).
  • Testing names what was tested, what wasn't, and what must still be tested, where and how.
  • Rebased on current main.
  • python -I -B tests/run_all.py passes (any failure that also fails on main is named).
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

When it applies

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value.
  • Screenshots of the same spot, captioned like the Measurements columns, for a change that shows on screen.
  • A file in docs/tested/ for each new measured run (format in docs/tested-configurations.md; no appended row or assembled view).
  • A game-free test added for the fix, failing on main and passing here.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. Built and linked for both at 99904f1d8dfb2088b374560f1d19348f74ea0e17 (under Testing); nothing was run on Windows at this head, so UTF-8 path behavior at run time is not claimed.
  • Docs updated.

New on this forge; not opened on GitHub.

**Type:** feat · **Risk:** low · **Tested on:** Linux, RTX 4090 (one windowless game run and the game-free checks); Windows built, not run · **Needs:** Windows; AMD on Linux or Windows, and Intel; a quiet PC for the cost of System (T-3CTT, T-5ZH5, T-W43A) ## Why The FPS counter shows the renderer's work, but not how much processor time the game uses or whether the PC is running short of RAM, which is what a tester asks first about a stutter (https://git.magicbean.dev/MagicBeanDev/Longshot/issues/92). This adds a System choice to the FPS counter and an optional local readings file. It is the first slice of that issue; GPU utilization, temperatures and the busiest thread are not in it. **What is measured:** one game run on Linux with an RTX 4090 (title screen, no window): the System counter drawn on screen, and a readings file of 48 rows whose values equal what the counter printed, picture by picture (12 of 12), with no fault in the log or the kernel. Game-free checks of the parsing, formatting, unknown values, the file and the menu's enable boundary, and a one-off native program that compared the Linux readers with second readings of the same quantities. **Not measured:** speed or the cost of sampling, Windows, AMD, Intel, the Steam Deck, a real window, longer runs, and the game run's own CPU, resident RAM and available RAM against an independent reader. ## What changed This adds a System level to the FPS counter, value 6 after Dev, that keeps FPS and frame time and replaces the graph and the renderer details with five lines: process CPU, resident RAM, available RAM, GPU heap used and GPU heap budget. It also adds an optional local readings file, a new versioned CSV written when `RX_VK_SYSTEM_LOG` names a path. The readings come from unprivileged Linux or Windows interfaces in `gpu/vk/system_readings.cpp`, an unavailable one shows `n/a` on screen and `na` in the file, never zero, and values 0 through 5 keep their meaning with the default Off. - Counter: `gpu/vk/fps_stats.h` appends System as value 6 after Dev. Values 0 through 5 keep their meaning and the default stays Off. System keeps FPS and frame time and replaces the graph and the renderer details with five lines: process CPU, resident RAM, available RAM, GPU heap used and GPU heap budget. It does not switch on Breakdown's extra timing. - Readings: `gpu/vk/system_readings.cpp` reads process CPU, resident memory and available physical RAM through unprivileged Linux or Windows interfaces. `system_tick()` in `gpu/vk/vk_menu.cpp` adds the existing selected-device Vulkan usage and budget. A reading that is unavailable shows `n/a` on screen and `na` in the file, never zero. - File: `RX_VK_SYSTEM_LOG=<path>` creates a new versioned CSV and refuses an existing file. Its columns are `monotonic_ns,cpu_process_pct,rss_bytes,ram_available_bytes,vram_used_bytes,vram_budget_bytes`; its header is `# longshot_system,1`. There are no frame identifiers and no frame statistics in it. - Reuse: strict procfs parsing, the CPU-delta rules, locale-independent numbers and exclusive UTF-8 file creation come from the earlier unpublished monitor, trimmed. Vendor probes, thread lists, battery readings and frame-statistic queues are not carried over. - Menu text: labels and help follow the existing sorted translation tables for all 13 languages. `README.md` and `docs/development.md` explain sources, units, unavailable readings and the cost of enabling it; `host/hos_main.c` keeps `RX_VK_SYSTEM_LOG` out of the shared environment file, like the other switches that name a file to write. - Tests: the real parser, formatting, interval choice, unknowns, CPU warm-up and reset, exclusive UTF-8 file creation and the actual menu enable boundary run without a game (`tests/test_system_readings.py`, `tests/test_system_readings.cpp`). The FPS and environment-file fixtures keep their assertions; the latter also refuses a file-output path from an ordinary shared environment file. - `docs/tested/2026-11-14-system-readings-mooki-run01.md`: the run below, as one row. **Reviewer notes:** Start with `system_readings::Sampler::tick`, the strict `proc_kib` and `cpu_percent` routines and the `system_tick` integration in `gpu/vk/vk_menu.cpp`. The compact schema deliberately does not pretend to be the larger proposed `perf.csv` protocol. Vendor sensors, per-field choices, busiest-thread readings, a background file writer and automatic report attachment are left for separate review on purpose. Related: https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/65 is a WIP memory and cache counter sampler with its own host callback; this change touches neither that interface nor the cache counters. Both use the driver's memory budget, so how they sample and write should be agreed before both land. The Dev level from https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/8 is kept as it is. The original request and plan were on the earlier tracker: https://github.com/NotRexed/Hookshot/issues/2915 and https://github.com/NotRexed/Hookshot/issues/2915#issuecomment-6834139903. Overlaps (the forge's public pull request list, read at 12:24 on 2026-11-14, each diff applied to this head and to main with its context cut to one line): of the 103 open pull requests that change a file this one changes, three apply to main but not to this head. https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/62 has `names_file` in `host/hos_main.c` as context, and this change adds `RX_VK_SYSTEM_LOG` to that line. https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/236 deletes the README sections, among them the FPS counter paragraph this change edits, so the System paragraph has to move with them. https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/244 inserts after the `frame_generation` row of the options table in `docs/development.md`, where this change adds the `fps_counter` row. All three are small; the second to land redoes its lines. Any pull request that adds menu text changes the same 13 language-count lines in `gpu/vk/menu_i18n.h` as this one (two conflicts in the rebase were exactly there). ## Measurements **Verdict:** the System counter and the readings file work in one Linux game run: 12 of 12 System pictures print exactly one file row each, in time order, from 48 rows with no fault in the log or the kernel, and the game-free checks pass (1064 assertions) and fail on main as expected (`FAIL: FPS counter value 6 is not System`). This is not a speed comparison: with a level other than System and no readings file the new code does no clock read, no system read and no file write (counted: 0, 0 and 0); what System costs when switched on is not measured (T-W43A), and the run's CPU, resident RAM and available RAM were not compared with an independent reader. <!-- branch-state --> Branch state: on main `543846ebb08ded4ce75d364403bc4dcf24e83e85`; head `99904f1d8dfb2088b374560f1d19348f74ea0e17`; whole game-free suite on this head: `555 passed, 1 failed, 16 skipped`. The 1 failing test fails on main the same way: `tests/test_walk_key.py` (a link error in `read_input`; fixed by https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/497). <!-- /branch-state --> main: 543846ebb08ded4ce75d364403bc4dcf24e83e85 (2026-11-14) this PR: feat/system-readings-92-m543846eb @ 99904f1d8 Hardware: RTX 4090 24 GB, NVIDIA 615.78.08, Core i9-13900K, 96 GB, Arch Linux 7.2.15 Scene: the title screen of a fresh profile (no save, no input), no window, output 1920x1080, pictures 1280x720; the counter set over the control bridge to Dev for 8 s, System for 12 s, then Off for 5 s with the file still on. Not a speed comparison Method: one 53 s game run, built at f7b4b4391 (this PR's head 99904f1d8 differs only by the `docs/tested` file); the numbers come from the readings file the game wrote, its log and the pictures it captured; the other rows are game-free tests compiled with GCC 16.2.1 and Clang 23.1.3. An ordinary run: other work shared the PC (load average 8.0 before, 5.5 after), so no frame rate here is a measurement How to reproduce: start the game with `RX_VK_SYSTEM_LOG=system.csv` in the environment (the file must not exist yet), open the menu, Gameplay, FPS counter, System; the title screen is enough. For the tests, `python -I -B tests/test_system_readings.py`, and with `--source <a checkout of main>` for the control that fails on main | | Before: main @ 543846ebb | After: this PR @ 99904f1d8 | |---|---|---| | Compiled check that FPS counter value 6 is System, GCC and Clang | `FAIL: FPS counter value 6 is not System` | `PASS: System follows the existing Dev level` | | Parser, formatting, CSV fixture | the new reader does not exist | 1064 assertions pass, including 1000 repeated disabled calls | | Existing levels 0 to 5, production menu integration | no System monitor | zero monitor clock, native-reader and GPU-budget calls while file output is off | | Game run: System counter on screen | no System choice, not run | five lines drawn under FPS and frame time (pictures below) | | Game run: readings file | no file | 48 rows, 1000.0 to 1076.3 ms apart, first CPU value `na`, then 67.79 to 191.00% of one logical CPU | | Game run: resident RAM, available RAM | not run | 2191 to 3779 MiB resident, 72959 to 74320 MiB available | | Game run: GPU heap used, budget | not run | 352 to 3796 MiB used, of a budget of 22761 to 23000 MiB | | Game run: counter against file | not run | 12 of 12 System pictures print exactly one file row each, in time order, rows 30 to 41 | | Game run: faults | not run | fatal=0 crash=0 devlost=0 kernel=0 nvrm=0, no core dump | The off-path result counts operations, not elapsed time. No zero-cost or speed claim follows from it. Sampling and the optional flush happen on the menu tick's thread; enabling them can delay a frame, especially with slow storage. How the counter was checked against the file: each of the 12 System pictures (one every 60 presents, about a second apart) was read by eye, its five values were written down, and each set of five equals one row of the file formatted the way the game formats it for the screen (whole MiB, CPU with two decimals). The first picture was taken 21 presents after the switch and shows the last row written in the Dev phase; the other 11 show System-phase rows. The four `vk: memory heaps` lines the game already logs (3732 of 22761 MB, then 3796 of 22762 MB three times) equal the file's heap pairs in whole MB. The game run's process CPU, resident RAM and available RAM were not compared with an independent reader such as `top`. Separately, a throwaway native program (not part of this change) called the branch's `read_process` and `cpu_percent` on Linux while one or two threads burned CPU and 300 MiB was held, and compared them with second readings in the same process: CPU against `utime` plus `stime` of `/proc/self/stat` (within 0.22 points: 99.65% against 99.50%, 199.55% against 199.49%, with GCC and with Clang), resident memory against the `rss` field of `/proc/self/stat` (within 1.6 MiB), and `MemAvailable` against a second parse of `/proc/meminfo` (equal). That covers the readers on Linux, not the numbers of the game run. The game folder and renderer were built natively with the project's updater (`compiled in 376s, 0 failed`, version `v3.0.0-rc.3-80-gf7b4b4391`, source stamp `679585dd`, a content fingerprint and not a commit). That is build success, not a build-speed comparison. No Windows game build is claimed. ### Screenshots Picture 1 (`hookshot-system-readings-counter.jpg`), this PR @ 99904f1d8, title screen, RTX 4090: left, the existing Dev level (the graph, the timing lines, VRAM `3.7 of 22.2 GB` and the renderer counts); right, System in the same corner: FPS and frame time as before, then Process CPU 137.35%, Resident RAM 3750 MiB, Available RAM 73032 MiB, GPU heap used 3796 MiB and GPU heap budget 22762 MiB. Look at the right side: five short lines instead of the graph and the renderer counts. ![Picture 1: the existing Dev level (left) and the new System level (right), title screen, RTX 4090](https://git.magicbean.dev/attachments/e8070698-ff24-44f9-9354-6387b4741825) Picture 2 (`hookshot-system-readings-over-time.jpg`), this PR @ 99904f1d8: four System frames from the same run, each captioned with the line of the readings file it prints (lines 34, 38, 42 and 44, for example `439301588217910,139.34,3952521216,76636049408,3980525568,23867686912` for the first: 139.34%, 3769 MiB, 73085 MiB, 3796 MiB). Look at the values: they change from frame to frame and equal the file's rows. ![Picture 2: four System frames, each labelled with the line of the readings file it prints](https://git.magicbean.dev/attachments/accacdef-bd26-4b55-b138-ac84cbd25ea7) There is no main picture: main has no System choice. ### Flags and settings This PR adds a System level to the FPS counter (`fps_counter=6`) and two switches, `RX_VK_SYSTEM_LOG` and `RX_VK_SYSTEM_INTERVAL_MS`; the counter stays Off by default and both switches are unset by default. | Flag | What it is | Before this PR | After this PR | Effect | Helps / doesn't help / untested | Recommendation | |---|---|---|---|---|---|---| | FPS counter: System (`fps_counter=6`) | FPS counter level (menu choice and `fps_counter=6`) | Off by default; no System level | Counter stays Off; values 0 to 5 unchanged | Shows process CPU, resident RAM, available RAM and GPU heap use against its budget, under FPS and frame time | Helps: Linux and Windows where the named interfaces answer. Doesn't help: Does not name a bottleneck; no GPU utilization or temperature. Untested: Windows run, AMD, Intel, translated layouts on screen. | Off by default; choose System when looking into resource use | | `RX_VK_SYSTEM_LOG=<csv>` | New switch | Absent (new) | Unset or empty: no file | Writes the same samples to a new local file, even with the counter Off; refuses an existing path | Helps: Keeping samples for a problem report. Doesn't help: No upload, no automatic report attachment, no frame-clock join. Untested: Windows run, full disks, slow or network storage. | Off by default; use a local absolute path or `system.csv` in the working directory | | `RX_VK_SYSTEM_INTERVAL_MS=<250..60000>` | New switch | Absent (new) | 1000 | Sets the interval in milliseconds for the counter and the file; an invalid value switches the readings off with a log line | Helps: Choosing the cadence. Doesn't help: Not a real-time guarantee; no catch-up after a pause. Untested: Other hardware, cost of sampling. | Keep 1000 | ### Log lines the numbers come from ```text PASS: System follows the existing Dev level PASS: production menu integration, six existing levels do no monitor work; file-only capture works Native availability: cpu=1 rss=1 ram=1 PASS: 1064 system-reading checks; disabled path clocks=0 reads=0 writes=0 PASS: CSV units, unknowns, decimal dot, UTF-8 path and no overwrite windowless run: exit 0 after 52.8 s, 2882 frames, mean 59.5 fps, 48 pictures at 1280x720, no fatal, crash, device-lost or kernel lines (RTX 4090) [hos MainThread] vk: system: CSV enabled, interval 1000 ms; CPU 100% is one logical CPU; bytes are not private commit # longshot_system,1 monotonic_ns,cpu_process_pct,rss_bytes,ram_available_bytes,vram_used_bytes,vram_budget_bytes 439270210143320,na,2298019840,77930774528,369295360,24117379072 439271212098120,100.61,3178713088,77147054080,453181440,24111087616 ... 439301588217910,139.34,3952521216,76636049408,3980525568,23867686912 ``` ## Testing **Tested:** the new game-free fixture with GCC and Clang, the same compiled level check against main's source (it fails there, above), the existing FPS statistics fixture with both compilers, translation keys, counts and format conversions, README menu rows and the switch policy. The whole game-free suite on this head (`tests/run_all.py --jobs 6 --strict`, Linux): 555 passed, 1 failed, 16 skipped in 158 s; the one failure, `tests/test_walk_key.py`, fails the same way on main (the same link error in `read_input`), and the 16 skips need Windows, a Vulkan device or the game's files. Seven Windows-target compilation calls pass on this head (syntax only: no Windows execution). A syntax-only compile of the merged `gpu/vk/vk_menu.cpp` passes. In the game on Linux (RTX 4090, NVIDIA 615.78.08, no window): one run of the title screen with the counter at Dev, System and Off and the file on, described above. Two failures on the way were fixed before this head: a deprecated C++ filesystem conversion in the new reader (now the C++20 form), and the touched C environment fixture, which now defines the project's CRT-warning macro before its includes. **Built for Windows and Linux at the current head, not run:** At `99904f1d8dfb2088b374560f1d19348f74ea0e17`, the Windows x86-64 Vulkan renderer DLL and all 50 selected host translation units were cross-built on Linux with llvm-mingw 20261104 (Clang 23.1.5), using the project's production flags and pinned dependencies (renderer: 36 newly compiled / 170 reused; host: 1 newly compiled / 49 reused; lifted C: 0 newly compiled / 80 reused; all Windows units: 37 newly compiled / 613 reused from matching object-cache entries), the renderer DLL linked and the full game executable linked; nothing was run on Windows or under Wine, and this is neither the project's hosted builder run nor a Windows UTF-8 run-time path test. The exact same head `99904f1d8dfb2088b374560f1d19348f74ea0e17` also compiled and linked its full native Linux executable and renderer with Clang 23.1.3 through the documented resume path (323 newly compiled units, 169 matching Linux-cache units, source stamp `679585dd`), using the verified target-neutral C from lift `5c175a4727d82f85e425e85400dca17fea0750fb` rather than carrying the patch onto another commit; neither Linux binary was run. **Not tested:** Windows execution of the new C and C++ fixtures or of the game; AMD, Intel and the Steam Deck; a real window (swapchain presentation); low-memory conditions; captures longer than 53 s; translated layouts on screen (only English was looked at); full disks and slow or network storage; the game run's process CPU, resident RAM and available RAM against an independent reader (only the Linux readers were compared, in a separate program); the cost of enabling the sampler (no paired runs); the Dev picture against a main build (it was looked at on this build only). GPU utilization, temperatures and the busiest thread are not in this slice. **Must be tested:** - Windows: run the fixtures, and System with an accented output file name, expecting real process, working-set and available-RAM readings and no replaced file (test request T-3CTT; by hand). - AMD on Linux or Windows, and Intel: check the GPU heap lines, expecting `n/a` where the driver has no memory budget and no shared-memory heap described as dedicated VRAM (test request T-5ZH5; by hand). - Cost: two interleaved runs each of main, this PR with the counter Off and this PR with System on, same scene and settings, on a quiet PC, expecting a median for each of the three, so that what System costs is known (test request T-W43A; by hand). ## Risks **What could break:** CPU 100% is one logical CPU, so the process can exceed 100% (the run's maximum was 191.00%); it is not whole-PC use. Resident memory includes shared pages and is not private commit. The Vulkan budget is a moving allowance, not capacity, and an integrated GPU's device-local heaps can be system RAM. A sampled reading is context, not proof of a bottleneck. While it is enabled the sampler reads a few small OS files and counters and flushes its optional file on the menu tick's thread, so it can add latency, and a pause delays the next sample. **Turning it off:** Choose another counter level and leave `RX_VK_SYSTEM_LOG` unset to stop sampling: the existing levels then do no clock read, OS read, formatting or file work, only an enable check. Reverting the change removes it. No synchronization or safety switch has to be turned off. **Compatibility:** - `longshot_options.ini`: `fps_counter` gains the value 6 (System); values 0 through 5 keep their meaning and the default stays Off. `RX_VK_SYSTEM_LOG` is kept out of the shared environment file (`host/hos_main.c`), like the other switches that name a file to write. - Saves: the text says nothing; the diff touches no save code. - Shader and pipeline caches: the text says nothing; the diff adds no shader file and does not touch the caches' code. - The vk ABI: the text says nothing; the diff does not touch `gpu/vk/vk_api.h`, so it is unchanged. - The builder's tools: the text says nothing; the diff touches none. ## Issues See https://git.magicbean.dev/MagicBeanDev/Longshot/issues/92 ## Checklist ### Everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`) of the status line's type. - [x] Why gives the reason plainly and links the issues that fit (the tracker searched). - [x] Measurements has a verdict, or one line saying why there is nothing to measure. No speed claim is made: with the counter on any level other than System and no readings file, the new code does no clock read, no system read and no file write (counted by the new test: 0, 0 and 0), so there is no cost to measure there. What System itself costs when switched on is the cost test request under Needs other testers (T-W43A). - [x] Testing names what was tested, what wasn't, and what must still be tested, where and how. - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### When it applies - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value. - [x] Screenshots of the same spot, captioned like the Measurements columns, for a change that shows on screen. - [x] A file in `docs/tested/` for each new measured run (format in `docs/tested-configurations.md`; no appended row or assembled view). - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. Built and linked for both at `99904f1d8dfb2088b374560f1d19348f74ea0e17` (under Testing); nothing was run on Windows at this head, so UTF-8 path behavior at run time is not claimed. - [x] Docs updated. --- New on this forge; not opened on GitHub.
Carry and trim procfs parsing, CPU-delta rules and exclusive UTF-8 CSV output from the earlier local monitor at 31ad1f5da. Keep existing counter levels unchanged and leave vendor sensors and frame statistics for later slices.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Keep its C runtime warning policy before every CRT include, matching the other changed fixtures. The existing source-path and precedence assertions remain unchanged.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
The System readings text sat between the heading and the table, so the table read as part of it. Move the text after the table.

Assisted-by: Claude Sonnet 5.5
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
docs(tested): the System counter and readings file in a windowless run on an RTX 4090
All checks were successful
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 7s
Website / Website on Cloudflare (pull_request_target) Has been skipped
Third-party parts / As their notes say (pull_request) Successful in 8s
PR description / Description follows the template (pull_request_target) Successful in 7s
beanstalk/approval Test farm not requested
99904f1d8d
One run of the title screen with the counter set to Dev, System and Off and the readings file on: 48 rows, 12 System pictures that each print one row, no fault.

Assisted-by: Claude Sonnet 5.5
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
mooki changed title from WIP: feat(fps): show process CPU and memory in a System counter to feat(fps): show process CPU and memory in a System counter 2026-11-14 20:08:26 +00:00
Author
Member

Built at the current head 99904f1d8dfb2088b374560f1d19348f74ea0e17 for Windows (llvm-mingw cross-build) and Linux (native); both linked without errors, nothing was run. The build box is ticked for this head and the details are under Testing. Every box is done, so this is ready for review.

Built at the current head `99904f1d8dfb2088b374560f1d19348f74ea0e17` for Windows (llvm-mingw cross-build) and Linux (native); both linked without errors, nothing was run. The build box is ticked for this head and the details are under Testing. Every box is done, so this is ready for review.
First-time contributor

I think it's better to added to other modes rather than exclusive standalone mode.

I think it's better to added to other modes rather than exclusive standalone mode.
All checks were successful
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 7s
Website / Website on Cloudflare (pull_request_target) Has been skipped
Third-party parts / As their notes say (pull_request) Successful in 8s
PR description / Description follows the template (pull_request_target) Successful in 7s
beanstalk/approval Test farm not requested
This pull request has changes conflicting with the target branch.
  • docs/development.md
  • gpu/vk/menu_i18n.h
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'feat/system-readings-92:mooki-feat/system-readings-92'
git switch 'mooki-feat/system-readings-92'
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!562
No description provided.