Long-job companion: phone notifications when your training finishes, fails, or silently hangs.
Project description
runmon
Long-job companion — when a training run, crawler, or long script is running on your server, your phone knows the moment it "finished, failed, or silently stalled." Zero-instrumentation, not a line of training code changed.
runmon is the Python CLI (mon) you install on the server that runs your jobs. It does two things: it pushes notifications to your phone when something happens, and — paired with the RunMon app — it streams a live view you can watch and remote-control from your phone.
Install
pip install runmon
Requires Python ≥ 3.10. GPU metrics use NVIDIA NVML; on a machine without a GPU it degrades gracefully (everything else keeps working).
conda users:
monis a system-level tool — no need to install it into every virtual env.pipx install runmoninstalls it once, globally available, no reinstalling when you switch envs.
Two ways to use it
A · Notifications only — no app, no relay
The lightest setup: you just want your phone to buzz when a job finishes, fails, or stalls. No RunMon app needed — notifications arrive in the ntfy / Bark / WeCom / Telegram app you already have.
# 1. Configure a channel (pick one)
mon init --ntfy-topic my-secret-topic-2333 # ntfy: install the ntfy app, subscribe to this topic
mon init --wecom-key <webhook> # WeCom (企业微信) group bot — most reliable in China
mon init --bark-key <key> # iOS Bark
# 2. Wrap your command
mon run -- python train.py
Done — the six event types below get pushed to your phone.
B · Live monitoring on your phone — RunMon app + relay
The full experience: watch the live terminal, resource charts, progress/ETA, and remote-control the job from the RunMon app.
# 1. Pair with the app (one time). Prints a QR code — scan it in the app.
mon pair
# 2. Keep the connection alive. The phone sees live data ONLY while this runs.
# Run it in tmux/nohup so it survives after you disconnect (see "mon daemon" below).
mon daemon
# 3. Run your job (in another shell)
mon run -- python train.py
Now open the app: live terminal, GPU/CPU/memory curves, progress/loss/ETA, and buttons to stop / re-run / pull logs / open a terminal.
First time? Run
mon demoinstead of a real job to test the whole chain end-to-end.
Command reference
| Command | What it does |
|---|---|
mon run -- <cmd> |
Run and monitor a command (the everyday wrapper) |
mon wait |
Camp for free GPUs: phone buzzes when cards free up; with a command, auto-start it (reserved run) |
mon attach |
Take over a job already running in tmux — no restart |
mon daemon |
Keep the live connection to your phone open |
mon pair |
Pair with the RunMon app (prints a QR code) |
mon init |
Configure notification channels |
mon ls |
List jobs (progress / elapsed) |
mon status <job> |
Job details + output tail + ETA/loss |
mon stop <job> |
Stop a job (SIGINT → SIGTERM → SIGKILL) |
mon logs -f <job> |
Follow a job's output live |
mon demo |
Run a fake training job to test the setup |
mon run — wrap and monitor
mon run -- python train.py # simplest — your command after the --
mon run --name exp1 --gpu 0,1 -- python train.py # name the job + bind specific GPUs
Progress, ETA, and loss are parsed automatically from stdout (works with tqdm / Epoch x/y / loss=…) — no instrumentation needed. Try mon demo / mon demo --fail first to see it in action.
mon wait — camp for GPUs & reserve a run
All the cards taken? Let RunMon watch them for you — your phone buzzes the moment they free up; attach a command and it auto-starts once they do:
mon wait --gpus 2 --free-gb 30 -d # phone buzzes when 2 cards each have 30GB free (-d = camp in background)
mon wait --gpus 2 -- python train.py # wait for 2 fully-idle cards, then auto-start with CUDA_VISIBLE_DEVICES set
- Without
--free-gba card must be fully idle (util ≤10%, VRAM ≤5% used); with it, only free VRAM matters (sharing a busy card is fine) - The condition must hold for 3 minutes before firing (tune with
--hold, 0 = immediate) — a brief gap between someone else's jobs won't fool it - The reserved job is a regular
mon run: live view, event notifications, and remote control all apply
mon attach — take over a tmux job
Already have a job running in a tmux session (maybe you started it days ago)? Take it over without restarting:
mon attach # attach to the job in the current tmux pane
From then on it's monitored exactly like a mon run job — events, live view, and remote control all apply.
mon daemon — keep the phone connection alive
mon daemon is the bridge to your phone: it syncs job state to the relay and receives your remote commands. The live view in the app only works while the daemon is running. (Notifications from mon run do not need it — those go out directly.)
By default it runs in the foreground and holds the terminal. To run it in the background so it survives closing your SSH session:
mon daemon -d # detach: runs in the background and frees the terminal
# prints the pid + log path; stop it later with: kill <pid>
Prefer to manage it yourself? Use tmux or nohup instead:
tmux new -s mon; mon daemon # Ctrl+B then D to detach; tmux attach -t mon to return
nohup mon daemon > ~/mon-daemon.log 2>&1 &
mon pair — pair with the app
mon pair # uses the default public relay; prints a QR code
mon pair --relay https://your-relay.com # point at your own self-hosted relay
Scan the printed QR code in the RunMon app to link this server to your phone. One-time per server.
mon logs — follow output
mon logs -f exp1 # tail -f style; works even for backgrounded re-runs
mon ls / mon status / mon stop — manage jobs
mon ls # all jobs, with progress and elapsed time
mon status exp1 # details + output tail + ETA/loss
mon stop exp1 # stop (SIGINT → SIGTERM → SIGKILL)
What lands on your phone (six events)
| Event | Trigger (default, configurable) | Level |
|---|---|---|
| ✅ Done | exit code 0 | info |
| ❌ Failed | non-zero exit code | critical |
| ⚠️ Error output | log shows Traceback / CUDA OOM / Segfault | critical |
| 🧊 GPU stall | process alive but GPU utilization <5% for 10 min | critical |
| 🤫 Log silence | no new output for 30 min | warning |
| 💾 Disk alert | any mount point over 90% used | warning |
The same kind of event notifies at most once per 30 minutes; failed notifications retry with exponential backoff (up to 1 hour) and are persisted locally so nothing is lost.
Notification channels
mon init accepts combinations and can push to several channels at once:
mon init --ntfy-topic TOPIC [--ntfy-server https://your-self-hosted-ntfy]
mon init --bark-key KEY # iOS Bark
mon init --wecom-key WEBHOOK_URL # WeCom (企业微信) group bot — most reliable in China
mon init --telegram BOT_TOKEN:CHAT_ID # Telegram bot
mon init --webhook https://your/hook # generic webhook (Feishu / DingTalk / custom)
Config
Config lives in ~/.config/runmon/config.toml; all thresholds are adjustable:
hang_gpu_minutes = 20 # GPU-stall detection window
silence_minutes = 60 # log-silence alert
disk_threshold_pct = 85
Notes
- GPU sampling relies on NVIDIA NVML; on machines without a GPU it degrades gracefully.
- For the phone app, self-hosting the relay, and the big picture, see the project README.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file runmon-1.0.6.tar.gz.
File metadata
- Download URL: runmon-1.0.6.tar.gz
- Upload date:
- Size: 46.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7f36ba2324bdcca772aa6b6d1c3f07541baa54fde31c54d2bfab2faa49a0a9d7
|
|
| MD5 |
6d1aa15aa6ebfe0f457a1bebcc474321
|
|
| BLAKE2b-256 |
0cf25393e5c938dd147f9ab9a3f058c6370e044cd323fd9823fb0b88f8875706
|
File details
Details for the file runmon-1.0.6-py3-none-any.whl.
File metadata
- Download URL: runmon-1.0.6-py3-none-any.whl
- Upload date:
- Size: 38.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a02e7e44f3b6976b8b5c8b101406f4e740ef4399809be547f03f1efe448eaff1
|
|
| MD5 |
9ccec7a8a78a206d35cf71c2ce22cf46
|
|
| BLAKE2b-256 |
ce91731ca765247a51691b7a60ccec220802c2d61772c80c6cf140ef5656c5f1
|