Skip to content
Flenser42Public

About

Per-application bandwidth limits and traffic prioritisation for Linux (GTK4 + TrafficToll/nethogs)

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Throtl

Throtl

Per-application bandwidth limits and traffic prioritisation for Linux.

CI License: GPL-3.0-or-later Python 3.11+ Platform: Linux

Install · How to use · Testing · Releasing

Watch who uses your connection — then decide who wins. Throtl shows every app's live download/upload and lets you limit, prioritise or schedule it: per app, per direction, per weekday/time window. Rolling budgets alert you — and can throttle you to a floor — before you hit your data cap. A native dashboard (Tauri 2 + React) drives a root daemon that does the real tc/cgroup shaping (TrafficToll) and nethogs measurement.

Install

git clone https://github.com/Flenser42/throtl.git && cd throtl && sudo ./setup/install.sh

One command — Arch · Debian/Ubuntu · Fedora · openSUSE (auto-detects your package manager). → Full installation guide · prebuilt packages · AUR

What it does

  • Monitor — a live graph and per-app rates: see exactly who's using the connection.
  • Control — per-app download/upload limits and Critical→Low priorities, plus a global cap.
  • Schedule — rules that apply only in a weekday/time window; profiles switch automatically.
  • Budget — rolling day/week volume caps with alerts and optional enforcement (throttle to a floor).
  • Automate — startup profile, background autostart and a live bar/tray widget (Omarchy/Waybar).

Screenshots

Throtl dashboard (dark)

The dashboard — more screenshots in the usage guide.


Features

Throtl is one loop: watch who uses the connection, then decide who wins. The features split into monitoring, control, budgets and automation.

Monitor

  • Live graph — download/upload over time on a real time axis. Shows the last 60 s by default and auto-scrolls; switch the window to 30 s / 1 min / 5 min / 15 min / All and scroll back through history at any time.
  • Live process table — per-app rates, editable limits and priority, sortable columns, colour-coded up/down values, and a filter box to focus on one app.
  • Explains itself — every affected row says which rule applies (own limit, global limit, time window and whether it is active right now).

Control

  • Per-application limits — download/upload caps for a single app, even when it runs many processes (they are grouped into one row and summed).
  • Priorities — Critical / High / Normal / Low for individual apps, plus a global default priority for everything that has no rule.
  • Time windows — give any rule a weekday + time window ("Firefox, Mon–Fri 20:00–00:00"); the rule only applies inside it. Windows across midnight work, and the engine re-applies automatically when a window opens or closes.
  • Global switch — turn all shaping on/off without losing your rules.
  • Interface switching — follow the default route automatically (WLAN ↔ LAN, VPN up/down) or pin shaping to a specific interface from Settings → Interface.

Budgets & statistics

  • Consumption budgets — a rolling daily/weekly volume limit (global or per app); the GUI warns and shows a desktop notification when it is exceeded.
  • Editable budgets — the Budgets sheet sets the global daily/weekly volume and per-application budgets, showing how much is already used. A desktop notification arrives at 80 % of a budget, so there is still time to react.
  • Statistics — persistent per-app history over 1 h / 2 days / 30 days, shown as a table and a graph.
  • Budget enforcement — optionally throttle an over-budget app (or the whole connection, for a global budget) to a configurable floor rate, and revert when the rolling window moves on. Alerts live in the daemon, so any client can react. See Budgets for the enforce / floor config.

Automation

  • Profiles, schedules & startup profile — save the current limits as named profiles ("University", "Evening", "Night"), switch between them automatically by weekday and time, and pick one to activate on daemon startup.

Experience & platform

  • Native-feeling design — follows your system light/dark preference (or pin Light/Dark in Settings); the graph and rate colours adapt with it. A guided tour introduces the layout on first launch.
  • Responsive — TrafficToll restarts are coalesced and happen off the UI thread, so the window never freezes while a change is applied.
  • Headless CLI — everything the GUI can do, plus monitor, top, watch (a timed report with an optional alert threshold) and a simulation mode that needs neither root nor TrafficToll.
  • Local only — a Unix socket, no network port. The one outbound request is the optional update check (below).
  • Knows when the service is missing — if the daemon is not reachable, the window shows a status page with Try again and Setup guide instead of an empty table.
  • Update notice — tells you when a newer release exists and opens its page in your browser. Throtl never installs anything by itself.
  • Start in background — launch on login and run minimised to the tray (Settings → Startup); offered by the first-run wizard too.
  • System-tray widget — live download/upload and a click-to-toggle shaping switch, for the Omarchy bar or Waybar (see Widgets).
  • Command palette — Ctrl/⌘ P opens a searchable, keyboard-driven list of actions (focus the filter, add a rule, open a sheet, switch profile, toggle theme or shaping, …).

