mirror of
https://github.com/open-webui/oikb.git
synced 2026-07-09 16:00:53 +00:00
0.2.6: Prometheus metrics endpoint for daemon observability
GET /metrics exposes sync counters (total, errors), duration histogram, and file upload/delete counters — all labeled by source. Includes oikb_info with build version. Adds prometheus-client dep.
This commit is contained in:
parent
dae21fa4de
commit
6efcee9af8
6 changed files with 112 additions and 5 deletions
|
|
@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [0.2.6] - 2025-05-21
|
||||
|
||||
### Added
|
||||
|
||||
- Prometheus metrics endpoint (`GET /metrics`) on the daemon — exports `oikb_sync_total`, `oikb_sync_duration_seconds`, `oikb_files_uploaded_total`, `oikb_files_deleted_total`, `oikb_sync_errors_total`, and `oikb_info` build metadata. All counters labeled by source.
|
||||
- `prometheus-client>=0.20` added as a core dependency.
|
||||
|
||||
## [0.2.5] - 2025-05-21
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ Features:
|
|||
- **Scheduled sync** — configurable per-source intervals (`30m`, `1h`, `6h`)
|
||||
- **Webhooks** — instant sync on push via `/webhooks/github`, `/webhooks/gitlab`, `/webhooks/slack`, `/webhooks/confluence`
|
||||
- **Health checks** — `GET /health` for Docker/K8s readiness probes
|
||||
- **Prometheus metrics** — `GET /metrics` exports sync counters, duration histograms, and error rates
|
||||
- **Sync history** — `GET /history` queryable log of all syncs
|
||||
- **On-demand sync** — `POST /sync/{identifier}` trigger by `name` or `kb-id`
|
||||
- **API key auth** — set `OIKB_API_KEY` to secure endpoints (Docker secrets `_FILE` supported)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "oikb"
|
||||
version = "0.2.5"
|
||||
version = "0.2.6"
|
||||
description = "Sync anything to Open WebUI Knowledge Bases"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
|
|
@ -11,6 +11,7 @@ license = {file = "LICENSE"}
|
|||
dependencies = [
|
||||
"click>=8.1",
|
||||
"httpx>=0.27",
|
||||
"prometheus-client>=0.20",
|
||||
"pyyaml>=6.0",
|
||||
"rich>=13.0",
|
||||
"watchdog>=4.0",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
"""oikb — Open WebUI Knowledge Base CLI."""
|
||||
|
||||
__version__ = "0.2.5"
|
||||
__version__ = "0.2.6"
|
||||
|
|
|
|||
|
|
@ -16,8 +16,10 @@ import uvicorn
|
|||
from fastapi import Depends, FastAPI, HTTPException
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
|
||||
from oikb import __version__
|
||||
from oikb.env import API_KEY
|
||||
from oikb.history import SyncHistory
|
||||
from oikb.metrics import record_sync, set_build_info
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -38,7 +40,7 @@ async def verify_api_key(
|
|||
app = FastAPI(
|
||||
title="oikb",
|
||||
description="Sync engine for Open WebUI Knowledge Bases. Trigger syncs, check status, and query history.",
|
||||
version="0.2.5",
|
||||
version="0.2.6",
|
||||
)
|
||||
|
||||
# Runtime state populated by start_daemon().
|
||||
|
|
@ -164,10 +166,12 @@ async def _run_entry(entry: dict) -> None:
|
|||
)
|
||||
client.close()
|
||||
|
||||
duration_ms = int((time.time() - started_at) * 1000)
|
||||
duration_s = time.time() - started_at
|
||||
duration_ms = int(duration_s * 1000)
|
||||
status = "success" if not result.errors else "partial"
|
||||
|
||||
_scheduler_state[source] = {
|
||||
"status": "success" if not result.errors else "partial",
|
||||
"status": status,
|
||||
"last_sync": time.time(),
|
||||
"duration_ms": duration_ms,
|
||||
"files_added": result.added,
|
||||
|
|
@ -177,6 +181,15 @@ async def _run_entry(entry: dict) -> None:
|
|||
"errors": result.errors or [],
|
||||
}
|
||||
|
||||
record_sync(
|
||||
source=source,
|
||||
status=status,
|
||||
duration_seconds=duration_s,
|
||||
files_added=result.added,
|
||||
files_modified=result.modified,
|
||||
files_deleted=result.deleted,
|
||||
)
|
||||
|
||||
if _history:
|
||||
await asyncio.to_thread(
|
||||
_history.log,
|
||||
|
|
@ -200,6 +213,11 @@ async def _run_entry(entry: dict) -> None:
|
|||
"last_sync": time.time(),
|
||||
"error": str(e),
|
||||
}
|
||||
record_sync(
|
||||
source=source,
|
||||
status="error",
|
||||
duration_seconds=time.time() - started_at,
|
||||
)
|
||||
if _history:
|
||||
await asyncio.to_thread(
|
||||
_history.log,
|
||||
|
|
@ -260,6 +278,12 @@ def start_daemon(
|
|||
global _history, _entries
|
||||
_entries = entries
|
||||
_history = SyncHistory()
|
||||
set_build_info(__version__)
|
||||
|
||||
# Mount Prometheus metrics endpoint.
|
||||
from prometheus_client import make_asgi_app
|
||||
metrics_app = make_asgi_app()
|
||||
app.mount("/metrics", metrics_app)
|
||||
|
||||
# Mount webhook routes.
|
||||
from oikb.webhooks import router as webhook_router, configure as configure_webhooks
|
||||
|
|
@ -287,6 +311,7 @@ def start_daemon(
|
|||
if webhook_entries:
|
||||
click.echo(f" {len(webhook_entries)} webhook-enabled source(s)")
|
||||
click.echo(f" GET http://localhost:{port}/health")
|
||||
click.echo(f" GET http://localhost:{port}/metrics")
|
||||
click.echo(f" GET http://localhost:{port}/history")
|
||||
click.echo(f" POST http://localhost:{port}/sync/{{kb_id}}")
|
||||
if webhook_entries:
|
||||
|
|
|
|||
73
src/oikb/metrics.py
Normal file
73
src/oikb/metrics.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
"""Prometheus metrics for the oikb daemon."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from prometheus_client import Counter, Histogram, Info
|
||||
|
||||
# ── Counters ─────────────────────────────────────────────────────
|
||||
|
||||
SYNC_TOTAL = Counter(
|
||||
"oikb_sync_total",
|
||||
"Total sync operations",
|
||||
["source", "status"],
|
||||
)
|
||||
|
||||
FILES_UPLOADED = Counter(
|
||||
"oikb_files_uploaded_total",
|
||||
"Total files uploaded (added + modified)",
|
||||
["source"],
|
||||
)
|
||||
|
||||
FILES_DELETED = Counter(
|
||||
"oikb_files_deleted_total",
|
||||
"Total files deleted during sync",
|
||||
["source"],
|
||||
)
|
||||
|
||||
SYNC_ERRORS = Counter(
|
||||
"oikb_sync_errors_total",
|
||||
"Total failed sync operations",
|
||||
["source"],
|
||||
)
|
||||
|
||||
# ── Histograms ───────────────────────────────────────────────────
|
||||
|
||||
SYNC_DURATION = Histogram(
|
||||
"oikb_sync_duration_seconds",
|
||||
"Sync duration in seconds",
|
||||
["source"],
|
||||
buckets=(1, 5, 10, 30, 60, 120, 300, 600, 1800),
|
||||
)
|
||||
|
||||
# ── Info ─────────────────────────────────────────────────────────
|
||||
|
||||
BUILD_INFO = Info(
|
||||
"oikb",
|
||||
"oikb build information",
|
||||
)
|
||||
|
||||
|
||||
def set_build_info(version: str) -> None:
|
||||
"""Set build info labels (called once on startup)."""
|
||||
BUILD_INFO.info({"version": version})
|
||||
|
||||
|
||||
def record_sync(
|
||||
source: str,
|
||||
status: str,
|
||||
duration_seconds: float,
|
||||
files_added: int = 0,
|
||||
files_modified: int = 0,
|
||||
files_deleted: int = 0,
|
||||
) -> None:
|
||||
"""Record metrics for a completed sync operation."""
|
||||
SYNC_TOTAL.labels(source=source, status=status).inc()
|
||||
SYNC_DURATION.labels(source=source).observe(duration_seconds)
|
||||
|
||||
uploaded = files_added + files_modified
|
||||
if uploaded:
|
||||
FILES_UPLOADED.labels(source=source).inc(uploaded)
|
||||
if files_deleted:
|
||||
FILES_DELETED.labels(source=source).inc(files_deleted)
|
||||
if status == "error":
|
||||
SYNC_ERRORS.labels(source=source).inc()
|
||||
Loading…
Add table
Add a link
Reference in a new issue