mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-02-03 03:21:54 +08:00
complex info handle
This commit is contained in:
48
kvmd/apps/kvmd/info/__init__.py
Normal file
48
kvmd/apps/kvmd/info/__init__.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# ========================================================================== #
|
||||
# #
|
||||
# KVMD - The main Pi-KVM daemon. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# 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 typing import List
|
||||
|
||||
from ....yamlconf import Section
|
||||
|
||||
from .base import BaseInfoSubmanager
|
||||
from .system import SystemInfoSubmanager
|
||||
from .meta import MetaInfoSubmanager
|
||||
from .extras import ExtrasInfoSubmanager
|
||||
from .hw import HwInfoSubmanager
|
||||
|
||||
|
||||
# =====
|
||||
class InfoManager:
|
||||
def __init__(self, config: Section) -> None:
|
||||
self.__subs = {
|
||||
"system": SystemInfoSubmanager(config.kvmd.streamer.cmd),
|
||||
"meta": MetaInfoSubmanager(config.kvmd.info.meta),
|
||||
"extras": ExtrasInfoSubmanager(config),
|
||||
"hw": HwInfoSubmanager(**config.kvmd.info.hw._unpack()),
|
||||
}
|
||||
|
||||
def get_subs(self) -> List[str]:
|
||||
return list(self.__subs)
|
||||
|
||||
def get_submanager(self, name: str) -> BaseInfoSubmanager:
|
||||
return self.__subs[name]
|
||||
30
kvmd/apps/kvmd/info/base.py
Normal file
30
kvmd/apps/kvmd/info/base.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# ========================================================================== #
|
||||
# #
|
||||
# KVMD - The main Pi-KVM daemon. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# 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 typing import Dict
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# =====
|
||||
class BaseInfoSubmanager:
|
||||
async def get_state(self) -> Optional[Dict]:
|
||||
raise NotImplementedError
|
||||
102
kvmd/apps/kvmd/info/extras.py
Normal file
102
kvmd/apps/kvmd/info/extras.py
Normal file
@@ -0,0 +1,102 @@
|
||||
# ========================================================================== #
|
||||
# #
|
||||
# KVMD - The main Pi-KVM daemon. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# 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 os
|
||||
import contextlib
|
||||
|
||||
from typing import Dict
|
||||
from typing import Optional
|
||||
|
||||
import dbus # pylint: disable=import-error
|
||||
import dbus.exceptions
|
||||
|
||||
from ....logging import get_logger
|
||||
|
||||
from ....yamlconf import Section
|
||||
from ....yamlconf.loader import load_yaml_file
|
||||
|
||||
from .... import aiotools
|
||||
|
||||
from .base import BaseInfoSubmanager
|
||||
|
||||
|
||||
# =====
|
||||
class ExtrasInfoSubmanager(BaseInfoSubmanager):
|
||||
def __init__(self, global_config: Section) -> None:
|
||||
self.__global_config = global_config
|
||||
|
||||
async def get_state(self) -> Optional[Dict]:
|
||||
return (await aiotools.run_async(self.__inner_get_state))
|
||||
|
||||
# =====
|
||||
|
||||
def __inner_get_state(self) -> Optional[Dict]:
|
||||
try:
|
||||
extras_path = self.__global_config.kvmd.info.extras
|
||||
extras: Dict[str, Dict] = {}
|
||||
for app in os.listdir(extras_path):
|
||||
if app[0] != "." and os.path.isdir(os.path.join(extras_path, app)):
|
||||
extras[app] = load_yaml_file(os.path.join(extras_path, app, "manifest.yaml"))
|
||||
self.__rewrite_app_daemon(extras[app])
|
||||
self.__rewrite_app_port(extras[app])
|
||||
return extras
|
||||
except Exception:
|
||||
get_logger(0).exception("Can't parse extras")
|
||||
return None
|
||||
|
||||
def __rewrite_app_daemon(self, extras: Dict) -> None:
|
||||
daemon = extras.get("daemon", "")
|
||||
if isinstance(daemon, str) and daemon.strip():
|
||||
extras["enabled"] = self.__is_daemon_enabled(daemon)
|
||||
|
||||
def __rewrite_app_port(self, extras: Dict) -> None:
|
||||
port_path = extras.get("port", "")
|
||||
if isinstance(port_path, str) and port_path.strip():
|
||||
extras["port"] = 0
|
||||
config = self.__global_config
|
||||
for item in filter(None, map(str.strip, port_path.split("/"))):
|
||||
config = getattr(config, item, None)
|
||||
if isinstance(config, int):
|
||||
extras["port"] = config
|
||||
|
||||
def __is_daemon_enabled(self, name: str) -> bool:
|
||||
if not name.startswith(".service"):
|
||||
name += ".service"
|
||||
|
||||
try:
|
||||
with contextlib.closing(dbus.SystemBus()) as bus:
|
||||
systemd = bus.get_object("org.freedesktop.systemd1", "/org/freedesktop/systemd1") # pylint: disable=no-member
|
||||
manager = dbus.Interface(systemd, dbus_interface="org.freedesktop.systemd1.Manager")
|
||||
|
||||
try:
|
||||
unit_proxy = bus.get_object("org.freedesktop.systemd1", manager.GetUnit(name)) # pylint: disable=no-member
|
||||
unit_properties = dbus.Interface(unit_proxy, dbus_interface="org.freedesktop.DBus.Properties")
|
||||
enabled = (unit_properties.Get("org.freedesktop.systemd1.Unit", "ActiveState") == "active")
|
||||
except dbus.exceptions.DBusException as err:
|
||||
if "NoSuchUnit" not in str(err):
|
||||
raise
|
||||
enabled = False
|
||||
|
||||
return (enabled or (manager.GetUnitFileState(name) in ["enabled", "enabled-runtime", "static", "indirect", "generated"]))
|
||||
except Exception as err:
|
||||
get_logger(0).error("Can't get info about the service %r: %s: %s", name, type(err).__name__, err)
|
||||
return True
|
||||
152
kvmd/apps/kvmd/info/hw.py
Normal file
152
kvmd/apps/kvmd/info/hw.py
Normal file
@@ -0,0 +1,152 @@
|
||||
# ========================================================================== #
|
||||
# #
|
||||
# KVMD - The main Pi-KVM daemon. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# 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
|
||||
|
||||
from .base import BaseInfoSubmanager
|
||||
|
||||
|
||||
# =====
|
||||
_RetvalT = TypeVar("_RetvalT")
|
||||
|
||||
|
||||
# =====
|
||||
class HwInfoSubmanager(BaseInfoSubmanager):
|
||||
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
|
||||
45
kvmd/apps/kvmd/info/meta.py
Normal file
45
kvmd/apps/kvmd/info/meta.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# ========================================================================== #
|
||||
# #
|
||||
# KVMD - The main Pi-KVM daemon. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# 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 typing import Dict
|
||||
from typing import Optional
|
||||
|
||||
from ....logging import get_logger
|
||||
|
||||
from ....yamlconf.loader import load_yaml_file
|
||||
|
||||
from .... import aiotools
|
||||
|
||||
from .base import BaseInfoSubmanager
|
||||
|
||||
|
||||
# =====
|
||||
class MetaInfoSubmanager(BaseInfoSubmanager):
|
||||
def __init__(self, meta_path: str) -> None:
|
||||
self.__meta_path = meta_path
|
||||
|
||||
async def get_state(self) -> Optional[Dict]:
|
||||
try:
|
||||
return ((await aiotools.run_async(load_yaml_file, self.__meta_path)) or {})
|
||||
except Exception:
|
||||
get_logger(0).exception("Can't parse meta")
|
||||
return None
|
||||
80
kvmd/apps/kvmd/info/system.py
Normal file
80
kvmd/apps/kvmd/info/system.py
Normal file
@@ -0,0 +1,80 @@
|
||||
# ========================================================================== #
|
||||
# #
|
||||
# KVMD - The main Pi-KVM daemon. #
|
||||
# #
|
||||
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# 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 os
|
||||
import asyncio
|
||||
import platform
|
||||
|
||||
from typing import List
|
||||
from typing import Dict
|
||||
|
||||
from ....logging import get_logger
|
||||
|
||||
from .... import aioproc
|
||||
|
||||
from .... import __version__
|
||||
|
||||
from .base import BaseInfoSubmanager
|
||||
|
||||
|
||||
# =====
|
||||
class SystemInfoSubmanager(BaseInfoSubmanager):
|
||||
def __init__(self, streamer_cmd: List[str]) -> None:
|
||||
self.__streamer_cmd = streamer_cmd
|
||||
|
||||
async def get_state(self) -> Dict:
|
||||
streamer_info = await self.__get_streamer_info()
|
||||
uname_info = platform.uname() # Uname using the internal cache
|
||||
return {
|
||||
"kvmd": {"version": __version__},
|
||||
"streamer": streamer_info,
|
||||
"kernel": {
|
||||
field: getattr(uname_info, field)
|
||||
for field in ["system", "release", "version", "machine"]
|
||||
},
|
||||
}
|
||||
|
||||
# =====
|
||||
|
||||
async def __get_streamer_info(self) -> Dict:
|
||||
version = ""
|
||||
features: Dict[str, bool] = {}
|
||||
try:
|
||||
path = self.__streamer_cmd[0]
|
||||
((_, version), (_, features_text)) = await asyncio.gather(
|
||||
aioproc.read_process([path, "--version"], err_to_null=True),
|
||||
aioproc.read_process([path, "--features"], err_to_null=True),
|
||||
)
|
||||
except Exception:
|
||||
get_logger(0).exception("Can't get streamer info")
|
||||
else:
|
||||
try:
|
||||
for line in features_text.split("\n"):
|
||||
(status, name) = map(str.strip, line.split(" "))
|
||||
features[name] = (status == "+")
|
||||
except Exception:
|
||||
get_logger(0).exception("Can't parse streamer features")
|
||||
return {
|
||||
"app": os.path.basename(path),
|
||||
"version": version,
|
||||
"features": features,
|
||||
}
|
||||
Reference in New Issue
Block a user