How it works

Throtl architecture

  • The privileged daemon (throtl-daemon) runs as root via systemd (throtl.service) because tc, the IFB device and nethogs need root.
  • The IPC is a local Unix socket (/run/throtl/daemon.sock) carrying newline-delimited JSON. No TCP port is opened.
  • Configuration is persisted as TOML: /etc/throtl/config.toml under systemd, or ~/.config/throtl/config.toml for manual runs. The GUI reads it only through the daemon API, never from the file.

How limits take effect

TrafficToll reads its YAML config once at startup and installs the tc filters for the matched processes; it has no SIGHUP reload. Throtl therefore writes the current config and restarts the tt subprocess on every change:

change (GUI/CLI) ──► render YAML ──► stop tt ──► start tt

This is the most reliable path and does not require restarting the target application — the tc rules apply immediately on the interface.

How priorities behave

Priority numbers map as Critical 0 … Low 3 (lower = served first). The global priority is the class used for traffic that matches no per-app rule. Prioritisation only has a visible effect when the link is saturated and a global download/upload cap is set — otherwise TrafficToll runs at line rate and there is no queue to prioritise. The GUI hint states this as well.

Throtl sets download and upload priority together from a single control.

Monitoring semantics

Throtl runs nethogs in trace mode with -v 1 (cumulative kB per process) and computes the rate itself from the delta between two ticks over a monotonic clock. nethogs' built-in rate mode divides by its assumed PERIOD and drifts badly under load (we measured 2.0 MB/s reported while the kernel and the application showed ~2.5 MB/s for the same traffic), so the deltas are used directly. nethogs counts KiB (1024-byte units), which Throtl converts to its internal kbit/s schema. Traffic that cannot be attributed to a process is kept as a synthetic (unattributed) row instead of being dropped.


Requirements

  • Linux — Arch / Manjaro / Omarchy, Debian / Ubuntu, Fedora or openSUSE (anything with systemd and a package manager). Developed on Omarchy (Wayland).
  • Python 3.11+
  • traffictoll — installed by the setup script into a venv under /opt/throtl
  • tc and the ifb kernel module (for ingress shaping)
  • nethogs and the WebKitGTK 4.1 runtime for the dashboard — install.sh installs these for you; the exact package names per distro are in INSTALL.md.
  • Building the dashboard from source additionally needs Node 20+ and Rust (see INSTALL.md); the CLI needs neither.

The backend itself has no third-party Python dependencies — it uses only the standard library.


Installation

One command works on Arch / Omarchy, Debian / Ubuntu, Fedora and openSUSE (the script detects your package manager):

git clone https://github.com/Flenser42/throtl.git
cd throtl
sudo ./setup/install.sh

Beginner-friendly guide — per-distro system packages, prebuilt packages, the dashboard build, and troubleshooting: INSTALL.md.

The script:

  1. installs the system packages it needs (nethogs, webkit2gtk-4.1, iproute2 for tc),

  2. creates /opt/throtl/venv and installs traffictoll,

  3. copies the code and launchers to /opt/throtl (and symlinks throtl-cli / throtl-daemon into /usr/local/bin),

  4. creates /etc/throtl/config.toml and /run/throtl,

  5. installs and starts the throtl systemd service,

  6. removes leftovers from the old GTK GUI (menu entry, launcher, icon),

  7. installs the dashboard GUI for your user (builds the AppImage once if needed, then installs it under ~/.local). Use --no-app to skip it, or run it separately later.

    The first GUI build compiles Rust and can take 5–15 minutes. The installer shows a live progress bar — a percentage driven by the number of compiled crates, with phase labels (deps → frontend → Rust → bundling) — so it is working, not stuck; don't interrupt it. Later installs reuse the cached build and finish in seconds, but the cache is freshness-checked: if any source file is newer than the built AppImage (e.g. after git pull), the dashboard is rebuilt automatically.

