Перейти к содержимому

Уведомления о завершении длинных задач

«Ушёл на обед, пока обучается модель» — классика. Чтобы не возвращаться через час и не обнаружить, что задача завершилась 50 минут назад (или упала через 30 секунд), просто шлите сообщение в Notifly из самого скрипта или его обёртки.

Запускайте любую длинную команду через эту обёртку — придёт сообщение об успехе/ошибке и времени:

/usr/local/bin/notifly-run
#!/usr/bin/env bash
set -eu
set -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" 5
else
RC=$?
DUR=$(( $(date +%s) - START ))
LAST=$(tail -c 800 "$LOG")
/usr/local/bin/notifly-send \
"❌ ${CMD:0:60} (rc=$RC)" \
"Длительность: ${DUR}s
Конец вывода:
$LAST" 9
fi
rm -f "$LOG"

Использование:

Окно терминала
notifly-run python train.py --epochs 100
notifly-run rsync -aHAX /huge /backup/
notifly-run docker compose pull

Эквивалент notifly-run для Windows. Используйте функцию Send-Notifly из рецепта про напоминания.

C:\scripts\Notifly-Run.ps1
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 -Force
exit $rc
Окно терминала
# Алиас в $PROFILE
Set-Alias nrun C:\scripts\Notifly-Run.ps1
nrun python train.py --epochs 100
nrun docker compose pull
nrun robocopy C:\src D:\backup /MIR

Python-примеры из этой страницы (декоратор, Jupyter-magic) работают на Windows без изменений — установите httpx и пропишите переменные окружения:

Окно терминала
[Environment]::SetEnvironmentVariable("NOTIFLY_URL", "https://your-notifly.example.com", "User")
[Environment]::SetEnvironmentVariable("NOTIFLY_TOKEN", "AGdjfk_L.dKe8q", "User")

Можно встроить уведомление прямо в код:

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()

Иногда удобно сообщать после длинной ячейки. Положите в начало ноутбука:

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"].