Compare commits

..
10 Commits
Author SHA1 Message Date
Maksim Totmin dc8d4db358 refactor: remove dead ArchiveCheckConfig and stale archive_task docs
- Remove ArchiveCheckConfig dataclass and archive_check field (never used)
- Remove archive parsing from _dict_to_scenario_config()
- Fix main.py docstring: archive_task never existed in _main_loop()
2026-06-24 13:39:57 +07:00
Maksim Totmin a5e17bcdd8 docs: move build instructions to cli/README.md, keep main README lean 2026-06-24 11:17:41 +07:00
Maksim Totmin 28f5a407c9 feat: add binary build instructions and __main__ guard for pyinstaller
- Add __main__ guard to cli.py for pyinstaller packaging
- Document clean-venv build producing ~11 MB binary
- Add *.spec to .gitignore
- Exclude heavy stdlib modules in build command
2026-06-24 11:14:32 +07:00
Maksim Totmin 9feb14a33a docs: add cli.py and cli/ to project structure 2026-06-24 11:04:41 +07:00
Maksim Totmin c066ae687a feat: separate ses-test CLI install via subdirectory
Add cli/pyproject.toml so analysts can install only ses-test
without the monitoring daemon or Zabbix integration.

pip install "ses-test @ git+https://...#subdirectory=cli"
→ only ses-test binary, no ses-monitor in PATH

Root pyproject.toml unchanged — daemon install still provides both.

docs: rename section to 'Установка ses-test'
2026-06-24 10:50:47 +07:00
Maksim Totmin fbfd073369 feat: add ses-test CLI for interactive YAML scenario testing
- New ses-test command: run a single YAML scenario against real SES
  step-by-step with persistent session tracking
