Trong bài viết này, tôi sẽ chia sẻ cách xây dựng một hệ thống giám sát SLA hoàn chỉnh cho HolySheep AI API — bao gồm đo lường P50/P95/P99 latency, error rate theo thời gian dài, và tạo dashboard trực quan. Đây là playbook mà tôi đã áp dụng thực tế khi migrate 3 hệ thống production từ các nhà cung cấp khác sang HolySheep, giúp team tiết kiệm 85%+ chi phí mà vẫn đảm bảo uptime 99.9%.

Vì sao cần giám sát SLA API?

Khi vận hành hệ thống AI ở production, latency không chỉ là con số trên dashboard — nó ảnh hưởng trực tiếp đến trải nghiệm người dùng và doanh thu. Một request 500ms thay vì 50ms có thể khiến conversion rate giảm 10-15%. Với HolySheep API, chúng ta được đảm bảo latency trung bình dưới 50ms, nhưng để thực sự yên tâm, bạn cần một hệ thống đo lường độc lập.

Kiến trúc hệ thống giám sát

Hệ thống bao gồm các thành phần chính:

Cài đặt Collector Agent

Đầu tiên, tạo một service đứng giữa ứng dụng và HolySheep API để capture metrics:

#!/usr/bin/env python3
"""
HolySheep API SLA Collector Agent
Ghi nhận latency, error rate, token count cho dashboard
"""

import httpx
import asyncio
import time
import json
from datetime import datetime, timedelta
from typing import Dict, List, Optional
from dataclasses import dataclass, asdict
from contextlib import asynccontextmanager
import psycopg2
from psycopg2.extras import execute_batch
import os

@dataclass
class RequestMetrics:
    request_id: str
    timestamp: datetime
    model: str
    latency_ms: float
    status_code: int
    tokens_input: int
    tokens_output: int
    error_message: Optional[str] = None
    is_timeout: bool = False

