Tutorial 156: Watch a Running Game in the Inspector
What you’ll learn: Build the Inspector, start a game with its agent, connect the cna-inspector bridge safely with a token file, read the eight browser views, and add the agent to your own game.
Development snapshot, desktop only, view-only. The Inspector does not exist in the v0.1.0-alpha.1 tag. Everything here targets CNA snapshot 009d40f5 (branch next, still reporting 0.1.0-alpha.1). It works on desktop Windows, Linux and macOS/POSIX only, and CNA_BUILD_INSPECTOR=ON is a configure error on Emscripten, Android and iOS. The steps use the demo CNA ships (cna_inspector_demo); the commands come from the CMake files and the demo’s source, and we have not run them ourselves, so what you see in the browser is described from the UI’s code, not from a screenshot. The agent’s token is a secret: do not paste it into shared logs, chats or screenshots.
Before you start
- A CNA checkout of the
nextbranch (git clone -b next https://github.com/libcna/cna.git, withsharp-runtimealso onnextand the sibling checkouts from Tutorial 02). - A browser on the same machine. The UI is served by a local bridge, loads no CDN assets and needs no network.
- Ideally, Tutorial 155: the Inspector shows what Diagnostics collects, so it only makes sense when
CNA_DIAGNOSTICSisSTATSorFULL.
Configure and build
Two options switch the feature on. CNA_DIAGNOSTICS=FULL gives the Inspector CPU zones and an event log (STATS gives metrics and resources only); CNA_BUILD_INSPECTOR=ON builds the agent library, the bridge and, because CNA_BUILD_EXAMPLES is on by default, the demo.
cd cna
cmake -S . -B build -DCNA_DIAGNOSTICS=FULL -DCNA_BUILD_INSPECTOR=ON
cmake --build build --target cna_inspector_demo cna-inspector -j"$(nproc)"
With no display, add -DCNA_PLATFORM=HEADLESS -DCNA_GRAPHICS_RENDERER=HEADLESS -DCNA_AUDIO_PLATFORM=NULL to the configure line; the demo “runs under any platform, including HEADLESS”, and the Inspector needs no particular renderer. The demo executable lands in the build root, build/cna_inspector_demo; the bridge lands in build/modules/inspector/cna-inspector.
Start the demo game
The demo is a small game that draws 48 sprites into a render target, composes them over a background, burns a little CPU each update, and creates and destroys a texture every two seconds so that resources come and go. It starts the Inspector agent before Game::Run() and prints the two values the bridge needs:
build/cna_inspector_demo --port 47001 --seconds 300
# Inspector port: 47001
# Inspector token: <a long random token>
--port is optional (without it the operating system picks a free port, which is what gets printed) and --seconds N ends the demo after N seconds (the default, 0, runs until you close it). Leave this terminal alone; in a second terminal, put the token in a file only you can read, so it never appears on a command line or in your shell history:
umask 077
printf '%s' 'PASTE-THE-TOKEN-HERE' > inspector.token
Start the bridge
cd cna
build/modules/inspector/cna-inspector --agent-port 47001 --token-file inspector.token
# CNA Inspector is available at http://127.0.0.1:<port>/
# The bridge is localhost-only. Press Ctrl+C to stop it.
The bridge needs the agent’s port and a token (from --token, --token-file or the CNA_INSPECTOR_TOKEN environment variable; prefer the file or the variable, because a --token argument is visible in the process list). It picks a free browser port unless you pass --http-port, and it always binds to 127.0.0.1. Open the printed URL.
A tour of the eight views
The page polls every 500 ms. A status light in the top bar reads connected while it works and reconnecting when a request fails. What you should find, view by view, given how the demo is written:
| View | What to look for in this demo |
|---|---|
| Session | Application name “CNA Inspector demo”, your platform backend and renderer, the negotiated capabilities, and the two metadata entries the demo supplies: Resolution 640x360 and Sprites per frame 48. Exposure reads local. |
| Performance | Frame rate, frame time and a live frame graph built from the 240-frame history, plus the metrics table: the engine’s Runtime/* and Graphics/* counters and the demo’s own gauge Demo/Sprites (48), each with unit, kind and accuracy. A dash means the game does not publish that metric. |
| CPU profiler | FULL only. Hot zones aggregated over the last 1,000 events: Game/Tick, Game/Update, Game/Draw from the engine and Demo/Update, Demo/Simulate (the busy loop that makes a measurable zone), Demo/DrawScene and Demo/Compose from the demo, with a recent timeline. |
| Graphics | Draw calls, submitted primitives, texture-binding changes, resource count and declared resource bytes. It says outright that there is no shader reflection. |
| Resources | Open the view, or press Refresh, to fetch the list. Expect a 32×32 and a 256×256 texture, a 320×180 render target, a vertex buffer and an index buffer, and a 64×64 texture that appears and disappears every two seconds (the engine may register more resources of its own). Sort by any column and filter by kind. The Preview buttons stay disabled: no renderer supplies a preview provider at this snapshot. |
| Audio | Audio/AllocatedVoices and Audio/VoiceCreations. The demo plays no sound, so expect zeros or dashes. |
| Input | An explicit “unavailable” message: no input metrics are published. |
| Events | A filterable log (sequence, frame, thread, kind, category, name, duration, value). Filter on Demo/ to see the Demo/ChurnResources zone and the Demo/ResourceChurn marker every two seconds, next to resource-created and resource-destroyed events. |
If a warning banner about discontinuities appears, the UI has detected events that were lost since it connected (dropped or overwritten before they could be read). That is a statement about the log, not a fault in your game. Nothing you click changes the running game.
When the demo’s --seconds elapse (or you close its window) the status light turns to reconnecting; start the demo again with the same port and a new token and restart the bridge with the new token to watch another run.
Add the Inspector to your own game
The Inspector is never automatic: it exists only if your code starts an agent. Link the module next to CNA and start the agent before you run the game, keeping the returned pointer alive for the whole run. This function was checked to compile against the snapshot’s headers:
// Start the Inspector agent explicitly, before Game::Run(), and keep it alive for the whole run.
#include "CNA/Inspector/Agent.hpp"
#include <cstdio>
#include <memory>
#include <string>
std::unique_ptr<CNA::Inspector::Agent> StartInspector()
{
CNA::Inspector::AgentConfiguration configuration;
configuration.applicationName = "My game";
configuration.port = 0; // 0 = let the OS choose; read it back below
configuration.metadata = {
{"Resolution", "640x360"},
{"Build", "tutorial"},
};
std::string error;
std::unique_ptr<CNA::Inspector::Agent> agent =
CNA::Inspector::Agent::Start(configuration, error);
if (!agent)
{
std::fprintf(stderr, "Inspector did not start: %s\n", error.c_str());
return nullptr;
}
std::printf("Inspector port: %u\n", static_cast<unsigned>(agent->GetPort()));
// The token is a secret: hand it to the developer over a local channel, not to shared logs.
std::printf("Inspector token: %s\n", agent->GetAuthenticationToken().c_str());
std::fflush(stdout);
return agent;
}
int main()
{
std::unique_ptr<CNA::Inspector::Agent> inspector = StartInspector();
if (!inspector)
return 1;
// ... construct your Game and call Run() here; the agent stops when `inspector` is destroyed.
return 0;
}
target_link_libraries(my_game PRIVATE CNA CNA::Inspector)
Configure your project with -DCNA_BUILD_INSPECTOR=ON and a Diagnostics level of at least STATS. Agent::Start returns null and fills error if it cannot start (for example, a non-loopback address without allowRemote). Give the developer the port and the token over a local channel; do not log the token in a build you share. Then run exactly as above: your game prints the values, you start cna-inspector with them, and you open the URL.
Keep it out of what you ship. Guard the whole block with your own build option (or #if) so a release build neither links CNA::Inspector nor opens a listening socket. The agent has no environment switch and no auto-start precisely so that this stays an explicit decision.
Inspecting another machine
The agent binds to 127.0.0.1 by default, and the agent–bridge protocol is plaintext TCP that carries the token in its handshake. Do not switch on allowRemote to reach a game across a network. Tunnel the loopback port instead, so the bridge on your machine still talks to a loopback address:
ssh -N -L 47001:127.0.0.1:47001 user@game-machine # first terminal
build/modules/inspector/cna-inspector --agent-port 47001 --token-file inspector.token # second terminal
Troubleshooting
| Symptom | What to check |
|---|---|
Build says there is no target cna_inspector_demo or cna-inspector | The configure line lacked -DCNA_BUILD_INSPECTOR=ON; the demo also needs CNA_BUILD_EXAMPLES (on by default). |
| Bridge exits with “--agent-port and an authentication token are required” | Pass the port the demo printed and one token source: --token-file, --token or CNA_INSPECTOR_TOKEN. |
| “Inspector did not start” from the demo | The agent could not open its port (for example, it is already in use). Try another --port, or omit it and use the port the demo prints. |
| Page loads but stays on reconnecting | Wrong agent port or an out-of-date token (a new token is generated on every start), or the demo already exited. |
| Performance shows numbers but CPU profiler and Events are empty | The build is CNA_DIAGNOSTICS=STATS (or OFF). Use FULL for zones and events. |
| Everything is empty | The build is CNA_DIAGNOSTICS=OFF; see Tutorial 155. |
Next steps
- Inspector reference — configuration table, security model, wire protocol and limits.
- Diagnostics reference — what the Inspector is reading.
- Tutorial 155 — instrument your own metrics and read them in code.