feat:
--ADDED WAL GENERATED PER SECOND --ADDED GET WAL SIZE --ADDED REPLICATION LAG
This commit is contained in:
1 parent
3722e71219
commit
f43eaee98f
1 file changed
+370
@@ -1626,19 +1626,389 @@ def database_autovacuum_activity(
|
||||
|
||||
return get_autovacuum_activity(data)
|
||||
|
||||
# WAL GENERATED PER SECOND
|
||||
def get_wal_generated_per_second(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 wal_bytes
|
||||
FROM pg_stat_wal;
|
||||
""")
|
||||
first_wal_bytes = float(cursor.fetchone()[0])
|
||||
|
||||
start_time = time.monotonic()
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
# Second sample
|
||||
cursor.execute("""
|
||||
SELECT wal_bytes
|
||||
FROM pg_stat_wal;
|
||||
""")
|
||||
|
||||
second_wal_bytes = float(cursor.fetchone()[0])
|
||||
|
||||
end_time = time.monotonic()
|
||||
|
||||
elapsed_time = end_time - start_time
|
||||
|
||||
generated_bytes = (
|
||||
second_wal_bytes - first_wal_bytes
|
||||
)
|
||||
|
||||
wal_bytes_per_second = (
|
||||
generated_bytes / elapsed_time
|
||||
)
|
||||
|
||||
wal_mb_per_second = round(
|
||||
wal_bytes_per_second / 1024 / 1024,
|
||||
2
|
||||
)
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
"wal_generated_bytes_per_second":
|
||||
round(wal_bytes_per_second, 2),
|
||||
"wal_generated_mb_per_second":
|
||||
wal_mb_per_second
|
||||
}
|
||||
|
||||
except psycopg2.Error as e:
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
"wal_generated_bytes_per_second": None,
|
||||
"wal_generated_mb_per_second": None,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
finally:
|
||||
|
||||
if cursor:
|
||||
cursor.close()
|
||||
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
@app.websocket("/ws/database/wal-generated")
|
||||
async def database_wal_generated_monitor(websocket: WebSocket):
|
||||
|
||||
await websocket.accept()
|
||||
|
||||
try:
|
||||
|
||||
raw_data = await websocket.receive_json()
|
||||
|
||||
data = DatabaseRequest(**raw_data)
|
||||
|
||||
while True:
|
||||
|
||||
try:
|
||||
|
||||
wal_result = await asyncio.wait_for(
|
||||
asyncio.to_thread(
|
||||
get_wal_generated_per_second,
|
||||
data
|
||||
),
|
||||
timeout=5
|
||||
)
|
||||
|
||||
result = {
|
||||
"database_name": data.dbname,
|
||||
"wal_generated_bytes_per_second":
|
||||
wal_result["wal_generated_bytes_per_second"],
|
||||
"wal_generated_mb_per_second":
|
||||
wal_result["wal_generated_mb_per_second"]
|
||||
}
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
|
||||
result = {
|
||||
"database_name": data.dbname,
|
||||
"wal_generated_bytes_per_second": None,
|
||||
"wal_generated_mb_per_second": None
|
||||
}
|
||||
|
||||
await websocket.send_json(result)
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
except WebSocketDisconnect:
|
||||
|
||||
print("WAL generated WebSocket disconnected")
|
||||
|
||||
|
||||
|
||||
# GET WAL SIZE
|
||||
def get_wal_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(size), 0)
|
||||
FROM pg_ls_waldir();
|
||||
""")
|
||||
|
||||
wal_size_bytes = cursor.fetchone()[0]
|
||||
|
||||
wal_size_mb = round(
|
||||
wal_size_bytes / 1024 / 1024,
|
||||
2
|
||||
)
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
"wal_size_bytes": wal_size_bytes,
|
||||
"wal_size_mb": wal_size_mb
|
||||
}
|
||||
|
||||
except psycopg2.Error as e:
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
"wal_size_bytes": None,
|
||||
"wal_size_mb": None,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
finally:
|
||||
|
||||
if cursor:
|
||||
cursor.close()
|
||||
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
|
||||
#REST API ENDPOINT FOR WAL SIZE
|
||||
@app.post("/database/wal-size")
|
||||
def database_wal_size(
|
||||
data: DatabaseRequest,
|
||||
token_data: dict = Depends(verify_token)
|
||||
):
|
||||
|
||||
return get_wal_size(data)
|
||||
|
||||
|
||||
#REPLICATION LAG
|
||||
def get_replication_lag(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
|
||||
pid,
|
||||
application_name,
|
||||
client_addr,
|
||||
state,
|
||||
|
||||
sent_lsn,
|
||||
write_lsn,
|
||||
flush_lsn,
|
||||
replay_lsn,
|
||||
|
||||
CASE
|
||||
WHEN replay_lag IS NOT NULL
|
||||
THEN ROUND(
|
||||
EXTRACT(EPOCH FROM replay_lag)::numeric * 1000,
|
||||
2
|
||||
)
|
||||
ELSE NULL
|
||||
END AS lag_ms,
|
||||
|
||||
CASE
|
||||
WHEN COALESCE(
|
||||
replay_lsn,
|
||||
flush_lsn,
|
||||
write_lsn,
|
||||
sent_lsn
|
||||
) IS NOT NULL
|
||||
THEN ROUND(
|
||||
(
|
||||
pg_wal_lsn_diff(
|
||||
pg_current_wal_lsn(),
|
||||
COALESCE(
|
||||
replay_lsn,
|
||||
flush_lsn,
|
||||
write_lsn,
|
||||
sent_lsn
|
||||
)
|
||||
) / 1024 / 1024
|
||||
)::numeric,
|
||||
2
|
||||
)
|
||||
ELSE NULL
|
||||
END AS lag_mb
|
||||
|
||||
FROM pg_stat_replication
|
||||
|
||||
ORDER BY application_name;
|
||||
""")
|
||||
|
||||
rows = cursor.fetchall()
|
||||
|
||||
replicas = []
|
||||
|
||||
for row in rows:
|
||||
|
||||
replicas.append({
|
||||
"pid": row["pid"],
|
||||
"application_name":
|
||||
row["application_name"],
|
||||
|
||||
"client_addr":
|
||||
str(row["client_addr"])
|
||||
if row["client_addr"]
|
||||
else None,
|
||||
|
||||
"state":
|
||||
row["state"],
|
||||
|
||||
"lag_ms":
|
||||
float(row["lag_ms"])
|
||||
if row["lag_ms"] is not None
|
||||
else None,
|
||||
|
||||
"lag_mb":
|
||||
float(row["lag_mb"])
|
||||
if row["lag_mb"] is not None
|
||||
else None,
|
||||
|
||||
"sent_lsn":
|
||||
str(row["sent_lsn"])
|
||||
if row["sent_lsn"]
|
||||
else None,
|
||||
|
||||
"write_lsn":
|
||||
str(row["write_lsn"])
|
||||
if row["write_lsn"]
|
||||
else None,
|
||||
|
||||
"flush_lsn":
|
||||
str(row["flush_lsn"])
|
||||
if row["flush_lsn"]
|
||||
else None,
|
||||
|
||||
"replay_lsn":
|
||||
str(row["replay_lsn"])
|
||||
if row["replay_lsn"]
|
||||
else None
|
||||
})
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
"replica_count": len(replicas),
|
||||
"replicas": replicas
|
||||
}
|
||||
|
||||
except psycopg2.Error as e:
|
||||
|
||||
return {
|
||||
"database_name": data.dbname,
|
||||
"replica_count": None,
|
||||
"replicas": [],
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
finally:
|
||||
|
||||
if cursor:
|
||||
cursor.close()
|
||||
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
# WEBSOCKET FOR REPLICATION LAG MONITORING
|
||||
#OUTPUT
|
||||
#{"database_name":"postgres","replica_count":2,"replicas":[{"pid":51339,"application_name":"spk_postgres_node3_node1_from_node3","client_addr":"172.16.10.123","state":"streaming","lag_ms":null,"lag_mb":0.0,"sent_lsn":"0/56681A0","write_lsn":"0/56681A0","flush_lsn":"0/56681A0","replay_lsn":"0/56681A0"},{"pid":51328,"application_name":"spk_postgres_node3_node2_from_node3","client_addr":"172.16.10.110","state":"streaming","lag_ms":null,"lag_mb":0.0,"sent_lsn":"0/56681A0","write_lsn":"0/56681A0","flush_lsn":"0/56681A0","replay_lsn":"0/56681A0"}]}
|
||||
|
||||
@app.websocket("/ws/database/replication-lag")
|
||||
async def database_replication_lag_monitor(websocket: WebSocket):
|
||||
|
||||
await websocket.accept()
|
||||
|
||||
try:
|
||||
|
||||
raw_data = await websocket.receive_json()
|
||||
data = DatabaseRequest(**raw_data)
|
||||
|
||||
while True:
|
||||
|
||||
try:
|
||||
|
||||
replication_result = await asyncio.wait_for(
|
||||
asyncio.to_thread(
|
||||
get_replication_lag,
|
||||
data
|
||||
),
|
||||
timeout=5
|
||||
)
|
||||
|
||||
result = replication_result
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
|
||||
result = {
|
||||
"database_name": data.dbname,
|
||||
"replica_count": None,
|
||||
"replicas": [],
|
||||
"error": "Replication lag check timed out"
|
||||
}
|
||||
|
||||
await websocket.send_json(result)
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
except WebSocketDisconnect:
|
||||
|
||||
print("Replication lag WebSocket disconnected")
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user