That is the whole install — daemon + GUI in one command:

sudo ./setup/install.sh              # everything (asks about the bar widget)
sudo ./setup/install.sh --no-app     # daemon + CLI only
sudo ./setup/install.sh --with-omarchy   # + the Omarchy bar widget
sudo ./setup/install.sh --with-waybar    # + the Waybar status module
sudo ./setup/install.sh --with-widget    # + whichever matches your shell
./setup/install-app.sh               # dashboard only (no root)
make install                         # same as the first line

The installer detects whether you run Omarchy (omarchy-shell) or a generic Arch setup, and asks only about the matching bar widget (or you can force one with the flags above). If the dashboard is already running, the installer closes it before reinstalling and relaunches it afterwards.

Once installed, the dashboard can grant your account access again without a terminal: if it shows "No access to the daemon socket", click Grant access — this runs the setup helper through polkit (pkexec) and asks for your admin password, then adds you to the throtl group (the socket stays root:throtl 0660). Log out and back in afterwards. The equivalent from a source checkout is pkexec ./setup/throtl-setup. The very first install still starts at the terminal (or a distro package), because the polkit helper and policy must already be on the machine.

Uninstall with sudo ./setup/uninstall.sh (add --purge to also remove code and configuration), which also removes the per-user dashboard; to drop only the dashboard by hand:

rm -rf ~/.local/opt/throtl ~/.local/bin/throtl-app \
       ~/.local/share/applications/throtl-app.desktop \
       ~/.local/share/icons/hicolor/scalable/apps/throtl-app.svg

Prebuilt artifacts / distro packages: The backend is pure Python, so there is nothing to compile for the daemon or CLI. Every release carries an sdist, a wheel, a Debian .deb for the backend (build it locally with make deb), and a Tauri .deb / .rpm for the dashboard (make app-build); there is an AUR PKGBUILD for Arch/Omarchy. How to install these by hand: INSTALL.md. See packaging/README.md.

CI lints and tests the Python backend (ruff + unittest), builds the frontend (npm run build) and denies Rust warnings (cargo clippy -D warnings).

The daemon and engine need root, so Throtl installs system-wide. The frontend (GUI/CLI) runs as your user and talks to the daemon over the Unix socket.


Usage

Dashboard (GUI)

Open Throtl from your launcher, or:

throtl-app

The window is one monitor surface — the live download/upload rates, the bandwidth graph and a compact status line (active rules, global caps, profile) — above the application list:

  • Graph — hover for a crosshair and a tooltip with the exact values at that moment, click to pin it; switch the window with 30s / 1m / 5m / 15m / all. Below it: min / avg / max and the peak application.
  • Applications — sortable and filterable. Each row shows the rates, a per-app arm switch and a ⋯ menu (edit rule, reset counters, copy the equivalent CLI command, remove rule). The second line keeps only what applies: limit (◧ 3.4 MB/s ↓ · 0.5 ↑ or unlimited), priority (icon + word), time window (with · now when active) and a budget bar.
  • Theme — follows the system; press L to toggle light/dark.
  • Filter — Ctrl/⌘ K focuses the search box.
  • Command palette — Ctrl/⌘ P opens a searchable list of actions (add a rule, open a sheet, switch profile, toggle theme or shaping, …); arrows + Enter run, Esc closes.

Colours, spacing and type come from the design tokens in throtl-app/src/styles/globals.css; the Rust side never exposes the socket to the webview.

Limits are displayed and interpreted in the selected unit (MB/s, Mbit/s, KB/s, kbit/s). An explicit suffix such as 2 kbps always wins over the unit.

Keyboard

Shortcut Action
L Toggle light/dark
Ctrl/⌘ K Focus the application filter
Ctrl/⌘ P Open the command palette
Esc Close the open sheet or menu

Updates

On start, Throtl asks the public GitHub release API which version is current and shows a banner when yours is older — “Throtl 0.12.0 is available — you have 0.11.0” — with a View release button that opens the release page in your browser. Throtl never downloads or installs anything, needs no root for this and asks nothing of the daemon.

