260707_1_대시보드정리 1차
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
"""시스템 리소스 계측 및 로그 관리.
|
||||
|
||||
- 2분(config 설정) 간격으로 CPU/메모리/디스크 사용률을 계측한다.
|
||||
- 계측값을 `system_resources` DB 테이블과 루트 `log/system_resources.log`에 함께 기록한다.
|
||||
- 로그 파일과 DB 이력은 항상 최근 1개월치만 유지한다(오래된 줄 삭제 + 새 줄 추가).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import shutil
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import psutil
|
||||
|
||||
from config.config_db import get_db_pool
|
||||
from config.config_system import (
|
||||
RESOURCE_LOG_PATH,
|
||||
RESOURCE_LOG_RETENTION_DAYS,
|
||||
RESOURCE_SAMPLE_INTERVAL_SEC,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _sample() -> dict[str, float | int]:
|
||||
"""현재 시점 리소스 사용률을 계측한다."""
|
||||
total, used, _free = shutil.disk_usage(".")
|
||||
return {
|
||||
"cpu_usage_percent": round(psutil.cpu_percent(interval=0.1), 2),
|
||||
"memory_usage_percent": round(psutil.virtual_memory().percent, 2),
|
||||
"disk_usage_percent": round(used / total * 100 if total else 0, 2),
|
||||
"total_storage_mb": round(used / 1024 / 1024, 2),
|
||||
}
|
||||
|
||||
|
||||
def _trim_log_file(retention_days: int) -> list[str]:
|
||||
"""로그 파일에서 보관 기간이 지난 줄을 제거하고 남은 줄을 반환한다."""
|
||||
path = Path(RESOURCE_LOG_PATH)
|
||||
if not path.exists():
|
||||
return []
|
||||
cutoff = datetime.now(timezone.utc) - timedelta(days=retention_days)
|
||||
kept: list[str] = []
|
||||
for line in path.read_text(encoding="utf-8").splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
try:
|
||||
ts = datetime.fromisoformat(line.split("\t", 1)[0])
|
||||
except ValueError:
|
||||
continue
|
||||
if ts >= cutoff:
|
||||
kept.append(line)
|
||||
return kept
|
||||
|
||||
|
||||
def _write_log(sample: dict[str, float | int], now: datetime) -> None:
|
||||
"""최근 1개월치만 유지하며 새 계측 줄을 로그 파일에 기록한다."""
|
||||
path = Path(RESOURCE_LOG_PATH)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
lines = _trim_log_file(RESOURCE_LOG_RETENTION_DAYS)
|
||||
new_line = (
|
||||
f"{now.isoformat()}\t"
|
||||
f"cpu={sample['cpu_usage_percent']}\t"
|
||||
f"mem={sample['memory_usage_percent']}\t"
|
||||
f"disk={sample['disk_usage_percent']}\t"
|
||||
f"storage_mb={sample['total_storage_mb']}"
|
||||
)
|
||||
lines.append(new_line)
|
||||
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
async def _write_db(sample: dict[str, float | int]) -> None:
|
||||
"""DB에 계측값을 저장하고 보관 기간이 지난 행을 삭제한다."""
|
||||
pool = get_db_pool()
|
||||
async with pool.acquire() as connection, connection.cursor() as cursor:
|
||||
await cursor.execute(
|
||||
"""SELECT
|
||||
(SELECT COUNT(*) FROM sessions
|
||||
WHERE expires_at > CURRENT_TIMESTAMP),
|
||||
(SELECT COUNT(*) FROM projects WHERE deleted_at IS NULL)"""
|
||||
)
|
||||
active_users, active_projects = await cursor.fetchone()
|
||||
await cursor.execute(
|
||||
"""INSERT INTO system_resources
|
||||
(cpu_usage_percent, memory_usage_percent, disk_usage_percent,
|
||||
active_user_count, active_project_count, total_storage_mb)
|
||||
VALUES (%s, %s, %s, %s, %s, %s)""",
|
||||
(
|
||||
sample["cpu_usage_percent"],
|
||||
sample["memory_usage_percent"],
|
||||
sample["disk_usage_percent"],
|
||||
active_users,
|
||||
active_projects,
|
||||
sample["total_storage_mb"],
|
||||
),
|
||||
)
|
||||
await cursor.execute(
|
||||
"DELETE FROM system_resources WHERE timestamp < %s",
|
||||
(datetime.utcnow() - timedelta(days=RESOURCE_LOG_RETENTION_DAYS),),
|
||||
)
|
||||
await connection.commit()
|
||||
|
||||
|
||||
async def sample_resources_loop() -> None:
|
||||
"""앱 라이프사이클 동안 주기적으로 리소스를 계측·기록하는 백그라운드 태스크."""
|
||||
while True:
|
||||
try:
|
||||
sample = _sample()
|
||||
_write_log(sample, datetime.now(timezone.utc))
|
||||
await _write_db(sample)
|
||||
except Exception as exc: # noqa: BLE001 — 계측 실패로 앱이 죽지 않게 방어
|
||||
logger.warning("리소스 계측 실패: %s", exc)
|
||||
await asyncio.sleep(RESOURCE_SAMPLE_INTERVAL_SEC)
|
||||
Reference in New Issue
Block a user