feat:
--ADDED API to get index size (REST) --ADDED API for tules read and write per second (WS) --ADDED API for get Dead tuples (REST API) --ADDED API for auto vaccume (REST API)
This commit is contained in:
1 parent
8b56c6db8c
commit
3722e71219
1 file changed
+478
@@ -1149,6 +1149,484 @@ def database_largest_tables(
|
||||
|
||||
|
||||
|
||||
# FUNCTION TO GET INDEX SIZE
|
||||
def get_index_size(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
|
||||
COALESCE(
|
||||
SUM(
|
||||
pg_indexes_size(
|
||||
quote_ident(schemaname) || '.' ||
|
||||
quote_ident(relname)
|
||||
)
|
||||
),
|
||||
0
|
||||
) AS index_size_bytes
|
||||
FROM pg_stat_user_tables;
|
||||
""")
|
||||
|
||||
row = cursor.fetchone()
|
||||
|
||||
index_size_bytes = row[0]
|
||||
|
||||
index_size_gb = round(
|
||||
index_size_bytes / 1024 / 1024 / 1024,
|
||||
2
|
||||
)
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
"index_size_bytes": index_size_bytes,
|
||||
"index_size_gb": index_size_gb
|
||||
}
|
||||
|
||||
except psycopg2.Error as e:
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
"index_size_bytes": None,
|
||||
"index_size_gb": None,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
finally:
|
||||
|
||||
if cursor:
|
||||
cursor.close()
|
||||
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
|
||||
# API ENDPOINT FOR INDEX SIZE REST API
|
||||
@app.post("/database/index-size")
|
||||
def database_index_size(
|
||||
data: DatabaseRequest,
|
||||
token_data: dict = Depends(verify_token)
|
||||
):
|
||||
|
||||
return get_index_size(data)
|
||||
|
||||
# GET TUPLE READS AND WRITES PER SECOND
|
||||
def get_tuple_reads_writes(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
|
||||
tup_returned,
|
||||
tup_inserted,
|
||||
tup_updated,
|
||||
tup_deleted
|
||||
FROM pg_stat_database
|
||||
WHERE datname = %s;
|
||||
""", (data.dbname,))
|
||||
|
||||
first = cursor.fetchone()
|
||||
|
||||
start_time = time.monotonic()
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
# Second sample
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
tup_returned,
|
||||
tup_inserted,
|
||||
tup_updated,
|
||||
tup_deleted
|
||||
FROM pg_stat_database
|
||||
WHERE datname = %s;
|
||||
""", (data.dbname,))
|
||||
|
||||
second = cursor.fetchone()
|
||||
|
||||
end_time = time.monotonic()
|
||||
|
||||
elapsed_time = end_time - start_time
|
||||
|
||||
# Reads
|
||||
reads = second[0] - first[0]
|
||||
|
||||
# Writes
|
||||
inserts = second[1] - first[1]
|
||||
updates = second[2] - first[2]
|
||||
deletes = second[3] - first[3]
|
||||
|
||||
writes = inserts + updates + deletes
|
||||
|
||||
reads_per_second = round(
|
||||
reads / elapsed_time,
|
||||
2
|
||||
)
|
||||
|
||||
writes_per_second = round(
|
||||
writes / elapsed_time,
|
||||
2
|
||||
)
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
|
||||
"tuple_reads_per_second":
|
||||
reads_per_second,
|
||||
|
||||
"tuple_writes_per_second":
|
||||
writes_per_second,
|
||||
|
||||
"inserts_per_second":
|
||||
round(inserts / elapsed_time, 2),
|
||||
|
||||
"updates_per_second":
|
||||
round(updates / elapsed_time, 2),
|
||||
|
||||
"deletes_per_second":
|
||||
round(deletes / elapsed_time, 2)
|
||||
}
|
||||
|
||||
except psycopg2.Error as e:
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
"tuple_reads_per_second": None,
|
||||
"tuple_writes_per_second": None,
|
||||
"inserts_per_second": None,
|
||||
"updates_per_second": None,
|
||||
"deletes_per_second": None,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
finally:
|
||||
|
||||
if cursor:
|
||||
cursor.close()
|
||||
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
# TUPLE READS AND WRITES PER SECOND WEB SOCKET
|
||||
@app.websocket("/ws/database/tuple-activity")
|
||||
async def database_tuple_activity_monitor(websocket: WebSocket):
|
||||
|
||||
await websocket.accept()
|
||||
|
||||
try:
|
||||
|
||||
raw_data = await websocket.receive_json()
|
||||
|
||||
data = DatabaseRequest(**raw_data)
|
||||
|
||||
while True:
|
||||
|
||||
try:
|
||||
|
||||
tuple_result = await asyncio.wait_for(
|
||||
asyncio.to_thread(
|
||||
get_tuple_reads_writes,
|
||||
data
|
||||
),
|
||||
timeout=5
|
||||
)
|
||||
|
||||
result = {
|
||||
"database_name": data.dbname,
|
||||
|
||||
"tuple_reads_per_second":
|
||||
tuple_result["tuple_reads_per_second"],
|
||||
|
||||
"tuple_writes_per_second":
|
||||
tuple_result["tuple_writes_per_second"],
|
||||
|
||||
"inserts_per_second":
|
||||
tuple_result["inserts_per_second"],
|
||||
|
||||
"updates_per_second":
|
||||
tuple_result["updates_per_second"],
|
||||
|
||||
"deletes_per_second":
|
||||
tuple_result["deletes_per_second"]
|
||||
}
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
|
||||
result = {
|
||||
"database_name": data.dbname,
|
||||
"tuple_reads_per_second": None,
|
||||
"tuple_writes_per_second": None,
|
||||
"inserts_per_second": None,
|
||||
"updates_per_second": None,
|
||||
"deletes_per_second": None
|
||||
}
|
||||
|
||||
await websocket.send_json(result)
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
except WebSocketDisconnect:
|
||||
|
||||
print("Tuple activity WebSocket disconnected")
|
||||
|
||||
|
||||
|
||||
# GET DEAD TUPLES
|
||||
def get_dead_tuples(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_factory=RealDictCursor
|
||||
)
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
schemaname,
|
||||
relname AS table_name,
|
||||
n_live_tup,
|
||||
n_dead_tup,
|
||||
last_vacuum,
|
||||
last_autovacuum
|
||||
|
||||
FROM pg_stat_user_tables
|
||||
WHERE n_dead_tup > 0
|
||||
ORDER BY n_dead_tup DESC;
|
||||
""")
|
||||
|
||||
rows = cursor.fetchall()
|
||||
|
||||
total_dead_tuples = sum(
|
||||
row["n_dead_tup"] or 0
|
||||
for row in rows
|
||||
)
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
"total_dead_tuples": total_dead_tuples,
|
||||
|
||||
"tables": [
|
||||
{
|
||||
"schema": row["schemaname"],
|
||||
"table_name": row["table_name"],
|
||||
"live_tuples": row["n_live_tup"],
|
||||
"dead_tuples": row["n_dead_tup"],
|
||||
|
||||
"last_vacuum":
|
||||
row["last_vacuum"].isoformat()
|
||||
if row["last_vacuum"]
|
||||
else None,
|
||||
|
||||
"last_autovacuum":
|
||||
row["last_autovacuum"].isoformat()
|
||||
if row["last_autovacuum"]
|
||||
else None
|
||||
}
|
||||
for row in rows
|
||||
]
|
||||
}
|
||||
|
||||
except psycopg2.Error as e:
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
"total_dead_tuples": None,
|
||||
"tables": [],
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
finally:
|
||||
|
||||
if cursor:
|
||||
cursor.close()
|
||||
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
#API ENDPOINT FOR DEAD TUPLES FOR EACH TABLE IN THE DATABASE
|
||||
@app.post("/database/dead-tuples")
|
||||
def database_dead_tuples(
|
||||
data: DatabaseRequest,
|
||||
token_data: dict = Depends(verify_token)
|
||||
):
|
||||
|
||||
return get_dead_tuples(data)
|
||||
|
||||
# AUTO VACUUM STATUS
|
||||
def get_autovacuum_activity(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_factory=RealDictCursor
|
||||
)
|
||||
|
||||
# Check whether autovacuum is globally enabled
|
||||
cursor.execute("""
|
||||
SHOW autovacuum;
|
||||
""")
|
||||
|
||||
autovacuum_enabled = cursor.fetchone()["autovacuum"] == "on"
|
||||
|
||||
|
||||
# Check currently running autovacuum workers
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) AS active_workers
|
||||
FROM pg_stat_activity
|
||||
WHERE datname = %s
|
||||
AND backend_type = 'autovacuum worker';
|
||||
""", (data.dbname,))
|
||||
|
||||
active_workers = cursor.fetchone()["active_workers"]
|
||||
|
||||
|
||||
# Get autovacuum statistics for tables
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
COUNT(*) AS total_tables,
|
||||
|
||||
COUNT(*) FILTER (
|
||||
WHERE autovacuum_count > 0
|
||||
) AS tables_autovacuumed,
|
||||
|
||||
COALESCE(
|
||||
SUM(autovacuum_count),
|
||||
0
|
||||
) AS total_autovacuum_runs,
|
||||
|
||||
MAX(last_autovacuum) AS last_autovacuum
|
||||
|
||||
FROM pg_stat_user_tables;
|
||||
""")
|
||||
|
||||
stats = cursor.fetchone()
|
||||
|
||||
|
||||
if not autovacuum_enabled:
|
||||
|
||||
status = "disabled"
|
||||
|
||||
elif active_workers > 0:
|
||||
|
||||
status = "running"
|
||||
|
||||
else:
|
||||
|
||||
status = "idle"
|
||||
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
|
||||
"status": status,
|
||||
|
||||
"autovacuum_enabled":
|
||||
autovacuum_enabled,
|
||||
|
||||
"active_workers":
|
||||
active_workers,
|
||||
|
||||
"total_tables":
|
||||
stats["total_tables"],
|
||||
|
||||
"tables_autovacuumed":
|
||||
stats["tables_autovacuumed"],
|
||||
|
||||
"total_autovacuum_runs":
|
||||
stats["total_autovacuum_runs"],
|
||||
|
||||
"last_autovacuum":
|
||||
stats["last_autovacuum"].isoformat()
|
||||
if stats["last_autovacuum"]
|
||||
else None
|
||||
}
|
||||
|
||||
except psycopg2.Error as e:
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
"status": "unknown",
|
||||
"autovacuum_enabled": None,
|
||||
"active_workers": None,
|
||||
"total_tables": None,
|
||||
"tables_autovacuumed": None,
|
||||
"total_autovacuum_runs": None,
|
||||
"last_autovacuum": None,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
finally:
|
||||
|
||||
if cursor:
|
||||
cursor.close()
|
||||
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
# API ENDPOINT FOR AUTO VACUUM STATUS
|
||||
@app.post("/database/autovacuum-activity")
|
||||
def database_autovacuum_activity(
|
||||
data: DatabaseRequest,
|
||||
token_data: dict = Depends(verify_token)
|
||||
):
|
||||
|
||||
return get_autovacuum_activity(data)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user