FPS capture (game + display + per-monitor) into Grafana via Alloy - #67
Open
simsteward wants to merge 10 commits into
Open
FPS capture (game + display + per-monitor) into Grafana via Alloy#67simsteward wants to merge 10 commits into
simsteward wants to merge 10 commits into
Conversation
PresentMon logs a warning on every start/respawn since these two flags are mutually exclusive (--output_stdout silently wins) — harmless but would spam the log file forever on an always-on service. Found during the fps-exporter-agent's smoke test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
game_fps/display_fps/frames_dropped_total now carry an output="<id>" label from PresentMon's VidPnSourceId (enabled via --write_display_metadata) alongside the existing process="..." label, so the same process across two monitors reads as two series instead of one blended average. Also adds an optional connector label (HDMI/DisplayPort/DVI/...) via a small connector-map.json that fps-exporter reads once at startup. That map can't be produced by fps-exporter itself — it runs as a LocalSystem service in Session 0, which has no real monitor topology to query — so dump-display-topology.ps1 is a separate script the user runs from their own interactive desktop session to generate it. Verified the P/Invoke QueryDisplayConfig code compiles and runs cleanly (correctly reported "no real connector" for the current RDP/remote session's virtual display, rather than crashing or fabricating data) — the real topology dump still needs to happen at the user's own keyboard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Combines the display-topology map, service install, and Alloy scrape-target config change into a single script run from an elevated PowerShell on the real console session, in the order that matters (topology map before service start, so connector-map.json is present on first read). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The service runs as LocalSystem (install-service.ps1), under which both resolve to C:\Windows\system32\config\systemprofile rather than the interactive user's profile. This sent PresentMon.exe lookup to a path that never existed, crashing the process immediately on every service start until NSSM's crash-loop protection paused the service entirely. Confirmed via NSSM's own stdout capture after the first live deploy attempt: "PresentMon.exe not found at C:\Windows\system32\config\systemprofile\Tools\PresentMon\PresentMon.exe". Single-user personal machine, so a fixed absolute path is the correct fix, not environment-derived resolution. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…dows reinstall Still the placeholder "Unknown" value from a remote/RDP session run of dump-display-topology.ps1, not real topology data — that still needs to be regenerated from the physical console after reinstall. Committing now purely so nothing is lost in the wipe; safe to overwrite later. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
fps-exporterservice (scripts/fps-exporter/): streams PresentMon's live frame-timing output, computes rolling 5s-window FPS per tracked process (iRacingSim64DX11.exe,chrome.exe) and per video output (VidPnSourceId, via--write_display_metadata), and servesgame_fps/display_fps/frames_dropped_totalas Prometheus metrics on127.0.0.1:9101game_fps= frames the app hands to the driver;display_fps= frames that actually reach the screen (excludes dropped frames) — when they diverge for the same process/output, that's a display/compositor-side stutter, not the app itself hitchingconnectorlabel (HDMI/DisplayPort/DVI/...) viaconnector-map.json, produced bydump-display-topology.ps1— run from the user's own interactive desktop session, since the service itself runs in Session 0 and can't see real monitor topologyinstall-service.ps1registers it as a Windows Service (NSSM via winget,LocalSystem) so PresentMon's admin/ETW requirement is satisfied once at install timedocs/superpowers/specs/2026-07-26-fps-capture-grafana-design.md; task-by-task plan indocs/superpowers/plans/2026-07-26-fps-capture-grafana.mdTest plan
node --test "scripts/fps-exporter/*.test.js"— 18/18 passnode --checkonfps-exporter.js— no syntax errorsfps-exporter.js: HTTP 200, correct zero-state Prometheus output, no fabricatedgame_fps/display_fpsdump-display-topology.ps1: verified the P/InvokeQueryDisplayConfigcode compiles and runs without crashing (correctly reported "no real topology" for the current remote/RDP session's virtual display rather than fabricating data)install-service.ps1actually run,FpsExporterservice confirmedRunninggame_fps/display_fpspopulate with plausible values and gap out (not0) after the app closesdump-display-topology.ps1run from the real desktop session (not RDP) to produce a realconnector-map.jsonconfig.alloyscrape-target block added +Restart-Service Alloy, confirmgame_fpsetc. actually land in Grafana Cloud🤖 Generated with Claude Code