Уведомления о завершении длинных задач
«Ушёл на обед, пока обучается модель» — классика. Чтобы не возвращаться через час и не обнаружить, что задача завершилась 50 минут назад (или упала через 30 секунд), просто шлите сообщение в Notifly из самого скрипта или его обёртки.
Universal CLI-обёртка
Заголовок раздела «Universal CLI-обёртка»Запускайте любую длинную команду через эту обёртку — придёт сообщение об успехе/ошибке и времени:
#!/usr/bin/env bashset -euset -a; source ~/.notifly.env; set +a
CMD="$*"START=$(date +%s)HOST=$(hostname -s)LOG=$(mktemp)
if "$@" 2>&1 | tee "$LOG"; then DUR=$(( $(date +%s) - START )) LAST=$(tail -c 800 "$LOG") /usr/local/bin/notifly-send \ "✅ ${CMD:0:60}" \ "Длительность: ${DUR}sХост: $HOST
Конец вывода:$LAST" 5else RC=$? DUR=$(( $(date +%s) - START )) LAST=$(tail -c 800 "$LOG") /usr/local/bin/notifly-send \ "❌ ${CMD:0:60} (rc=$RC)" \ "Длительность: ${DUR}s
Конец вывода:$LAST" 9firm -f "$LOG"Использование:
notifly-run python train.py --epochs 100notifly-run rsync -aHAX /huge /backup/notifly-run docker compose pullWindows: PowerShell Notifly-Run
Заголовок раздела «Windows: PowerShell Notifly-Run»Эквивалент notifly-run для Windows. Используйте функцию Send-Notifly
из рецепта про напоминания.
param([Parameter(Mandatory, ValueFromRemainingArguments)] [string[]]$Cmd). $env:USERPROFILE\Documents\WindowsPowerShell\Notifly.ps1
$start = Get-Date$log = New-TemporaryFile$label = ($Cmd -join ' ').Substring(0, [Math]::Min(60, ($Cmd -join ' ').Length))
& $Cmd[0] $Cmd[1..($Cmd.Count-1)] *> $log$rc = $LASTEXITCODE$dur = ((Get-Date) - $start).TotalSeconds$tail = (Get-Content $log -Tail 20) -join "`n"
if ($rc -eq 0) { Send-Notifly -Title "✅ $label" ` -Message "Длительность: $([int]$dur)s`nХост: $env:COMPUTERNAME`n`nКонец вывода:`n$tail" ` -Priority 5} else { Send-Notifly -Title "❌ $label (rc=$rc)" ` -Message "Длительность: $([int]$dur)s`n`nКонец вывода:`n$tail" ` -Priority 9}Remove-Item $log -Forceexit $rc# Алиас в $PROFILESet-Alias nrun C:\scripts\Notifly-Run.ps1
nrun python train.py --epochs 100nrun docker compose pullnrun robocopy C:\src D:\backup /MIRPython-примеры из этой страницы (декоратор, Jupyter-magic) работают на Windows
без изменений — установите httpx и пропишите переменные окружения:
[Environment]::SetEnvironmentVariable("NOTIFLY_URL", "https://your-notifly.example.com", "User")[Environment]::SetEnvironmentVariable("NOTIFLY_TOKEN", "AGdjfk_L.dKe8q", "User")Python: декоратор для функции
Заголовок раздела «Python: декоратор для функции»Можно встроить уведомление прямо в код:
import os, time, traceback, functools, httpx
def notify_on_finish(name=None, priority=5): def deco(fn): @functools.wraps(fn) def wrap(*args, **kwargs): label = name or fn.__name__ t0 = time.time() try: result = fn(*args, **kwargs) _send(f"✅ {label} OK", f"Длительность: {time.time()-t0:.1f}s", priority) return result except Exception as e: _send( f"❌ {label} FAIL", f"{type(e).__name__}: {e}\n\n{traceback.format_exc()[-1200:]}", max(priority, 9), ) raise return wrap return deco
def _send(title, message, priority): try: httpx.post( f"{os.environ['NOTIFLY_URL']}/message", params={"token": os.environ["NOTIFLY_TOKEN"]}, json={"title": title, "message": message, "priority": priority}, timeout=5, ) except Exception: pass
@notify_on_finish("Обучение CIFAR-10")def train(): for epoch in range(100): ...
train()Jupyter-ноутбук: магия в одну строку
Заголовок раздела «Jupyter-ноутбук: магия в одну строку»Иногда удобно сообщать после длинной ячейки. Положите в начало ноутбука:
def notify(title, message="", priority=5): import os, httpx httpx.post( f"{os.environ['NOTIFLY_URL']}/message", params={"token": os.environ["NOTIFLY_TOKEN"]}, json={"title": title, "message": message, "priority": priority}, timeout=5, )И в любой ячейке после долгой работы:
model.fit(train_ds, epochs=50, validation_data=val_ds)notify("✅ fit() готов", f"val_acc={history.history['val_acc'][-1]:.3f}")ML / эксперименты: важные метрики в сообщении
Заголовок раздела «ML / эксперименты: важные метрики в сообщении»Шлите краткую сводку — её удобно читать с телефона:
notify( title=f"✅ run {run_id} done", message=( f"Model: {model_name}\n" f"Time: {minutes:.1f} min\n" f"Loss: {final_loss:.4f}\n" f"Acc: {final_acc:.4f}\n" f"Best: {best_acc:.4f} @ epoch {best_epoch}" ), priority=4,)Промежуточные «маяки»
Заголовок раздела «Промежуточные «маяки»»Для очень долгих задач полезно слать «маяки» — что задача не зависла:
for epoch in range(100): train_one_epoch() if epoch % 10 == 0 and epoch > 0: notify(f"⏳ {epoch}/100 эпох", f"loss={current_loss:.4f}, eta {eta_min}m", priority=2)- Свобода во времени. Можно уйти, заняться другим делом, не сидеть глядя в терминал.
- Контекст в сообщении. Не надо снова заходить на сервер — главные метрики уже в кармане.
- Никаких пропущенных падений. Скрипт упал → сразу видно
❌.
Что улучшить дальше
Заголовок раздела «Что улучшить дальше»- Слать в сообщение ссылку на «весь лог» (например, на gist/паст в private S3).
- Прикладывать график (как ссылку на изображение) через поле
extras["client::display"].