Engineering · HTTP Debugging

Why httpstat makes HTTP Debugging useful

Homebrew (macOS / Linux)

brew install vandancd/tap/httpstat

From source

go install github.com/vandancd/httpstat@latest

You're debugging a slow API endpoint. You run curl -v. You get 200 lines of protocol noise, a handful of timestamps, and no clear picture of where the 800ms went. Was it DNS? TLS? The server taking forever to respond? You manually diff timestamps and try to reconstruct the sequence.

There's a better way.

httpstat is a Go CLI that wraps an HTTP probe and renders the results as a waterfall bar chart in your terminal. No browser required. No log scraping. One command, one picture.


The problem with how we debug HTTP today

Most engineers reach for two tools when debugging HTTP performance: curl -v and browser DevTools. Both have serious blind spots.

curl -v dumps raw protocol events with wall-clock timestamps you have to diff by hand. There's no visual sense of proportion. A 2ms DNS lookup and a 600ms TTFB look equally unimportant as lines of text. The signal is buried in noise.

Browser DevTools is excellent, but it requires a browser. That rules it out entirely for CI pipelines, staging servers, scripted diagnostics, or anything you're SSH'd into. And when you need to compare performance across environments like production vs. staging, CDN PoP A vs. PoP B; opening DevTools on each is slow and inconsistent.

Neither tool handles redirect chains well. You get timing for hop #1 or hop #2, never both scaled together so you can see which hop introduced the latency.

The mental model engineers actually need is: "How much of the total round-trip was DNS? Was TCP? Was the server? And where in the redirect chain did things go wrong?"

httpstat is built around that mental model.

The waterfall output

Run httpstat http://vandan.co and you get this:

  http://vandan.co  301 Moved Permanently  HTTP/1.1  new connection

   DNS Lookup      ████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░   7.31ms
   TCP Connection  ███░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  18.43ms
   TLS Handshake   ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  28.96ms
   TTFB            ███░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  26.74ms

  https://www.vandan.co  200 OK  HTTP/2.0  new connection

   DNS Lookup      ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░   2.11ms
   TCP Connection  ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  14.27ms
   TLS Handshake   ████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░  49.84ms
   TTFB            ██████████████████████████████░░░░░░░░░░ 574.21ms
   TTLB            ████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  78.56ms

   Total           ████████████████████████████████████████ 716.00ms

A few things to notice immediately, before even reading the numbers:

The bars are scaled to the grand total. Every bar is proportional to the sum of all time across all hops. That TLS bar in hop #1 and the TTFB bar in hop #2 are drawn on the same scale. You can glance at the chart and know at a moment's notice that the server response time on the final hop is dominating everything else. That's the insight, not the 574ms number, but the visual weight of that bar relative to everything else.

Color carries meaning. DNS and TCP are cyan, pure network cost. TLS is yellow, crypto overhead. TTFB changes color based on severity: green below 200ms, yellow below 500ms, red at 500ms and above. TTLB is blue. The color palette turns the waterfall into a triage tool: scan for red, investigate yellow.

marks redirects, marks the final response. This notation is borrowed from network topology — is a transit hop, is a terminal node. At a glance you know you're looking at a redirect chain and which entry is the final destination.

TTLB only appears on the final hop. Redirects don't return response bodies, so rendering a Time-to-Last-Byte for them would be meaningless. The tool makes the right decision for you.

Connection reuse is explicit. When a connection is reused, the output says so and hides the DNS/TCP/TLS rows — those phases simply didn't happen. This is easy to overlook in raw timing data, but httpstat makes it structurally obvious: a reused connection has fewer rows, not zeroed-out rows.

This last point matters more than it sounds. If you're looking at benchmark data and wondering why two similar requests have wildly different TLS times, connection reuse is often the answer. httpstat makes that visible without you having to go looking.

The trace output: --trace

The waterfall answers "where did time go overall." The trace answers "what exactly happened, in what order, and how long between events?"

Trace

  http://vandan.co  301 Moved Permanently

      +0ms   [+0ms]    Getting connection for vandan.co:443
      +1ms   [+1ms]    DNS lookup starting for vandan.co
      +8ms   [+7ms]    Connection attempt to 151.101.194.187:443
     +26ms  [+18ms]   TLS handshake starting
     +55ms  [+29ms]   TLS handshake completed
     +81ms  [+26ms]   First response byte received (TTFB)

  https://www.vandan.co  200 OK

     +82ms   [+1ms]    Getting connection for www.vandan.co:443
     +82ms   [+0ms]    DNS lookup starting for www.vandan.co
     +84ms   [+2ms]    Connection attempt to 146.75.38.187:443
     +98ms  [+14ms]   TLS handshake starting
    +148ms  [+50ms]   TLS handshake completed
    +658ms [+510ms]  First response byte received (TTFB)
    +737ms  [+79ms]   Response body fully read (TTLB)

