What udpscope plots, the parts of the window, and one pass through the application from connecting to saving. Martino Ferrari Overview
What udpscope is

udpscope plots a live UDPStreamer source. One window holds up to eight rows of plots, each with up to four traces, and every row shares one time axis, so the whole figure scrolls together. Acquisition runs on its own thread and writes a ring buffer sized from --buffer-samples; the drawing thread reads whatever that ring holds when a frame starts, which is why a trace never makes the window stutter.

The stream is the clock. Every interval you read on screen is measured between timestamps the source sent, never your machine's, so a stalled source freezes the view instead of scrolling it — and a source whose clock steps still gets the window you asked for.

The time axis is relative to now. While streaming it is a fixed [-window .. 0] with the newest sample at 0, so x reads as an age: -1.5s is a sample a second and a half old. In trigger mode 0 is the event's edge instead, and a paused view keeps the ages it was frozen with. The source's own clock is still there when you need it — an export writes it as a second column.

The udpscope window The header bar above the plots, the badge bar and the status bar under them, and the dock on the right.

One figure of three rows with three signals plotted, a badge for each in the badge bar, the dock with its readouts and its events block, and the status bar reading live.

How to reach it

Start the app with a source (udpscope --server 127.0.0.1 --port 8000), or start it with no --server and pick one from the chooser, which lists the sessions you have opened before. A unicast source serves one client at a time: if a second viewer connects, they take turns and the status bar says so. To watch one source from several instances, use --multicast with the group and --interface with the local address.

The signals arrive disabled. Nothing is acquired until you turn one on in the signal list, and enabling is what starts acquisition — so a freshly opened scope with an empty badge bar is working correctly, not failing to connect. For the same reason a start that has enabled nothing prints no buffer allocated: line on stderr.

Over SSH with X11 forwarding the window may never appear, and the cure is udpscope --renderer cairo. Two symptoms tell the two halves apart: a vkCreateSwapchainKHR() ... VK_ERROR_INITIALIZATION_FAILED warning is GTK's default Vulkan renderer failing to make a swapchain and is what the option's GSK_RENDERER=cairo answers; a window whose allocation stays zero (window_button_w=0) means the plot's GL area has no context either, and that is what its LIBGL_ALWAYS_SOFTWARE=1 answers. Both variables are exported before GTK initialises, because they are read when the renderer and the GL context are created. --renderer gl is faster when indirect GL works.

See Choosing a source for the chooser and the multicast options in full.

The window The header bar

Left to right: the time window (2 s), the trigger and the pause; then, on the right, the cursors switch, the dock switch and the menu, which holds Fit to data, the Layout editor, Export visible range, Configurations, Help, Preferences and About. The trigger button turns trigger mode on and off; the trigger's settings are in the panel its badge opens.

The plot

Up to eight rows, each with its own y axis and up to four traces, all on one shared time axis. Right-click a row for its own menu (fit, add a signal, remove a trace).

The badge bar

The strip under the plot. On the left, one badge for each signal you have switched on, then a +; past six signals they fold into a single N signals badge. On the right, Trigger, which names the trigger's state, and Source, which is there only when the source has controls of its own. A badge opens its page in the panel described next; a signal's badge is also what you drag onto a row.

The side panel over the plot

One panel, 360 px wide, slides over the plot's right edge and shows one page at a time: the trigger, a signal's colour, calibration and rows, the list of signals to add, the cursors, the measurements, the events, or a source's own controls. It closes with a second click on the badge that opened it, with Esc, with Close, or with a click on the plot, and it never resizes the plot.

The dock

The column on the right, which the dock button in the header bar (s) shows and hides. Its Readouts are a read-only copy of the cursors and of every measurement; Edit beside each heading opens that instrument's full page. Under them, Events lists the captured events, newest first; a click replays one and Manage opens the full list. While the dock is hidden, the readouts show in a strip under the plot instead.

The status bar

The last line, under the badge bar — the state word, the stored sample rate and the window, and the ⓘ beside it for packets, gaps and the buffer's own figures. The second screenshot below is what a capture looks like: the trigger marker at the edge and the axis pinned to the event.

A held capture The trigger has latched, the state reads event held, and the axis is pinned to the event rather than to the live edge.

The trigger marker sits at x = 0 and the pre-trigger share of the window is drawn to its left.

The first five minutes

Connect. With no --server, pick a stored session or type a host and port.

Enable a signal in the Signals page (the + badge, or g). The plot stays empty for the first fraction of a second while the first window fills — the buffer holds only what has arrived.

Drag its badge onto a row. Drop a second signal on another row, or the same signal again for a second view of it. The colour is per signal, so it is the same one wherever it appears.

Set the window with the control in the header bar: type a number, pick a unit, or step a decade. The trigger's event window is a share of this span, so a window change moves it.

Pause (space) to look at a still picture; the producer keeps filling the buffer behind it, and resuming shows what arrived meanwhile rather than a hole.

Arm the trigger (a) and wait for a latch: the view freezes on one event, and you can zoom in on it. Disarming mid-capture keeps what the capture got.

Measure it — press m, or Edit beside Measurements in the dock. A measurement is a statistic of the visible window, its value is shown in the dock, and show on plot marks it as a ruler or a pair of markers.

Save the data with Export visible range, which writes the plotted signals over the window at the stored rate to .npy, .csv or .mf3 (MDF 3.30).

Worth knowing

A signal nobody enables costs nothing — no buffer, no socket work, no measurements.

The window remembers itself. Rows, traces, the time window, calibration, which signals were enabled, the math channels with their graphs, and the measurements are written per host and port, and reloaded the next time you open that source.

A very wide window on a fast stream is drawn from averaged groups of samples, so the rate in the status bar falls: that is the anti-alias filter, not a fault — striding would fold a fast signal back as a slow one, which looks entirely plausible on screen.

Every instrument the app computed — the trigger marker, the measurement marks — is locked to the pointer's reach. The cursors are yours to drag; those are not.

Installing this manual

The manual is a help set the desktop viewer reads off disk, so F1 opens it only once a copy sits where help: resolves. Either of two targets installs it on its own, without building the application, and each prints where it wrote. Prefer the system-wide one: it is the copy every viewer can read, and the reason is worth knowing before you pick the other:

For a system-wide or packaged install: cmake --build build --target install-help writes it under the configured CMAKE_INSTALL_PREFIX, so point that at /usr (cmake -DCMAKE_INSTALL_PREFIX=/usr ..) and wrap the target in sudo. A viewer reads its figures as separate files, and its sandbox exposes the system help directories, so this copy always shows them.

For your own account, needing no root: cmake --build build --target install-help-user writes it under $XDG_DATA_HOME (default ~/.local/share). Be aware that the same sandbox does not expose the user's data directory: those pages open, but a viewer such as Yelp shows them without their figures.