Skip to content

Add system monitoring interface #135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions backend/app/api/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,23 @@
from backend.app.api.v1.role import router as role_router
from backend.app.api.v1.menu import router as menu_router
from backend.app.api.v1.api import router as api_router
from backend.app.api.v1.config import router as config_router
from backend.app.api.v1.login_log import router as login_log_router
from backend.app.api.v1.opera_log import router as opera_log_router
from backend.app.api.v1.task_demo import router as task_demo_router
from backend.app.api.v1.dict_type import router as dict_type_router
from backend.app.api.v1.dict_data import router as dict_data_router
from backend.app.api.v1.mixed import router as mixed_router

v1 = APIRouter(prefix=settings.API_V1_STR)

v1.include_router(auth_router)
v1.include_router(auth_router, prefix='/auth', tags=['认证'])
v1.include_router(user_router, prefix='/users', tags=['用户管理'])
v1.include_router(casbin_router, prefix='/casbin', tags=['权限管理'])
v1.include_router(dept_router, prefix='/depts', tags=['部门管理'])
v1.include_router(role_router, prefix='/roles', tags=['角色管理'])
v1.include_router(menu_router, prefix='/menus', tags=['菜单管理'])
v1.include_router(api_router, prefix='/apis', tags=['API管理'])
v1.include_router(config_router, prefix='/configs', tags=['系统配置'])
v1.include_router(dict_type_router, prefix='/dict_types', tags=['字典类型管理'])
v1.include_router(dict_data_router, prefix='/dict_datas', tags=['字典数据管理'])
v1.include_router(login_log_router, prefix='/login_logs', tags=['登录日志管理'])
v1.include_router(opera_log_router, prefix='/opera_logs', tags=['操作日志管理'])
v1.include_router(task_demo_router, prefix='/tasks', tags=['任务管理'])
v1.include_router(dict_type_router, prefix='/dict-types', tags=['字典类型管理'])
v1.include_router(dict_data_router, prefix='/dict-datas', tags=['字典数据管理'])
v1.include_router(login_log_router, prefix='/login-logs', tags=['登录日志管理'])
v1.include_router(opera_log_router, prefix='/opera-logs', tags=['操作日志管理'])
v1.include_router(mixed_router, prefix='/mixes', tags=['杂项'])
2 changes: 1 addition & 1 deletion backend/app/api/v1/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from backend.app.api.v1.auth.auth import router as auth_router
from backend.app.api.v1.auth.captcha import router as captcha_router

router = APIRouter(prefix='/auth', tags=['认证'])
router = APIRouter()

router.include_router(auth_router)
router.include_router(captcha_router)
12 changes: 12 additions & 0 deletions backend/app/api/v1/mixed/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import APIRouter
from backend.app.api.v1.mixed.monitor import router as monitor_router
from backend.app.api.v1.mixed.config import router as config_router
from backend.app.api.v1.mixed.task_demo import router as task_demo_router

router = APIRouter()

router.include_router(monitor_router)
router.include_router(config_router)
router.include_router(task_demo_router)
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
router = APIRouter()


@router.get('', summary='获取系统配置', dependencies=[DependsRBAC])
@router.get('/configs', summary='获取系统配置', dependencies=[DependsRBAC])
async def get_sys_config():
return await response_base.success(
data={
Expand Down
39 changes: 39 additions & 0 deletions backend/app/api/v1/mixed/monitor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import APIRouter

from backend.app.common.jwt import DependsJwtAuth
from backend.app.common.redis import redis_client
from backend.app.common.response.response_schema import response_base
from backend.app.utils.server_info import ServerInfo

router = APIRouter(prefix='/monitors')


@router.get('/redis', summary='redis 监控', dependencies=[DependsJwtAuth])
async def redis_info():
info = await redis_client.info()
fmt_info = {}
for key, value in info.items():
if isinstance(value, dict):
value = ','.join({f'{k}={v}' for k, v in value.items()})
else:
value = str(value)
fmt_info[key] = value
db_size = await redis_client.dbsize()
command_stats = await redis_client.info('commandstats')
stats_list = []
for k, v in command_stats.items():
stats_list.append({'name': k.split('_')[-1], 'value': str(v.get('calls', ''))})
return await response_base.success(data={'info': fmt_info, 'stats': stats_list, 'size': db_size})


@router.get('/server', summary='server 监控', dependencies=[DependsJwtAuth])
async def server_info():
return await response_base.success(data={
'cpu': ServerInfo.get_cpu_info(),
'mem': ServerInfo.get_mem_info(),
'sys': ServerInfo.get_sys_info(),
'disk': ServerInfo.get_disk_info(),
'service': ServerInfo.get_service_info(),
})
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
from fastapi import APIRouter, Query
from typing_extensions import Annotated

from backend.app.common.response.response_schema import response_base
from backend.app.common.task import scheduler

router = APIRouter()
router = APIRouter(prefix='/tasks')


def task_demo():
Expand Down Expand Up @@ -37,16 +38,16 @@ async def task_demo_get():
'next_run_time': job.next_run_time,
}
)
return {'msg': 'success', 'data': tasks}
return await response_base.success({'msg': 'success', 'data': tasks})