class HolySheepMetricsCollector:
    """
    Collector gửi request đến HolySheep API và ghi nhận metrics
    """
    
    def __init__(
        self,
        api_key: str,
        base_url: str = "https://api.holysheep.ai/v1",
        db_host: str = "localhost",
        db_port: int = 5432,
        db_name: str = "holysheep_sla",
        db_user: str = "postgres",
        db_password: str = "postgres"
    ):
        self.api_key = api_key
        self.base_url = base_url
        self.db_config = {
            "host": db_host,
            "port": db_port,
            "dbname": db_name,
            "user": db_user,
            "password": db_password
        }
        self.client = httpx.AsyncClient(
            timeout=httpx.Timeout(60.0, connect=10.0),
            follow_redirects=True
        )
        self._ensure_database()
    
    def _ensure_database(self):
        """Tạo database và bảng nếu chưa tồn tại"""
        conn = psycopg2.connect(
            **{k: v for k, v in self.db_config.items() if k != "dbname"}
        )
        conn.autocommit = True
        cur = conn.cursor()
        
        # Tạo database nếu chưa có
        try:
            cur.execute("CREATE DATABASE holysheep_sla")
        except psycopg2.errors.DuplicateDatabase:
            pass
        
        conn.close()
        
        # Kết nối vào database mới
        conn = psycopg2.connect(**self.db_config)
        cur = conn.cursor()
        
        # Tạo bảng metrics với TimescaleDB extension
        cur.execute("""
            CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
            
            CREATE TABLE IF NOT EXISTS api_request_metrics (
                request_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
                recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
                model VARCHAR(100) NOT NULL,
                latency_ms FLOAT NOT NULL,
                status_code INTEGER NOT NULL,
                tokens_input INTEGER DEFAULT 0,
                tokens_output INTEGER DEFAULT 0,
                error_message TEXT,
                is_timeout BOOLEAN DEFAULT FALSE,
                project_id VARCHAR(50),
                request_type VARCHAR(20) -- 'chat' or 'embedding'
            );
            
            -- Convert sang TimescaleDB hypertable
            SELECT create_hypertable(
                'api_request_metrics',
                'recorded_at',
                if_not_exists => TRUE,
                migrate_data => TRUE
            );
            
            -- Tạo index để query nhanh hơn
            CREATE INDEX IF NOT EXISTS idx_metrics_model 
                ON api_request_metrics (model, recorded_at DESC);
            CREATE INDEX IF NOT EXISTS idx_metrics_status 
                ON api_request_metrics (status_code, recorded_at DESC);
        """)
        
        conn.commit()
        cur.close()
        conn.close()
        print(f"[{datetime.now()}] Database initialized successfully")
    
    async def send_chat_request(
        self,
        messages: List[Dict],
        model: str = "gpt-4.1",
        temperature: float = 0.7,
        max_tokens: int = 1000,
        project_id: Optional[str] = None
    ) -> RequestMetrics:
        """Gửi chat completion request và ghi nhận metrics"""
        request_id = f"{datetime.now().timestamp()}-{os.urandom(4).hex()}"
        start_time = time.perf_counter()
        
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }
        
        payload = {
            "model": model,
            "messages": messages,
            "temperature": temperature,
            "max_tokens": max_tokens
        }
        
        try:
            response = await self.client.post(
                f"{self.base_url}/chat/completions",
                headers=headers,
                json=payload
            )
            
            latency_ms = (time.perf_counter() - start_time) * 1000
            
            if response.status_code == 200:
                data = response.json()
                return RequestMetrics(
                    request_id=request_id,
                    timestamp=datetime.now(),
                    model=model,
                    latency_ms=latency_ms,
                    status_code=200,
                    tokens_input=data.get("usage", {}).get("prompt_tokens", 0),
                    tokens_output=data.get("usage", {}).get("completion_tokens", 0),
                    project_id=project_id
                )
            else:
                error_data = response.json() if response.text else {}
                return RequestMetrics(
                    request_id=request_id,
                    timestamp=datetime.now(),
                    model=model,
                    latency_ms=latency_ms,
                    status_code=response.status_code,
                    error_message=error_data.get("error", {}).get("message", response.text),
                    project_id=project_id
                )
                
        except httpx.TimeoutException as e:
            latency_ms = (time.perf_counter() - start_time) * 1000
            return RequestMetrics(
                request_id=request_id,
                timestamp=datetime.now(),
                model=model,
                latency_ms=latency_ms,
                status_code=0,
                error_message=f"Timeout: {str(e)}",
                is_timeout=True,
                project_id=project_id
            )
        except Exception as e:
            latency_ms = (time.perf_counter() - start_time) * 1000
            return RequestMetrics(
                request_id=request_id,
                timestamp=datetime.now(),
                model=model,
                latency_ms=latency_ms,
                status_code=0,
                error_message=str(e),
                project_id=project_id
            )
    
    def save_metrics(self, metrics: List[RequestMetrics]):
        """Lưu metrics vào database"""
        if not metrics:
            return
        
        conn = psycopg2.connect(**self.db_config)
        cur = conn.cursor()
        
        query = """
            INSERT INTO api_request_metrics 
            (request_id, recorded_at, model, latency_ms, status_code, 
             tokens_input, tokens_output, error_message, is_timeout, project_id)
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
        """
        
        values = [
            (
                m.request_id, m.timestamp, m.model, m.latency_ms, m.status_code,
                m.tokens_input, m.tokens_output, m.error_message, m.is_timeout, m.project_id
            )
            for m in metrics
        ]
        
        execute_batch(cur, query, values)
        conn.commit()
        cur.close()
        conn.close()
    
    async def run_load_test(
        self,
        model: str,
        duration_seconds: int = 60,
        concurrent_requests: int = 10
    ):
        """
        Chạy load test để thu thập SLA metrics
        """
        print(f"[{datetime.now()}] Starting load test for {model}")
        print(f"  Duration: {duration_seconds}s, Concurrent: {concurrent_requests}")
        
        all_metrics = []
        start_time = time.time()
        
        async def single_request():
            messages = [{"role": "user", "content": "Xin chào, hãy kể cho tôi nghe về AI."}]
            return await self.send_chat_request(messages, model=model)
        
        while time.time() - start_time < duration_seconds:
            tasks = [single_request() for _ in range(concurrent_requests)]
            batch_results = await asyncio.gather(*tasks, return_exceptions=True)
            
            for result in batch_results:
                if isinstance(result, RequestMetrics):
                    all_metrics.append(result)
            
            await asyncio.sleep(0.1)  # Brief pause between batches
        
        # Save all metrics
        if all_metrics:
            self.save_metrics(all_metrics)
        
        return all_metrics
    
    async def close(self):
        await self.client.aclose()


