From 51e369cf14d52b899fa61167ded2e30660995b5e Mon Sep 17 00:00:00 2001 From: Stefan Stammberger Date: Sat, 12 Jun 2021 15:07:35 +0200 Subject: [PATCH] feat: implement get hardware info api --- app/auth/__init__.py | 0 app/repositories/__init__.py | 0 app/repositories/hardware_info.py | 83 +++++++++++++++++++++++++++++++ app/routers/setup.py | 14 ++++++ app/routers/system.py | 45 +++++++++++++++-- app/routers/system_docs.py | 60 ++++++++++++++++++++++ requirements.txt | 4 +- 7 files changed, 200 insertions(+), 6 deletions(-) create mode 100644 app/auth/__init__.py create mode 100644 app/repositories/__init__.py create mode 100644 app/repositories/hardware_info.py create mode 100644 app/routers/setup.py create mode 100644 app/routers/system_docs.py diff --git a/app/auth/__init__.py b/app/auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/repositories/__init__.py b/app/repositories/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/repositories/hardware_info.py b/app/repositories/hardware_info.py new file mode 100644 index 0000000..712a442 --- /dev/null +++ b/app/repositories/hardware_info.py @@ -0,0 +1,83 @@ +import json +import time +import psutil +from fastapi import Request + +SLEEP_TIME = 1 +CPU_USAGE_INTERVAL = 0.5 +HW_INFO_YIELD_TIME = SLEEP_TIME+CPU_USAGE_INTERVAL + + +async def subscribe_hardware_info(request: Request): + while True: + if await request.is_disconnected(): + # stop if client disconnects + break + yield get_hardware_info() + time.sleep(SLEEP_TIME) + + +def get_hardware_info() -> map: + info = {} + + info['cpu_overall_percent'] = psutil.cpu_percent( + interval=CPU_USAGE_INTERVAL) + info['cpu_per_cpu_percent'] = psutil.cpu_percent( + interval=CPU_USAGE_INTERVAL, percpu=True) + + v = psutil.virtual_memory() + info['vram_total_bytes'] = v.total + info['vram_available_bytes'] = v.available + info['vram_used_bytes'] = v.used + info['vram_usage_percent'] = v.percent + + s = psutil.swap_memory() + info['swap_ram_total_bytes'] = s.total + info['swap_used_bytes'] = s.used + info['swap_usage_bytes'] = s.percent + + info['temperatures_celsius'] = psutil.sensors_temperatures() + info['boot_time_timestamp'] = psutil.boot_time() + + disk_io = psutil.disk_io_counters() + info['disk_io_read_count'] = disk_io.read_count + info['disk_io_write_count'] = disk_io.write_count + info['disk_io_read_bytes'] = disk_io.read_bytes + info['disk_io_write_bytes'] = disk_io.write_bytes + + disks = [] + partitions = psutil.disk_partitions() + for partition in partitions: + p = {} + p['device'] = partition.device + p['mountpoint'] = partition.mountpoint + p['filesystem_type'] = partition.fstype + + try: + usage = psutil.disk_usage(partition.mountpoint) + p['partition_usage_bytes'] = usage.total + p['partition_used_bytes'] = usage.used + p['partition_free_bytes'] = usage.free + p['partition_percent'] = usage.percent + except PermissionError: + continue + disks.append(p) + + nets = [] + addresses = psutil.net_if_addrs() + for name, address in addresses.items(): + net = {} + nets.append(net) + net['interface_name'] = name + for a in address: + if str(a.family) == 'AddressFamily.AF_INET': + net['address'] = a.address + elif str(a.family) == 'AddressFamily.AF_PACKET': + net['mac_address'] = a.address + + net_io = psutil.net_io_counters() + info['networks'] = nets + info['networks_bytes_sent'] = net_io.bytes_sent + info['networks_bytes_received'] = net_io.bytes_recv + + return json.dumps(info) diff --git a/app/routers/setup.py b/app/routers/setup.py new file mode 100644 index 0000000..a1d495c --- /dev/null +++ b/app/routers/setup.py @@ -0,0 +1,14 @@ +from fastapi import APIRouter, HTTPException, status +from fastapi.params import Depends + +from app.auth.auth_bearer import JWTBearer + +router = APIRouter( + prefix="/setup", + tags=["Setup"] +) + + +@router.get("/status", dependencies=[Depends(JWTBearer())]) +def get_status(): + return HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED) diff --git a/app/routers/system.py b/app/routers/system.py index ec23ba0..9983714 100644 --- a/app/routers/system.py +++ b/app/routers/system.py @@ -1,8 +1,11 @@ -from fastapi import APIRouter, HTTPException, status +from fastapi import APIRouter, HTTPException, status, Request from fastapi.params import Depends +from sse_starlette.sse import EventSourceResponse from app.auth.auth_handler import signJWT from app.auth.auth_bearer import JWTBearer +from app.repositories.hardware_info import HW_INFO_YIELD_TIME, get_hardware_info, subscribe_hardware_info +from app.routers.system_docs import get_hw_info_json router = APIRouter( prefix="/system", @@ -11,7 +14,8 @@ router = APIRouter( @router.post("/login", summary="Logs the user in with password A", - response_description="JWT token for the current session.") + response_description="JWT token for the current session.", + status_code=status.HTTP_200_OK) def login(password_a: str): if password_a == "123": return signJWT() @@ -20,6 +24,37 @@ def login(password_a: str): detail="Password is wrong") -@router.get("/temperatures", dependencies=[Depends(JWTBearer())]) -def get_temperatures(): - return "45 °C" +@router.get("/hardware_info", + summary="Get hardware status information.", + response_description="Returns a JSON string with hardware information:\n" + + get_hw_info_json, + dependencies=[Depends(JWTBearer())], + status_code=status.HTTP_200_OK) +def hw_info() -> map: + return get_hardware_info() + + +@router.get("/hardware_info_sub", + summary="Subscribe to hardware status information.", + response_description=f"Yields a JSON string with hardware information every {HW_INFO_YIELD_TIME} seconds:\n" + + get_hw_info_json, + dependencies=[Depends(JWTBearer())], + status_code=status.HTTP_200_OK) +async def hw_info_sub(request: Request): + return EventSourceResponse(subscribe_hardware_info(request)) + + +@ router.post("/reboot", + summary="Reboots the system", + dependencies=[Depends(JWTBearer())], + status_code=status.HTTP_200_OK) +def reboot_system(): + return HTTPException(status.HTTP_501_NOT_IMPLEMENTED) + + +@ router.post("/shutdown", + summary="Shuts the system down", + dependencies=[Depends(JWTBearer())], + status_code=status.HTTP_200_OK) +def reboot_system(): + return HTTPException(status.HTTP_501_NOT_IMPLEMENTED) diff --git a/app/routers/system_docs.py b/app/routers/system_docs.py new file mode 100644 index 0000000..60b5cf7 --- /dev/null +++ b/app/routers/system_docs.py @@ -0,0 +1,60 @@ +get_hw_info_json = """ +```JSON +{ + "cpu_overall_percent": 15.8, + "cpu_per_cpu_percent": [ + 11.8, + 6.1, + 12.5, + ], + "vram_total_bytes": 25134919680, + "vram_available_bytes": 17240051712, + "vram_used_bytes": 6044856320, + "vram_usage_percent": 31.4, + "swap_ram_total_bytes": 2147479552, + "swap_used_bytes": 0, + "swap_usage_bytes": 0, + "temperatures_celsius": { + "coretemp": [ + [ + "Core 1", + 51, + 84, + 100 + ], + [ + "Core 2", + 53, + 84, + 100 + ], + [ + "Core 3", + 50, + 84, + 100 + ] + ] + }, + "boot_time_timestamp": 1623486468, + "disk_io_read_count": 254574, + "disk_io_write_count": 133353, + "disk_io_read_bytes": 5306839040, + "disk_io_write_bytes": 5593076736, + "networks": [ + { + "interface_name": "lo", + "address": "127.0.0.1", + "mac_address": "00:00:00:00:00:00" + }, + { + "interface_name": "enp4s0", + "address": "192.168.1.23", + "mac_address": "35:a3:5c:6a:4a:f0" + }, + ], + "networks_bytes_sent": 137088249, + "networks_bytes_received": 1603400654 +} +``` +""" diff --git a/requirements.txt b/requirements.txt index 2269d50..1392ef3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,6 @@ pydantic==1.8.2 uvicorn==0.14.0 PyJWT==2.1.0 python-decouple==3.4 -autopep8 \ No newline at end of file +autopep +psutil==5.8.0 +sse_starlette==0.7.2 \ No newline at end of file