cadence

command module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 6, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

README

cadence

OTLP traces in your terminal. Browse with the TUI, pipe with the CLI.

Point it at a Tempo instance and you get a fuzzy-findable trace list with span trees on the right. There's also a headless CLI for wiring into Television, fzf, or shell pipelines.

Tempo is the only backend wired up right now. The interface is generic, so Jaeger, SigNoz, or any other trace store is fair game; you just need to write the search + fetch layer. Trace decoding is already shared for anything that speaks OTLP. PRs welcome.

What it looks like

 cadence  [tempo]  q={}  ·  50 results · live 2s
 SERVICE        NAME                                 DUR      AGE  │ a1b2c3d4e5f6a7b8  12 spans  48.21ms  ·  14:02:11 (5s ago)
 frontend       GET /api/checkout              12.34ms     5s      │ services: frontend, checkout, inventory
 checkout       POST /order/submit             45.21ms    12s      │ SPAN                                   DUR  TIMELINE
 inventory      SELECT inventory WHERE ...      2.11ms    34s      │ ● GET /api/checkout                12.34ms  ━━━━━━────────────
 ...                                                               │   ● └─ POST /order/submit           45.21ms  ────━━━━━━━━━━━━━━
                                                                   │     ● └─ SELECT inventory WHERE…    2.11ms  ────────────━─────
                                                                   │
                                                                   │ ATTRIBUTES · GET /api/checkout
                                                                   │ http.method          GET
                                                                   │ http.status_code     200
                                                                   │ http.route           /api/checkout
                                                                   │ user.id              u_8c1a
 j/k move  ·  gg/G top/bot  ·  tab → trace  ·  / query  ·  r refresh  ·  p auto:on  ·  q quit

Left pane is the live trace list. Right pane is the selected trace: header, services, span tree with a waterfall, and attributes for whichever span is highlighted.

Install

Homebrew (macOS):

brew install --cask johnletey/tap/cadence

Otherwise grab a binary from the releases page (macOS and Linux, amd64/arm64), or install from source:

go install github.com/johnletey/cadence@latest

Quick start

Point it at a local Tempo:

cadence --url http://localhost:3200

Default query is {}, window is the last hour, auto-refresh every 2 seconds.

Config file

For anything past a single local Tempo, put a YAML file at ~/.config/cadence/config.yaml (or wherever $XDG_CONFIG_HOME/cadence/config.yaml resolves). Example:

default_source: prod
refresh: 2s                # global default, overridden per-source or by --refresh

sources:
  prod:
    type: tempo
    url: https://tempo.prod.example.com
    headers:
      X-Scope-OrgID: "1"
    refresh: 5s            # prod gets a gentler refresh

  local:
    type: tempo
    url: http://localhost:3200

  grafana-cloud:
    type: tempo
    url: https://tempo-us-central1.grafana.net
    user: "12345"          # basic auth (your stack id)
    pass: "glc_…"          # your Grafana Cloud API token

A source is a configured connection. Each has a type naming its backend (only tempo today). Pick one at launch with --source local. Omit the flag and cadence uses default_source.

Refresh precedence, specific beats general: --refresh flag, then the source's refresh, then the top-level refresh, then 2s.

The TUI

Run cadence with no subcommand (or cadence tui if you prefer being explicit).

Layout

Above 100 columns, horizontal split with the list on the left. Below 100, vertical stack with the list on top. I picked 100 because narrower made the detail pane too cramped to be useful. The breakpoint isn't configurable yet.

Keys

Vim-ish where it fits:

↑/↓, j/k                   move cursor in the focused pane
gg, G                      jump to top / bottom
ctrl-u, ctrl-d             half-page up / down
ctrl-b, ctrl-f, pgup/pgdn  full-page up / down
←/→                        focus list / detail pane
ctrl-w h, ctrl-w l         focus list / detail pane (vim-style)
ctrl-w w, tab              toggle focus
enter                      load the selected trace into the right pane
/                          edit the TraceQL query
r                          manual refresh
p                          pause/resume auto-refresh
s                          switch source
q, ctrl-c                  quit
esc                        close query input or source picker, or move focus back to list
How the right pane fills in

Cursor moves in the list debounce for 150ms, then load the trace. Neighbours (two above, two below) prefetch in the background and cache, so scrolling through a region you've already visited is instant. Cache is per-session.

The attribute pane always tracks the highlighted span, not the trace root. Hit tab or to move into the tree, walk down, and attributes update.

Auto-refresh without the cursor jumping

