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:
- Collector Agent: Ghi nhận mọi request/response từ HolySheep API
- Metrics Store: PostgreSQL + TimescaleDB để lưu time-series data
- Aggregator: Tính toán P50/P95/P99 theo sliding window
- Dashboard: Grafana để visualize dữ liệu
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'\