This is the only outbound request the app makes. It carries no account, no identifier and no usage data — just a GET for the latest tag. Switch it off in Settings under Updates; Check now runs it once on demand.

CLI

The daemon is fully controllable without the dashboard:

throtl-cli status
throtl-cli list-processes

# Global caps (2 Mbit/s down, 1 Mbit/s up)
throtl-cli set-global --download-limit 2mbps --upload-limit 1mbps
throtl-cli set-global --download-priority hoch

# Rule for Firefox
throtl-cli set-process --name Firefox --exe /usr/lib/firefox/firefox \
    --download-limit 2mbps --priority hoch

# Only throttle Firefox on weekdays between 20:00 and midnight
throtl-cli set-process --name Firefox --exe /usr/lib/firefox/firefox \
    --download-limit 1mbps --window-days mo-fr \
    --window-start 20:00 --window-end 00:00

throtl-cli remove-process --key 'exe:/usr/lib/firefox/firefox'

throtl-cli toggle --enabled false      # pause all shaping
throtl-cli monitor                     # live rates, once per second
throtl-cli top                         # full-screen live ranking (htop-style)

# Timed report for scripting; exit code 4 if an app exceeds the threshold
throtl-cli watch --duration 30 --alert 5mbps
throtl-cli watch --duration 10 --app firefox --json

# End-to-end check that limits actually throttle (needs root + TrafficToll)
throtl-cli selftest --limit 2mbps

# Temporarily boost an app to Critical priority, then revert it automatically
throtl-cli boost --name Firefox --seconds 300

# Machine-readable output (for scripting or the bar widget)
throtl-cli status --json
throtl-cli list-processes --json

# Profiles
throtl-cli profiles                    # list profiles (+ active)
throtl-cli profile-save University            # save current settings as "University"
throtl-cli profile-use University             # activate "University"
throtl-cli profile-delete University

# Activate a profile automatically when the daemon starts
throtl-cli start-profile University
throtl-cli start-profile                # show the current startup profile
throtl-cli start-profile --clear

# Statistics: last hour, last two days or last 30 days
throtl-cli stats --window minute
throtl-cli stats --window day

# Consumption budgets (rolling last 24 h / last 7 days)
throtl-cli budget-set --day 20gb --week 100gb
throtl-cli budget-set --app firefox --day 5gb
throtl-cli budgets
throtl-cli budget-remove --app firefox

# Export/import the whole configuration, and diagnose the host
throtl-cli export --output throtl.toml
throtl-cli import throtl.toml
throtl-cli doctor

Profiles & schedules

A profile is a named snapshot of the global limits and all process rules. The top-level global/processes are always the active view (v0.1.0 compatibility), so the GUI and CLI keep working unchanged. Profiles live additively next to them in config.toml:

active_profile = "Standard"

[profiles.University]
global_download_limit = 2048
global_upload_limit = 512
global_priority = "hoch"

[[profiles.University.processes]]
name = "Spotify"
match_type = "exe"
match_value = "spotify"
download_limit = 512
priority = "niedrig"

[[schedule]]
profile = "University"
days = ["Mon", "Tue", "Wed", "Thu", "Fri"]
start = "08:00"
end = "14:00"

The daemon checks the schedule once per monitoring tick and activates the first matching profile automatically (only when schedule is non-empty). days accepts mo…so (Mo = 0), ranges like "mo-fr", and English names; overnight rules (end < start) run until the next morning.

A startup profile is the fallback for when no schedule matches:

start_profile = "University"

It is applied once when the daemon starts; a schedule that matches at that moment still takes priority.

Time windows (per rule)

Individual rules can be limited to a weekday + time window. Add a window to a rule in config.toml (flat form shown here):

[[processes]]
name = "Firefox"
match_type = "exe"
match_value = "/usr/lib/firefox/firefox"
download_limit = 1024
priority = "normal"
window_days = ["Mon", "Tue", "Wed", "Thu", "Fri"]
window_start = "20:00"
window_end = "00:00"       # before start -> runs across midnight

