Skip to content
hollow

Observability

Watch a running server

The control socket is opt-in — nothing extra binds without the flag. Start a server with one, then attach from another terminal. The dashboard is a separate process rather than a flag on the server, so the server stays headless and scriptable and you can attach and detach from one that keeps running.

$hollow serve --control 127.0.0.1:15354
$hollow dash
hollow dash --plain --target 127.0.0.1:15374 --width 118 --height 24
+- hollow --------------------------------------------------------------------------------- 127.0.0.1:15374  up 13s -+| qps 0       cache 62.5%   blocked 42.9%   p50 0.00ms    p99 574ms                                                  || :.#........................                                                                                        |+---------------------------------------------------------------------+----------------------------------------------+| LIVE                                                                | TOP NAMES                                    || 18:03:26 127.0.0.1       A     NOERROR  example.com.              + |  1 example.com.                            4 || 18:03:26 127.0.0.1       A     blocked  tracker.example.org.        |  2 ads.example.net.                        2 || 18:03:26 127.0.0.1       A     NOERROR  wikipedia.org.            + |  3 cloudflare.com.                         2 || 18:03:26 127.0.0.1       A     blocked  doubleclick.net.            |  4 doubleclick.net.                        2 || 18:03:26 127.0.0.1       A     NOERROR  cloudflare.com.           + |  5 tracker.example.org.                    2 || 18:03:26 127.0.0.1       A     blocked  ads.example.net.            |  6 wikipedia.org.                          2 || 18:03:26 127.0.0.1       A     NOERROR  example.com.              + |                                              || 18:03:26 127.0.0.1       A     NOERROR  example.com.              + | TOP BLOCKED                                  || 18:03:26 127.0.0.1       A     blocked  tracker.example.org.        |  1 ads.example.net.                        2 || 18:03:25 127.0.0.1       A     NOERROR  wikipedia.org.              |  2 doubleclick.net.                        2 || 18:03:25 127.0.0.1       A     blocked  doubleclick.net.            |  3 tracker.example.org.                    2 || 18:03:25 127.0.0.1       A     NOERROR  cloudflare.com.             |                                              || 18:03:25 127.0.0.1       A     blocked  ads.example.net.            | CLIENTS                                      || 18:03:25 127.0.0.1       A     NOERROR  example.com.                |  1 127.0.0.1                              14 ||                                                                     |                                              ||                                                                     |                                              |+---------------------------------------------------------------------+----------------------------------------------+| cache 3 entries   stale 0   dropped 0   ^C quit                                                                    |+--------------------------------------------------------------------------------------------------------------------+

What is in the frame

  • Query rate, cache hit rate, blocked share and the two latency percentiles. The rate is derived from the server's own uptime, not the dashboard's clock, so a paused or slow dashboard cannot invent a spike.

No raw mode, anywhere

The terminal libraries exist mostly to abstract raw mode: TCGETS and TCSETS on Linux, TIOCGETA and TIOCSETA with different constants on macOS, SetConsoleMode on Windows. Declining to read a keypress removes all three, and the terminal is never left in a state this program has to remember to undo.

Length-prefixed JSON, not HTTP

A control plane is one request and a stream of records. HTTP would bring a server, a router and a set of status-code decisions to it. Four octets of length and then an object is the shape DNS over TCP already has two packages away.

Watching never slows serving

The event broadcast drops rather than blocks. A consumer that stopped reading — a terminal suspended with Ctrl-Z, say — would otherwise stall the worker holding a client's query, and one slow watcher would become every client's timeout. The drops are counted and shown.

Reading is all it can do

Every control socket command reads and none change behaviour. That is what makes an unauthenticated loopback port defensible, and it is why blocklists load once at startup rather than being reloadable over the wire.

One-shot, for scripts

hollow stats prints one snapshot and exits, which composes with watch, with a cron line, and with a pipe into jq. The continuous view is hollow dash.

hollow stats
$ hollow statsup 4m51s, 10 queries, 0 blocked, 0 upstream failurescache: 6 hits, 4 misses, 60.0% hit rate, 4 entries, 0 served stalelatency: p50 0.00ms, p99 328mstop names:       6  example.com.       2  cloudflare.com.       1  wikipedia.org.       1  www.github.com.top clients:      10  127.0.0.1