@router.post('', summary='添加同步任务')
@router.post('/sync', summary='添加同步任务')
async def task_demo_add():
scheduler.add_job(
task_demo, 'interval', seconds=1, id='task_demo', replace_existing=True, start_date=datetime.datetime.now()
)

return {'msg': 'success'}
return await response_base.success({'msg': 'success'})


@router.post('/async', summary='添加异步任务')
Expand All @@ -60,11 +61,11 @@ async def task_demo_add_async():
start_date=datetime.datetime.now(),
)

return {'msg': 'success'}
return await response_base.success({'msg': 'success'})


@router.delete('', summary='删除任务')
async def task_demo_delete(job_id: Annotated[str, Query(..., description='任务id')]):
scheduler.remove_job(job_id=job_id)

return {'msg': 'success'}
return await response_base.success({'msg': 'success'})
114 changes: 114 additions & 0 deletions backend/app/utils/server_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import os
import platform
import socket
import sys
from datetime import datetime, timedelta
from typing import List

import psutil


class ServerInfo:
@staticmethod
def get_size(data, suffix='B') -> str:
"""按照正确的格式缩放字节"""
factor = 1024
for unit in ['', 'K', 'M', 'G', 'T', 'P']:
if data < factor:
return f'{data:.2f}{unit}{suffix}'
data /= factor

@staticmethod
def fmt_timedelta(td: timedelta) -> str:
"""格式化时间戳"""
rem = td.seconds
days, rem = rem // 86400, rem % 86400
hours, rem = rem // 3600, rem % 3600
minutes = rem // 60
res = f'{minutes} 分钟'
if hours > 0:
res = f'{hours} 小时 {res}'
if days > 0:
res = f'{days} 天 {res}'
return res

@staticmethod
def get_cpu_info() -> dict:
"""获取CPU信息"""
info = dict()
info.update({'cpu_num': psutil.cpu_count(logical=True)})
cpu_times = psutil.cpu_times()
total = cpu_times.user + cpu_times.nice + cpu_times.system + cpu_times.idle \
+ getattr(cpu_times, 'iowait', 0.0) + getattr(cpu_times, 'irq', 0.0) \
+ getattr(cpu_times, 'softirq', 0.0) + getattr(cpu_times, 'steal', 0.0)
info.update({'total': round(total, 2)})
info.update({'sys': round(cpu_times.system / total, 2)})
info.update({'used': round(cpu_times.user / total, 2)})
info.update({'wait': round(getattr(cpu_times, 'iowait', 0.0) / total, 2)})
info.update({'free': round(cpu_times.idle / total, 2)})
return info

@staticmethod
def get_mem_info() -> dict:
"""获取内存信息"""
number = 1024 ** 3
return {
'total': round(psutil.virtual_memory().total / number, 2),
'used': round(psutil.virtual_memory().used / number, 2),
'free': round(psutil.virtual_memory().available / number, 2),
'usage': round(psutil.virtual_memory().percent, 2)
}

@staticmethod
def get_sys_info() -> dict:
"""获取服务器信息"""
sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
sk.connect(('10.0.0.0', 0))
ip = sk.getsockname()[0]
except Exception:
ip = '127.0.0.1'
finally:
sk.close()
return {
'name': socket.gethostname(),
'ip': ip,
'os': platform.system(),
'arch': platform.machine()
}

@staticmethod
def get_disk_info() -> List[dict]:
"""获取磁盘信息"""
disk_info = []
for disk in psutil.disk_partitions():
usage = psutil.disk_usage(disk.mountpoint)
disk_info.append({
'dir': disk.mountpoint,
'type': disk.fstype,
'device': disk.device,
'total': ServerInfo.get_size(usage.total),
'free': ServerInfo.get_size(usage.free),
'used': ServerInfo.get_size(usage.used),
'usage': round(usage.percent, 2),
})
return disk_info

@staticmethod
def get_service_info():
"""获取服务信息"""
number = 1024 ** 2
cur_proc = psutil.Process(os.getpid())
mem_info = cur_proc.memory_info()
start_time = datetime.fromtimestamp(cur_proc.create_time())
return {
'name': 'Python3',
'version': platform.python_version(),
'home': sys.executable,
'total': round(mem_info.vms / number, 2),
'max': round(mem_info.vms / number, 2),
'free': round((mem_info.vms - mem_info.rss) / number, 2),
'usage': round(mem_info.rss / number, 2),
'elapsed': ServerInfo.fmt_timedelta(datetime.now() - start_time),
'startup': start_time.strftime('%Y-%m-%d %H:%M:%S'),
}
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ loguru==0.6.0
passlib==1.7.4
path==15.1.2
pre-commit==3.2.2
psutil==5.9.5
pydantic==1.10.5
pymysql==0.9.3
pytest==7.2.2
Expand Down