Inside the window the rule is applied as usual; outside it the rule is left out of the generated TrafficToll config, so the app is unthrottled. The daemon re-applies automatically on the transition (once per monitoring tick). In the GUI, the small clock button in each row opens the window editor; Clear removes the window. The CLI accepts --window-days / --window-start / --window-end (and --clear-window).

Budgets

Budgets are rolling volume limits (day = last 24 h, week = last 7 days), global or per app. The daemon tracks alert levels and exposes them through get_budgets (the alerts list), so any client can react to a budget crossing 80 % or its limit. Optionally enforce them — throttle an over-budget app (or the whole connection for a global budget) to a floor rate, and revert when the window moves on:

[budgets]
enabled = true
enforce = true
floor = 64        # kbit/s — over-budget apps are throttled to this

[[budget_rules]]
app = "firefox"
day = "5gb"

Set limits with throtl-cli budget-set, list them with throtl-cli budgets.

Statistics

The daemon accumulates, per monitoring tick, the download/upload volume of every application into three rolling windows and stores them as <config_dir>/stats.json:

Window Bucket Kept
minute 1 minute 1 hour
hour 1 hour 2 days
day 1 day 30 days

The dashboard exposes this in the Statistics sheet (window switcher + per-app list and history graph); the CLI prints it with throtl-cli stats. Stored values are bytes, derived from the sampled rates — see docs/TESTING.md for the accuracy limits.

Simulation mode

For a quick look without root or TrafficToll:

throtl-daemon --simulate --socket /tmp/throtl.sock &
throtl-cli --socket /tmp/throtl.sock status
throtl-cli --socket /tmp/throtl.sock set-global --download-limit 2mbps

Verifying that limits really work

There are two layers:

  1. Did the config reach TrafficToll? throtl-cli status shows preflight warnings and the engine state; systemctl status throtl and the files under /etc/throtl show the details.

  2. Is bandwidth actually throttled? Add a rule for your test tool and measure the real rate:

    throtl-cli set-process --name curl --exe /usr/bin/curl \
        --download-limit 512kbps --priority normal
    
    curl -o /dev/null -w 'Speed: %{speed_download} B/s\n' \
         https://example.com/bigfile.bin

Without a real interface to shape, the simulation mode above is enough to test the control flow.

See docs/TESTING.md for manual test recipes and known limitations.


Widgets

A live bar/tray widget shows the current download/upload and a click-to-toggle shaping switch. It ships in two flavours, and the installer detects which one your system uses:

  • Omarchy (omarchy-shell) — a bar widget (flenser.throtl) with a detail popup (shaping switch, download/upload rates, global caps, top apps). Click the pill to open it, right-click to refresh.
  • Waybar (generic Arch) — a custom module (throtl-waybar) showing live rates; click toggles shaping.

Install with --with-omarchy, --with-waybar or --with-widget (auto-detect); see Installation.


Security

  • Local Unix socket only — no TCP port. The socket lives in /run/throtl/ and is owned by root:throtl with mode 0660, so only members of the throtl group (added by install.sh) can reach the root daemon. Access is purely local; there is no network listener.
  • The frontend can only set limits and priorities via the API; there is no arbitrary shell access over the socket.
  • The TrafficToll YAML is rendered deterministically; exe/name match values are escaped, cmdline is explicitly a regex.
  • The systemd unit deliberately runs without additional sandboxing because nethogs needs cap_sys_ptrace/cap_dac_read_search and the engine needs CAP_NET_ADMIN; the reasoning is documented inline in the unit file.

Project layout

throtl/
  __init__.py        path/app constants
  units.py           kbit/s schema, rate/size parse/format
  protocol.py        IPC: JSON over Unix socket, client with reader thread
  config.py          TOML schema, priorities, rules, profiles, schedules,
                     time windows, rule/persistence helpers
  monitor.py         nethogs cumulative-mode parser + NethogsMonitor
  engine.py          TrafficToll YAML renderer + tt process + SimEngine
  budgets.py         rolling daily/weekly volume budgets
  stats.py           persistent per-app history (ring buffers, 3 windows)
  daemon.py          Unix-socket RPC daemon (root)
  cli.py             CLI (throtl-cli)
throtl-app/          dashboard GUI: Tauri 2 + React + TS + Tailwind v4
  src/               React UI (Overview, LiveGraph, ProcessList, sheets, …)
  src-tauri/         Rust shell: Unix-socket JSON-RPC bridge + commands