=== SỬ DỤNG ===

if __name__ == "__main__": collector = HolySheepMetricsCollector( api_key=os.environ.get("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY"), db_host="localhost" ) # Chạy load test 2 phút metrics = asyncio.run( collector.run_load_test( model="gpt-4.1", duration_seconds=120, concurrent_requests=5 ) ) print(f"\n=== Load Test Results ===") print(f"Total requests: {len(metrics)}") latencies = [m.latency_ms for m in metrics if m.status_code == 200] if latencies: latencies.sort() p50_idx = int(len(latencies) * 0.50) p95_idx = int(len(latencies) * 0.95) p99_idx = int(len(latencies) * 0.99) print(f"P50 Latency: {latencies[p50_idx]:.2f}ms") print(f"P95 Latency: {latencies[p95_idx]:.2f}ms") print(f"P99 Latency: {latencies[p99_idx]:.2f}ms") error_count = sum(1 for m in metrics if m.status_code != 200) print(f"Error Rate: {error_count/len(metrics)*100:.2f}%") asyncio.run(collector.close())

Tạo Dashboard Grafana với P50/P95/P99

Sau khi đã thu thập dữ liệu, bước tiếp theo là tạo dashboard để visualize. Dưới đây là SQL queries và Grafana dashboard JSON:

-- ============================================================
-- HOLYSHEEP API SLA DASHBOARD - SQL QUERIES
-- Chạy trên PostgreSQL/TimescaleDB
-- ============================================================

-- 1. P50/P95/P99 Latency theo từng khoảng thời gian
CREATE OR REPLACE FUNCTION calculate_percentile(
    latencies FLOAT[],
    percentile FLOAT
) RETURNS FLOAT AS $$
DECLARE
    sorted_latencies FLOAT[];
BEGIN
    sorted_latencies := array_sort(latencies);
    RETURN sorted_latencies[
        greatest(1, ceil(array_length(sorted_latencies, 1) * percentile)::INTEGER)
    ];
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- 2. SLA Summary cho 1 giờ gần nhất
CREATE OR REPLACE VIEW v_sla_hourly_summary AS
SELECT 
    time_bucket('5 minutes', recorded_at) AS bucket,
    model,
    COUNT(*) AS total_requests,
    COUNT(*) FILTER (WHERE status_code = 200) AS successful_requests,
    AVG(latency_ms) FILTER (WHERE status_code = 200) AS avg_latency_ms,
    PERCENTILE_CONT(0.50) WITHIN GROUP (
        ORDER BY latency_ms FILTER (WHERE status_code = 200)
    ) AS p50_latency_ms,
    PERCENTILE_CONT(0.95) WITHIN GROUP (
        ORDER BY latency_ms FILTER (WHERE status_code = 200)
    ) AS p95_latency_ms,
    PERCENTILE_CONT(0.99) WITHIN GROUP (
        ORDER BY latency_ms FILTER (WHERE status_code = 200)
    ) AS p99_latency_ms,
    MAX(latency_ms) FILTER (WHERE status_code = 200) AS max_latency_ms,
    COUNT(*) FILTER (WHERE status_code != 200 OR is_timeout = TRUE) * 100.0 / COUNT(*) 
        AS error_rate_percent,
    SUM(tokens_input) AS total_input_tokens,
    SUM(tokens_output) AS total_output_tokens
