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.
RestrictionRegistry.
Restricted/Safety always wins; Racing wins during early swarm; Category Throttle and Ratio Tuning are steady-state optimizers.
Contents
- Architecture & dataflow
- Controller ownership & order of operation
- RestrictionRegistry (the truth)
- Audit log semantics
- Settings & safety gates
- Racing subsystem (pump & dump)
- Ratio events (snapshot + classification)
- Ratio tuning engine (per-torrent DL caps)
- Category throttle engine (Primary/Limited/Exempt)
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)
- Restricted / Safety — always wins. Hard overrides, deletion safety logic, peer blocking, manual restrictions.
- Racing — early-swarm optimizer; may apply download caps, burst focus, and dump decisions.
- Category Throttle — steady-state shaping; protects “Primary” by capping Limited torrents’ download.
- Ratio Tuning — steady-state efficiency tuning; caps worst downloaders’ download.
Suppression rules (actual implementation intent)
- If a torrent has an active Racing restriction, other optimizers treat it as owned and avoid applying competing caps.
- If a torrent has an active Category Throttle restriction, Ratio Tuning avoids applying per-torrent caps on that torrent.
- Restricted/Safety is never suppressed.
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
- source:
racing,ratio_tuning,category_throttle,restricted, etc. - reason: short machine label such as
burst_focus,nonwinner_suppressed,limited. - upLimitBps and downLimitBps: desired caps.
0typically means “unlimited” in qB. - updatedUtc: last update timestamp.
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:
RestrictedAsync(...)= “a restriction was applied or reinforced” (usually a cap)UnrestrictedAsync(...)= “a restriction was cleared”SystemAsync(...)= informational / lifecycle events
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
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:
pause: pauses the torrent(s)remove: implemented as delete-from-client and disk (deleteFiles=true)
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
| Setting | Meaning |
|---|---|
| racing.early_window_seconds | How long a torrent is considered “early swarm” and managed aggressively. |
| racing.burst_seconds | How long a burst focus runs when triggered. |
| racing.burst_cooldown_seconds | Minimum 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:
- Sets the winner’s upload cap to unlimited (qB cap = 0)
- Caps all non-winners’ upload to racing.nonwinner_up_cap_bps
- Writes registry ownership entries with reason
burst_focusandnonwinner_suppressed
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:
- Optionally sets racing.post_down_cap_bps on kept torrents.
- If a torrent fails to ramp upload (
up_bps < racing.min_up_bps_to_keep), it is dumped asfailed_to_ramp. - Optional dump triggers:
- racing.dump_uploaded_bytes (uploaded threshold)
- racing.dump_ratio (ratio threshold)
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:
- Global totals:
up_speed,down_speed, active counts - Category breakdown: active/uploading/downloading/stalled + speeds + percent share
- Top UL/DL contributors (top 5)
- Attribution candidates (top 50 by instantaneous traffic) with rich per-torrent fields
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
| Type | Trigger |
|---|---|
ratio_spike | ratio_delta_hr >= SpikeDeltaHr with cooldown |
ratio_drop | ratio_delta_hr <= DropDeltaHr with cooldown |
ratio_gain / ratio_loss | abs(ratio_delta_hr) >= MinAbsDeltaHr with cooldown |
mode_shift_uploading | Crosses above ratio 1.0 + hysteresis band |
mode_shift_downloading | Crosses below ratio 1.0 - hysteresis band |
idle_inefficient | Many active but little uploading and low total upload |
high_efficiency | Few 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:
- Has a valid hash
down_bps >= ratio_tuning.min_dl_bps- Not restricted by another controller (Racing/CategoryThrottle/Restricted). Ratio Tuning ignores its own entries.
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:
| Model | Selection |
|---|---|
classic | lowest efficiency, take max_targets |
weather | lowest efficiency, tie-break by highest down_bps |
bandit | lowest efficiency (reserved for future exploration; currently same shape) |
hybrid | sort 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:
- WarmingUp: gather minimum snapshots + sustained confidence
- Active: normal operation
- Suppressed: health degraded or rollback; enforcement is forced off temporarily
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
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: user-selected multi-select categories
- Exempt: user-selected multi-select categories (never throttled)
- Limited: computed = everything else
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):
- Absolute: Primary speed ≥
primary_min_bps - Relative: Primary share ≥
primary_min_percentof global speed
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:
- Cap if
dlspeed ≥ on_threshold_bpsforon_grace_seconds - Uncap if
dlspeed ≤ off_threshold_bpsforoff_grace_seconds
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:
- category_throttle.adaptive_cap_fraction: fraction of limited torrents to cap
- category_throttle.adaptive_floor_percentile: skip the worst X% (MUY floor) before selecting caps
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.
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:
- Health gate results (absolute/relative + grace)
- Strategy and profile selection
- Confidence and threshold
- Effective cap and MUY median
- Counts: Primary/Exempt/Limited/Restricted
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:
- Selection rules: decide which torrents are “deletion candidates”
- Safety gates: decide whether we actually delete from disk or only preview/log
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”
- Check
Controller Ownership (debug)first. The registry may not reflect caps set outside QBMonitor. - qB uses
0to mean “unlimited” for per-torrent caps; UI should render that as ∞.
“Controllers are fighting / thrashing caps”
- In racing mode, disable steady-state optimizers (Category Throttle) and keep Ratio Tuning observational.
- Use ownership card to see which controller is attempting to enforce.
“Why did Ratio Tuning turn itself off?”
- Rollback logic may have detected sustained efficiency collapse. Check learning status and audit lines.
- Suppression may have triggered due to stale runtime state (“health degraded”).
Appendix: key settings
This is not an exhaustive list, but it covers the high-leverage controls referenced above.