feat:
--ADDED performance matrix for db
This commit is contained in:
1 parent
0313fbfee0
commit
39507a1a8d
2 files changed
+236
-1
No files matched your search
@@ -50,3 +50,45 @@ Using my uploaded photo, show me what I would have looked like around 1985. Pres
|
||||
| **Zabbix** | Infrastructure monitoring | Servers, databases, network, disk, CPU, alerts |
|
||||
| **Datadog** | Commercial monitoring | DB + server + application monitoring |
|
||||
| **New Relic** | Application + DB | Slow SQL, response times, application/database relationship |
|
||||
|
||||
|
||||
DASHBOARD
|
||||
|
||||
| Metric | What it tells you | Good dashboard display |
|
||||
| ------------------------ | --------------------------------------------------------- | ------------------------ |
|
||||
| **Database Status** | PostgreSQL reachable or not | 🟢 Online / 🔴 Offline |
|
||||
| **Uptime** | How long PostgreSQL has been running | `12d 7h 32m` |
|
||||
| **Active Connections** | Connections currently executing queries | Gauge |
|
||||
| **Total Connections** | Connections in use | `42 / 100` |
|
||||
| **Connection Usage %** | How close you are to `max_connections` | Gauge / % |
|
||||
| **Idle Connections** | Connections sitting idle | Number |
|
||||
| **TPS** | Transactions per second | Time-series graph |
|
||||
| **Commits/sec** | Successful transaction rate | Graph |
|
||||
| **Rollbacks/sec** | Failed/aborted transaction rate | Graph |
|
||||
| **Cache Hit Ratio** | How often PostgreSQL serves data from RAM instead of disk | % |
|
||||
| **Query Latency** | Average query execution time | ms |
|
||||
| **Slow Queries** | Queries exceeding threshold, e.g. >1s | Table |
|
||||
| **Long Running Queries** | Queries running unusually long | Table |
|
||||
| **Blocked Queries** | Queries waiting on another transaction | Number/Table |
|
||||
| **Locks** | Current DB locks | Gauge |
|
||||
| **Deadlocks** | Deadlocks detected | Counter |
|
||||
| **Database Size** | Total database disk usage | GB |
|
||||
| **Table Size** | Largest tables | Bar chart |
|
||||
| **Index Size** | Storage consumed by indexes | GB |
|
||||
| **Tuple Reads / Writes** | Database activity | Graph |
|
||||
| **Rows Inserted/sec** | INSERT activity | Graph |
|
||||
| **Rows Updated/sec** | UPDATE activity | Graph |
|
||||
| **Rows Deleted/sec** | DELETE activity | Graph |
|
||||
| **Sequential Scans** | Potentially expensive full-table scans | Counter |
|
||||
| **Index Scans** | Index usage | Counter |
|
||||
| **Dead Tuples** | Rows VACUUM needs to clean | Number |
|
||||
| **Autovacuum Activity** | Whether cleanup is working | Status |
|
||||
| **WAL Generated/sec** | Write workload / replication workload | MB/s |
|
||||
| **WAL Size** | WAL storage usage | GB |
|
||||
| **Replication Lag** | Delay between nodes | ms / MB |
|
||||
| **Replication Status** | Whether link is healthy | 🟢 Replicating / 🔴 Down |
|
||||
| **Replication Slot Lag** | WAL retained because subscriber hasn't consumed it | MB |
|
||||
| **CPU Usage** | DB server CPU load | % |
|
||||
| **RAM Usage** | Server memory pressure | % |
|
||||
| **Disk Usage** | Remaining DB server disk | % |
|
||||
| **Disk I/O** | Storage read/write activity | MB/s |
|
||||
@@ -8,7 +8,7 @@ import jwt
|
||||
from pydantic import BaseModel
|
||||
from psycopg2.extras import RealDictCursor
|
||||
import asyncio
|
||||
|
||||
import time
|
||||
load_dotenv()
|
||||
app = FastAPI()
|
||||
|
||||
@@ -590,12 +590,205 @@ async def database_active_connections_monitor(websocket: WebSocket):
|
||||
)
|
||||
|
||||
|
||||
#TPS
|
||||
def get_database_tps(data: DatabaseRequest):
|
||||
|
||||
conn = None
|
||||
cursor = None
|
||||
|
||||
try:
|
||||
|
||||
conn = psycopg2.connect(
|
||||
host=data.host,
|
||||
port=data.port,
|
||||
dbname=data.dbname,
|
||||
user=data.user,
|
||||
password=data.password,
|
||||
connect_timeout=3
|
||||
)
|
||||
|
||||
conn.autocommit = True
|
||||
cursor = conn.cursor()
|
||||
|
||||
# First sample
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
xact_commit + xact_rollback
|
||||
FROM pg_stat_database
|
||||
WHERE datname = %s;
|
||||
""", (data.dbname,))
|
||||
|
||||
first_count = cursor.fetchone()[0]
|
||||
|
||||
start_time = time.monotonic()
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
# Second sample
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
xact_commit + xact_rollback
|
||||
FROM pg_stat_database
|
||||
WHERE datname = %s;
|
||||
""", (data.dbname,))
|
||||
|
||||
second_count = cursor.fetchone()[0]
|
||||
|
||||
end_time = time.monotonic()
|
||||
|
||||
elapsed_time = end_time - start_time
|
||||
|
||||
transactions = second_count - first_count
|
||||
|
||||
tps = round(
|
||||
transactions / elapsed_time,
|
||||
2
|
||||
)
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
"tps": tps
|
||||
}
|
||||
|
||||
except psycopg2.Error as e:
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
"tps": None,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
finally:
|
||||
|
||||
if cursor:
|
||||
cursor.close()
|
||||
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
# CACHE HIT RATIO
|
||||
def get_cache_hit_ratio(data: DatabaseRequest):
|
||||
|
||||
conn = None
|
||||
cursor = None
|
||||
|
||||
try:
|
||||
|
||||
conn = psycopg2.connect(
|
||||
host=data.host,
|
||||
port=data.port,
|
||||
dbname=data.dbname,
|
||||
user=data.user,
|
||||
password=data.password,
|
||||
connect_timeout=3
|
||||
)
|
||||
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
CASE
|
||||
WHEN (blks_hit + blks_read) = 0 THEN 0
|
||||
ELSE ROUND(
|
||||
(blks_hit::numeric /
|
||||
(blks_hit + blks_read)) * 100,
|
||||
2
|
||||
)
|
||||
END AS cache_hit_ratio
|
||||
FROM pg_stat_database
|
||||
WHERE datname = %s;
|
||||
""", (data.dbname,))
|
||||
|
||||
row = cursor.fetchone()
|
||||
|
||||
cache_hit_ratio = float(row[0]) if row else 0.0
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
"cache_hit_ratio": cache_hit_ratio
|
||||
}
|
||||
|
||||
except psycopg2.Error as e:
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
"cache_hit_ratio": None,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
finally:
|
||||
|
||||
if cursor:
|
||||
cursor.close()
|
||||
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
#TPS AND CACHE HIT RATIO WEB SOCKET
|
||||
@app.websocket("/ws/database/performance")
|
||||
async def database_performance_monitor(websocket: WebSocket):
|
||||
|
||||
await websocket.accept()
|
||||
|
||||
try:
|
||||
|
||||
raw_data = await websocket.receive_json()
|
||||
|
||||
data = DatabaseRequest(**raw_data)
|
||||
|
||||
while True:
|
||||
|
||||
try:
|
||||
|
||||
(
|
||||
tps_result,
|
||||
cache_hit_result
|
||||
) = await asyncio.wait_for(
|
||||
|
||||
asyncio.gather(
|
||||
|
||||
asyncio.to_thread(
|
||||
get_database_tps,
|
||||
data
|
||||
),
|
||||
|
||||
asyncio.to_thread(
|
||||
get_cache_hit_ratio,
|
||||
data
|
||||
)
|
||||
|
||||
),
|
||||
|
||||
timeout=5
|
||||
)
|
||||
|
||||
result = {
|
||||
"database_name": data.dbname,
|
||||
|
||||
"tps": tps_result[
|
||||
"tps"
|
||||
],
|
||||
|
||||
"cache_hit_ratio": cache_hit_result[
|
||||
"cache_hit_ratio"
|
||||
]
|
||||
}
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
|
||||
result = {
|
||||
"database_name": data.dbname,
|
||||
"tps": None,
|
||||
"cache_hit_ratio": None
|
||||
}
|
||||
|
||||
await websocket.send_json(result)
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
except WebSocketDisconnect:
|
||||
|
||||
print("Database performance WebSocket disconnected")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user