Tutorial 156: Watch a Running Game in the Inspector

CNA Tutorials  ·  CNA snapshot 009d40f5

ℹ

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 next branch (git clone -b next https://github.com/libcna/cna.git, with sharp-runtime also on next and 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_DIAGNOSTICS is STATS or FULL.

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:

ViewWhat to look for in this demo
SessionApplication 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.
PerformanceFrame 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 profilerFULL 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.
GraphicsDraw calls, submitted primitives, texture-binding changes, resource count and declared resource bytes. It says outright that there is no shader reflection.
ResourcesOpen 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.
AudioAudio/AllocatedVoices and Audio/VoiceCreations. The demo plays no sound, so expect zeros or dashes.
InputAn explicit “unavailable” message: no input metrics are published.
EventsA 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

SymptomWhat to check
Build says there is no target cna_inspector_demo or cna-inspectorThe 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 demoThe 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 reconnectingWrong 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 emptyThe build is CNA_DIAGNOSTICS=STATS (or OFF). Use FULL for zones and events.
Everything is emptyThe build is CNA_DIAGNOSTICS=OFF; see Tutorial 155.

Next steps