FROM api_request_metrics
WHERE recorded_at >= NOW() - INTERVAL '1 hour'
GROUP BY bucket, model
ORDER BY bucket DESC, model;

-- 3. SLA Summary cho 7 ngày gần nhất (sliding window)
CREATE OR REPLACE VIEW v_sla_daily_summary AS
SELECT 
    DATE(recorded_at) AS date,
    model,
    COUNT(*) AS total_requests,
    COUNT(*) FILTER (WHERE status_code = 200) AS successful_requests,
    ROUND(
        COUNT(*) FILTER (WHERE status_code = 200) * 100.0 / COUNT(*), 
        4
    ) AS availability_percent,
    ROUND(AVG(latency_ms) FILTER (WHERE status_code = 200), 2) AS avg_latency_ms,
    ROUND(
        PERCENTILE_CONT(0.50) WITHIN GROUP (
            ORDER BY latency_ms FILTER (WHERE status_code = 200)
        ), 2
    ) AS p50_latency_ms,
    ROUND(
        PERCENTILE_CONT(0.95) WITHIN GROUP (
            ORDER BY latency_ms FILTER (WHERE status_code = 200)
        ), 2
    ) AS p95_latency_ms,
    ROUND(
        PERCENTILE_CONT(0.99) WITHIN GROUP (
            ORDER BY latency_ms FILTER (WHERE status_code = 200)
        ), 2
    ) AS p99_latency_ms,
    ROUND(
        MAX(latency_ms) FILTER (WHERE status_code = 200), 2
    ) AS max_latency_ms,
    COUNT(*) FILTER (WHERE is_timeout = TRUE) AS timeout_count,
    SUM(tokens_input) AS total_input_tokens,
    SUM(tokens_output) AS total_output_tokens,
    ROUND(
        SUM(tokens_input + tokens_output)::NUMERIC / 
        NULLIF(SUM(latency_ms) FILTER (WHERE status_code = 200), 0) * 1000,
        2
    ) AS throughput_tokens_per_second
FROM api_request_metrics
WHERE recorded_at >= NOW() - INTERVAL '7 days'
GROUP BY DATE(recorded_at), model
ORDER BY date DESC, model;

-- 4. Kiểm tra SLA compliance (so sánh với targets)
CREATE OR REPLACE VIEW v_sla_compliance AS
WITH thresholds AS (
    SELECT 
        'p99_latency_ms' AS metric_name,
        200.0 AS target_value,
        'P99 Latency phải dưới 200ms' AS description
    UNION ALL
    SELECT 'error_rate_percent', 0.1, 'Error rate phải dưới 0.1%'
    UNION ALL
    SELECT 'availability_percent', 99.9, 'Availability phải đạt 99.9%'
)
SELECT 
    t.metric_name,
    t.target_value,
    t.description,
    CASE 
        WHEN s.avg_latency_ms IS NULL THEN 0
        ELSE ROUND(
            CASE 
                WHEN t.metric_name = 'p99_latency_ms' THEN s.p99_latency_ms
                WHEN t.metric_name = 'error_rate_percent' THEN s.error_rate_percent
                WHEN t.metric_name = 'availability_percent' THEN s.availability_percent
            END, 4
        )
    END AS actual_value,
    CASE 
        WHEN t.metric_name IN ('p99_latency_ms', 'error_rate_percent') 
        THEN CASE WHEN actual_value <= t.target_value THEN '✅ PASS' ELSE '❌ FAIL' END
        ELSE CASE WHEN actual_value >= t.target_value THEN '✅ PASS' ELSE '❌ FAIL' END
    END AS status
FROM thresholds t
CROSS JOIN (
    SELECT 
        ROUND(AVG(latency_ms), 2) AS avg_latency_ms,
        ROUND(
            PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY latency_ms 
                FILTER (WHERE status_code = 200)), 2
        ) AS p99_latency_ms,
        ROUND(
            COUNT(*) FILTER (WHERE status_code != 200) * 100.0 / NULLIF(COUNT(*), 0), 
            4
        ) AS error_rate_percent,
        ROUND(
            COUNT(*) FILTER (WHERE status_code = 200) * 100.0 / NULLIF(COUNT(*), 0), 
            4
        ) AS availability_percent
    FROM api_request_metrics
    WHERE recorded_at >= NOW() - INTERVAL '24 hours'
) s;

