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 |
|
| **Zabbix** | Infrastructure monitoring | Servers, databases, network, disk, CPU, alerts |
|
||||||
| **Datadog** | Commercial monitoring | DB + server + application monitoring |
|
| **Datadog** | Commercial monitoring | DB + server + application monitoring |
|
||||||
| **New Relic** | Application + DB | Slow SQL, response times, application/database relationship |
|
| **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 pydantic import BaseModel
|
||||||
from psycopg2.extras import RealDictCursor
|
from psycopg2.extras import RealDictCursor
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import time
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
app = FastAPI()
|
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__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in new issue
Block a user