tests/               unittest suite (no display or root needed)
setup/               install/uninstall (+ install-app.sh) and systemd unit
packaging/           AUR PKGBUILD, Debian .deb builder + distro notes
data/                icon (SVG)
docs/                TESTING.md, RELEASING.md
docs/images/         banner, architecture diagram and screenshots

Development

make check          # ruff + full unittest suite
make test           # python3 -m unittest discover -s tests
make lint           # ruff check .
make lint-fix       # ruff autofixes (imports/formatting)
make build          # sdist + wheel into dist/ (needs `build`)
make deb            # Debian package into dist/ (needs `dpkg-deb`)

The dashboard (throtl-app/)

cd throtl-app
npm install          # once
npm run dev          # browser preview on http://localhost:1420 (mock data)
VITE_MOCK=1 npm run tauri dev   # real Tauri window, mock data
npm run tauri dev               # real window, live daemon data
npm run tauri build             # .deb / .rpm / .AppImage (first build: 5–15 min)

?static=1&theme=light&pin=27 freezes the mock and pins the graph crosshair. Other URL parameters are handy for screenshots and deep links: theme=light|dark, sheet=new|rule|stats|budgets|settings, pop=profile, menu=1 and mockstate=offline|denied|connecting. To regenerate the README screenshots, serve dist/ and capture with headless Chromium, e.g. chromium --headless=new --window-size=1240,1000 --screenshot=docs/images/dashboard-dark.png "http://localhost:4173/?static=1&pin=27&theme=dark". Rust is only needed for tauri dev/build; install it with rustup and put ~/.cargo/bin on your PATH.

# lint + typecheck the frontend, and the Rust shell
cd throtl-app && npm run build && cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warnings && cargo test --manifest-path src-tauri/Cargo.toml

See throtl-app/README.md for the architecture.

  • The test suite needs no display and no root.
  • Run make clean to drop build artifacts and caches.

See CONTRIBUTING.md and docs/RELEASING.md; the project follows CODE_OF_CONDUCT.md and has a SECURITY.md policy.


Troubleshooting

The dashboard tells you when the service is missing instead of showing an empty list:

Throtl with the service unreachable

  • No process list in the GUI — the daemon could not start nethogs. Check throtl-cli status (Monitor error) and systemctl status throtl.
  • Limits don't seem to apply — verify the interface. Throtl auto-detects the default-route interface; for VPN tunnels pin it explicitly: throtl-daemon --interface tailscale0.
  • Prioritisation does nothing — set global download/upload caps; see How priorities behave.
  • tt not found — re-run sudo ./setup/install.sh (it installs TrafficToll into /opt/throtl/venv).
  • Permission denied on the socket / "daemon not reachable" — the daemon socket belongs to the throtl group. install.sh added you to it, but a running session keeps its old groups: run newgrp throtl in the terminal or log out and back in. throtl-cli doctor detects exactly this.

A small request

Throtl is free software. The GPL-3.0-or-later explicitly permits commercial use, and you are not required to tell me anything. But if you do use Throtl commercially, I would honestly love to hear about it — where it ends up and how big it grows. That is pure curiosity, not a license condition.

A short note — an issue or a discussion is more than enough, and keeps my inbox clean.


License

Throtl is licensed under GPL-3.0-or-later (see LICENSE). Commercial use is allowed — see A small request.

Third-party components

Throtl does not vendor or modify its backends; it installs and drives them as separate programs, so their licenses do not restrict Throtl's own licensing:

Component License How Throtl uses it
TrafficToll GPL-3.0 installed unmodified via pip into a venv (/opt/throtl/venv) and run as a subprocess through its CLI
nethogs GPL-2.0 system package, run as a subprocess; Throtl parses its -t output

Throtl is an independent frontend, not a fork of either project. Running a separate program and parsing its output is generally not considered creating a derivative work; even a real fork would be permitted by the GPL, but would carry the usual obligations (keep the license and notices, publish the source).

This is not legal advice.

About

Per-application bandwidth limits and traffic prioritisation for Linux (GTK4 + TrafficToll/nethogs)

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages