상태 저장 기능을 개선하고, 상태 저장 시도 및 성공/실패 로그를 추가하였습니다. 또한, 세션 풀 상태 조회 방식을 최적화하고, 모니터링 대시보드의 데이터 수집 로직을 강화하였습니다. JSON 파일 포맷을 개선하여 가독성을 높였습니다.

This commit is contained in:
AGX 2025-08-28 01:45:58 +09:00
parent 4e1898463a
commit 5ee280ea27
10 changed files with 14803 additions and 304 deletions

View File

@ -225,17 +225,16 @@ class SessionPool:
"""세션 풀의 현재 상태를 반환합니다."""
status_by_model = {}
all_sessions = list(self.pools.values()) # Flatten all sessions from all models
for model_type in ModelType:
model_sessions = [s for s in all_sessions if s.model_type == model_type]
in_use_count = sum(1 for s in model_sessions if s.in_use)
available_count = len(model_sessions) - in_use_count
pool = self.pools[model_type]
total = len(pool)
in_use = sum(1 for session in pool if session.in_use)
available = total - in_use
status_by_model[model_type.value] = {
"total": len(model_sessions),
"in_use": in_use_count,
"available": available_count
"total": total,
"in_use": in_use,
"available": available
}
return status_by_model

View File

@ -310,7 +310,7 @@ class WorkerManager:
"error": []
}
for worker in self.workers:
for worker_id, worker in self.workers.items():
status_data = {
"id": worker.worker_id,
"status": worker.status.value,

View File

@ -89,64 +89,131 @@ class MonitoringData:
async def collect_data(self) -> Dict[str, Any]:
"""주기적으로 서버 상태 데이터를 수집합니다."""
status = read_status_from_file()
worker_status = status.get("worker_status", {})
session_status = status.get("session_status", {})
timestamp = status.get("timestamp", 0)
# 워커 매니저 상태 (안전하게 가져오기)
try:
worker_status = worker_manager.get_status() if worker_manager else self._get_default_worker_status()
except Exception as e:
logger.warning(f"워커 매니저 상태 조회 실패: {e}")
worker_status = self._get_default_worker_status()
# 세션 풀 상태 (안전하게 가져오기)
try:
session_status = session_pool.get_status() if session_pool else self._get_default_session_status()
except Exception as e:
logger.warning(f"세션 풀 상태 조회 실패: {e}")
session_status = self._get_default_session_status()
# Jetson 전용 정보 (안전하게 가져오기)
jetson_info = {}
if settings.IS_JETSON:
try:
jetson_info = gpu_monitor.get_jetson_specific_info()
if jetson_info is None:
jetson_info = {}
except Exception as e:
logger.warning(f"Jetson 전용 정보 조회 실패: {e}")
jetson_info = {}
# API 통계
api_stats = self._get_api_statistics()
# 알림 및 경고
alerts = self._check_alerts(worker_status)
data = {
"timestamp": datetime.now().isoformat(),
"system_type": "Jetson Xavier" if settings.IS_JETSON else "x86_64",
"gpu": {
**gpu_monitor.get_gpu_memory_info(),
"utilization": gpu_monitor.get_gpu_utilization()
},
"system_memory": gpu_monitor.get_system_memory_info(),
"system_performance": self._get_system_performance(),
"workers": worker_status,
"sessions": session_status,
"jetson": jetson_info,
"api_stats": api_stats,
"alerts": alerts
}
# 히스토리에 추가
self.history.append(data)
if len(self.history) > self.max_history:
self.history.pop(0)
logger.info("데이터 수집 시작")
return data
# status.json 파일에서 상태 읽기
status = read_status_from_file()
logger.info(f"status.json 읽기 완료: {bool(status)}")
worker_status = status.get("worker_status", {})
session_status = status.get("session_status", {})
api_stats = status.get("api_stats", {})
timestamp = status.get("timestamp", 0)
logger.info(f"워커 상태: {bool(worker_status)}, 세션 상태: {bool(session_status)}")
# status.json에서 읽어온 데이터가 없으면 기본값 사용
if not worker_status:
logger.info("워커 상태가 비어있어 기본값 사용")
worker_status = self._get_default_worker_status()
if not session_status:
logger.info("세션 상태가 비어있어 기본값 사용")
session_status = self._get_default_session_status()
# GPU 정보 (안전하게 가져오기)
gpu_info = {}
try:
logger.info("GPU 정보 수집 시작")
gpu_info = {
**gpu_monitor.get_gpu_memory_info(),
"utilization": gpu_monitor.get_gpu_utilization()
}
logger.info("GPU 정보 수집 완료")
except Exception as e:
logger.warning(f"GPU 정보 조회 실패: {e}")
gpu_info = {"total": 0, "used": 0, "free": 0, "usage_percent": 0, "utilization": 0}
# 시스템 메모리 정보 (안전하게 가져오기)
system_memory = {}
try:
logger.info("시스템 메모리 정보 수집 시작")
system_memory = gpu_monitor.get_system_memory_info()
logger.info("시스템 메모리 정보 수집 완료")
except Exception as e:
logger.warning(f"시스템 메모리 정보 조회 실패: {e}")
system_memory = {"total": 0, "used": 0, "free": 0, "usage_percent": 0}
# 시스템 성능 정보 (안전하게 가져오기)
system_performance = {}
try:
logger.info("시스템 성능 정보 수집 시작")
system_performance = self._get_system_performance()
logger.info("시스템 성능 정보 수집 완료")
except Exception as e:
logger.warning(f"시스템 성능 정보 조회 실패: {e}")
system_performance = {"cpu_percent": 0, "cpu_count": 1, "cpu_freq": 0}
# Jetson 전용 정보 (안전하게 가져오기)
jetson_info = {}
if settings.IS_JETSON:
try:
logger.info("Jetson 전용 정보 수집 시작")
jetson_info = gpu_monitor.get_jetson_specific_info()
if jetson_info is None:
jetson_info = {}
logger.info("Jetson 전용 정보 수집 완료")
except Exception as e:
logger.warning(f"Jetson 전용 정보 조회 실패: {e}")
jetson_info = {}
# API 통계는 status.json에서 읽어온 것을 사용
if not api_stats:
logger.info("API 통계가 비어있어 기본값 사용")
api_stats = self._get_api_statistics()
# 알림 및 경고 (안전하게 가져오기)
alerts = []
try:
logger.info("알림 확인 시작")
alerts = self._check_alerts(worker_status)
logger.info("알림 확인 완료")
except Exception as e:
logger.warning(f"알림 확인 실패: {e}")
alerts = []
logger.info("데이터 구조 생성 시작")
data = {
"timestamp": datetime.now().isoformat(),
"system_type": "Jetson Xavier" if settings.IS_JETSON else "x86_64",
"gpu": gpu_info,
"system_memory": system_memory,
"system_performance": system_performance,
"workers": worker_status,
"sessions": session_status,
"jetson": jetson_info,
"api_stats": api_stats,
"alerts": alerts
}
logger.info("히스토리에 데이터 추가 시작")
# 히스토리에 추가
self.history.append(data)
if len(self.history) > self.max_history:
self.history.pop(0)
logger.info("데이터 수집 완료")
return data
except Exception as e:
logger.error(f"데이터 수집 중 오류 발생: {e}")
import traceback
logger.error(f"상세 오류: {traceback.format_exc()}")
# 기본 데이터 반환
return {
"timestamp": datetime.now().isoformat(),
"system_type": "Jetson Xavier" if settings.IS_JETSON else "x86_64",
"gpu": {"total": 0, "used": 0, "free": 0, "usage_percent": 0, "utilization": 0},
"system_memory": {"total": 0, "used": 0, "free": 0, "usage_percent": 0},
"system_performance": {"cpu_percent": 0, "cpu_count": 1, "cpu_freq": 0},
"workers": self._get_default_worker_status(),
"sessions": self._get_default_session_status(),
"jetson": {},
"api_stats": self._get_api_statistics(),
"alerts": [],
"error": str(e)
}
def _get_system_performance(self) -> Dict[str, Any]:
"""시스템 성능 지표를 수집합니다."""
@ -219,14 +286,31 @@ class MonitoringData:
alerts = []
current_time = datetime.now()
# 워커 상태 경고
if worker_status.get("active_workers", 0) == 0:
# 워커 상태 경고 - total_workers 필드 사용
total_workers = worker_status.get("total_workers", 0)
running = worker_status.get("running", False)
if not running:
alerts.append({
"level": "critical",
"message": "워커 매니저가 중지되었습니다",
"timestamp": current_time.isoformat(),
"category": "workers"
})
elif total_workers == 0:
alerts.append({
"level": "critical",
"message": "활성 워커가 없습니다",
"timestamp": current_time.isoformat(),
"category": "workers"
})
elif total_workers < 1:
alerts.append({
"level": "warning",
"message": f"워커 수가 부족합니다 (현재: {total_workers}개)",
"timestamp": current_time.isoformat(),
"category": "workers"
})
# Jetson 전용 경고
if settings.IS_JETSON:
@ -737,46 +821,62 @@ HTML_TEMPLATE = """
};
function updateDashboard(data) {
console.log("받은 데이터:", data); // 디버깅용 로그 추가
// 기본 메트릭 업데이트
document.getElementById('system-type').textContent = data.system_type;
document.getElementById('cpu-usage').textContent = data.system_performance?.cpu?.usage_percent?.toFixed(1) + '%' || '-';
document.getElementById('system-memory').textContent = data.system_memory?.usage_percent?.toFixed(1) + '%' || '-';
document.getElementById('system-type').textContent = data.system_type || '-';
document.getElementById('cpu-usage').textContent = (data.system_performance?.cpu?.usage_percent || 0).toFixed(1) + '%';
document.getElementById('system-memory').textContent = (data.system_memory?.usage_percent || 0).toFixed(1) + '%';
document.getElementById('process-count').textContent = data.system_performance?.processes || '-';
document.getElementById('gpu-memory').textContent = data.gpu?.usage_percent?.toFixed(1) + '%' || '-';
document.getElementById('gpu-util').textContent = data.gpu?.utilization?.toFixed(1) + '%' || '-';
document.getElementById('gpu-temp').textContent = data.jetson?.temperature?.gpu?.toFixed(1) + '°C' || '-';
document.getElementById('gpu-clock').textContent = data.jetson?.gpu_frequency?.toFixed(0) + 'MHz' || '-';
// GPU 정보 업데이트
document.getElementById('gpu-memory').textContent = (data.gpu?.usage_percent || 0).toFixed(1) + '%';
document.getElementById('gpu-util').textContent = (data.gpu?.utilization || 0).toFixed(1) + '%';
document.getElementById('gpu-temp').textContent = (data.jetson?.temperature?.gpu || 0).toFixed(1) + '°C';
document.getElementById('gpu-clock').textContent = (data.jetson?.gpu_frequency || 0).toFixed(0) + 'MHz';
document.getElementById('worker-count').textContent = data.workers?.active_workers || '-';
document.getElementById('session-pool').textContent = data.sessions?.total_sessions || '-';
document.getElementById('queue-size').textContent = data.workers?.queue_size || '-';
document.getElementById('worker-status').textContent = data.workers?.status || '-';
// 워커 정보 업데이트
if (data.workers) {
document.getElementById('worker-count').textContent = data.workers.total_workers || 0;
document.getElementById('session-pool').textContent = Object.keys(data.sessions || {}).length;
document.getElementById('queue-size').textContent = data.workers.queue_size || 0;
document.getElementById('worker-status').textContent = data.workers.running ? '실행 중' : '중지됨';
// 워커 상태별 상세 정보 표시 (디버깅용)
console.log("워커 상태:", data.workers);
if (data.workers.workers_by_status) {
const idleCount = data.workers.workers_by_status.idle?.length || 0;
const busyCount = data.workers.workers_by_status.busy?.length || 0;
console.log(`유휴 워커: ${idleCount}, 작업 워커: ${busyCount}`);
}
}
// API 통계 업데이트
document.getElementById('total-requests').textContent = data.api_stats?.total_requests || '-';
document.getElementById('success-rate').textContent = (data.api_stats?.success_rate?.toFixed(1) || '0') + '%';
document.getElementById('avg-response-time').textContent = (data.api_stats?.average_response_time?.toFixed(2) || '0') + 'ms';
document.getElementById('error-count').textContent = data.api_stats?.failed_requests || '-';
if (data.api_stats) {
document.getElementById('total-requests').textContent = data.api_stats.total_requests || 0;
document.getElementById('success-rate').textContent = (data.api_stats.success_rate || 0).toFixed(1) + '%';
document.getElementById('avg-response-time').textContent = (data.api_stats.average_response_time || 0).toFixed(2) + 'ms';
document.getElementById('error-count').textContent = data.api_stats.failed_requests || 0;
}
// 시스템 성능 상세 업데이트
if (data.system_performance?.cpu) {
document.getElementById('cpu-count').textContent = data.system_performance.cpu.count || '-';
document.getElementById('cpu-freq').textContent = (data.system_performance.cpu.frequency_mhz?.toFixed(0) || '0') + 'MHz';
document.getElementById('load-1min').textContent = data.system_performance.cpu.load_average?.toFixed(2) || '-';
document.getElementById('load-5min').textContent = data.system_performance.cpu.load_average?.toFixed(2) || '-';
document.getElementById('cpu-freq').textContent = (data.system_performance.cpu.frequency_mhz || 0).toFixed(0) + 'MHz';
document.getElementById('load-1min').textContent = (data.system_performance.cpu.load_average?.['1min'] || 0).toFixed(2);
document.getElementById('load-5min').textContent = (data.system_performance.cpu.load_average?.['5min'] || 0).toFixed(2);
}
if (data.system_performance?.disk) {
document.getElementById('disk-read').textContent = (data.system_performance.disk.read_bytes / 1024 / 1024).toFixed(2);
document.getElementById('disk-write').textContent = (data.system_performance.disk.write_bytes / 1024 / 1024).toFixed(2);
document.getElementById('disk-read').textContent = ((data.system_performance.disk.read_bytes || 0) / 1024 / 1024).toFixed(2);
document.getElementById('disk-write').textContent = ((data.system_performance.disk.write_bytes || 0) / 1024 / 1024).toFixed(2);
document.getElementById('disk-read-count').textContent = data.system_performance.disk.read_count || '-';
document.getElementById('disk-write-count').textContent = data.system_performance.disk.write_count || '-';
}
if (data.system_performance?.network) {
document.getElementById('net-sent').textContent = (data.system_performance.network.bytes_sent / 1024 / 1024).toFixed(2);
document.getElementById('net-recv').textContent = (data.system_performance.network.bytes_recv / 1024 / 1024).toFixed(2);
document.getElementById('net-sent').textContent = ((data.system_performance.network.bytes_sent || 0) / 1024 / 1024).toFixed(2);
document.getElementById('net-recv').textContent = ((data.system_performance.network.bytes_recv || 0) / 1024 / 1024).toFixed(2);
document.getElementById('net-sent-pkts').textContent = data.system_performance.network.packets_sent || '-';
document.getElementById('net-recv-pkts').textContent = data.system_performance.network.packets_recv || '-';
}
@ -877,20 +977,50 @@ async def get_status():
"""실시간 서버 상태 데이터를 반환합니다."""
return await monitoring_data.collect_data()
# 간단한 상태 엔드포인트
@api_router.get("/simple")
def get_simple_status():
"""간단한 상태 정보를 반환합니다."""
async def get_simple_status():
"""간단한 서버 상태를 반환합니다."""
try:
import psutil
# status.json 파일에서 상태 읽기
status = read_status_from_file()
# 시스템 메모리 정보
memory_info = gpu_monitor.get_system_memory_info()
return {
"timestamp": time.time(),
"system_type": "Jetson Xavier" if settings.IS_JETSON else "x86_64",
"cpu_percent": psutil.cpu_percent(),
"memory_percent": psutil.virtual_memory().percent,
"cpu_percent": psutil.cpu_percent(interval=0.1),
"memory_percent": memory_info.get("usage_percent", 0),
"status": "running"
}
except Exception as e:
return {"error": f"간단한 상태 수집 실패: {str(e)}"}
logger.error(f"간단한 상태 조회 실패: {e}")
return {"error": str(e)}
# 테스트용 엔드포인트
@api_router.get("/test")
async def test_endpoint():
"""테스트용 엔드포인트입니다."""
try:
# status.json 파일 읽기 테스트
status = read_status_from_file()
# GPU 모니터 테스트
gpu_memory = gpu_monitor.get_gpu_memory_info()
system_memory = gpu_monitor.get_system_memory_info()
return {
"message": "테스트 성공",
"status_file": "읽기 성공" if status else "읽기 실패",
"gpu_memory": gpu_memory,
"system_memory": system_memory,
"timestamp": time.time()
}
except Exception as e:
logger.error(f"테스트 엔드포인트 실패: {e}")
return {"error": str(e)}
@api_router.get("/test_data")
async def get_test_data():

File diff suppressed because it is too large Load Diff

View File

@ -1 +1 @@
396102
406750

Binary file not shown.

View File

@ -1 +1 @@
396175
406772

161
main.py
View File

@ -31,17 +31,38 @@ start_time = time.time()
async def save_status_periodically():
"""주기적으로 워커와 세션 상태를 파일에 저장합니다."""
logger.info("🔄 상태 저장 백그라운드 작업 시작됨")
iteration = 0
while True:
try:
iteration += 1
logger.debug(f"상태 저장 시도 #{iteration}")
# 워커 상태 수집
worker_status = worker_manager.get_status()
logger.debug(f"워커 상태 수집 완료: {worker_status}")
# 세션 상태 수집
session_status = session_pool.get_status()
logger.debug(f"세션 상태 수집 완료: {session_status}")
status = {
"worker_status": worker_manager.get_status(),
"session_status": session_pool.get_status(),
"worker_status": worker_status,
"session_status": session_status,
"timestamp": time.time()
}
# 파일에 저장
with open("status.json", "w") as f:
json.dump(status, f)
json.dump(status, f, indent=2)
logger.info(f"상태 저장 완료 #{iteration}: {time.strftime('%H:%M:%S')}")
except Exception as e:
logger.warning(f"상태 저장 실패: {e}")
logger.error(f"상태 저장 실패 #{iteration}: {e}")
import traceback
logger.error(f"상세 오류: {traceback.format_exc()}")
await asyncio.sleep(1)
@asynccontextmanager
@ -56,7 +77,9 @@ async def lifespan(app: FastAPI):
logger.info("✅ 공유 객체를 app.state에 저장 완료")
# 상태 저장 백그라운드 작업 시작
logger.info("🔄 상태 저장 백그라운드 작업 생성 중...")
status_task = asyncio.create_task(save_status_periodically())
logger.info("✅ 상태 저장 백그라운드 작업 생성 완료")
try:
# 세션 풀 초기화
@ -116,134 +139,8 @@ app.include_router(router)
# app.mount("/monitoring", monitor_app, name="monitoring")
# 모니터링 데이터 직접 제공 (완전 통합)
@app.get("/monitoring/api/status")
async def get_monitoring_status():
"""모니터링 상태를 직접 반환합니다."""
try:
import psutil
from app.utils.gpu_monitor import GPUMonitor
# 시스템 정보 수집
cpu_percent = psutil.cpu_percent()
memory = psutil.virtual_memory()
process_count = len(psutil.pids())
# GPU 정보 수집
gpu_monitor = GPUMonitor()
gpu_info = await gpu_monitor.get_gpu_info()
# 워커 매니저 상태 (안전한 방식)
try:
worker_status = {
"total_workers": getattr(worker_manager, 'workers', {}).__len__() if hasattr(worker_manager, 'workers') else 0,
"queue_size": getattr(worker_manager, 'queue', None).qsize() if hasattr(worker_manager, 'queue') else 0,
"workers_by_status": {"idle": [], "busy": [], "starting": [], "stopping": [], "error": []},
"running": getattr(worker_manager, 'running', False)
}
except Exception as e:
logger.warning(f"워커 상태 수집 실패: {e}")
worker_status = {
"total_workers": 0,
"queue_size": 0,
"workers_by_status": {"idle": [], "busy": [], "starting": [], "stopping": [], "error": []},
"running": False
}
# 세션 풀 상태 (안전한 방식)
try:
sessions = getattr(session_pool, 'sessions', {})
total_sessions = len(sessions) if sessions else 0
available_sessions = len([s for s in sessions.values() if getattr(s, 'available', False)]) if sessions else 0
session_status = {
"total_sessions": total_sessions,
"available_sessions": available_sessions,
"model_distribution": {"simple-lama": 0, "migan": 0, "rembg": 0}
}
except Exception as e:
logger.warning(f"세션 상태 수집 실패: {e}")
session_status = {
"total_sessions": 0,
"available_sessions": 0,
"model_distribution": {"simple-lama": 0, "migan": 0, "rembg": 0}
}
# API 통계 (간단한 버전)
api_stats = {
"total_requests": 0, # 실제로는 카운터가 필요
"success_rate": 100.0,
"average_response_time": 0.0,
"error_count": 0
}
return {
"timestamp": time.time(),
"system_type": "Jetson Xavier" if settings.IS_JETSON else "x86_64",
"system": {
"cpu_percent": cpu_percent,
"memory_percent": memory.percent,
"process_count": process_count
},
"gpu": gpu_info,
"worker_status": worker_status,
"session_status": session_status,
"api_stats": api_stats
}
except Exception as e:
logger.error(f"모니터링 데이터 수집 실패: {e}")
return {"error": f"모니터링 데이터 수집 실패: {str(e)}"}
@app.get("/monitoring/api/simple")
async def get_simple_monitoring():
"""간단한 모니터링 상태를 반환합니다."""
try:
import psutil
return {
"timestamp": time.time(),
"system_type": "Jetson Xavier" if settings.IS_JETSON else "x86_64",
"cpu_percent": psutil.cpu_percent(),
"memory_percent": psutil.virtual_memory().percent,
"status": "running"
}
except Exception as e:
return {"error": f"간단한 상태 수집 실패: {str(e)}"}
@app.get("/monitoring/api/worker-status")
async def get_worker_status():
"""워커 상태를 반환합니다."""
try:
workers = getattr(worker_manager, 'workers', {})
queue = getattr(worker_manager, 'queue', None)
running = getattr(worker_manager, 'running', False)
return {
"total_workers": len(workers) if workers else 0,
"queue_size": queue.qsize() if queue else 0,
"running": running,
"status": "active" if running else "stopped"
}
except Exception as e:
logger.warning(f"워커 상태 조회 실패: {e}")
return {"error": f"워커 상태 조회 실패: {str(e)}"}
@app.get("/monitoring/api/session-status")
async def get_session_status():
"""세션 풀 상태를 반환합니다."""
try:
sessions = getattr(session_pool, 'sessions', {})
total_sessions = len(sessions) if sessions else 0
available_sessions = len([s for s in sessions.values() if getattr(s, 'available', False)]) if sessions else 0
return {
"total_sessions": total_sessions,
"available_sessions": available_sessions,
"model_distribution": {"simple-lama": 0, "migan": 0, "rembg": 0}
}
except Exception as e:
logger.warning(f"세션 상태 조회 실패: {e}")
return {"error": f"세션 상태 조회 실패: {str(e)}"}
# 모니터링은 독립적인 서버(포트 8001)에서 처리됩니다.
# status.json 파일을 통해 데이터를 공유합니다.
if __name__ == "__main__":

View File

@ -379,11 +379,10 @@ start_servers() {
--daemon \
--pid "$LOG_DIR/main_server.pid"
else
# 개발 모드: Uvicorn 사용
# 개발 모드: Uvicorn 사용 (단일 프로세스로 실행하여 lifespan 함수가 정상 작동하도록 함)
nohup python -m uvicorn main:app \
--host 0.0.0.0 \
--port $MAIN_SERVER_PORT \
--workers $WORKERS \
--log-level info \
--access-log \
> "$LOG_DIR/main_server.log" 2>&1 &

View File

@ -1 +1,40 @@
{"worker_status": {"running": true, "total_workers": 1, "queue_size": 0, "workers_by_status": {"idle": [{"id": "worker_d5e6bd0a", "status": "idle", "task_count": 0, "error_count": 0, "last_task_at": null}], "busy": [], "starting": [], "stopping": [], "error": []}}, "session_status": {"simple_lama": {"total": 2, "in_use": 0, "available": 2}, "migan": {"total": 2, "in_use": 0, "available": 2}, "rembg": {"total": 1, "in_use": 0, "available": 1}}, "api_stats": {"total_requests": 3, "successful_requests": 2, "failed_requests": 1, "endpoint_usage": {"/": 1, "/health": 1, "/api/v1/model": 1}, "start_time": 1756296888.0927753, "uptime_seconds": 253.7529318332672, "average_response_time_ms": 2.473115921020508}, "timestamp": 1756297141.8458862}
{
"worker_status": {
"running": true,
"total_workers": 1,
"queue_size": 0,
"workers_by_status": {
"idle": [
{
"id": "worker_0d4f9bb0",
"status": "idle",
"task_count": 0,
"error_count": 0,
"last_task_at": null
}
],
"busy": [],
"starting": [],
"stopping": [],
"error": []
}
},
"session_status": {
"simple_lama": {
"total": 2,
"in_use": 0,
"available": 2
},
"migan": {
"total": 2,
"in_use": 0,
"available": 2
},
"rembg": {
"total": 1,
"in_use": 0,
"available": 1
}
},
"timestamp": 1756313157.7682712
}