diff options
author | Devaev Maxim <[email protected]> | 2020-07-09 05:04:21 +0300 |
---|---|---|
committer | Devaev Maxim <[email protected]> | 2020-07-09 05:13:03 +0300 |
commit | 53eb74670d040c9e8745433a2641f18796da365c (patch) | |
tree | b321d856280e6fe4bb2b410b582c046ae4e7ee20 | |
parent | 69f77ce48b279c614f1aaca809c1d74e411f1fd1 (diff) |
hw monitoring
-rw-r--r-- | kvmd/apps/__init__.py | 7 | ||||
-rw-r--r-- | kvmd/apps/kvmd/__init__.py | 2 | ||||
-rw-r--r-- | kvmd/apps/kvmd/api/hw.py | 41 | ||||
-rw-r--r-- | kvmd/apps/kvmd/hw.py | 150 | ||||
-rw-r--r-- | kvmd/apps/kvmd/server.py | 5 | ||||
-rw-r--r-- | testenv/Dockerfile | 12 | ||||
-rw-r--r-- | testenv/fakes/cpu_temp | 1 | ||||
-rw-r--r-- | testenv/fakes/dt_model | 1 | ||||
-rw-r--r-- | testenv/fakes/msd/cdrom | 1 | ||||
-rw-r--r-- | testenv/fakes/msd/file | 0 | ||||
-rw-r--r-- | testenv/fakes/msd/ro | 1 | ||||
-rwxr-xr-x | testenv/fakes/vcgencmd | 5 | ||||
-rw-r--r-- | testenv/v1-vga-rpi3.override.yaml | 4 | ||||
-rw-r--r-- | testenv/v2-hdmi-rpi4.override.yaml | 4 |
14 files changed, 230 insertions, 4 deletions
diff --git a/kvmd/apps/__init__.py b/kvmd/apps/__init__.py index a5ad6f25..4b61a65e 100644 --- a/kvmd/apps/__init__.py +++ b/kvmd/apps/__init__.py @@ -221,6 +221,13 @@ def _get_config_scheme() -> Dict: "extras": Option("/usr/share/kvmd/extras", type=valid_abs_dir), }, + "hw": { + "vcgencmd_cmd": Option(["/opt/vc/bin/vcgencmd"], type=valid_command), + "procfs_prefix": Option("", type=(lambda arg: str(arg).strip())), + "sysfs_prefix": Option("", type=(lambda arg: str(arg).strip())), + "state_poll": Option(10.0, type=valid_float_f01), + }, + "wol": { "ip": Option("255.255.255.255", type=(lambda arg: valid_ip(arg, v6=False))), "port": Option(9, type=valid_port), diff --git a/kvmd/apps/kvmd/__init__.py b/kvmd/apps/kvmd/__init__.py index 1684339f..dad0da42 100644 --- a/kvmd/apps/kvmd/__init__.py +++ b/kvmd/apps/kvmd/__init__.py @@ -35,6 +35,7 @@ from .. import init from .auth import AuthManager from .info import InfoManager +from .hw import HwManager from .logreader import LogReader from .wol import WakeOnLan from .streamer import Streamer @@ -77,6 +78,7 @@ def main(argv: Optional[List[str]]=None) -> None: enabled=config.auth.enabled, ), info_manager=InfoManager(global_config), + hw_manager=HwManager(**config.hw._unpack()), log_reader=LogReader(), wol=WakeOnLan(**config.wol._unpack()), diff --git a/kvmd/apps/kvmd/api/hw.py b/kvmd/apps/kvmd/api/hw.py new file mode 100644 index 00000000..ff097c4f --- /dev/null +++ b/kvmd/apps/kvmd/api/hw.py @@ -0,0 +1,41 @@ +# ========================================================================== # +# # +# KVMD - The main Pi-KVM daemon. # +# # +# Copyright (C) 2018 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +# ========================================================================== # + + +from aiohttp.web import Request +from aiohttp.web import Response + +from ..hw import HwManager + +from ..http import exposed_http +from ..http import make_json_response + + +# ===== +class HwApi: + def __init__(self, hw_manager: HwManager) -> None: + self.__hw_manager = hw_manager + + # ===== + + @exposed_http("GET", "/hw") + async def __state_handler(self, _: Request) -> Response: + return make_json_response(await self.__hw_manager.get_state()) diff --git a/kvmd/apps/kvmd/hw.py b/kvmd/apps/kvmd/hw.py new file mode 100644 index 00000000..13f7e9de --- /dev/null +++ b/kvmd/apps/kvmd/hw.py @@ -0,0 +1,150 @@ +# ========================================================================== # +# # +# KVMD - The main Pi-KVM daemon. # +# # +# Copyright (C) 2018 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +# ========================================================================== # + + +import asyncio + +from typing import List +from typing import Dict +from typing import Callable +from typing import AsyncGenerator +from typing import TypeVar +from typing import Optional + +import aiofiles + +from ...logging import get_logger + +from ... import aioproc + + +# ===== +_RetvalT = TypeVar("_RetvalT") + + +# ===== +class HwManager: + def __init__( + self, + vcgencmd_cmd: List[str], + procfs_prefix: str, + sysfs_prefix: str, + state_poll: float, + ) -> None: + + self.__vcgencmd_cmd = vcgencmd_cmd + self.__sysfs_prefix = sysfs_prefix + self.__procfs_prefix = procfs_prefix + self.__state_poll = state_poll + + async def get_state(self) -> Dict: + (model, cpu_temp, gpu_temp, throttling) = await asyncio.gather( + self.__get_dt_model(), + self.__get_cpu_temp(), + self.__get_gpu_temp(), + self.__get_throttling(), + ) + return { + "platform": { + "type": "rpi", + "base": model, + }, + "state": { + "temp": { + "cpu": cpu_temp, + "gpu": gpu_temp, + }, + "throttling": throttling, + }, + } + + async def poll_state(self) -> AsyncGenerator[Dict, None]: + prev_state: Dict = {} + while True: + state = await self.get_state() + if state != prev_state: + yield state + prev_state = state + await asyncio.sleep(self.__state_poll) + + # ===== + + async def __get_dt_model(self) -> Optional[str]: + model_path = f"{self.__procfs_prefix}/proc/device-tree/model" + try: + async with aiofiles.open(model_path) as model_file: + return (await model_file.read()).strip(" \t\r\n\0") + except Exception as err: + get_logger(0).error("Can't read DT model from %s: %s", model_path, err) + return None + + async def __get_cpu_temp(self) -> Optional[float]: + temp_path = f"{self.__sysfs_prefix}/sys/class/thermal/thermal_zone0/temp" + try: + async with aiofiles.open(temp_path) as temp_file: + return int((await temp_file.read()).strip()) / 1000 + except Exception as err: + get_logger(0).error("Can't read CPU temp from %s: %s", temp_path, err) + return None + + async def __get_throttling(self) -> Optional[Dict]: + # https://www.raspberrypi.org/forums/viewtopic.php?f=63&t=147781&start=50#p972790 + if (flags := await self.__parse_vcgencmd( + arg="get_throttled", + parser=(lambda text: int(text.split("=")[-1].strip(), 16)), + )) is not None: + return { + "raw_flags": flags, + "parsed_flags": { + "undervoltage": { + "now": bool(flags & (1 << 0)), + "past": bool(flags & (1 << 16)), + }, + "freq_capped": { + "now": bool(flags & (1 << 1)), + "past": bool(flags & (1 << 17)), + }, + "throttled": { + "now": bool(flags & (1 << 2)), + "past": bool(flags & (1 << 18)), + }, + }, + } + return None + + async def __get_gpu_temp(self) -> Optional[float]: + return (await self.__parse_vcgencmd( + arg="measure_temp", + parser=(lambda text: float(text.split("=")[1].split("'")[0])), + )) + + async def __parse_vcgencmd(self, arg: str, parser: Callable[[str], _RetvalT]) -> Optional[_RetvalT]: + cmd = [*self.__vcgencmd_cmd, arg] + try: + text = (await aioproc.read_process(cmd, err_to_null=True))[1] + except Exception: + get_logger(0).exception("Error while executing %s", cmd) + return None + try: + return parser(text) + except Exception as err: + get_logger(0).error("Can't parse %s output: %r: %s", cmd, text, err) + return None diff --git a/kvmd/apps/kvmd/server.py b/kvmd/apps/kvmd/server.py index 31fb8b88..2e5975c6 100644 --- a/kvmd/apps/kvmd/server.py +++ b/kvmd/apps/kvmd/server.py @@ -62,6 +62,7 @@ from ... import aioproc from .auth import AuthManager from .info import InfoManager +from .hw import HwManager from .logreader import LogReader from .wol import WakeOnLan from .streamer import Streamer @@ -81,6 +82,7 @@ from .api.auth import AuthApi from .api.auth import check_request_auth from .api.info import InfoApi +from .api.hw import HwApi from .api.log import LogApi from .api.wol import WolApi from .api.hid import HidApi @@ -123,6 +125,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins self, auth_manager: AuthManager, info_manager: InfoManager, + hw_manager: HwManager, log_reader: LogReader, wol: WakeOnLan, @@ -148,6 +151,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins self.__components = [ _Component("Auth manager", "", auth_manager), _Component("Info manager", "info_state", info_manager), + _Component("HW manager", "hw_state", hw_manager), _Component("Wake-on-LAN", "wol_state", wol), _Component("HID", "hid_state", hid), _Component("ATX", "atx_state", atx), @@ -159,6 +163,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins self, AuthApi(auth_manager), InfoApi(info_manager), + HwApi(hw_manager), LogApi(log_reader), WolApi(wol), HidApi(hid, keymap_path), diff --git a/testenv/Dockerfile b/testenv/Dockerfile index 9c23d29c..9d9e99f9 100644 --- a/testenv/Dockerfile +++ b/testenv/Dockerfile @@ -44,10 +44,14 @@ RUN pip install -r requirements.txt RUN mkdir -p \ /etc/kvmd/nginx \ /var/lib/kvmd/msd/{images,meta} \ + /opt/vc/bin \ /fake_sysfs/sys/kernel/config/usb_gadget/kvmd/functions/mass_storage.usb0/lun.0 \ - && cd /fake_sysfs/sys/kernel/config/usb_gadget/kvmd/functions/mass_storage.usb0/lun.0 \ - && touch file \ - && echo 1 > cdrom \ - && echo 1 > ro + /fake_sysfs/sys/class/thermal/thermal_zone0 \ + /fake_procfs/proc/device-tree + +COPY testenv/fakes/vcgencmd /opt/vc/bin/ +COPY testenv/fakes/msd/* /fake_sysfs/sys/kernel/config/usb_gadget/kvmd/functions/mass_storage.usb0/lun.0/ +COPY testenv/fakes/cpu_temp /fake_sysfs/sys/class/thermal/thermal_zone0/temp +COPY testenv/fakes/dt_model /fake_procfs/proc/device-tree/model CMD /bin/bash diff --git a/testenv/fakes/cpu_temp b/testenv/fakes/cpu_temp new file mode 100644 index 00000000..9db0c8a7 --- /dev/null +++ b/testenv/fakes/cpu_temp @@ -0,0 +1 @@ +36511 diff --git a/testenv/fakes/dt_model b/testenv/fakes/dt_model new file mode 100644 index 00000000..3afd3566 --- /dev/null +++ b/testenv/fakes/dt_model @@ -0,0 +1 @@ +Virtual Raspberry Pi diff --git a/testenv/fakes/msd/cdrom b/testenv/fakes/msd/cdrom new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/testenv/fakes/msd/cdrom @@ -0,0 +1 @@ +1 diff --git a/testenv/fakes/msd/file b/testenv/fakes/msd/file new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/testenv/fakes/msd/file diff --git a/testenv/fakes/msd/ro b/testenv/fakes/msd/ro new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/testenv/fakes/msd/ro @@ -0,0 +1 @@ +1 diff --git a/testenv/fakes/vcgencmd b/testenv/fakes/vcgencmd new file mode 100755 index 00000000..3f4990c6 --- /dev/null +++ b/testenv/fakes/vcgencmd @@ -0,0 +1,5 @@ +#!/bin/sh +case $1 in + get_throttled) echo "throttled=0x0";; + measure_temp) echo "temp=35.0'C";; +esac diff --git a/testenv/v1-vga-rpi3.override.yaml b/testenv/v1-vga-rpi3.override.yaml index 718112f7..192ffa53 100644 --- a/testenv/v1-vga-rpi3.override.yaml +++ b/testenv/v1-vga-rpi3.override.yaml @@ -2,6 +2,10 @@ kvmd: server: unix_mode: 0666 + hw: + procfs_prefix: /fake_procfs + sysfs_prefix: /fake_sysfs + hid: device: /dev/ttyS10 noop: true diff --git a/testenv/v2-hdmi-rpi4.override.yaml b/testenv/v2-hdmi-rpi4.override.yaml index b5003a55..47a1d32c 100644 --- a/testenv/v2-hdmi-rpi4.override.yaml +++ b/testenv/v2-hdmi-rpi4.override.yaml @@ -2,6 +2,10 @@ kvmd: server: unix_mode: 0666 + hw: + procfs_prefix: /fake_procfs + sysfs_prefix: /fake_sysfs + hid: keyboard: device: /dev/null |