-- 5. Cost tracking
CREATE OR REPLACE VIEW v_cost_tracking AS
WITH model_pricing AS (
    SELECT 'gpt-4.1' AS model, 8.0 AS price_per_mtok
    UNION ALL SELECT 'claude-sonnet-4.5', 15.0
    UNION ALL SELECT 'gemini-2.5-flash', 2.50
    UNION ALL SELECT 'deepseek-v3.2', 0.42
)
SELECT 
    DATE(m.recorded_at) AS date,
    m.model,
    p.price_per_mtok,
    SUM(m.tokens_input + m.tokens_output) AS total_tokens,
    ROUND(
        SUM(m.tokens_input + m.tokens_output)::NUMERIC / 1000000 * p.price_per_mtok,
        2
    ) AS cost_usd,
    COUNT(*) AS total_requests,
    ROUND(AVG(m.latency_ms), 2) AS avg_latency_ms
FROM api_request_metrics m
LEFT JOIN model_pricing p ON m.model = p.model
WHERE m.recorded_at >= NOW() - INTERVAL '30 days'
    AND m.status_code = 200
GROUP BY DATE(m.recorded_at), m.model, p.price_per_mtok
ORDER BY date DESC, cost_usd DESC;

-- 6. Real-time health check
CREATE OR REPLACE FUNCTION fn_health_check()
RETURNS TABLE (
    metric VARCHAR(50),
    value TEXT,
    status VARCHAR(10)
) AS $$
BEGIN
    RETURN QUERY
    SELECT 
        'total_requests_5m'::VARCHAR AS metric,
        COUNT(*)::TEXT AS value,
        CASE 
            WHEN COUNT(*) >= 10 THEN '✅ OK'
            ELSE '⚠️ LOW'
        END AS status
    FROM api_request_metrics
    WHERE recorded_at >= NOW() - INTERVAL '5 minutes';
    
    RETURN QUERY
    SELECT 
        'error_rate_5m'::VARCHAR AS metric,
        ROUND(
            COUNT(*) FILTER (WHERE status_code != 200) * 100.0 / 
            NULLIF(COUNT(*), 0), 2
        )::TEXT || '%' AS value,
        CASE 
            WHEN COUNT(*) FILTER (WHERE status_code != 200) * 100.0 / 
                 NULLIF(COUNT(*), 0) < 0.1 THEN '✅ OK'
            ELSE '❌ HIGH'
        END AS status
    FROM api_request_metrics
    WHERE recorded_at >= NOW() - INTERVAL '5 minutes';
    
    RETURN QUERY
    SELECT 
        'p95_latency_5m'::VARCHAR AS metric,
        ROUND(
            PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY latency_ms), 2
        )::TEXT || 'ms' AS value,
        CASE 
            WHEN PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY latency_ms) < 200
            THEN '✅ OK'
            ELSE '❌ HIGH'
        END AS status
    FROM api_request_metrics
    WHERE recorded_at >= NOW() - INTERVAL '5 minutes';
END;
$$ LANGUAGE plpgsql;

-- Test: Xem kết quả
SELECT * FROM v_sla_compliance;
SELECT * FROM v_cost_tracking LIMIT 20;
SELECT * FROM fn_health_check();

Tạo Grafana Dashboard JSON

