check retention

This commit is contained in:
2026-03-10 14:30:10 +00:00
parent 7f0447f82c
commit 3ffaf0cc4d
6 changed files with 65 additions and 3 deletions

View File

@@ -3,3 +3,7 @@
DOCKER_REGISTRY=docker.io DOCKER_REGISTRY=docker.io
DOCKER_IMAGE=myorg/myapp DOCKER_IMAGE=myorg/myapp
IMAGE_TAG=latest IMAGE_TAG=latest
# Optional: check retention (limits DB growth)
# CHECK_RETENTION_COUNT=5000 # keep last N checks per service (default 5000)
# CHECK_RETENTION_DAYS=30 # also delete checks older than N days (0=disabled)

View File

@@ -42,6 +42,21 @@ docker run -p 8080:8080 -v $(pwd)/data:/app/data myapp:test
Add services from the dashboard (e.g. `https://example.com`, `google.com:443` for TCP) and view reports. Add services from the dashboard (e.g. `https://example.com`, `google.com:443` for TCP) and view reports.
### Check Retention
To limit database growth, the app prunes old checks every 15 minutes:
| Env var | Default | Description |
|---------|---------|-------------|
| `CHECK_RETENTION_COUNT` | 5000 | Keep last N checks per service |
| `CHECK_RETENTION_DAYS` | 0 (disabled) | Also delete checks older than N days |
Example: keep 2000 checks per service and drop anything older than 30 days:
```bash
docker run -e CHECK_RETENTION_COUNT=2000 -e CHECK_RETENTION_DAYS=30 ...
```
## Jenkins Pipeline ## Jenkins Pipeline
The pipeline: The pipeline:

View File

@@ -141,7 +141,7 @@ def report(service_id):
status_filter = request.args.get("status") status_filter = request.args.get("status")
search = request.args.get("search", "").strip() or None search = request.args.get("search", "").strip() or None
page = max(1, int(request.args.get("page", 1))) page = max(1, int(request.args.get("page", 1)))
per_page = min(100, max(10, int(request.args.get("per_page", 25)))) per_page = min(100, max(10, int(request.args.get("per_page", 10))))
stats = models.get_report_stats(service_id, from_ts=from_ts, to_ts=to_ts) stats = models.get_report_stats(service_id, from_ts=from_ts, to_ts=to_ts)
checks_total = models.get_checks_count(service_id, from_ts=from_ts, to_ts=to_ts, status_filter=status_filter, search=search) checks_total = models.get_checks_count(service_id, from_ts=from_ts, to_ts=to_ts, status_filter=status_filter, search=search)
checks = models.get_checks( checks = models.get_checks(

View File

@@ -2,12 +2,16 @@
import os import os
import sqlite3 import sqlite3
from contextlib import contextmanager from contextlib import contextmanager
from datetime import datetime from datetime import datetime, timedelta, timezone
from pathlib import Path from pathlib import Path
DATA_PATH = os.environ.get("DATA_PATH", "/app/data") DATA_PATH = os.environ.get("DATA_PATH", "/app/data")
DB_PATH = Path(DATA_PATH) / "monitor.db" DB_PATH = Path(DATA_PATH) / "monitor.db"
# Retention: keep last N checks per service, and optionally drop checks older than N days
CHECK_RETENTION_COUNT = int(os.environ.get("CHECK_RETENTION_COUNT", "5000"))
CHECK_RETENTION_DAYS = int(os.environ.get("CHECK_RETENTION_DAYS", "0")) or None
def _ensure_data_dir(): def _ensure_data_dir():
Path(DATA_PATH).mkdir(parents=True, exist_ok=True) Path(DATA_PATH).mkdir(parents=True, exist_ok=True)
@@ -232,3 +236,38 @@ def get_all_services_for_scheduler():
with get_db() as conn: with get_db() as conn:
rows = conn.execute("SELECT id, target, protocol, interval_seconds FROM services").fetchall() rows = conn.execute("SELECT id, target, protocol, interval_seconds FROM services").fetchall()
return [dict(r) for r in rows] return [dict(r) for r in rows]
def prune_checks_retention() -> int:
"""
Remove old checks to limit storage. Keeps last CHECK_RETENTION_COUNT per service.
If CHECK_RETENTION_DAYS is set, also deletes checks older than that.
Returns number of rows deleted.
"""
with get_db() as conn:
deleted = 0
# Delete checks older than N days (if configured)
if CHECK_RETENTION_DAYS:
cutoff = (datetime.now(timezone.utc) - timedelta(days=CHECK_RETENTION_DAYS)).isoformat()
cur = conn.execute("DELETE FROM checks WHERE timestamp < ?", (cutoff,))
deleted += cur.rowcount
# Keep only last N checks per service
service_ids = [r[0] for r in conn.execute("SELECT id FROM services").fetchall()]
for sid in service_ids:
# Get ids of checks to keep (most recent N)
keep_ids = conn.execute(
"SELECT id FROM checks WHERE service_id = ? ORDER BY timestamp DESC LIMIT ?",
(sid, CHECK_RETENTION_COUNT),
).fetchall()
keep_ids = [r[0] for r in keep_ids]
if not keep_ids:
continue
placeholders = ",".join("?" * len(keep_ids))
cur = conn.execute(
f"DELETE FROM checks WHERE service_id = ? AND id NOT IN ({placeholders})",
[sid] + keep_ids,
)
deleted += cur.rowcount
return deleted

View File

@@ -2,7 +2,7 @@
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from app.checker import run_check from app.checker import run_check
from app.models import get_all_services_for_scheduler from app.models import get_all_services_for_scheduler, prune_checks_retention
def _run_all_checks(): def _run_all_checks():
@@ -54,4 +54,7 @@ def start_scheduler():
# Sync job list every 60 seconds (only adds/removes when services change) # Sync job list every 60 seconds (only adds/removes when services change)
scheduler.add_job(sync_jobs, "interval", seconds=60, id="sync_jobs") scheduler.add_job(sync_jobs, "interval", seconds=60, id="sync_jobs")
# Prune old checks every 15 minutes (retention/compression)
scheduler.add_job(prune_checks_retention, "interval", minutes=15, id="prune_checks")
scheduler.start() scheduler.start()

View File

@@ -10,4 +10,5 @@ services:
- ./data:/app/data - ./data:/app/data
environment: environment:
- VERSION=${IMAGE_TAG:-latest} - VERSION=${IMAGE_TAG:-latest}
# Optional: CHECK_RETENTION_COUNT=5000, CHECK_RETENTION_DAYS=30
restart: unless-stopped restart: unless-stopped