There are two timestamp columns and both are pulling their weight.

The left column (+elapsed) is time since the probe started. It tells you when in the overall picture an event occurred. The right column ([+delta]) is time since the previous event. It tells you how long that specific step took.

These serve different debugging needs. If you're trying to understand why a request took 737ms, you scan the delta column for the large number; there it is, [+510ms] on the TTFB event at the final hop. That's your culprit. If you're trying to understand whether the redirect itself added latency (did following the 301 eat time before the second DNS lookup started?), you look at the elapsed column: +81ms to complete hop #1, +82ms to start hop #2. The redirect handoff cost 1ms; negligible.

The trace events use the same color coding as the waterfall bars. DNS events are cyan, TLS events are yellow, TTFB events are severity-colored. In a long trace this matters: you can visually cluster events by phase without parsing the text of each line.

One deliberate omission: there are no wall-clock timestamps. Absolute timestamps are nearly useless for performance debugging; what matters is the relative sequence and the time between steps. httpstat optimizes for that and only that.

The waterfall and trace are designed to be read together. The waterfall gives you the summary; the trace, grouped under the same / headers, gives you the drill-down for the same hops. Scan the waterfall, identify the slow phase, flip to the trace for that hop, find the exact event.


Why the output design matters as much as the data

httpstat isn't the only tool that collects HTTP timing data. curl exposes --write-out with timing variables. Many APMs and tracing tools capture similar phase breakdowns. What makes httpstat useful in practice isn't the data, it's the rendering.

The waterfall's proportional bars create a preattentive signal. Before you consciously read any number, your visual system has already identified the longest bar and therefore the dominant cost. This is not a small thing when you're debugging under pressure. The difference between "scan a list of numbers to find the largest" and "look at a bar chart and immediately see the longest bar" is the difference between seconds and milliseconds of comprehension.

The trace's delta column has the same property. You don't have to subtract timestamps; the subtraction is already done, right-aligned in a fixed-width column so large numbers pop.

Neither of these would matter much for a one-off debug session. But for an engineer running dozens of probes across environments, comparing performance before and after a config change, triaging a CDN issue at 2am; the rendering is the feature.


Other flags worth knowing

Flag What it does
--json Emits all timing and trace data as structured JSON. Pipe into jq, log to a metrics system, or diff across runs.
--dns-servers Overrides DNS resolution. Useful for testing split-horizon DNS, comparing CDN PoPs directly, or diagnosing DNS propagation after a record change.
--http1 / --http1.1 Forces protocol version. Run the same endpoint over HTTP/1.x then HTTP/2 and compare the waterfalls to quantify whether an upgrade is actually helping.
--no-keepalive Disables connection reuse. Always shows the full cold-start cost — fresh DNS, TCP handshake, full TLS negotiation.
--timeout Sets a probe-level deadline in seconds. Pair with --json for scripted diagnostics that need a hard upper bound.
--ipv6 Prefers IPv6 routing for the probe.
--max-redirects Controls redirect depth. Default 5, range 2–10.
--user-agent string The default User-Agent is now the Chrome browser string, so sites with bot protection work out of the box. To probe with raw UA for comparison: --user-agent "", or to spoof a different client: --user-agent "MyMonitor/1.0".

Installation

Homebrew (macOS / Linux)

brew install vandancd/tap/httpstat

From source

go install github.com/vandancd/httpstat@latest

When to reach for it

🔀
Is our redirect chain adding meaningful latency? Run it against your root domain and read the hop-by-hop waterfall.
🔐
Is TLS overhead the bottleneck? If the TLS bar is wider than TTFB, you have a crypto cost problem, not a server problem.
🌐
Did adding a CDN help? Run it before and after, compare DNS and TCP phases; CDNs should push both down by getting the server closer to the client.
🔍
Why does this endpoint feel slow in staging but fast in production? Use --dns-servers to probe both environments' DNS, then compare waterfalls.
♻️
Is connection reuse happening when I expect it to? The "reused connection" annotation and hidden DNS/TCP/TLS rows make this immediately visible.

The tool installs as a single binary. No configuration, no agent, no dashboard. For ad-hoc HTTP performance debugging, that's exactly what you want.