obs-stats

A btop-style terminal dashboard for OBS Studio.

Watch CPU, frame pacing, encoder health, outputs, scenes and audio — live, from a terminal, over obs-websocket 5.x. Get told the moment frames start dropping. One static Rust binary. No daemon.

Rust 2024 ratatui + crossterm obs-websocket 5.x desktop notifications 24 themes MIT licensed
$ curl -fsSL https://raw.githubusercontent.com/worxbend/obs-stats/main/scripts/install.sh | sh $ export OBS_WEBSOCKET_PASSWORD='…' $ obs-stats
The obs-stats overview dashboard: system, video and health cards above live stream and recording panels, with a stream bitrate chart along the bottom
The Overview deck. Screenshots on this page are rendered by the app itself, with representative sample data.

Why not just the OBS stats dock?

OBS tells you that frames were dropped. It does not tell you which pipeline dropped them, whether it started five minutes ago, or whether the cause was your GPU, your encoder, or your uplink. Those have different fixes.

Three loss channels, kept apart

Renderer skips are a GPU problem. Encoder skips are a CPU problem. Network drops are an uplink problem. They are never merged into one number.

Counted per stream

OBS reports totals since it launched. obs-stats captures a baseline when you go live, so yesterday's numbers can't hide today's problem.

Warning before loss

Frame render time against the frame budget, and encoder congestion, both move before anything is dropped — which is the moment to act.

A verdict, not a number

Drop rates, congestion, frame budget and FPS-against-target reduce to one word you can read from across the room.

🔔

It tells you, unprompted

You are presenting, not watching a terminal. Frames actually being lost raise a banner across every view and a desktop notification naming the cause and the fix.

Meters that mean something

Audio drawn on a dB scale, not a linear one, so speech at −20 dBFS fills two thirds of the bar instead of looking like silence.

🔒

Secret-safe by design

The password comes from an environment variable and is never written to config, logs or error messages. The config file is always safe to commit.

● HEALTHY ▲ STRAINED ✖ DROPPING

Six dashboards, one keypress apart

Press a digit, or Tab through them.

Overview

1

System load, video pipeline and a health verdict across the top; the live stream and recording outputs below; a bitrate chart along the bottom.

Overview dashboard

Performance

2

Achieved FPS against the configured target and average render time against the frame budget, over an accounting panel that separates the three loss channels.

Performance dashboard with FPS and frame render time charts over frame accounting

Stream

3

Everything OBS reports about both outputs, with a bitrate chart, a congestion trend, and a “how much longer can I record?” estimate from the write rate and free disk.

Stream dashboard with bitrate and congestion charts over stream and recording detail

Scenes

4

The scene list with program and preview tagged, beside the active profile and scene collection. Read-only — this is a monitor, not a control surface.

Scenes dashboard listing scenes with PROGRAM and PREVIEW tags beside profiles and collections

Audio

5

Every audio-bearing input with its fader position and a live peak meter, on a dB scale with a −60 dBFS floor. Segments turn amber before you clip.

Audio dashboard with per-input dB meters and a scale legend

Log

6

Scene switches, output state changes and mute toggles, timestamped relative to start — so “how long before the drop did the scene change?” isn't arithmetic.

Log view listing timestamped OBS events

You get told when it matters

The dashboard is only useful if you are looking at it, and while you are streaming you are not. So frames actually being lost raise a banner across every view — and a Linux desktop notification, so it reaches you behind a full-screen scene.

The Stream view with a red DROPPING FRAMES banner reading '14 frames in 10s — viewers are seeing stutter', followed by the remedy
The banner names the cause, the count, and what to change.

Rate, not totals

A stream that dropped forty frames an hour ago and none since is healthy. Alerts fire on frames lost within a rolling window, so they mean “right now”.

Quiet by default

A threshold, a rolling window, a cooldown, and only while an output is running. One stray frame in a 60 fps stream will not train you to ignore the alert.

No false starts

The first poll only sets a baseline, and a counter going backwards — every output restart — counts as zero loss rather than an enormous one.

Nothing to install

Notifications speak D-Bus directly with a pure-Rust client. No libnotify to link, no notify-send to shell out to, and the binary stays static.

Never blocks a frame

Delivery runs on its own thread. A wedged notification daemon cannot stall the render loop, and after three failures obs-stats stops trying.

Degrades quietly

No session bus — a TTY, ssh, a container — and notifications are skipped automatically. The banner still works, and so does the log.

AlertCause
DROPPING FRAMESNetwork could not deliver encoded frames
RECONNECTINGThe stream dropped
ENCODER LAGEncoder could not keep up
RENDER LAGRenderer could not build frames in time
ControlEffect
--no-notifyKeep the banner, skip the desktop
--no-alertsTurn the whole thing off
min_framesHow many lost frames it takes
cooldown_secondsMinimum gap per cause
# see what your desktop does with them $ cargo run --example notification_check

Notifications are Linux-only — on macOS and Windows the dependency is not even compiled in, and alerts appear in the dashboard alone. Full detail and tuning in docs/alerts.md.

What the numbers mean

The full guide lives in docs/metrics.md. The short version:

SymptomCause
Renderer skippedGPU — too many sources, heavy filters, a busy game
Encoder skippedCPU or encoder — preset too slow, no hardware encoder
Network droppedUplink — not enough bandwidth, congestion, packet loss
Congestion risingThe uplink, about to start dropping
Frame budgetMeaning
under 50%Comfortable
50–80%Strained; a spike will cost frames
over 80%On the edge; skips imminent
16.7 ms at 60 fps, 33.3 ms at 30

Keyboard first

Vim keys or arrows, digits to jump, mouse optional. Press ? in the app for the same table, generated from the keymap itself.

KeyNavigation
Tab / ⇧TabNext / previous view
16Jump to a view
j kMove the list cursor
g / GTop / bottom
KeyData & display
rRefresh now
spacePause the display
+ / Poll faster / slower
t / F2Theme picker
The help overlay listing every view and key binding

24 themes, previewed live

Press F2. Moving the cursor restyles the entire UI, not just a swatch — Enter keeps it, Esc puts the old one back.

The theme picker showing palette swatches with Dracula previewed across the whole dashboard
Claude
OBS Studio
Btop
Nord
Dracula
Gruvbox Dark
Gruvbox Light
Solarized Dark
Solarized Light
Tokyo Night
Catppuccin Mocha
Catppuccin Latte
Everforest
Kanagawa
Rosé Pine
One Dark
Monokai
Ayu Dark
GitHub Dark
GitHub Light
Synthwave
Matrix
High Contrast
Mono (TTY-safe)

mono uses only the 16 ANSI colours and never paints a background of its own — right for a Linux virtual console. Pair it with --ascii when the font lacks box-drawing glyphs. high-contrast distinguishes every state by more than colour alone.

Install

A single static binary. No runtime, no daemon, no dependencies.

# Linux, macOS — picks your platform and verifies the checksum $ curl -fsSL https://raw.githubusercontent.com/worxbend/obs-stats/main/scripts/install.sh | sh # or from source (Rust 1.88+) $ cargo install --git https://github.com/worxbend/obs-stats

Then in OBS: Tools → WebSocket Server Settings → Enable WebSocket server, export the password from Show Connect Info, and run obs-stats. Prebuilt archives cover Linux (x86-64, aarch64, musl), macOS (Intel, Apple Silicon) and Windows (x86-64).