diff --git a/platypush/message/response/system/__init__.py b/platypush/message/response/system/__init__.py deleted file mode 100644 index 26631b12..00000000 --- a/platypush/message/response/system/__init__.py +++ /dev/null @@ -1,77 +0,0 @@ -from datetime import datetime -from typing import Optional, List - -from platypush.message.response import Response - - -class SystemResponse(Response): - pass - - -class ProcessResponse(SystemResponse): - def __init__( - self, - pid: int, - name: str, - started: datetime, - ppid: Optional[int], - children: Optional[List[int]] = None, - exe: Optional[List[str]] = None, - status: Optional[str] = None, - username: Optional[str] = None, - terminal: Optional[str] = None, - cpu_user_time: Optional[float] = None, - cpu_system_time: Optional[float] = None, - cpu_children_user_time: Optional[float] = None, - cpu_children_system_time: Optional[float] = None, - mem_rss: Optional[int] = None, - mem_vms: Optional[int] = None, - mem_shared: Optional[int] = None, - mem_text: Optional[int] = None, - mem_data: Optional[int] = None, - mem_lib: Optional[int] = None, - mem_dirty: Optional[int] = None, - mem_percent: Optional[float] = None, - *args, - **kwargs - ): - super().__init__( - *args, - output={ - 'pid': pid, - 'name': name, - 'started': started, - 'ppid': ppid, - 'exe': exe, - 'status': status, - 'username': username, - 'terminal': terminal, - 'cpu_user_time': cpu_user_time, - 'cpu_system_time': cpu_system_time, - 'cpu_children_user_time': cpu_children_user_time, - 'cpu_children_system_time': cpu_children_system_time, - 'mem_rss': mem_rss, - 'mem_vms': mem_vms, - 'mem_shared': mem_shared, - 'mem_text': mem_text, - 'mem_data': mem_data, - 'mem_lib': mem_lib, - 'mem_dirty': mem_dirty, - 'mem_percent': mem_percent, - 'children': children or [], - }, - **kwargs - ) - - -class SystemResponseList(SystemResponse): - def __init__(self, responses: List[SystemResponse], *args, **kwargs): - super().__init__(output=[r.output for r in responses], *args, **kwargs) - - -class ProcessResponseList(SystemResponseList): - def __init__(self, responses: List[ProcessResponse], *args, **kwargs): - super().__init__(responses=responses, *args, **kwargs) - - -# vim:sw=4:ts=4:et: diff --git a/platypush/plugins/system/__init__.py b/platypush/plugins/system/__init__.py index 9d527be7..1bb1a242 100644 --- a/platypush/plugins/system/__init__.py +++ b/platypush/plugins/system/__init__.py @@ -1,6 +1,5 @@ import os -from datetime import datetime from typing import Tuple, Union, List, Optional from typing_extensions import override @@ -23,10 +22,6 @@ from platypush.entities.system import ( SystemFan, SystemTemperature, ) -from platypush.message.response.system import ( - ProcessResponseList, - ProcessResponse, -) from platypush.plugins import action from platypush.plugins.sensor import SensorPlugin from platypush.schemas.system import ( @@ -49,6 +44,8 @@ from platypush.schemas.system import ( MemoryStatsSchema, NetworkInterface, NetworkInterfaceSchema, + Process, + ProcessSchema, SwapStats, SwapStatsSchema, SystemInfoSchema, @@ -366,7 +363,8 @@ class SystemPlugin(SensorPlugin, EntityManager): """ return TemperatureSchema().dump(self._sensors_temperature(), many=True) - def _sensors_fan(self) -> List[Fan]: + @staticmethod + def _sensors_fan() -> List[Fan]: return FanSchema().load( # type: ignore [ { @@ -389,7 +387,8 @@ class SystemPlugin(SensorPlugin, EntityManager): """ return FanSchema().dump(self._sensors_fan(), many=True) - def _sensors_battery(self) -> Optional[Battery]: + @staticmethod + def _sensors_battery() -> Optional[Battery]: battery = psutil.sensors_battery() return BatterySchema().load(battery) if battery else None # type: ignore @@ -403,7 +402,8 @@ class SystemPlugin(SensorPlugin, EntityManager): battery = self._sensors_battery() return BatterySchema().dump(battery) if battery else None # type: ignore - def _connected_users(self) -> List[User]: + @staticmethod + def _connected_users() -> List[User]: return UserSchema().load(psutil.users(), many=True) # type: ignore @action @@ -415,64 +415,40 @@ class SystemPlugin(SensorPlugin, EntityManager): """ return UserSchema().dump(self._connected_users(), many=True) - @action - def processes(self, filter: Optional[str] = '') -> ProcessResponseList: + @classmethod + def _processes(cls) -> List[Process]: """ Get the list of running processes. :param filter: Filter the list by name. :return: List of :class:`platypush.message.response.system.ProcessResponse`. """ - processes = [psutil.Process(pid) for pid in psutil.pids()] - p_list = [] + return ProcessSchema().load( # type: ignore + filter( # type: ignore + lambda proc: proc is not None, + [cls._get_process_if_exists(pid) for pid in psutil.pids()], + ), + many=True, + ) - for p in processes: - if filter and filter not in p.name(): - continue + @action + def processes(self) -> List[dict]: + """ + Get the list of running processes. - args = {} + :return: .. schema:: system.ProcessSchema + """ + return ProcessSchema().dump(self._processes(), many=True) - try: - mem = p.memory_info() - times = p.cpu_times() - args.update( - pid=p.pid, - name=p.name(), - started=datetime.fromtimestamp(p.create_time()), - ppid=p.ppid(), - children=[pp.pid for pp in p.children()], - status=p.status(), - username=p.username(), - terminal=p.terminal(), - cpu_user_time=times.user, - cpu_system_time=times.system, - cpu_children_user_time=times.children_user, - cpu_children_system_time=times.children_system, - mem_rss=mem.rss, - mem_vms=mem.vms, - mem_shared=mem.shared, - mem_text=mem.text, - mem_data=mem.data, - mem_lib=mem.lib, - mem_dirty=mem.dirty, - mem_percent=p.memory_percent(), - ) - except psutil.Error: - continue - - try: - args.update( - exe=p.exe(), - ) - except psutil.Error: - pass - - p_list.append(ProcessResponse(**args)) - - return ProcessResponseList(p_list) + @classmethod + def _get_process_if_exists(cls, pid: int) -> Optional[psutil.Process]: + try: + return cls._get_process(pid) + except psutil.NoSuchProcess: + return None @staticmethod - def _get_process(pid: int): + def _get_process(pid: int) -> psutil.Process: return psutil.Process(pid) @action diff --git a/platypush/schemas/system/__init__.py b/platypush/schemas/system/__init__.py index 7292522c..f6cc3bf8 100644 --- a/platypush/schemas/system/__init__.py +++ b/platypush/schemas/system/__init__.py @@ -16,6 +16,7 @@ from ._fan import Fan, FanSchema from ._memory import MemoryStats, MemoryStatsSchema, SwapStats, SwapStatsSchema from ._model import SystemInfo from ._network import NetworkInterface, NetworkInterfaceSchema +from ._process import Process, ProcessSchema from ._schemas import SystemInfoSchema from ._temperature import Temperature, TemperatureSchema from ._user import User, UserSchema @@ -41,6 +42,8 @@ __all__ = [ "FanSchema", "MemoryStats", "MemoryStatsSchema", + "Process", + "ProcessSchema", "SwapStats", "SwapStatsSchema", "NetworkInterface", diff --git a/platypush/schemas/system/_process/__init__.py b/platypush/schemas/system/_process/__init__.py new file mode 100644 index 00000000..4d053fc7 --- /dev/null +++ b/platypush/schemas/system/_process/__init__.py @@ -0,0 +1,4 @@ +from ._model import Process +from ._schemas import ProcessSchema + +__all__ = ['Process', 'ProcessSchema'] diff --git a/platypush/schemas/system/_process/_base.py b/platypush/schemas/system/_process/_base.py new file mode 100644 index 00000000..72b505e0 --- /dev/null +++ b/platypush/schemas/system/_process/_base.py @@ -0,0 +1,28 @@ +from datetime import datetime + +from dateutil.tz import gettz +from marshmallow import pre_load + +from .._base import SystemBaseSchema + + +class ProcessBaseSchema(SystemBaseSchema): + """ + Base schema for system processes. + """ + + @pre_load + def pre_load(self, data, **_) -> dict: + if hasattr(data, 'as_dict'): + data = data.as_dict() + + started_ts = data.pop('create_time', None) + if started_ts is not None: + data['started'] = datetime.fromtimestamp(started_ts).replace(tzinfo=gettz()) + + data['command_line'] = data.pop('cmdline', None) + data['cpu_percent'] = data.pop('cpu_percent') / 100 + data['current_directory'] = data.pop('cwd', None) + data['memory_percent'] = data.pop('memory_percent') / 100 + data['parent_pid'] = data.pop('ppid', None) + return data diff --git a/platypush/schemas/system/_process/_model.py b/platypush/schemas/system/_process/_model.py new file mode 100644 index 00000000..30513b54 --- /dev/null +++ b/platypush/schemas/system/_process/_model.py @@ -0,0 +1,110 @@ +from dataclasses import dataclass, field +from datetime import datetime +from typing import List, Optional + +from platypush.schemas.dataclasses import percent_field + + +@dataclass +class Process: + """ + System process data class. + """ + + pid: int = field( + metadata={ + 'metadata': { + 'description': 'Process PID', + 'example': 12345, + } + } + ) + + name: str = field( + metadata={ + 'metadata': { + 'description': 'Process name', + 'example': 'python', + } + } + ) + + parent_pid: Optional[int] = field( + metadata={ + 'metadata': { + 'description': 'PID of the parent process', + 'example': 1000, + } + } + ) + + username: str = field( + metadata={ + 'metadata': { + 'description': 'Username that owns the process', + 'example': 'root', + } + } + ) + + command_line: List[str] = field( + metadata={ + 'metadata': { + 'description': 'Command line of the process', + 'example': ['/usr/bin/python', '-m', 'platypush'], + } + } + ) + + status: str = field( + metadata={ + 'metadata': { + 'description': 'Process status', + 'example': 'running', + } + } + ) + + terminal: Optional[str] = field( + metadata={ + 'metadata': { + 'description': 'Terminal the process is running on', + 'example': 'pts/1', + } + } + ) + + current_directory: Optional[str] = field( + metadata={ + 'metadata': { + 'description': 'Current directory of the process', + 'example': '/root', + } + } + ) + + started: Optional[datetime] = field( + metadata={ + 'metadata': { + 'description': 'When the process started', + } + } + ) + + cpu_percent: float = percent_field( + metadata={ + 'metadata': { + 'description': 'Percentage of CPU used by the process, between 0 and 1', + 'example': 0.1, + } + } + ) + + memory_percent: float = percent_field( + metadata={ + 'metadata': { + 'description': 'Percentage of memory used by the process, between 0 and 1', + 'example': 0.05, + } + } + ) diff --git a/platypush/schemas/system/_process/_schemas.py b/platypush/schemas/system/_process/_schemas.py new file mode 100644 index 00000000..c4cca1ab --- /dev/null +++ b/platypush/schemas/system/_process/_schemas.py @@ -0,0 +1,7 @@ +from marshmallow_dataclass import class_schema + +from ._base import ProcessBaseSchema +from ._model import Process + + +ProcessSchema = class_schema(Process, base_schema=ProcessBaseSchema) diff --git a/platypush/schemas/system/_user/_model.py b/platypush/schemas/system/_user/_model.py index d957547b..2fc9e974 100644 --- a/platypush/schemas/system/_user/_model.py +++ b/platypush/schemas/system/_user/_model.py @@ -31,7 +31,6 @@ class User: metadata={ 'metadata': { 'description': 'When the user session started', - 'example': 'pts/1', } } )