Tempo traces are immutable once ingested, so new entries prepend to the top of the list and nothing else moves. Your cursor stays on the trace it was on; its index shifts down, but the selection and detail pane don't reload. If the list was empty and the first trace arrives, the cursor lands on row 0.

The header shows · live 2s while auto-refresh is running, so you can see if it's on and how often it fires.

Switching sources

Press s to open a modal listing every source in your config. The active source is marked, j/k moves the cursor, enter switches, esc cancels. Switching resets the query to {}, drops the trace cache (trace IDs are not portable across backends), and starts a fresh search at the new source's refresh interval. The picker only shows up when there's more than one source configured — with --url or a single-source config, the s key and its footer hint are hidden.

The CLI (headless mode)

Three subcommands for scripts and pipes. Each shares source-selection flags with the TUI (--config, --source, --url, --header, --basic-auth).

cadence list

Prints matching traces, one per line. Built for pipe consumption.

cadence list                              # last hour, default {} query
cadence list --query '{ status = error }' # only failed traces
cadence list --last 24h --limit 200       # wider window
cadence list --format json | jq           # structured output
cadence list --format table               # styled table, human readable

TSV columns, tab-separated:

trace_id   service   name   duration_ms   start_rfc3339   span_count
cadence show <id>

Prints a single trace as structured data.

cadence show a1b2c3d4...               # JSON (default)
cadence show a1b2c3d4... --format tree # plain text tree, no color
cadence show a1b2c3d4... --last 7d     # widen lookup window

Tempo needs a time hint for the lookup to reach block storage rather than just the ingester, so show defaults to a 72-hour window. Bump it with --last if the trace is older.

cadence preview <id>

Styled render of the trace, same layout as the TUI's right pane but as a one-shot printout. Defaults to --color=always because the usual consumer is a preview pane in another tool.

cadence preview a1b2c3d4...
cadence preview a1b2c3d4... --width 120
cadence preview a1b2c3d4... --color never | less # plain output

Backends

Cadence separates a source (a configured connection: URL, headers, auth) from a backend (what's on the other end). Tempo, Jaeger, and SigNoz are obvious candidates, but nothing in the design is tied to any of them. A source's type names its backend.

Tempo is the only one today. Adding more:

  • internal/source/ holds the Source interface and each backend as a subpackage (source/tempo/, source/jaeger/, ...).
  • internal/otlp/ is a shared OTLP-JSON decoder. If the backend returns OTLP, a new implementation can call otlp.DecodeTrace and skip re-parsing spans and attributes. If it doesn't, you'll need a format-specific decoder alongside.

Search APIs are a separate problem. OTLP standardises the trace data model, not the query endpoint. Tempo gives us TraceQL and /api/search; other backends each bring their own search layer.

Architecture

internal/
  source/         Source interface + backend implementations
    source.go
    tempo/        Tempo HTTP client (search + trace fetch)
  otlp/           Shared OTLP JSON decoder for trace payloads
  config/         YAML config loader, Duration type
  render/         Styles, span tree, waterfall bars, list rows, attribute block
  tui/            Bubble Tea model
cmd_tui.go        Launches the TUI
cmd_list.go       cadence list
cmd_show.go       cadence show
cmd_preview.go    cadence preview
resolver.go       Source resolution + refresh-interval precedence
main.go           Subcommand dispatch

Rendering lives in one place so the TUI and preview can't drift. The TUI adds selection highlighting on top; the CLI just prints the raw rendered output.

Development

Tool versions are pinned in mise.toml and managed with mise. Install mise, then from the repo root:

mise install         # installs the pinned go, golangci-lint, and goreleaser
go build ./...
golangci-lint run
goreleaser check

CI uses the same mise.toml via jdx/mise-action, so local and CI toolchains stay in lockstep. Bumping a tool is a one-line change in mise.toml.

Why "cadence"

Traces are about timing: when things happen and how long they take. "Cadence" is the word for that rhythm in prose and music.

License

Apache-2.0. See LICENSE.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal
otlp
Package otlp decodes OpenTelemetry OTLP trace payloads into cadence's backend domain types.
Package otlp decodes OpenTelemetry OTLP trace payloads into cadence's backend domain types.
render
Package render turns cadence's backend domain types into formatted strings for both the TUI and the headless CLI.
Package render turns cadence's backend domain types into formatted strings for both the TUI and the headless CLI.
source
Package source models a configured connection to a trace backend (Tempo, Jaeger, SigNoz, or anything else that exposes traces) and the domain types cadence reads from it.
Package source models a configured connection to a trace backend (Tempo, Jaeger, SigNoz, or anything else that exposes traces) and the domain types cadence reads from it.
tui

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL