qbmonitor
manual
settings tune dashboard changelog

QBMonitor system manual

This is the “what it actually does” manual: the logic, calculations, gates, and order of operations for the live system. If the UI and this doc ever disagree, treat the RestrictionRegistry (and Controller Ownership debug card) as the source of truth.

Recommended mental model: QBMonitor is multiple controllers sharing one client. To prevent controller fights, caps/actions are “owned” per torrent via the in-memory RestrictionRegistry. Restricted/Safety always wins; Racing wins during early swarm; Category Throttle and Ratio Tuning are steady-state optimizers.

Contents

Architecture & dataflow

QBMonitor is a polling-and-control loop around qBittorrent:

qBittorrent Web API  ->  RuntimeState snapshot (torrents + transfer)
                          |
                          +--> Engines evaluate (Racing / RatioTuning / CategoryThrottle / Deletion / PeerBlock)
                          |
                          +--> RestrictionRegistry updated (ownership + caps + reasons)
                          |
                          +--> qB Web API commands (setTorrentDownloadLimit / setTorrentUploadLimit / pause / deleteFiles)
                          |
                          +--> UI reads: status endpoints + registry-sourced reporting + audit log
        

Engines should be written “observational-first”: they can compute intent without enforcing. Enforcement is controlled by enable toggles. You explicitly prefer enable checkboxes as sufficient caution, so most engines apply changes when enabled.

Controller ownership & order of operation

Multiple subsystems can apply caps/actions. To avoid fights, QBMonitor treats control as owned per torrent. Ownership is visible on the dashboard in Controller Ownership (debug).

Authority order (highest wins)

  1. Restricted / Safety — always wins. Hard overrides, deletion safety logic, peer blocking, manual restrictions.
  2. Racing — early-swarm optimizer; may apply download caps, burst focus, and dump decisions.
  3. Category Throttle — steady-state shaping; protects “Primary” by capping Limited torrents’ download.
  4. Ratio Tuning — steady-state efficiency tuning; caps worst downloaders’ download.

Suppression rules (actual implementation intent)

Important: “Enabled” does not mean “wins.” It means “may attempt.” Ownership decides who actually gets to enforce.

RestrictionRegistry (the truth)

The in-memory RestrictionRegistry is the authoritative record of what QBMonitor thinks it has enforced and why. Each entry is keyed by torrent hash and a source (controller name).

What a restriction contains

Why registry beats qB state

qB’s live cap reporting can be confusing (0 vs unlimited, and other controllers can set caps). The registry records which controller believes it owns the cap. The UI should render from this registry to avoid misreporting.

Audit log semantics

Audit events are emitted for human diagnosis. In general:

If you see audit lines that disagree with qB’s internal view, trust qB for “what is enforced right now” and use the registry + ownership card to find “who tried to enforce and why”.

Settings & safety gates

QBMonitor settings are stored in the settings DB (key/value). Many dangerous actions are gated behind explicit toggles.

Settings Key Reference: the running app can generate a live, drift-resistant catalog combining code defaults, DB values, and UI bindings. Open: /docs/settings

Deletion safety gate

Deletion is preview-only unless both gates are open:

delete_files_enabled = 1
delete_files_confirmed = 1
        
If both are enabled, deletion calls qB’s delete endpoint with deleteFiles=true (disk delete). This is intended for “limited space” operation.

Racing dump action

Racing’s dump action is controlled by racing.dump_action. In this build:

The value name “remove” is legacy wording; treat it as “delete_filesystem” behavior in practice.

Racing subsystem (pump & dump)

Racing is optimized for the first minutes of a torrent (initial swarm). The goal is: maximize early upload credit and dump losers fast.

Eligibility

A torrent is racing-eligible if its qB state indicates downloading (states containing dl or downloading). The engine tracks torrents by hash and keeps a short rolling window of samples (~2 minutes at ~2s polling).

Key windows

SettingMeaning
racing.early_window_secondsHow long a torrent is considered “early swarm” and managed aggressively.
racing.burst_secondsHow long a burst focus runs when triggered.
racing.burst_cooldown_secondsMinimum delay between bursts per torrent.

Scoring & winner selection

Racing selects a winner among active early torrents using a score that favors upload but penalizes download:

score = up_bps - (down_penalty * down_bps)

winner = argmax(score), tie-break by higher up_bps
        

racing.down_penalty controls how harshly download is penalized (ratio efficiency pressure).

Admission control (max active racing torrents)

If more torrents are in the early window than allowed by racing.max_active, the engine keeps the top-scored torrents and “dumps” the rest immediately:

active = torrents where age <= early_window_seconds
scored = sort(active by score desc)
keep = top max_active
dump = the rest -> DumpAsync(reason="admission_control")
        

Ramp download cap (optional)

During early racing, the engine can apply a download cap to the kept set:

SetTorrentDownloadLimit(kept, racing.ramp_down_cap_bps)   // 0 = unlimited
        

Burst logic (what “burst” means)

Burst is an explicit “focus the winner” phase. It triggers when:

not currently in burst
AND cooldown satisfied
AND winner.up_bps >= racing.burst_trigger_up_bps
        

When a burst starts, the engine:

In other words: burst is not “download harder.” Burst is “let the winner breathe on upload while holding others down.”

Burst oscillation (optional)

During burst, Racing can oscillate the winner’s download cap to reduce waste:

period = max(5, racing.oscillate_period_seconds)
phase toggles every period seconds
osc = post_down_cap_bps * racing.oscillate_pct
cap = phase==0 ? post_down_cap_bps : (post_down_cap_bps - osc)
SetTorrentDownloadLimit(winner, cap)
        

Post-window clamps & dumping

After racing.early_window_seconds:

Dump action: in this build, the “remove” action calls qB delete with deleteFiles=true (disk delete). This is intentional for limited space racing.

Ratio events (snapshot + classification)

Ratio events are the system’s “inflection detector”: it periodically builds a global snapshot and classifies meaningful changes into event types (gain/loss, spikes/drops, mode shifts, idle inefficiency, high efficiency).

Snapshot contents

A snapshot is built from the current torrent list:

Computed ratio & delta/hr

Snapshot “ratio” is computed as:

ratio = (down_speed > 0) ? up_speed / down_speed : (up_speed > 0 ? up_speed : 0)
        

The event poller computes ratio_delta_hr between snapshots (implementation detail depends on poll interval):

ratio_delta_hr ≈ (cur.ratio - prev.ratio) * (3600 / Δseconds)
        

Severity score

Severity is a “how much should I care” scalar:

slope = abs(ratio_delta_hr)
balance = clamp( up_bps/down_bps, 0..3 )  (with safe handling for down=0)
activity = min(active_total/5, 2)

severity = slope * (0.75 + 0.25*balance) * (0.75 + 0.25*activity)
        

Classification rules

TypeTrigger
ratio_spikeratio_delta_hr >= SpikeDeltaHr with cooldown
ratio_dropratio_delta_hr <= DropDeltaHr with cooldown
ratio_gain / ratio_lossabs(ratio_delta_hr) >= MinAbsDeltaHr with cooldown
mode_shift_uploadingCrosses above ratio 1.0 + hysteresis band
mode_shift_downloadingCrosses below ratio 1.0 - hysteresis band
idle_inefficientMany active but little uploading and low total upload
high_efficiencyFew active, strong upload, minimal download

All types are cooldown-gated via CooldownSeconds to prevent spam.

Ratio tuning engine (per-torrent download caps)

Ratio Tuning is the steady-state “stop downloaders from harming upload efficiency” controller. It is designed to cap the download rate of a small number of torrents that are consuming bandwidth without yielding upload.

Observation is always on

Even when disabled, Ratio Tuning records learning snapshots into an in-memory ring buffer and periodically persists to ratio_learning.db.

Actable candidate set

Candidates are selected from runtime torrents by:

Efficiency score

The core score is a bounded “upload yield proxy”:

efficiency = up_bps / (down_bps + 1024)
        

Low efficiency means “spending download but not producing upload.”

Models (selection logic)

All current models pick the worst performers (lowest efficiency first), with slight variations:

ModelSelection
classiclowest efficiency, take max_targets
weatherlowest efficiency, tie-break by highest down_bps
banditlowest efficiency (reserved for future exploration; currently same shape)
hybridsort by 0.7*efficiency + 0.3*normalize(down_bps) (still chooses “bad drains”)

Enforcement (what it actually caps)

When enabled (ratio_tuning.enabled=1 and model != disabled), it applies:

SetTorrentDownloadLimit(target_hashes, cap_bps)
        

It does not cap upload. It registers ownership: source=ratio_tuning with reason ratio_tuning and downLimitBps=cap_bps.

Learning confidence & suppression

The engine maintains a learning state machine:

Confidence is computed from variance of recent efficiency samples:

variance = sample variance(efficiencies)
confidence = clamp( 1 / (1 + variance), 0..1 )
        

Rollback (self-protection)

If measured efficiency collapses relative to a slow EMA baseline, the engine auto-disables itself:

baseline_eff = EMA(mean_eff, alpha=0.05)

if recent_mean_eff drops by >= rollback_drop_pct (over rollback_window_minutes)
AND recent_confidence >= rollback_min_confidence
  -> set ratio_tuning.enabled = 0
  -> suppress for suppress_cooldown_minutes
        
This is conservative by design: it prefers turning itself off rather than causing harm.

Category throttle engine (Primary/Limited/Exempt)

Category Throttle protects a set of “Primary” categories by capping the download speed of torrents in “Limited” categories. Limited categories are computed as: all categories that are not Primary and not Exempt.

Bucket classification

Primary health gate

Throttling is allowed only when Primary is healthy continuously for a grace window. Health uses two checks on the chosen metric (download or upload):

healthy = (abs_check) AND (rel_check)
healthAfterGrace = healthy sustained for primary_health_grace_seconds
        

Confidence gating (anti-flap safety)

