Usage#

Quick Start gets tinytap running. This page covers what to do once it’s up: reading the TUI, reading the stdout JSONL format, and when to reach for doctor.

TUI#

output = "auto" (the default) opens the TUI whenever stdout/stdin are an interactive terminal of at least 120x24. See Configuration to force it with output = "tui" or opt out with output = "stdout".

tinytap's TUI: the request table on top, and the detail panel below showing the full request and response, including headers and the decoded JSON body

Request table#

Every captured exchange is a row: timestamp, process[pid], method, path, status, response size, latency. New rows arrive at the bottom; the view follows the newest row automatically until you scroll away from it.

KeyDoes
j / , k / Move the selection (stops following new rows)
g / GJump to the oldest / newest row (G re-arms following)
/Start typing a filter, matches live as you type
Enter (while filtering)Keep the filter, stop editing it
Esc (while filtering)Clear the filter
EnterOpen the detail panel for the selected row
dOpen the diagnostics panel
q / Ctrl-CQuit

Detail panel#

Enter on a row opens its full request/response: headers, JSON body when decodable, hex otherwise. From there:

KeyDoes
TabMove focus into the detail panel to scroll long headers/bodies independently of the table
j/k, g/GScroll the detail panel once it has focus
bToggle hex/text body view
EscStep back out: unfocus the panel, then close it
EnterClose the detail panel

Diagnostics panel#

tinytap redirects its own internal log lines (process attach/detach, TLS uprobe attach, teardown errors) into a diagnostics buffer instead of printing them over the TUI, since a bare log.Printf mid-render would corrupt the screen. Press d to view them; the footer shows ⚠ N diag (d) whenever there are lines waiting. They’re flushed to stderr when tinytap exits, so nothing is lost if you never open the panel. g/G jump to the top/bottom; Esc, d, or Enter closes it.

stdout mode#

output = "stdout" prints one JSON object per exchange (JSONL) instead of drawing the TUI, useful when piping into jq, running over SSH, or in CI. Full request/response headers are always included, with no separate verbose toggle; reqBody/resBody/abandonReason/ssl are omitted when empty rather than sent as zero values:

{
  "reqTsNs": 1754887677005000000,
  "latencyNs": 300000,
  "pid": 27122,
  "fd": 6,
  "comm": "python3",
  "method": "GET",
  "path": "/api",
  "reqVersion": "HTTP/1.1",
  "status": 200,
  "reason": "OK",
  "resVersion": "HTTP/1.1",
  "resBytes": 1304,
  "reqBytes": 0,
  "reqHeaders": [{"name": "Host", "value": "localhost:8000"}],
  "resHeaders": [{"name": "Content-Type", "value": "application/json"}],
  "abandoned": false,
  "reqBodyTruncated": false,
  "resBody": "eyJvayI6dHJ1ZX0=",
  "resBodyTruncated": false,
  "sslFallback": false
}

An abandoned exchange (the connection closed, or tinytap gave up waiting) sets abandoned and abandonReason (peer closed or timed out); status/resBytes stay at their zero value since no response arrived. reqBody/resBody are base64 (JSON has no raw byte type); pipe through jq -r '.resBody | @base64d' to decode one, or filter the whole stream by field, e.g. tinytap --config … | jq 'select(.abandoned)'.

doctor#

tinytap doctor runs a read-only preflight (kernel version, capabilities, libssl execute bit) without loading any eBPF. Run it first when something doesn’t work; see Troubleshooting.

--version#

Prints the build’s version, commit, and date, and exits before touching eBPF at all. Safe to run without root or capabilities.