{
  "annotations": {
    "list": [
      {
        "builtIn": 1,
        "datasource": {
          "type": "grafana/dashboard",
          "uid": "-- Grafana --"
        },
        "enable": true,
        "hide": true,
        "iconColor": "rgba(0, 211, 255, 1)",
        "name": "Annotations & Alerts",
        "type": "dashboard"
      }
    ]
  },
  "editable": true,
  "fiscalYearStartMonth": 0,
  "graphTooltip": 0,
  "id": null,
  "links": [],
  "liveNow": false,
  "panels": [
    {
      "collapsed": false,
      "gridPos": {
        "h": 1,
        "w": 24,
        "x": 0,
        "y": 0
      },
      "id": 1,
      "panels": [],
      "title": "SLA Overview",
      "type": "row"
    },
    {
      "datasource": {
        "type": "postgres",
        "uid": "${DS_POSTGRES}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              { "color": "green", "value": null },
              { "color": "yellow", "value": 100 },
              { "color": "red", "value": 200 }
            ]
          },
          "unit": "ms"
        }
      },
      "gridPos": { "h": 4, "w": 4, "x": 0, "y": 1 },
      "id": 2,
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "justifyMode": "auto",
        "orientation": "auto",
        "reduceOptions": {
          "calcs": ["lastNotNull"],
          "fields": "",
          "values": false
        },
        "textMode": "auto"
      },
      "pluginVersion": "10.0.0",
      "targets": [
        {
          "datasource": { "type": "postgres", "uid": "${DS_POSTGRES}" },
          "format": "table",
          "group": [],
          "metricColumn": "none",
          "rawQuery": true,
          "rawSql": "SELECT PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY latency_ms)\nFROM api_request_metrics\nWHERE recorded_at >= NOW() - INTERVAL '5 minutes' AND status_code = 200",
          "refId": "A",
          "sql": { "column": "", "type": "query" }
        }
      ],
      "title": "P50 Latency (5m)",
      "type": "stat"
    },
    {
      "datasource": {
        "type": "postgres",
        "uid": "${DS_POSTGRES}"
      },
      "fieldConfig": {
        "defaults": {
          "color": { "mode": "thresholds" },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              { "color": "green", "value": null },
              { "color": "yellow", "value": 150 },
              { "color": "red", "value": 250 }
            ]
          },
          "unit": "ms"
        }
      },
      "gridPos": { "h": 4, "w": 4, "x": 4, "y": 1 },
      "id": 3,
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "justifyMode": "auto",
        "orientation": "auto",
        "reduceOptions": {
          "calcs": ["lastNotNull"],
          "fields": "",
          "values": false
        },
        "textMode": "auto"
      },
      "targets": [
        {
          "datasource": { "type": "postgres", "uid": "${DS_POSTGRES}" },
          "format": "table",
          "group": [],
          "rawQuery": true,
          "rawSql": "SELECT PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY latency_ms)\nFROM api_request_metrics\nWHERE recorded_at >= NOW() - INTERVAL '5 minutes' AND status_code = 200",
          "refId": "A"
        }
      ],
      "title": "P95 Latency (5m)",
      "type": "stat"
    },
    {
      "datasource": {
        "type": "postgres",
        "uid": "${DS_POSTGRES}"
      },
      "fieldConfig": {
        "defaults": {
          "color": { "mode": "thresholds" },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              { "color": "green", "value": null },
              { "color": "yellow", "value": 180 },
              { "color": "red", "value": 300 }
            ]
          },
          "unit": "ms"
        }
      },
      "gridPos": { "h": 4, "w": 4, "x": 8, "y": 1 },
      "id": 4,
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "justifyMode": "auto",
        "orientation": "auto",
        "reduceOptions": {
          "calcs": ["lastNotNull"],
          "fields": "",
          "values": false
        },
        "textMode": "auto"
      },
      "targets": [
        {
          "datasource": { "type": "postgres", "uid": "${DS_POSTGRES}" },
          "format": "table",
          "group": [],
          "rawQuery": true,
          "rawSql": "SELECT PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY latency_ms)\nFROM api_request_metrics\nWHERE recorded_at >= NOW() - INTERVAL '5 minutes' AND status_code = 200",
          "refId": "A"
        }
      ],
      "title": "P99 Latency (5m)",
      "type": "stat"
    },
    {
      "datasource": {
        "type": "postgres",
        "uid": "${DS_POSTGRES}"
      },
      "fieldConfig": {
        "defaults": {
          "color": { "mode": "thresholds" },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              { "color": "red", "value": null },
              { "color": "yellow", "value": 99.9 },
              { "color": "green", "value": 99.95 }
            ]
          },
          "unit": "percent"
        }
      },
      "gridPos": { "h": 4, "w": 4, "x": 12, "y": 1 },
      "id": 5,
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "justifyMode": "auto",
        "orientation": "auto",
        "reduceOptions": {
          "calcs": ["lastNotNull"],
          "fields": "",
          "values": false
        },
        "textMode": "auto"
      },
      "targets": [
        {
          "datasource": { "type": "postgres", "uid": "${DS_POSTGRES}" },
          "format": "table",
          "group": [],
          "rawQuery": true,
          "rawSql": "SELECT ROUND(\n  COUNT(*) FILTER (WHERE status_code = 200) * 100.0 / NULLIF(COUNT(*), 0), \n  4\n)\nFROM api_request_metrics\nWHERE recorded_at >= NOW() - INTERVAL '24 hours'",
          "refId": "A"
        }
      ],
      "title": "Availability (24h)",
      "type": "stat"
    },
    {
      "datasource": {
        "type": "postgres",
        "uid": "${DS_POSTGRES}"
      },
      "fieldConfig": {
        "defaults": {
          "color": { "mode": "thresholds" },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              { "color": "green", "value": null },
              { "color": "yellow", "value": 0.05 },
              { "color": "red", "value": 0.1 }
            ]
          },
          "unit": "percentunit"
        }
      },
      "gridPos": { "h": 4, "w": 4, "x": 16, "y": 1 },
      "id": 6,
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "justifyMode": "auto",
        "orientation": "auto",
        "reduceOptions": {
          "calcs": ["lastNotNull"],
          "fields": "",
          "values": false
        },
        "textMode": "auto"
      },
      "targets": [
        {
          "datasource": { "type": "postgres", "uid": "${DS_POSTGRES}" },
          "format": "table",
          "group": [],
          "rawQuery": true,
          "rawSql": "SELECT ROUND(\n  COUNT(*) FILTER (WHERE status_code != 200 OR is_timeout = TRUE) * 100.0 / NULLIF(COUNT(*), 0), \n  4\n)\nFROM api_request_metrics\nWHERE recorded_at >= NOW() - INTERVAL '5 minutes'",
          "refId": "A"
        }
      ],
      "title": "Error Rate (5m)",
      "type": "stat"
    },
    {
      "datasource": {
        "type": "postgres",
        "uid": "${DS_POSTGRES}"
      },
      "fieldConfig": {
        "defaults": {
          "color": { "mode": "thresholds" },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              { "color": "green", "value": null }
            ]
          },
          "unit": "currencyUSD"
        }
      },
      "gridPos": { "h": 4, "w": 4, "x": 20, "y": 1 },
      "id": 7,
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "justifyMode": "auto",
        "orientation": "auto",
        "reduceOptions": {
          "calcs": ["lastNotNull"],
          "fields": "",
          "values": false
        },
        "textMode": "auto"
      },
      "targets": [
        {
          "datasource": { "type": "postgres", "uid": "${DS_POSTGRES}" },
          "format": "table",
          "group": [],
          "rawQuery": true,
          "rawSql": "WITH model_pricing AS (\n  SELECT 'gpt-4.1' AS model, 8.0 AS price_per_mtok\n  UNION ALL SELECT 'claude-sonnet-4.5', 15.0\n  UNION ALL SELECT 'gemini-2.5-flash', 2.50\n  UNION ALL SELECT 'deepseek-v3.2', 0.42\n)\nSELECT ROUND(\n  SUM(m.tokens_input + m.tokens_output)::NUMERIC / 1000000 * \n  COALESCE(p.price_per_mtok, 1.0),\n  2\n)\nFROM api_request_metrics m\nLEFT JOIN model_pricing p ON m.model = p.model\nWHERE m.recorded_at >= NOW() - INTERVAL '24 hours'\