The engine tracks how recently health flipped. If health flipped too recently, confidence is treated as low and the engine clears caps:

flipAgeSec = seconds since last health flip
confidence = (healthAfterGrace ? (flipAgeSec >= max(5, on_grace_seconds) ? 1 : 0) : 0)

if confidence < adaptive_confidence_threshold:
  clear all caps owned by category_throttle
        

Strategies

Classic (hysteresis per torrent)

In classic mode, each limited torrent is judged by its download speed:

Adaptive (MUY-based ranking)

Adaptive ranks limited torrents by a hybrid MUY score (upload-yield proxy over time) and caps the lowest-yield group first.

MUY tracking is based on uploaded bytes deltas over time and is aggregated into:

torrent_muy_bps = median( Δuploaded_bytes / Δseconds ) over rolling window (tracked by MuyTracker)
category_muy_bps = median(torrent_muy_bps) within category

hybrid_muy = 0.7*torrent_muy_bps + 0.3*category_muy_bps
rank ascending (lowest yield first)
        

Selection uses two knobs:

capPriorityCount = round(ranked_count * cap_fraction)
floorIdx = floor(ranked_count * floor_percentile)
candidates = ranked.skip(floorIdx)
selected = candidates.take(capPriorityCount)
        

What it caps

Category Throttle caps download on selected limited torrents:

SetTorrentDownloadLimit(selected_hashes, category_throttle.limit_bytes)
        

It records ownership in the registry as source=category_throttle, reason limited.

Category Throttle is not recommended during Racing. Racing wants freedom to focus a winner; Category Throttle wants stability to protect Primary. Different objectives.

Adaptive Controller card (Category throttle UI)

The dashboard “Adaptive Controller” card is a UI for Category Throttle status and evaluation snapshots. It reads the live evaluator endpoint and shows:

The evaluator is read-only; enforcement is controlled by category_throttle.enabled and the health/confidence gates.

Torrent deletion system (rules + gates)

The deletion system exists to reclaim disk and remove low-value torrents. It has two layers:

Rules (high level)

Exact rules depend on your current build; the system commonly includes errored deletion, low-progress with seed abundance, and low-ratio-with-time-under-threshold. Restricted torrents are typically exempt until unrestricted.

Safety gates

Actual disk deletion requires:

delete_files_enabled = 1
delete_files_confirmed = 1
        

Peer blocking (banned IPs)

Peer blocking is a temporary shaping tool. QBMonitor edits qBittorrent’s banned_IPs preference list via app/setPreferences. For safety, it should never ban private/LAN IP ranges.

Persistency across restarts depends on writing to qB preferences, not in-memory state. The intended design is: store “active blocks with TTL” in a DB table, and re-apply at startup and periodically.

qBittorrent client tuning (experimental)

qbmonitor includes an experimental client tuning mode to systematically adjust qB preferences (connections/slots/queue/toggles) and measure impact against baseline.

Workflow: baseline → perturb one setting → settle → measure → pick best → next.

Open the tuning page here: /qb_tuning.html

Troubleshooting & interpretation

“I see caps in qB but not in the UI”

“Controllers are fighting / thrashing caps”

“Why did Ratio Tuning turn itself off?”

Appendix: key settings

This is not an exhaustive list, but it covers the high-leverage controls referenced above.

Racing

racing.enabled racing.poll_ms racing.early_window_seconds racing.max_active racing.down_penalty racing.ramp_down_cap_bps racing.post_down_cap_bps racing.burst_trigger_up_bps racing.burst_seconds racing.burst_cooldown_seconds racing.nonwinner_up_cap_bps racing.oscillate_enabled racing.oscillate_pct racing.oscillate_period_seconds racing.min_up_bps_to_keep racing.dump_ratio racing.dump_uploaded_bytes racing.dump_action

Ratio tuning

ratio_tuning.enabled ratio_tuning.model ratio_tuning.cap_bps ratio_tuning.max_targets ratio_tuning.min_dl_bps ratio_learning.warmup_min_snapshots ratio_learning.warmup_min_confidence ratio_learning.warmup_sustain_minutes ratio_learning.suppress_cooldown_minutes ratio_learning.rollback_min_confidence ratio_learning.rollback_drop_pct ratio_learning.rollback_window_minutes

Category throttle

category_throttle.enabled category_throttle.strategy category_throttle.limit_bytes category_throttle.interval_seconds category_throttle.primary_metric category_throttle.primary_min_bps category_throttle.primary_min_percent category_throttle.primary_health_grace_seconds category_throttle.on_grace_seconds category_throttle.off_grace_seconds category_throttle.on_threshold_bps category_throttle.off_threshold_bps category_throttle.primary_categories_json category_throttle.exempt_categories_json category_throttle.adaptive_confidence_threshold category_throttle.adaptive_cap_fraction category_throttle.adaptive_floor_percentile

Deletion

delete_files_enabled delete_files_confirmed
v0.9.7.06: Audit Log is now persistent (rolling 500) and heatmap/throughput cards support "Since midnight" and "1d" range options.