diff --git a/feat.txt b/feat.txt index 5f7e946..64c51cb 100644 --- a/feat.txt +++ b/feat.txt @@ -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 | diff --git a/main.py b/main.py index b0c49e2..9860dd4 100644 --- a/main.py +++ b/main.py @@ -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__":