- --verbose shows raw JSON responses, --dry-run validates YAML only
- --session / --robot-id overrides for iterative debugging
- Add verify param to SESClient for self-signed HTTPS support
- Add load_scenario_file() to config.py for single-file loading
- Update README with ses-test usage examples and output samples
2026-06-24 10:37:41 +07:00
Maksim Totmin fd45734a74 fix Zabbix 6.0 template triggers: avg(#3) on scenario.failed, remove duplicate ok trigger
- Change ses.scenario.failed triggers: last() -> avg(#3) for reliable detection
- Remove ses.scenario.ok trigger (duplicates scenario.failed > 0)
- Fix preprocessing: parameters with parameter child elements for Zabbix 6.0
- Remove UUID dashes (32 hex chars) for Zabbix 6.0 compatibility
- Fix template_groups -> groups format for Zabbix 6.0
- Fix strlen() -> length() for Zabbix 6.0 trigger compatibility
- All templates now have 6 triggers each
2026-06-23 19:24:03 +07:00
Maksim Totmin cc53577e31 fix parameters tag: wrap value in <parameter> element for Zabbix 5.0/6.0 compatibility 2026-06-23 18:18:29 +07:00
Maksim Totmin b3b18b019b fix preprocessing step tag for Zabbix 5.0/6.0 compatibility: params -> parameters 2026-06-23 18:17:01 +07:00
Maksim Totmin 11a96222f4 add triggers to all Zabbix templates
- add WARNING trigger on ses.license.days_remaining (< 30 days)
- add CRITICAL trigger on ses.scenario.error (strlen > 0)
- populate all 5 missing triggers in zbx_ses_template_5.0_up.xml

All 4 templates now have 7 triggers each.
2026-06-23 18:13:43 +07:00
13 changed files with 727 additions and 102 deletions
+1
View File
@@ -5,6 +5,7 @@ __pycache__/
*.egg *.egg
dist/ dist/
build/ build/
*.spec
# --- Virtual environment --- # --- Virtual environment ---
.venv/ .venv/
+106 -4
View File
@@ -83,6 +83,7 @@ Zabbix агент по запросу сервера запускает скри
│ ├── scenario_runner.py # Движок выполнения сценариев │ ├── scenario_runner.py # Движок выполнения сценариев
│ ├── log_client.py # Чтение логов SES (задел на будущее) │ ├── log_client.py # Чтение логов SES (задел на будущее)
│ ├── zabbix.py # Интеграция с Zabbix (sender / UserParameter) │ ├── zabbix.py # Интеграция с Zabbix (sender / UserParameter)
│ ├── cli.py # CLI ses-test: тестирование YAML-сценариев
│ └── utils.py # Утилиты (логирование, JSON-пути, проверки) │ └── utils.py # Утилиты (логирование, JSON-пути, проверки)
├── config/ ├── config/
│ ├── config.yaml.example # Шаблон конфигурации (скопировать в config.yaml) │ ├── config.yaml.example # Шаблон конфигурации (скопировать в config.yaml)
@@ -102,6 +103,9 @@ Zabbix агент по запросу сервера запускает скри
├── tests/ ├── tests/
│ ├── __init__.py │ ├── __init__.py
│ └── test_scenario_runner.py # 48 тестов │ └── test_scenario_runner.py # 48 тестов
├── cli/
│ ├── pyproject.toml # Отдельная сборка: только ses-test
│ └── README.md # Инструкции: установка, сборка бинарника
├── pyproject.toml # Зависимости и метаданные пакета ├── pyproject.toml # Зависимости и метаданные пакета
└── README.md └── README.md
``` ```
@@ -199,6 +203,26 @@ journalctl -u ses-monitor -f
3. Нажать Import 3. Нажать Import
4. Привязать шаблон "SES Monitor" к нужному хосту 4. Привязать шаблон "SES Monitor" к нужному хосту
## Установка ses-test
Если нужна только утилита `ses-test` для отладки YAML-сценариев (без демона и Zabbix):
```bash
pip install "ses-test @ git+https://git.totmin.ru/en2zmax/ses-monitor.git#subdirectory=cli"
```
После установки доступна одна команда — `ses-test`:
```bash
ses-test --help
ses-test my_scenario.yaml --ses-url http://ses-host:6190
ses-test my_scenario.yaml --ses-url http://ses-host:6190 --dry-run
```
Подробнее — см. раздел [«Как адаптировать под ваш проект»](#как-адаптировать-под-ваш-проект).
Сборка в исполняемый файл — см. [cli/README.md](cli/README.md).
## Формат сценариев ## Формат сценариев
Сценарии описываются в YAML-файлах в каталоге `config/scenarios/`. Это гибкий DSL, который не зависит от конкретного проекта SES. Сценарии описываются в YAML-файлах в каталоге `config/scenarios/`. Это гибкий DSL, который не зависит от конкретного проекта SES.
@@ -286,15 +310,93 @@ path: "request.data[0].reply.confidence"
3. **Создайте файл сценария**, описав шаги диалога: 3. **Создайте файл сценария**, описав шаги диалога:
```bash ```bash
cp config/scenarios/example_liveness.yaml config/scenarios/$(hostname).yaml cp config/scenarios/example_dialog.yaml config/scenarios/my_project.yaml
vim config/scenarios/$(hostname).yaml vim config/scenarios/my_project.yaml
``` ```
4. **Протестируйте вручную** шаги через curl: 4. **Протестируйте сценарий** утилитой `ses-test`:
```bash ```bash
curl -X POST "http://<ses-host>:6190/ses/ask/<robot_id>?text=Привет&channel=monitoring" # Базовый прогон
ses-test config/scenarios/my_project.yaml --ses-url http://ses-host:6190
# С детальным выводом JSON-ответов (режим отладки)
ses-test config/scenarios/my_project.yaml --ses-url http://ses-host:6190 -v
# Только валидация YAML без запросов к SES
ses-test config/scenarios/my_project.yaml --ses-url http://ses-host:6190 --dry-run
# С переопределением robot_id (если не указан в YAML)
ses-test config/scenarios/my_project.yaml --ses-url http://ses-host:6190 --robot-id abc-123
# С фиксированной сессией для повторных прогонов
ses-test config/scenarios/my_project.yaml --ses-url http://ses-host:6190 --session debug-1
``` ```
**Примеры вывода:**
Успешный прогон (без `-v`):
```
=== Сценарий: 'test: МФЦ диалог' ===
SES: http://ses-host:6190
Robot ID: abc-123-def-456
Канал: monitoring
Шагов: 2
Сессия: monitoring_test_1782271424
── Шаг 1/2 ────────────────────────────────────────
→ Привет
← Время ответа: 176ms
[PASS] path=error equals=0
[PASS] path=answer[0].messages[0] not_empty
── Шаг 2/2 ────────────────────────────────────────
→ Записаться на приём
← Время ответа: 382ms
[PASS] path=error equals=0
[PASS] path=answer[0].messages[0] not_empty
──────────────────────────────────────────────────
РЕЗУЛЬТАТ: УСПЕШНО (2/2 шагов)
```
Проваленный шаг:
```
── Шаг 1/1 ────────────────────────────────────────
→ Привет
← Время ответа: 177ms
[PASS] path=error equals=0
[FAIL] Поле 'answer[0].messages[0]' = 'пожалуйста выберите...'
не содержит 'несуществующая_фраза_xyz_999'
──────────────────────────────────────────────────
РЕЗУЛЬТАТ: ПРОВАЛЕН (шаг 1)
```
Режим отладки (`-v`):
```
── Шаг 1/2 ────────────────────────────────────────
→ Привет
← Время ответа: 176ms
← RAW JSON:
{
"error": 0,
"question": "Привет",
"answer": [{
"messages": [
"Пожалуйста выберите действие или задайте вопрос.\n\n{button:...}\n"
],
"voice": "Коля",
"interruptible": false
}],
"session": "monitoring_test_1782271424",
"uuid": "132e530c-..."
}
[PASS] path=error equals=0
[PASS] path=answer[0].messages[0] not_empty
```
**Exit code:** `0` — все шаги пройдены, `1` — есть ошибки, `2` — ошибка аргументов.
5. **Запустите демон** — метрики агрегируются автоматически (шаблон Zabbix универсален) 5. **Запустите демон** — метрики агрегируются автоматически (шаблон Zabbix универсален)
## Расширение системы ## Расширение системы
+51
View File
@@ -0,0 +1,51 @@
# ses-test
CLI-утилита для пошагового тестирования YAML-сценариев SES.
## Установка
```bash
pip install "ses-test @ git+https://git.totmin.ru/en2zmax/ses-monitor.git#subdirectory=cli"
```
## Сборка исполняемого файла
Можно собрать `ses-test` в один бинарник (~11 MB), не требующий Python на целевой машине.
Сборка производится в чистом виртуальном окружении — чтобы в бинарник не попали лишние системные пакеты (numpy, pillow и др.):
```bash
# 1. Клонируем репозиторий
git clone https://git.totmin.ru/en2zmax/ses-monitor.git
cd ses-monitor
# 2. Чистое окружение
python -m venv /tmp/build
/tmp/build/bin/pip install ./cli pyinstaller
# 3. Сборка
/tmp/build/bin/pyinstaller --onefile --name ses-test \
--paths src/ \
--hidden-import ses_monitor.utils \
--strip \
--exclude-module tkinter \
--exclude-module turtle \
--exclude-module sqlite3 \
--exclude-module curses \
--exclude-module idlelib \
--exclude-module pydoc \
--exclude-module venv \
--exclude-module ensurepip \
--exclude-module unittest \
--exclude-module test \
--exclude-module multiprocessing \
src/ses_monitor/cli.py
```
Готовый бинарник: `dist/ses-test` (~11 MB, Linux) или `dist/ses-test.exe` (Windows — запустить те же команды на Windows).
Для сборки под другую ОС нужна эта ОС — PyInstaller не кросс-компилирует.
## Справка
Основная документация: [README.md](../README.md)
+34
View File
@@ -0,0 +1,34 @@
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "ses-test"
version = "1.0.0"
description = "CLI для пошагового тестирования YAML-сценариев SES"
requires-python = ">=3.9"
license = {text = "MIT"}
authors = [
{name = "SES Monitoring Team"}
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Topic :: Software Development :: Testing",
]
dependencies = [
"httpx>=0.21",
"pyyaml>=5.4",
]
[project.scripts]
ses-test = "ses_monitor.cli:main"
[tool.setuptools.packages.find]
where = ["../src"]
include = ["ses_monitor*"]
+1
View File
@@ -36,6 +36,7 @@ dev = [
[project.scripts] [project.scripts]
ses-monitor = "ses_monitor.main:main" ses-monitor = "ses_monitor.main:main"
ses-test = "ses_monitor.cli:main"
[build-system] [build-system]
requires = ["setuptools>=61.0"] requires = ["setuptools>=61.0"]
+303
View File
@@ -0,0 +1,303 @@
"""
CLI-утилита для интерактивного тестирования YAML-сценариев SES.
Позволяет аналитикам прогонять сценарий пошагово против реального SES-сервера
и видеть результат каждой проверки expect — без запуска всего демона ses-monitor.
Usage:
ses-test scenario.yaml --ses-url http://ses-host:6190
ses-test scenario.yaml --ses-url http://ses-host:6190 --verbose
ses-test scenario.yaml --ses-url http://ses-host:6190 --dry-run
"""
from __future__ import annotations
import argparse
import asyncio
import json
import sys
import time
from typing import Any, Dict, List
from ses_monitor.config import SESConfig, load_scenario_file
from ses_monitor.session_client import SESClient
from ses_monitor.utils import timer
def _format_expectation_results(
expectations: List[Dict[str, Any]],
response: Dict[str, Any],
) -> List[str]:
"""Форматирует результаты проверок ожиданий для одного шага.
Возвращает список строк [PASS]/[FAIL] для каждого expect-условия.
"""
results: List[str] = []
for exp in expectations:
path = exp.get("path", "?")
for condition, expected in exp.items():
if condition == "path":
continue
# check_expectation импортируется динамически для компактности —
# используем check_all_expectations с одним expectation
from ses_monitor.utils import check_expectation
ok, msg = check_expectation(response, path, condition, expected)
if ok:
if condition in ("exists", "not_empty"):
results.append(f" [PASS] path={path} {condition}")
else:
results.append(f" [PASS] path={path} {condition}={expected!r}")
else:
results.append(f" [FAIL] {msg}")
return results
def _truncate(text: str, max_len: int = 120) -> str:
"""Обрезает длинную строку для вывода."""
if len(text) <= max_len:
return text
return text[:max_len] + ""
def parse_args(argv: List[str] | None = None) -> argparse.Namespace:
"""Разбирает аргументы командной строки."""
parser = argparse.ArgumentParser(
prog="ses-test",
description="Пошаговое тестирование YAML-сценария SES",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""\
Примеры:
ses-test my_scenario.yaml --ses-url http://ses:6190
ses-test my_scenario.yaml --ses-url http://ses:6190 -v
ses-test my_scenario.yaml --ses-url http://ses:6190 --robot-id abc-123
ses-test my_scenario.yaml --ses-url http://ses:6190 --dry-run
ses-test my_scenario.yaml --ses-url https://ses:443 --no-verify
ses-test my_scenario.yaml --ses-url http://ses:6190 --session debug-1 -v
""",
)
parser.add_argument(
"scenario",
help="Путь к YAML-файлу сценария",
)
parser.add_argument(
"--ses-url",
required=True,
metavar="URL",
help="URL SES API (например, http://ses-host:6190)",
)
parser.add_argument(
"--robot-id",
metavar="ID",
help="Переопределить robot_id из YAML",
)
parser.add_argument(
"--channel",
metavar="CH",
default="monitoring",
help="Канал диалога (по умолчанию: monitoring)",
)
parser.add_argument(
"--timeout",
type=int,
metavar="SEC",
help="Таймаут HTTP-запроса, секунды (по умолчанию: из сценария или 30)",
)
parser.add_argument(
"--session",
metavar="ID",
help="ID сессии для повторных прогонов (по умолчанию: авто-генерация)",
)
parser.add_argument(
"--restart-closed",
action="store_true",
help="Переоткрывать закрытую сессию (restartClosed=1)",
)
parser.add_argument(
"--no-verify",
action="store_true",
help="Не проверять SSL-сертификат (для self-signed HTTPS)",
)
parser.add_argument(
"--verbose", "-v",
action="store_true",
help="Выводить полный JSON-ответ SES для каждого шага",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Только валидация YAML, без запросов к SES",
)
return parser.parse_args(argv)
def main(argv: List[str] | None = None) -> int:
"""Точка входа CLI-утилиты ses-test.
Returns:
0 — все шаги пройдены.
1 — сценарий провален или ошибка выполнения.
2 — ошибка аргументов или конфигурации.
"""
args = parse_args(argv)
# Принудительный UTF-8 для Windows-консоли
if sys.platform == "win32":
sys.stdout.reconfigure(encoding="utf-8")
# Загрузка сценария
try:
scenario = load_scenario_file(args.scenario)
except FileNotFoundError as e:
print(f"ОШИБКА: {e}", file=sys.stderr)
return 2
except RuntimeError as e:
print(f"ОШИБКА: {e}", file=sys.stderr)
return 2
# Применяем CLI-переопределения поверх YAML
if args.robot_id:
scenario.robot_id = args.robot_id
if args.channel:
scenario.channel = args.channel
if args.timeout:
scenario.timeout = args.timeout
if args.restart_closed:
scenario.restart_closed = True
# Проверка обязательных полей
if not scenario.robot_id:
print(
"ОШИБКА: не указан robot_id.\n"
" Добавьте 'robot_id: \"uuid-...\"' в YAML-сценарий\n"
" или передайте через --robot-id.\n"
" Узнать ID: GET http://<ses-host>:6190/ses/robot/listall",
file=sys.stderr,
)
return 2
if not scenario.steps:
print(
f"ПРЕДУПРЕЖДЕНИЕ: сценарий '{scenario.name}' не содержит шагов.",
file=sys.stderr,
)
# Dry-run: только валидация, без запросов
if args.dry_run:
total_expects = sum(len(s.expect) for s in scenario.steps)
print(f"Сценарий '{scenario.name}' валиден.")
print(f" Шагов: {len(scenario.steps)}")
print(f" Ожиданий: {total_expects}")
print(f" Robot ID: {scenario.robot_id}")
print(f" Канал: {scenario.channel or 'monitoring'}")
print(" Режим: dry-run (запросы к SES не выполнялись)")
return 0
# Полноценный прогон
try:
return asyncio.run(_async_main(args, scenario))
except KeyboardInterrupt:
print("\nПрервано пользователем.", file=sys.stderr)
return 1
async def _async_main(args: argparse.Namespace, scenario) -> int:
"""Асинхронное ядро: создаёт клиент, прогоняет шаги, выводит результат."""
ses_config = SESConfig(
base_url=args.ses_url,
channel=scenario.channel or args.channel,
request_timeout=scenario.timeout,
)
client = SESClient(ses_config, verify=not args.no_verify)
session_id: str = args.session or f"monitoring_test_{int(time.time())}"
try:
# -------------------------------
# Шапка
print()
print(f"=== Сценарий: '{scenario.name}' ===")
print(f"SES: {args.ses_url}")
print(f"Robot ID: {scenario.robot_id}")
print(f"Канал: {scenario.channel or 'monitoring'}")
print(f"Шагов: {len(scenario.steps)}")
print(f"Сессия: {session_id}")
if args.no_verify:
print("SSL: проверка ОТКЛЮЧЕНА")
print()
# -------------------------------
# Пошаговое выполнение
all_passed = True
failed_step = 0
for i, step in enumerate(scenario.steps):
step_num = i + 1
total = len(scenario.steps)
print(f"── Шаг {step_num}/{total} {''*40}")
print(f"{step.send}")
with timer() as t:
response = await client.ask(
text=step.send,
session_id=session_id,
robot_id=scenario.robot_id,
channel=scenario.channel,
restart_closed=scenario.restart_closed,
)
response_time_ms = t.get("elapsed", 0) * 1000
print(f"← Время ответа: {response_time_ms:.0f}ms")
if args.verbose:
raw_json = json.dumps(response.raw, ensure_ascii=False, indent=2)
print("← RAW JSON:")
print(raw_json)
print()
if response.error != 0:
print(f" [FAIL] SES API error={response.error}")
all_passed = False
failed_step = step_num
break
if not step.expect:
print(" [PASS] OK (без проверок)\n")
continue
# Проверяем ожидания
results = _format_expectation_results(step.expect, response.raw)
step_passed = True
for line in results:
print(line)
if "[FAIL]" in line:
step_passed = False
print()
if not step_passed:
all_passed = False
failed_step = step_num
break
# -------------------------------
# Итог
print(f"{''*50}")
if all_passed:
print(
f"РЕЗУЛЬТАТ: УСПЕШНО "
f"({len(scenario.steps)}/{len(scenario.steps)} шагов)"
)
return 0
else:
print(f"РЕЗУЛЬТАТ: ПРОВАЛЕН (шаг {failed_step})")
return 1
finally:
await client.close()
if __name__ == "__main__":
sys.exit(main())
+33 -20
View File
@@ -103,15 +103,6 @@ class ScenarioStep:
# contains/equals/not_contains/not_empty/regex: условие # contains/equals/not_contains/not_empty/regex: условие
@dataclass
class ArchiveCheckConfig:
"""Настройки проверки архива для сценария."""
enabled: bool = False
lookback_minutes: int = 10
expect: List[Dict[str, Any]] = field(default_factory=list)
@dataclass @dataclass
class ScenarioConfig: class ScenarioConfig:
"""Конфигурация одного сценария мониторинга.""" """Конфигурация одного сценария мониторинга."""
@@ -125,7 +116,6 @@ class ScenarioConfig:
channel: Optional[str] = None channel: Optional[str] = None
restart_closed: bool = False restart_closed: bool = False
steps: List[ScenarioStep] = field(default_factory=list) steps: List[ScenarioStep] = field(default_factory=list)
archive_check: Optional[ArchiveCheckConfig] = None
def _dict_to_scenario_step(data: Dict[str, Any]) -> ScenarioStep: def _dict_to_scenario_step(data: Dict[str, Any]) -> ScenarioStep:
@@ -140,15 +130,6 @@ def _dict_to_scenario_config(data: Dict[str, Any]) -> ScenarioConfig:
"""Преобразует словарь из YAML в ScenarioConfig.""" """Преобразует словарь из YAML в ScenarioConfig."""
steps = [_dict_to_scenario_step(s) for s in data.get("steps", [])] steps = [_dict_to_scenario_step(s) for s in data.get("steps", [])]
archive_data = data.get("archive_check")
archive_check = None
if archive_data:
archive_check = ArchiveCheckConfig(
enabled=archive_data.get("enabled", False),
lookback_minutes=archive_data.get("lookback_minutes", 10),
expect=archive_data.get("expect", []),
)
return ScenarioConfig( return ScenarioConfig(
name=data.get("name", ""), name=data.get("name", ""),
description=data.get("description", ""), description=data.get("description", ""),
@@ -159,7 +140,6 @@ def _dict_to_scenario_config(data: Dict[str, Any]) -> ScenarioConfig:
channel=data.get("channel"), channel=data.get("channel"),
restart_closed=data.get("restart_closed", False), restart_closed=data.get("restart_closed", False),
steps=steps, steps=steps,
archive_check=archive_check,
) )
@@ -297,3 +277,36 @@ def load_scenarios(scenarios_dir: str) -> List[ScenarioConfig]:
raise RuntimeError(f"Ошибка чтения сценария {yml_file}: {e}") from e raise RuntimeError(f"Ошибка чтения сценария {yml_file}: {e}") from e
return scenarios return scenarios
def load_scenario_file(path: str) -> ScenarioConfig:
"""Загружает один YAML-файл сценария.
Предназначена для CLI-утилиты ses-test. В отличие от load_scenarios(),
не фильтрует по флагу enabled — сценарий загружается всегда.
Args:
path: Путь к YAML-файлу сценария.
Returns:
ScenarioConfig с заполненными полями.
Raises:
FileNotFoundError: Файл не найден.
yaml.YAMLError: Ошибка парсинга YAML.
RuntimeError: Ошибка структуры сценария.
"""
file_path = Path(path)
if not file_path.exists():
raise FileNotFoundError(f"Файл сценария не найден: {path}")
with open(file_path, "r", encoding="utf-8") as f:
data = yaml.safe_load(f)
if data is None:
raise RuntimeError(f"Файл сценария пуст: {path}")
try:
return _dict_to_scenario_config(data)
except (yaml.YAMLError, KeyError) as e:
raise RuntimeError(f"Ошибка чтения сценария {path}: {e}") from e
+1 -2
View File
@@ -2,10 +2,9 @@
Главный цикл watchdog-демона для мониторинга SES. Главный цикл watchdog-демона для мониторинга SES.
Архитектура: Архитектура:
- Бесконечный asyncio-цикл с тремя параллельными задачами: - Бесконечный asyncio-цикл с двумя параллельными задачами:
1. liveness_task: быстрые проверки каждые 30 секунд. 1. liveness_task: быстрые проверки каждые 30 секунд.
2. scenarios_task: запуск сценариев по их индивидуальному расписанию. 2. scenarios_task: запуск сценариев по их индивидуальному расписанию.
3. archive_task: анализ архива каждые 10 минут.
- Hot-reload сценариев из каталога каждые 60 секунд. - Hot-reload сценариев из каталога каждые 60 секунд.
- Все метрики отправляются в Zabbix единым батчем после проверок. - Все метрики отправляются в Zabbix единым батчем после проверок.
- Graceful shutdown по SIGTERM/SIGINT. - Graceful shutdown по SIGTERM/SIGINT.
+3 -1
View File
@@ -135,11 +135,12 @@ class SESClient:
Все методы асинхронные для работы в asyncio-цикле. Все методы асинхронные для работы в asyncio-цикле.
""" """
def __init__(self, config: SESConfig): def __init__(self, config: SESConfig, verify: bool = True):
self._base_url = config.base_url.rstrip("/") self._base_url = config.base_url.rstrip("/")
self._channel = config.channel self._channel = config.channel
self._timeout = config.request_timeout self._timeout = config.request_timeout
self._robot_list_endpoint = config.robot_list_endpoint self._robot_list_endpoint = config.robot_list_endpoint
self._verify = verify
self._client: Optional[httpx.AsyncClient] = None self._client: Optional[httpx.AsyncClient] = None
@@ -149,6 +150,7 @@ class SESClient:
self._client = httpx.AsyncClient( self._client = httpx.AsyncClient(
timeout=httpx.Timeout(self._timeout), timeout=httpx.Timeout(self._timeout),
limits=httpx.Limits(max_keepalive_connections=5), limits=httpx.Limits(max_keepalive_connections=5),
verify=self._verify,
) )
return self._client return self._client
+20 -10
View File
@@ -112,6 +112,15 @@
<value_type>FLOAT</value_type> <value_type>FLOAT</value_type>
<units>d</units> <units>d</units>
<description>Количество дней до окончания лицензии SES. -1 = бессрочная.</description> <description>Количество дней до окончания лицензии SES. -1 = бессрочная.</description>
<triggers>
<trigger>
<uuid>82479a2d-72d3-4935-905e-5aa5ac2c24e3</uuid>
<expression>last(/SES Monitor/ses.license.days_remaining)&gt;0 and last(/SES Monitor/ses.license.days_remaining)&lt;30</expression>
<name>SES: лицензия истекает через (&lt;30 дней) на {HOST.NAME}</name>
<priority>WARNING</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item> </item>
<item> <item>
@@ -137,15 +146,7 @@
<trends>30d</trends> <trends>30d</trends>
<value_type>UNSIGNED</value_type> <value_type>UNSIGNED</value_type>
<description>1 — все сценарии прошли, 0 — есть упавшие.</description> <description>1 — все сценарии прошли, 0 — есть упавшие.</description>
<triggers>
<trigger>
<uuid>d2699acb49dd4af3af88fbcb96c6bf23</uuid>
<expression>last(/SES Monitor/ses.scenario.ok)=0</expression>
<name>SES: тестовые сценарии мониторинга падают на {HOST.NAME}</name>
<priority>AVERAGE</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item> </item>
<item> <item>
@@ -173,7 +174,7 @@
<triggers> <triggers>
<trigger> <trigger>
<uuid>b5a9deae3de74d68b3a92617541bf0c8</uuid> <uuid>b5a9deae3de74d68b3a92617541bf0c8</uuid>
<expression>last(/SES Monitor/ses.scenario.failed)&gt;0</expression> <expression>avg(/SES Monitor/ses.scenario.failed,#3)&gt;0</expression>
<name>SES: тестовые сценарии мониторинга падают на {HOST.NAME}</name> <name>SES: тестовые сценарии мониторинга падают на {HOST.NAME}</name>
<priority>WARNING</priority> <priority>WARNING</priority>
<manual_close>YES</manual_close> <manual_close>YES</manual_close>
@@ -217,6 +218,15 @@
<trends>0</trends> <trends>0</trends>
<value_type>TEXT</value_type> <value_type>TEXT</value_type>
<description>Текст ошибки: "N сценариев упали". Пустая строка если всё хорошо.</description> <description>Текст ошибки: "N сценариев упали". Пустая строка если всё хорошо.</description>
<triggers>
<trigger>
<uuid>71bad15f-2b6e-4130-8567-4fd88f419bb5</uuid>
<expression>last(/SES Monitor/ses.scenario.error) &lt;&gt; ''</expression>
<name>SES: критическая ошибка выполнения сценариев на {HOST.NAME}</name>
<priority>DISASTER</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item> </item>
</items> </items>
+18 -9
View File
@@ -108,6 +108,14 @@
<value_type>FLOAT</value_type> <value_type>FLOAT</value_type>
<units>d</units> <units>d</units>
<description>Количество дней до окончания лицензии SES. -1 = бессрочная.</description> <description>Количество дней до окончания лицензии SES. -1 = бессрочная.</description>
<triggers>
<trigger>
<expression>{SES Monitor:ses.license.days_remaining.last()}&gt;0 and {SES Monitor:ses.license.days_remaining.last()}&lt;30</expression>
<name>SES: лицензия истекает через (&lt;30 дней) на {HOST.NAME}</name>
<priority>WARNING</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item> </item>
<item> <item>
@@ -133,14 +141,7 @@
<trends>31536000</trends> <trends>31536000</trends>
<value_type>UNSIGNED</value_type> <value_type>UNSIGNED</value_type>
<description>1 — все сценарии прошли, 0 — есть упавшие.</description> <description>1 — все сценарии прошли, 0 — есть упавшие.</description>
<triggers>
<trigger>
<expression>{SES Monitor:ses.scenario.ok.last()}=0</expression>
<name>SES: тестовые сценарии мониторинга падают на {HOST.NAME}</name>
<priority>AVERAGE</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item> </item>
<item> <item>
@@ -165,7 +166,7 @@
<description>Количество упавших сценариев за последний цикл.</description> <description>Количество упавших сценариев за последний цикл.</description>
<triggers> <triggers>
<trigger> <trigger>
<expression>{SES Monitor:ses.scenario.failed.last()}&gt;0</expression> <expression>{SES Monitor:ses.scenario.failed.avg(#3)}&gt;0</expression>
<name>SES: тестовые сценарии мониторинга падают на {HOST.NAME}</name> <name>SES: тестовые сценарии мониторинга падают на {HOST.NAME}</name>
<priority>WARNING</priority> <priority>WARNING</priority>
<manual_close>YES</manual_close> <manual_close>YES</manual_close>
@@ -206,6 +207,14 @@
<trends>31536000</trends> <trends>31536000</trends>
<value_type>TEXT</value_type> <value_type>TEXT</value_type>
<description>Текст ошибки: "N сценариев упали". Пустая строка если всё хорошо.</description> <description>Текст ошибки: "N сценариев упали". Пустая строка если всё хорошо.</description>
<triggers>
<trigger>
<expression>{SES Monitor:ses.scenario.error.last()} &lt;&gt; ''</expression>
<name>SES: критическая ошибка выполнения сценариев на {HOST.NAME}</name>
<priority>5</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item> </item>
</items> </items>
+82 -12
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<zabbix_export> <zabbix_export>
<version>5.0</version> <version>6.0</version>
<date>2026-06-19T12:00:00Z</date> <date>2026-06-19T12:00:00Z</date>
<groups> <groups>
<group> <group>
@@ -60,13 +60,23 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.api.liveness"]</params> <parameters>
<parameter>$["ses.api.liveness"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
<trends>31536000</trends> <trends>31536000</trends>
<value_type>UNSIGNED</value_type> <value_type>UNSIGNED</value_type>
<key>ses.api.liveness</key> <key>ses.api.liveness</key>
<triggers>
<trigger>
<expression>{SES Monitor:ses.api.liveness.last()}=0</expression>
<name>SES API недоступен на {HOST.NAME}</name>
<priority>HIGH</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item> </item>
<item> <item>
@@ -78,7 +88,9 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.api.response_time"]</params> <parameters>
<parameter>$["ses.api.response_time"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
@@ -86,6 +98,14 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<value_type>FLOAT</value_type> <value_type>FLOAT</value_type>
<units>ms</units> <units>ms</units>
<key>ses.api.response_time</key> <key>ses.api.response_time</key>
<triggers>
<trigger>
<expression>{SES Monitor:ses.api.response_time.last()}&gt;{$SES_API_TIMEOUT_MS}</expression>
<name>SES: медленный ответ API (&gt;{$SES_API_TIMEOUT_MS} мс)</name>
<priority>WARNING</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item> </item>
<item> <item>
@@ -97,13 +117,23 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.license.ok"]</params> <parameters>
<parameter>$["ses.license.ok"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
<trends>31536000</trends> <trends>31536000</trends>
<value_type>UNSIGNED</value_type> <value_type>UNSIGNED</value_type>
<key>ses.license.ok</key> <key>ses.license.ok</key>
<triggers>
<trigger>
<expression>{SES Monitor:ses.license.ok.last()}=0</expression>
<name>SES: проблема с лицензией на {HOST.NAME}</name>
<priority>HIGH</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item> </item>
<item> <item>
@@ -115,7 +145,9 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.license.days_remaining"]</params> <parameters>
<parameter>$["ses.license.days_remaining"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
@@ -123,6 +155,14 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<value_type>FLOAT</value_type> <value_type>FLOAT</value_type>
<units>d</units> <units>d</units>
<key>ses.license.days_remaining</key> <key>ses.license.days_remaining</key>
<triggers>
<trigger>
<expression>{SES Monitor:ses.license.days_remaining.last()}&gt;0 and {SES Monitor:ses.license.days_remaining.last()}&lt;30</expression>
<name>SES: лицензия истекает через (&lt;30 дней) на {HOST.NAME}</name>
<priority>WARNING</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item> </item>
<item> <item>
@@ -134,7 +174,9 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.robots.count"]</params> <parameters>
<parameter>$["ses.robots.count"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
@@ -154,13 +196,16 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.scenario.ok"]</params> <parameters>
<parameter>$["ses.scenario.ok"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
<trends>31536000</trends> <trends>31536000</trends>
<value_type>UNSIGNED</value_type> <value_type>UNSIGNED</value_type>
<key>ses.scenario.ok</key> <key>ses.scenario.ok</key>
</item> </item>
<item> <item>
@@ -172,7 +217,9 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.scenario.total"]</params> <parameters>
<parameter>$["ses.scenario.total"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
@@ -190,13 +237,29 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.scenario.failed"]</params> <parameters>
<parameter>$["ses.scenario.failed"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
<trends>31536000</trends> <trends>31536000</trends>
<value_type>UNSIGNED</value_type> <value_type>UNSIGNED</value_type>
<key>ses.scenario.failed</key> <key>ses.scenario.failed</key>
<triggers>
<trigger>
<expression>{SES Monitor:ses.scenario.failed.avg(#3)}&gt;0</expression>
<name>SES: тестовые сценарии мониторинга падают на {HOST.NAME}</name>
<priority>WARNING</priority>
<manual_close>YES</manual_close>
</trigger>
<trigger>
<expression>{SES Monitor:ses.scenario.failed.avg(#3)}&gt;0</expression>
<name>SES: критическая ошибка выполнения сценариев на {HOST.NAME}</name>
<priority>DISASTER</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item> </item>
<item> <item>
@@ -208,7 +271,9 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.scenario.avg_time"]</params> <parameters>
<parameter>$["ses.scenario.avg_time"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
@@ -227,7 +292,9 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.scenario.max_time"]</params> <parameters>
<parameter>$["ses.scenario.max_time"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
@@ -246,13 +313,16 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.scenario.error"]</params> <parameters>
<parameter>$["ses.scenario.error"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
<trends>31536000</trends> <trends>31536000</trends>
<value_type>TEXT</value_type> <value_type>TEXT</value_type>
<key>ses.scenario.error</key> <key>ses.scenario.error</key>
</item> </item>
</items> </items>
+74 -44
View File
@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<zabbix_export> <zabbix_export>
<version>6.4</version> <version>6.0</version>
<template_groups> <groups>
<template_group> <group>
<uuid>88166f35-c76b-44e2-b8ad-333b3b4b9692</uuid> <uuid>88166f35c76b44e2b8ad333b3b4b9692</uuid>
<name>Templates</name> <name>Templates</name>
</template_group> </group>
</template_groups> </groups>
<templates> <templates>
<template> <template>
<uuid>f4e7d78a-8163-46db-a7cf-e9a79df9a740</uuid> <uuid>f4e7d78a816346dba7cfe9a79df9a740</uuid>
<template>SES Monitor</template> <template>SES Monitor</template>
<name>SES Monitor — Service Engine Server</name> <name>SES Monitor — Service Engine Server</name>
<description>Шаблон для мониторинга диалоговой сценарной машины SES. <description>Шаблон для мониторинга диалоговой сценарной машины SES.
@@ -40,7 +40,7 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<items> <items>
<item> <item>
<uuid>b5d05b4d-7b9d-4e63-98de-7fe507748eaa</uuid> <uuid>b5d05b4d7b9d4e6398de7fe507748eaa</uuid>
<name>SES: все метрики (JSON)</name> <name>SES: все метрики (JSON)</name>
<type>ZABBIX_PASSIVE</type> <type>ZABBIX_PASSIVE</type>
<key>ses.metrics</key> <key>ses.metrics</key>
@@ -54,7 +54,7 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<!-- Liveness --> <!-- Liveness -->
<item> <item>
<uuid>b6a64924-6ecf-406a-80e1-cb5ad8f4d9e4</uuid> <uuid>b6a649246ecf406a80e1cb5ad8f4d9e4</uuid>
<name>SES: API доступность</name> <name>SES: API доступность</name>
<type>DEPENDENT</type> <type>DEPENDENT</type>
<key>ses.api.liveness</key> <key>ses.api.liveness</key>
@@ -64,7 +64,9 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.api.liveness"]</params> <parameters>
<parameter>$["ses.api.liveness"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
@@ -73,7 +75,7 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<description>0 — API недоступен, 1 — работает. Проверяется GET /ses/hostname.</description> <description>0 — API недоступен, 1 — работает. Проверяется GET /ses/hostname.</description>
<triggers> <triggers>
<trigger> <trigger>
<uuid>b5798e51-dc19-4d90-aa68-45107a7945e5</uuid> <uuid>b5798e51dc194d90aa6845107a7945e5</uuid>
<expression>last(/SES Monitor/ses.api.liveness)=0</expression> <expression>last(/SES Monitor/ses.api.liveness)=0</expression>
<name>SES API недоступен на {HOST.NAME}</name> <name>SES API недоступен на {HOST.NAME}</name>
<priority>HIGH</priority> <priority>HIGH</priority>
@@ -83,7 +85,7 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
</item> </item>
<item> <item>
<uuid>ce800e89-14a8-4084-bd16-0abd594b8ef4</uuid> <uuid>ce800e8914a84084bd160abd594b8ef4</uuid>
<name>SES: время ответа API</name> <name>SES: время ответа API</name>
<type>DEPENDENT</type> <type>DEPENDENT</type>
<key>ses.api.response_time</key> <key>ses.api.response_time</key>
@@ -93,7 +95,9 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.api.response_time"]</params> <parameters>
<parameter>$["ses.api.response_time"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
@@ -103,7 +107,7 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<description>Время ответа GET /ses/hostname в миллисекундах.</description> <description>Время ответа GET /ses/hostname в миллисекундах.</description>
<triggers> <triggers>
<trigger> <trigger>
<uuid>6813663c-2e56-4b42-aecd-3dfd54d99ffb</uuid> <uuid>6813663c2e564b42aecd3dfd54d99ffb</uuid>
<expression>last(/SES Monitor/ses.api.response_time)&gt;{$SES_API_TIMEOUT_MS}</expression> <expression>last(/SES Monitor/ses.api.response_time)&gt;{$SES_API_TIMEOUT_MS}</expression>
<name>SES: медленный ответ API (&gt;{$SES_API_TIMEOUT_MS} мс)</name> <name>SES: медленный ответ API (&gt;{$SES_API_TIMEOUT_MS} мс)</name>
<priority>WARNING</priority> <priority>WARNING</priority>
@@ -113,7 +117,7 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
</item> </item>
<item> <item>
<uuid>05fa180f-9e01-4fde-b73c-fa7b1d9522f8</uuid> <uuid>05fa180f9e014fdeb73cfa7b1d9522f8</uuid>
<name>SES: статус лицензии</name> <name>SES: статус лицензии</name>
<type>DEPENDENT</type> <type>DEPENDENT</type>
<key>ses.license.ok</key> <key>ses.license.ok</key>
@@ -123,7 +127,9 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.license.ok"]</params> <parameters>
<parameter>$["ses.license.ok"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
@@ -132,7 +138,7 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<description>0 — проблема с лицензией, 1 — в порядке.</description> <description>0 — проблема с лицензией, 1 — в порядке.</description>
<triggers> <triggers>
<trigger> <trigger>
<uuid>863d9b4b-8a9d-4e0c-9b9d-fd3c8022df82</uuid> <uuid>863d9b4b8a9d4e0c9b9dfd3c8022df82</uuid>
<expression>last(/SES Monitor/ses.license.ok)=0</expression> <expression>last(/SES Monitor/ses.license.ok)=0</expression>
<name>SES: проблема с лицензией на {HOST.NAME}</name> <name>SES: проблема с лицензией на {HOST.NAME}</name>
<priority>HIGH</priority> <priority>HIGH</priority>
@@ -142,7 +148,7 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
</item> </item>
<item> <item>
<uuid>5c090eb8-6152-4fb5-a49a-ebf7d803f975</uuid> <uuid>5c090eb861524fb5a49aebf7d803f975</uuid>
<name>SES: дней до окончания лицензии</name> <name>SES: дней до окончания лицензии</name>
<type>DEPENDENT</type> <type>DEPENDENT</type>
<key>ses.license.days_remaining</key> <key>ses.license.days_remaining</key>
@@ -152,7 +158,9 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.license.days_remaining"]</params> <parameters>
<parameter>$["ses.license.days_remaining"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
@@ -160,10 +168,19 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<value_type>FLOAT</value_type> <value_type>FLOAT</value_type>
<units>d</units> <units>d</units>
<description>Количество дней до окончания лицензии SES. -1 = бессрочная.</description> <description>Количество дней до окончания лицензии SES. -1 = бессрочная.</description>
<triggers>
<trigger>
<uuid>67f94034c31f480faa4768c986bdb2e9</uuid>
<expression>last(/SES Monitor/ses.license.days_remaining)&gt;0 and last(/SES Monitor/ses.license.days_remaining)&lt;30</expression>
<name>SES: лицензия истекает через (&lt;30 дней) на {HOST.NAME}</name>
<priority>WARNING</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item> </item>
<item> <item>
<uuid>696ac7fe-11f3-42f9-a56e-c2533bc7d71c</uuid> <uuid>696ac7fe11f342f9a56ec2533bc7d71c</uuid>
<name>SES: количество сценариев (роботов)</name> <name>SES: количество сценариев (роботов)</name>
<type>DEPENDENT</type> <type>DEPENDENT</type>
<key>ses.robots.count</key> <key>ses.robots.count</key>
@@ -173,7 +190,9 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.robots.count"]</params> <parameters>
<parameter>$["ses.robots.count"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
@@ -186,7 +205,7 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<!-- Сценарии --> <!-- Сценарии -->
<item> <item>
<uuid>48930571-519a-4071-adec-fb22a732eaad</uuid> <uuid>48930571519a4071adecfb22a732eaad</uuid>
<name>SES: тестовые сценарии — все успешны</name> <name>SES: тестовые сценарии — все успешны</name>
<type>DEPENDENT</type> <type>DEPENDENT</type>
<key>ses.scenario.ok</key> <key>ses.scenario.ok</key>
@@ -196,26 +215,20 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.scenario.ok"]</params> <parameters>
<parameter>$["ses.scenario.ok"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
<trends>30d</trends> <trends>30d</trends>
<value_type>UNSIGNED</value_type> <value_type>UNSIGNED</value_type>
<description>1 — все сценарии прошли, 0 — есть упавшие.</description> <description>1 — все сценарии прошли, 0 — есть упавшие.</description>
<triggers>
<trigger>
<uuid>a44be8fd-d652-40a2-8be2-b9760df19975</uuid>
<expression>last(/SES Monitor/ses.scenario.ok)=0</expression>
<name>SES: тестовые сценарии мониторинга падают на {HOST.NAME}</name>
<priority>AVERAGE</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item> </item>
<item> <item>
<uuid>b28ac087-8070-4195-95ea-99ccb24fa370</uuid> <uuid>b28ac0878070419595ea99ccb24fa370</uuid>
<name>SES: тестовые сценарии — всего</name> <name>SES: тестовые сценарии — всего</name>
<type>DEPENDENT</type> <type>DEPENDENT</type>
<key>ses.scenario.total</key> <key>ses.scenario.total</key>
@@ -225,7 +238,9 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.scenario.total"]</params> <parameters>
<parameter>$["ses.scenario.total"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
@@ -235,7 +250,7 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
</item> </item>
<item> <item>
<uuid>5b003d5f-f88b-47e3-a3dc-17d196036351</uuid> <uuid>5b003d5ff88b47e3a3dc17d196036351</uuid>
<name>SES: тестовые сценарии — упало</name> <name>SES: тестовые сценарии — упало</name>
<type>DEPENDENT</type> <type>DEPENDENT</type>
<key>ses.scenario.failed</key> <key>ses.scenario.failed</key>
@@ -245,7 +260,9 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.scenario.failed"]</params> <parameters>
<parameter>$["ses.scenario.failed"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
@@ -254,17 +271,24 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<description>Количество упавших сценариев за последний цикл.</description> <description>Количество упавших сценариев за последний цикл.</description>
<triggers> <triggers>
<trigger> <trigger>
<uuid>4de7a731-b6b7-41c6-85a2-71a5f1a45a66</uuid> <uuid>4de7a731b6b741c685a271a5f1a45a66</uuid>
<expression>last(/SES Monitor/ses.scenario.failed)&gt;0</expression> <expression>avg(/SES Monitor/ses.scenario.failed,#3)&gt;0</expression>
<name>SES: тестовые сценарии мониторинга падают на {HOST.NAME}</name> <name>SES: тестовые сценарии мониторинга падают на {HOST.NAME}</name>
<priority>WARNING</priority> <priority>WARNING</priority>
<manual_close>YES</manual_close> <manual_close>YES</manual_close>
</trigger> </trigger>
<trigger>
<uuid>89a0f35dcfc74d58a0bf96d9c56c85a2</uuid>
<expression>avg(/SES Monitor/ses.scenario.failed,#3)&gt;0</expression>
<name>SES: критическая ошибка выполнения сценариев на {HOST.NAME}</name>
<priority>DISASTER</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers> </triggers>
</item> </item>
<item> <item>
<uuid>0f911f5a-7529-495d-b35a-d1784a32a0de</uuid> <uuid>0f911f5a7529495db35ad1784a32a0de</uuid>
<name>SES: тестовые сценарии — среднее время</name> <name>SES: тестовые сценарии — среднее время</name>
<type>DEPENDENT</type> <type>DEPENDENT</type>
<key>ses.scenario.avg_time</key> <key>ses.scenario.avg_time</key>
@@ -274,7 +298,9 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.scenario.avg_time"]</params> <parameters>
<parameter>$["ses.scenario.avg_time"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
@@ -285,7 +311,7 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
</item> </item>
<item> <item>
<uuid>5747ec81-622c-44dc-8eca-c9be38458dad</uuid> <uuid>5747ec81622c44dc8ecac9be38458dad</uuid>
<name>SES: тестовые сценарии — макс. время</name> <name>SES: тестовые сценарии — макс. время</name>
<type>DEPENDENT</type> <type>DEPENDENT</type>
<key>ses.scenario.max_time</key> <key>ses.scenario.max_time</key>
@@ -295,7 +321,9 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.scenario.max_time"]</params> <parameters>
<parameter>$["ses.scenario.max_time"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>
@@ -306,7 +334,7 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
</item> </item>
<item> <item>
<uuid>bafb3afc-2ac4-4608-9f5b-d49d88960921</uuid> <uuid>bafb3afc2ac446089f5bd49d88960921</uuid>
<name>SES: тестовые сценарии — ошибка</name> <name>SES: тестовые сценарии — ошибка</name>
<type>DEPENDENT</type> <type>DEPENDENT</type>
<key>ses.scenario.error</key> <key>ses.scenario.error</key>
@@ -316,7 +344,9 @@ JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json
<preprocessing> <preprocessing>
<step> <step>
<type>JSONPATH</type> <type>JSONPATH</type>
<params>$["ses.scenario.error"]</params> <parameters>
<parameter>$["ses.scenario.error"]</parameter>
</parameters>
</step> </step>
</preprocessing> </preprocessing>
<history>7d</history> <history>7d</history>