"""Prometheus-compatible metrics endpoint.""" import calendar from fastapi import APIRouter, Depends, Response from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from ..db.engine import get_session from ..db.models import Finding, Scan router = APIRouter(tags=["metrics"]) @router.get("/metrics") async def metrics(session: AsyncSession = Depends(get_session)): total = await session.scalar(select(func.count(Scan.id))) or 0 flagged = await session.scalar( select(func.count(Scan.id)).where(Scan.flagged == True) ) or 0 findings_total = await session.scalar(select(func.count(Finding.id))) or 0 # By status status_rows = ( await session.execute( select(Scan.status, func.count(Scan.id)).group_by(Scan.status) ) ).all() by_status = {row[0]: row[1] for row in status_rows} # By ecosystem eco_rows = ( await session.execute( select(Scan.ecosystem, func.count(Scan.id)).group_by(Scan.ecosystem) ) ).all() by_eco = {row[0]: row[1] for row in eco_rows} # Latest scan timestamp latest = await session.scalar( select(func.max(Scan.started_at)) ) lines = [ "# HELP guarddog_scans_total Total number of package scans.", "# TYPE guarddog_scans_total counter", f"guarddog_scans_total {total}", "", "# HELP guarddog_scans_flagged_total Total flagged (vulnerable) scans.", "# TYPE guarddog_scans_flagged_total counter", f"guarddog_scans_flagged_total {flagged}", "", "# HELP guarddog_findings_total Total security findings.", "# TYPE guarddog_findings_total counter", f"guarddog_findings_total {findings_total}", "", "# HELP guarddog_scans_by_status Scans grouped by status.", "# TYPE guarddog_scans_by_status gauge", ] for status, count in sorted(by_status.items()): lines.append(f'guarddog_scans_by_status{{status="{status}"}} {count}') lines += [ "", "# HELP guarddog_scans_by_ecosystem Scans grouped by ecosystem.", "# TYPE guarddog_scans_by_ecosystem gauge", ] for eco, count in sorted(by_eco.items()): lines.append(f'guarddog_scans_by_ecosystem{{ecosystem="{eco}"}} {count}') if latest: ts = calendar.timegm(latest.timetuple()) lines += [ "", "# HELP guarddog_last_scan_timestamp_seconds Unix timestamp of most recent scan.", "# TYPE guarddog_last_scan_timestamp_seconds gauge", f"guarddog_last_scan_timestamp_seconds {ts:.0f}", ] return Response(content="\n".join(lines) + "\n", media_type="text/plain")