From 98a300c4b107a2a608ef36078164d896c1452825 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Fri, 21 Apr 2023 00:45:15 +0200 Subject: [PATCH] Added `NetworkInterface` entities to `system` plugin. Plus, `platypush.schemas.system` has now been split into multiple submodules to avoid a single-file mega-module with all the system schemas definitions. --- .../panels/Entities/NetworkInterface.vue | 167 ++++++ .../src/components/panels/Entities/meta.json | 8 + platypush/entities/system.py | 27 + platypush/message/response/system/__init__.py | 32 -- platypush/plugins/system/__init__.py | 90 +-- platypush/schemas/system.py | 522 ------------------ platypush/schemas/system/__init__.py | 39 ++ platypush/schemas/system/_base.py | 16 + platypush/schemas/system/_cpu/__init__.py | 15 + platypush/schemas/system/_cpu/_base.py | 37 ++ platypush/schemas/system/_cpu/_model.py | 178 ++++++ platypush/schemas/system/_cpu/_schemas.py | 12 + platypush/schemas/system/_disk/__init__.py | 5 + platypush/schemas/system/_disk/_base.py | 22 + platypush/schemas/system/_disk/_model.py | 129 +++++ platypush/schemas/system/_disk/_schemas.py | 7 + platypush/schemas/system/_memory/__init__.py | 5 + platypush/schemas/system/_memory/_base.py | 16 + platypush/schemas/system/_memory/_model.py | 117 ++++ platypush/schemas/system/_memory/_schemas.py | 8 + platypush/schemas/system/_model.py | 20 + platypush/schemas/system/_network/__init__.py | 8 + platypush/schemas/system/_network/_base.py | 22 + platypush/schemas/system/_network/_model.py | 82 +++ platypush/schemas/system/_network/_schemas.py | 9 + platypush/schemas/system/_schemas.py | 8 + 26 files changed, 1006 insertions(+), 595 deletions(-) create mode 100644 platypush/backend/http/webapp/src/components/panels/Entities/NetworkInterface.vue delete mode 100644 platypush/schemas/system.py create mode 100644 platypush/schemas/system/__init__.py create mode 100644 platypush/schemas/system/_base.py create mode 100644 platypush/schemas/system/_cpu/__init__.py create mode 100644 platypush/schemas/system/_cpu/_base.py create mode 100644 platypush/schemas/system/_cpu/_model.py create mode 100644 platypush/schemas/system/_cpu/_schemas.py create mode 100644 platypush/schemas/system/_disk/__init__.py create mode 100644 platypush/schemas/system/_disk/_base.py create mode 100644 platypush/schemas/system/_disk/_model.py create mode 100644 platypush/schemas/system/_disk/_schemas.py create mode 100644 platypush/schemas/system/_memory/__init__.py create mode 100644 platypush/schemas/system/_memory/_base.py create mode 100644 platypush/schemas/system/_memory/_model.py create mode 100644 platypush/schemas/system/_memory/_schemas.py create mode 100644 platypush/schemas/system/_model.py create mode 100644 platypush/schemas/system/_network/__init__.py create mode 100644 platypush/schemas/system/_network/_base.py create mode 100644 platypush/schemas/system/_network/_model.py create mode 100644 platypush/schemas/system/_network/_schemas.py create mode 100644 platypush/schemas/system/_schemas.py diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/NetworkInterface.vue b/platypush/backend/http/webapp/src/components/panels/Entities/NetworkInterface.vue new file mode 100644 index 000000000..9b7a91509 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/NetworkInterface.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/meta.json b/platypush/backend/http/webapp/src/components/panels/Entities/meta.json index 436a88520..801665c76 100644 --- a/platypush/backend/http/webapp/src/components/panels/Entities/meta.json +++ b/platypush/backend/http/webapp/src/components/panels/Entities/meta.json @@ -71,6 +71,14 @@ } }, + "network_interface": { + "name": "System", + "name_plural": "System", + "icon": { + "class": "fas fa-ethernet" + } + }, + "current_sensor": { "name": "Sensor", "name_plural": "Sensors", diff --git a/platypush/entities/system.py b/platypush/entities/system.py index dd9d4040b..bdadc9dd4 100644 --- a/platypush/entities/system.py +++ b/platypush/entities/system.py @@ -175,3 +175,30 @@ if 'disk' not in Base.metadata: __mapper_args__ = { 'polymorphic_identity': __tablename__, } + + +if 'network_interface' not in Base.metadata: + + class NetworkInterface(Entity): + """ + ``NetworkInterface`` ORM model. + """ + + __tablename__ = 'network_interface' + + id = Column( + Integer, ForeignKey(Entity.id, ondelete='CASCADE'), primary_key=True + ) + + bytes_sent = Column(Integer) + bytes_recv = Column(Integer) + packets_sent = Column(Integer) + packets_recv = Column(Integer) + errors_in = Column(Integer) + errors_out = Column(Integer) + drop_in = Column(Integer) + drop_out = Column(Integer) + + __mapper_args__ = { + 'polymorphic_identity': __tablename__, + } diff --git a/platypush/message/response/system/__init__.py b/platypush/message/response/system/__init__.py index b3fe3538d..98374a2ee 100644 --- a/platypush/message/response/system/__init__.py +++ b/platypush/message/response/system/__init__.py @@ -28,38 +28,6 @@ class SensorResponse(SystemResponse): pass -class NetworkIoCountersResponse(NetworkResponse): - def __init__( - self, - bytes_sent: int, - bytes_recv: int, - packets_sent: int, - packets_recv: int, - errin: int, - errout: int, - dropin: int, - dropout: int, - nic: Optional[str] = None, - *args, - **kwargs - ): - super().__init__( - *args, - output={ - 'bytes_sent': bytes_sent, - 'bytes_recv': bytes_recv, - 'packets_sent': packets_sent, - 'packets_recv': packets_recv, - 'errin': errin, - 'errout': errout, - 'dropin': dropin, - 'dropout': dropout, - 'nic': nic, - }, - **kwargs - ) - - class NetworkConnectionResponse(NetworkResponse): # noinspection PyShadowingBuiltins def __init__( diff --git a/platypush/plugins/system/__init__.py b/platypush/plugins/system/__init__.py index 3e0d2b4d1..4b71e3c4b 100644 --- a/platypush/plugins/system/__init__.py +++ b/platypush/plugins/system/__init__.py @@ -18,10 +18,10 @@ from platypush.entities.system import ( CpuTimes as CpuTimesModel, Disk as DiskModel, MemoryStats as MemoryStatsModel, + NetworkInterface as NetworkInterfaceModel, SwapStats as SwapStatsModel, ) from platypush.message.response.system import ( - NetworkIoCountersResponse, NetworkResponseList, NetworkConnectionResponse, NetworkAddressResponse, @@ -50,6 +50,8 @@ from platypush.schemas.system import ( DiskSchema, MemoryStats, MemoryStatsSchema, + NetworkInterface, + NetworkInterfaceSchema, SwapStats, SwapStatsSchema, SystemInfoSchema, @@ -257,48 +259,45 @@ class SystemPlugin(SensorPlugin, EntityManager): """ return DiskSchema().dump(self._disk_info(), many=True) - @action - def net_io_counters( - self, nic: Optional[str] = None, per_nic: bool = False - ) -> Union[NetworkIoCountersResponse, NetworkResponseList]: - """ - Get the I/O counters stats for the network interfaces. - - :param nic: Select the stats for a specific network device (e.g. 'eth0'). Default: get stats for all NICs. - :param per_nic: Return the stats broken down per interface (default: False). - :return: :class:`platypush.message.response.system.NetIoCountersResponse` or list of - :class:`platypush.message.response.system.NetIoCountersResponse`. - """ - - def _expand_response(_nic, _stats): - return NetworkIoCountersResponse( - bytes_sent=_stats.bytes_sent, - bytes_recv=_stats.bytes_recv, - packets_sent=_stats.packets_sent, - packets_recv=_stats.packets_recv, - errin=_stats.errin, - errout=_stats.errout, - dropin=_stats.dropin, - dropout=_stats.dropout, - nic=_nic, - ) - - if nic: - per_nic = True - - io = psutil.net_io_counters(pernic=per_nic) - if nic: - stats = [d for name, d in io.items() if name == nic] - assert stats, 'No such network interface: {}'.format(nic) - return _expand_response(nic, stats[0]) - - if not per_nic: - return _expand_response(nic, io) - - return NetworkResponseList( - [_expand_response(nic, stats) for nic, stats in io.items()] + def _net_io_counters(self) -> List[NetworkInterface]: + return NetworkInterfaceSchema().load( # type: ignore + [ + {'interface': interface, **stats._asdict()} + for interface, stats in psutil.net_io_counters(pernic=True).items() + if any(bool(val) for val in stats._asdict().values()) + ], + many=True, ) + def _net_io_counters_avg(self) -> NetworkInterface: + stats = psutil.net_io_counters(pernic=False) + return NetworkInterfaceSchema().load( # type: ignore + { + 'interface': None, + **stats._asdict(), + } + ) + + @action + def net_io_counters(self, per_nic: bool = False): + """ + Get the information and statistics for the network interfaces. + + :param per_nic: Return the stats grouped by interface (default: False). + :return: If ``per_nic=False``: + + .. schema:: system.NetworkInterfaceSchema + + If ``per_nic=True`` then a list will be returned, where each item + identifies the statistics per network interface: + + .. schema:: system.NetworkInterfaceSchema(many=True) + """ + + if per_nic: + return NetworkInterfaceSchema().dump(self._net_io_counters(), many=True) + return NetworkInterfaceSchema().dump(self._net_io_counters_avg()) + @action def net_connections( self, type: Optional[str] = None @@ -688,6 +687,7 @@ class SystemPlugin(SensorPlugin, EntityManager): 'memory': self._mem_virtual(), 'swap': self._mem_swap(), 'disks': self._disk_info(), + 'network': self._net_io_counters(), } ) @@ -772,6 +772,14 @@ class SystemPlugin(SensorPlugin, EntityManager): ) for disk in entities['disks'] ], + *[ + NetworkInterfaceModel( + id=f'system:network_interface:{nic["interface"]}', + name=nic.pop('interface'), + **nic, + ) + for nic in entities['network'] + ], ] diff --git a/platypush/schemas/system.py b/platypush/schemas/system.py deleted file mode 100644 index adcd05777..000000000 --- a/platypush/schemas/system.py +++ /dev/null @@ -1,522 +0,0 @@ -from dataclasses import dataclass, field -from typing import List, Optional, Tuple - -from marshmallow import pre_load -from marshmallow.validate import Range -from marshmallow_dataclass import class_schema - -from platypush.schemas.dataclasses import DataClassSchema - - -def percent_field(**kwargs): - """ - Field used to model percentage float fields between 0 and 1. - """ - return field( - default_factory=float, - metadata={ - 'validate': Range(min=0, max=1), - **kwargs, - }, - ) - - -class CpuInfoBaseSchema(DataClassSchema): - """ - Base schema for CPU info. - """ - - @pre_load - def pre_load(self, data: dict, **_) -> dict: - if data.get('hz_advertised'): - data['frequency_advertised'] = data.pop('hz_advertised')[0] - if data.get('hz_actual'): - data['frequency_actual'] = data.pop('hz_actual')[0] - - return data - - -class MemoryStatsBaseSchema(DataClassSchema): - """ - Base schema for memory stats. - """ - - @pre_load - def pre_load(self, data: dict, **_) -> dict: - # Normalize the percentage between 0 and 1 - if data.get('percent') is not None: - data['percent'] /= 100 - return data - - -class CpuTimesBaseSchema(DataClassSchema): - """ - Base schema for CPU times. - """ - - @pre_load - def pre_load(self, data, **_) -> dict: - """ - Convert the underlying object to dict and normalize all the percentage - values from [0, 100] to [0, 1]. - """ - return { - key: value / 100.0 - for key, value in ( - data if isinstance(data, dict) else data._asdict() - ).items() - } - - -class DiskBaseSchema(DataClassSchema): - """ - Base schema for disk stats. - """ - - @pre_load - def pre_load(self, data: dict, **_) -> dict: - # Convert read/write/busy times from milliseconds to seconds - for attr in ['read_time', 'write_time', 'busy_time']: - if data.get(attr) is not None: - data[attr] /= 1000 - - # Normalize the percentage between 0 and 1 - if data.get('percent') is not None: - data['percent'] /= 100 - - return data - - -@dataclass -class CpuInfo: - """ - CPU info data class. - """ - - architecture: Optional[str] = field( - metadata={ - 'data_key': 'arch_string_raw', - 'metadata': { - 'description': 'CPU architecture', - 'example': 'x86_64', - }, - } - ) - - bits: int = field( - metadata={ - 'metadata': { - 'description': 'CPU bits / register size', - 'example': 64, - } - } - ) - - cores: int = field( - metadata={ - 'data_key': 'count', - 'metadata': { - 'description': 'Number of cores', - 'example': 4, - }, - } - ) - - vendor: Optional[str] = field( - metadata={ - 'data_key': 'vendor_id_raw', - 'metadata': { - 'description': 'Vendor string', - 'example': 'GenuineIntel', - }, - } - ) - - brand: Optional[str] = field( - metadata={ - 'data_key': 'brand_raw', - 'metadata': { - 'description': 'CPU brand string', - 'example': 'Intel(R) Core(TM) i7-5500U CPU @ 2.40GHz', - }, - } - ) - - frequency_advertised: Optional[int] = field( - metadata={ - 'metadata': { - 'description': 'Advertised CPU frequency, in Hz', - 'example': 2400000000, - } - } - ) - - frequency_actual: Optional[int] = field( - metadata={ - 'metadata': { - 'description': 'Actual CPU frequency, in Hz', - 'example': 2350000000, - } - } - ) - - flags: List[str] = field( - metadata={ - 'metadata': { - 'description': 'CPU flags', - 'example': ['acpi', 'aes', 'cpuid'], - } - } - ) - - l1_instruction_cache_size: Optional[int] = field( - metadata={ - 'metadata': { - 'description': 'Size of the L1 instruction cache, in bytes', - 'example': 65536, - } - } - ) - - l1_data_cache_size: Optional[int] = field( - metadata={ - 'metadata': { - 'description': 'Size of the L1 data cache, in bytes', - 'example': 65536, - } - } - ) - - l2_cache_size: Optional[int] = field( - metadata={ - 'metadata': { - 'description': 'Size of the L2 cache, in bytes', - 'example': 524288, - } - } - ) - - l3_cache_size: Optional[int] = field( - metadata={ - 'metadata': { - 'description': 'Size of the L2 cache, in bytes', - 'example': 4194304, - } - } - ) - - -@dataclass -class CpuTimes: - """ - CPU times data class. - """ - - user: Optional[float] = percent_field() - nice: Optional[float] = percent_field() - system: Optional[float] = percent_field() - idle: Optional[float] = percent_field() - iowait: Optional[float] = percent_field() - irq: Optional[float] = percent_field() - softirq: Optional[float] = percent_field() - steal: Optional[float] = percent_field() - guest: Optional[float] = percent_field() - guest_nice: Optional[float] = percent_field() - - -@dataclass -class CpuStats: - """ - CPU stats data class. - """ - - ctx_switches: int - interrupts: int - soft_interrupts: int - syscalls: int - - -@dataclass -class CpuFrequency: - """ - CPU frequency data class. - """ - - current: float - min: float - max: float - - -@dataclass -class CpuData: - """ - CPU data aggregate dataclass. - """ - - info: CpuInfo - times: CpuTimes - frequency: CpuFrequency - stats: CpuStats - load_avg: Tuple[float, float, float] - percent: float = percent_field() - - -@dataclass -class MemoryStats: - """ - Memory stats data class. - """ - - total: int = field( - metadata={ - 'metadata': { - 'description': 'Total available memory, in bytes', - } - } - ) - - available: int = field( - metadata={ - 'metadata': { - 'description': 'Available memory, in bytes', - } - } - ) - - used: int = field( - metadata={ - 'metadata': { - 'description': 'Used memory, in bytes', - } - } - ) - - free: int = field( - metadata={ - 'metadata': { - 'description': 'Free memory, in bytes', - } - } - ) - - active: int = field( - metadata={ - 'metadata': { - 'description': 'Size of the active memory, in bytes', - } - } - ) - - inactive: int = field( - metadata={ - 'metadata': { - 'description': 'Size of the inactive memory, in bytes', - } - } - ) - - buffers: int = field( - metadata={ - 'metadata': { - 'description': 'Size of the buffered memory, in bytes', - } - } - ) - - cached: int = field( - metadata={ - 'metadata': { - 'description': 'Size of the cached memory, in bytes', - } - } - ) - - shared: int = field( - metadata={ - 'metadata': { - 'description': 'Size of the shared memory, in bytes', - } - } - ) - - percent: float = percent_field() - - -@dataclass -class SwapStats: - """ - Swap memory stats data class. - """ - - total: int = field( - metadata={ - 'metadata': { - 'description': 'Total available memory, in bytes', - } - } - ) - - used: int = field( - metadata={ - 'metadata': { - 'description': 'Used memory, in bytes', - } - } - ) - - free: int = field( - metadata={ - 'metadata': { - 'description': 'Free memory, in bytes', - } - } - ) - - percent: float = percent_field() - - -@dataclass -class Disk: - """ - Disk data class. - """ - - device: str = field( - metadata={ - 'metadata': { - 'description': 'Path/identifier of the disk/partition', - 'example': '/dev/sda1', - } - } - ) - - mountpoint: Optional[str] = field( - metadata={ - 'metadata': { - 'description': 'Where the disk is mounted', - 'example': '/home', - } - } - ) - - fstype: Optional[str] = field( - metadata={ - 'metadata': { - 'description': 'Filesystem type', - 'example': 'ext4', - } - } - ) - - opts: Optional[str] = field( - metadata={ - 'metadata': { - 'description': 'Extra mount options passed to the partition', - 'example': 'rw,relatime,fmask=0022,dmask=0022,utf8', - } - } - ) - - total: int = field( - metadata={ - 'metadata': { - 'description': 'Total available space, in bytes', - } - } - ) - - used: int = field( - metadata={ - 'metadata': { - 'description': 'Used disk space, in bytes', - } - } - ) - - free: int = field( - metadata={ - 'metadata': { - 'description': 'Free disk space, in bytes', - } - } - ) - - read_count: int = field( - metadata={ - 'metadata': { - 'description': 'Number of recorded read operations', - } - } - ) - - write_count: int = field( - metadata={ - 'metadata': { - 'description': 'Number of recorded write operations', - } - } - ) - - read_bytes: int = field( - metadata={ - 'metadata': { - 'description': 'Number of read bytes', - } - } - ) - - write_bytes: int = field( - metadata={ - 'metadata': { - 'description': 'Number of written bytes', - } - } - ) - - read_time: float = field( - metadata={ - 'metadata': { - 'description': 'Time spent reading, in seconds', - } - } - ) - - write_time: float = field( - metadata={ - 'metadata': { - 'description': 'Time spent writing, in seconds', - } - } - ) - - busy_time: float = field( - metadata={ - 'metadata': { - 'description': 'Total disk busy time, in seconds', - } - } - ) - - percent: float = percent_field() - - -@dataclass -class SystemInfo: - """ - Aggregate system info dataclass. - """ - - cpu: CpuData - memory: MemoryStats - swap: SwapStats - disks: List[Disk] - - -CpuFrequencySchema = class_schema(CpuFrequency, base_schema=DataClassSchema) -CpuInfoSchema = class_schema(CpuInfo, base_schema=CpuInfoBaseSchema) -CpuTimesSchema = class_schema(CpuTimes, base_schema=CpuTimesBaseSchema) -CpuStatsSchema = class_schema(CpuStats, base_schema=DataClassSchema) -DiskSchema = class_schema(Disk, base_schema=DiskBaseSchema) -MemoryStatsSchema = class_schema(MemoryStats, base_schema=MemoryStatsBaseSchema) -SwapStatsSchema = class_schema(SwapStats, base_schema=MemoryStatsBaseSchema) -SystemInfoSchema = class_schema(SystemInfo, base_schema=DataClassSchema) diff --git a/platypush/schemas/system/__init__.py b/platypush/schemas/system/__init__.py new file mode 100644 index 000000000..2e45aae89 --- /dev/null +++ b/platypush/schemas/system/__init__.py @@ -0,0 +1,39 @@ +from ._cpu import ( + Cpu, + CpuFrequency, + CpuFrequencySchema, + CpuInfo, + CpuInfoSchema, + CpuStats, + CpuStatsSchema, + CpuTimes, + CpuTimesSchema, +) +from ._disk import Disk, DiskSchema +from ._memory import MemoryStats, MemoryStatsSchema, SwapStats, SwapStatsSchema +from ._model import SystemInfo +from ._network import NetworkInterface, NetworkInterfaceSchema +from ._schemas import SystemInfoSchema + + +__all__ = [ + "Cpu", + "CpuFrequency", + "CpuFrequencySchema", + "CpuInfo", + "CpuInfoSchema", + "CpuStats", + "CpuStatsSchema", + "CpuTimes", + "CpuTimesSchema", + "Disk", + "DiskSchema", + "MemoryStats", + "MemoryStatsSchema", + "SwapStats", + "SwapStatsSchema", + "NetworkInterface", + "NetworkInterfaceSchema", + "SystemInfo", + "SystemInfoSchema", +] diff --git a/platypush/schemas/system/_base.py b/platypush/schemas/system/_base.py new file mode 100644 index 000000000..aa7addaf8 --- /dev/null +++ b/platypush/schemas/system/_base.py @@ -0,0 +1,16 @@ +from dataclasses import field + +from marshmallow.validate import Range + + +def percent_field(**kwargs): + """ + Field used to model percentage float fields between 0 and 1. + """ + return field( + default_factory=float, + metadata={ + 'validate': Range(min=0, max=1), + **kwargs, + }, + ) diff --git a/platypush/schemas/system/_cpu/__init__.py b/platypush/schemas/system/_cpu/__init__.py new file mode 100644 index 000000000..e2c2b01e3 --- /dev/null +++ b/platypush/schemas/system/_cpu/__init__.py @@ -0,0 +1,15 @@ +from ._model import Cpu, CpuFrequency, CpuInfo, CpuStats, CpuTimes +from ._schemas import CpuFrequencySchema, CpuInfoSchema, CpuStatsSchema, CpuTimesSchema + + +__all__ = [ + "Cpu", + "CpuFrequency", + "CpuFrequencySchema", + "CpuInfo", + "CpuInfoSchema", + "CpuStats", + "CpuStatsSchema", + "CpuTimes", + "CpuTimesSchema", +] diff --git a/platypush/schemas/system/_cpu/_base.py b/platypush/schemas/system/_cpu/_base.py new file mode 100644 index 000000000..eb49a5109 --- /dev/null +++ b/platypush/schemas/system/_cpu/_base.py @@ -0,0 +1,37 @@ +from marshmallow import pre_load + +from platypush.schemas.dataclasses import DataClassSchema + + +class CpuInfoBaseSchema(DataClassSchema): + """ + Base schema for CPU info. + """ + + @pre_load + def pre_load(self, data: dict, **_) -> dict: + if data.get('hz_advertised'): + data['frequency_advertised'] = data.pop('hz_advertised')[0] + if data.get('hz_actual'): + data['frequency_actual'] = data.pop('hz_actual')[0] + + return data + + +class CpuTimesBaseSchema(DataClassSchema): + """ + Base schema for CPU times. + """ + + @pre_load + def pre_load(self, data, **_) -> dict: + """ + Convert the underlying object to dict and normalize all the percentage + values from [0, 100] to [0, 1]. + """ + return { + key: value / 100.0 + for key, value in ( + data if isinstance(data, dict) else data._asdict() + ).items() + } diff --git a/platypush/schemas/system/_cpu/_model.py b/platypush/schemas/system/_cpu/_model.py new file mode 100644 index 000000000..2f4f30b5c --- /dev/null +++ b/platypush/schemas/system/_cpu/_model.py @@ -0,0 +1,178 @@ +from dataclasses import dataclass, field +from typing import List, Optional, Tuple + +from .._base import percent_field + + +@dataclass +class CpuInfo: + """ + CPU info data class. + """ + + architecture: Optional[str] = field( + metadata={ + 'data_key': 'arch_string_raw', + 'metadata': { + 'description': 'CPU architecture', + 'example': 'x86_64', + }, + } + ) + + bits: int = field( + metadata={ + 'metadata': { + 'description': 'CPU bits / register size', + 'example': 64, + } + } + ) + + cores: int = field( + metadata={ + 'data_key': 'count', + 'metadata': { + 'description': 'Number of cores', + 'example': 4, + }, + } + ) + + vendor: Optional[str] = field( + metadata={ + 'data_key': 'vendor_id_raw', + 'metadata': { + 'description': 'Vendor string', + 'example': 'GenuineIntel', + }, + } + ) + + brand: Optional[str] = field( + metadata={ + 'data_key': 'brand_raw', + 'metadata': { + 'description': 'CPU brand string', + 'example': 'Intel(R) Core(TM) i7-5500U CPU @ 2.40GHz', + }, + } + ) + + frequency_advertised: Optional[int] = field( + metadata={ + 'metadata': { + 'description': 'Advertised CPU frequency, in Hz', + 'example': 2400000000, + } + } + ) + + frequency_actual: Optional[int] = field( + metadata={ + 'metadata': { + 'description': 'Actual CPU frequency, in Hz', + 'example': 2350000000, + } + } + ) + + flags: List[str] = field( + metadata={ + 'metadata': { + 'description': 'CPU flags', + 'example': ['acpi', 'aes', 'cpuid'], + } + } + ) + + l1_instruction_cache_size: Optional[int] = field( + metadata={ + 'metadata': { + 'description': 'Size of the L1 instruction cache, in bytes', + 'example': 65536, + } + } + ) + + l1_data_cache_size: Optional[int] = field( + metadata={ + 'metadata': { + 'description': 'Size of the L1 data cache, in bytes', + 'example': 65536, + } + } + ) + + l2_cache_size: Optional[int] = field( + metadata={ + 'metadata': { + 'description': 'Size of the L2 cache, in bytes', + 'example': 524288, + } + } + ) + + l3_cache_size: Optional[int] = field( + metadata={ + 'metadata': { + 'description': 'Size of the L2 cache, in bytes', + 'example': 4194304, + } + } + ) + + +@dataclass +class CpuTimes: + """ + CPU times data class. + """ + + user: Optional[float] = percent_field() + nice: Optional[float] = percent_field() + system: Optional[float] = percent_field() + idle: Optional[float] = percent_field() + iowait: Optional[float] = percent_field() + irq: Optional[float] = percent_field() + softirq: Optional[float] = percent_field() + steal: Optional[float] = percent_field() + guest: Optional[float] = percent_field() + guest_nice: Optional[float] = percent_field() + + +@dataclass +class CpuStats: + """ + CPU stats data class. + """ + + ctx_switches: int + interrupts: int + soft_interrupts: int + syscalls: int + + +@dataclass +class CpuFrequency: + """ + CPU frequency data class. + """ + + current: float + min: float + max: float + + +@dataclass +class Cpu: + """ + CPU data aggregate dataclass. + """ + + info: CpuInfo + times: CpuTimes + frequency: CpuFrequency + stats: CpuStats + load_avg: Tuple[float, float, float] + percent: float = percent_field() diff --git a/platypush/schemas/system/_cpu/_schemas.py b/platypush/schemas/system/_cpu/_schemas.py new file mode 100644 index 000000000..f3cbf939c --- /dev/null +++ b/platypush/schemas/system/_cpu/_schemas.py @@ -0,0 +1,12 @@ +from marshmallow_dataclass import class_schema + +from platypush.schemas.dataclasses import DataClassSchema + +from ._base import CpuInfoBaseSchema, CpuTimesBaseSchema +from ._model import CpuFrequency, CpuInfo, CpuStats, CpuTimes + + +CpuFrequencySchema = class_schema(CpuFrequency, base_schema=DataClassSchema) +CpuInfoSchema = class_schema(CpuInfo, base_schema=CpuInfoBaseSchema) +CpuTimesSchema = class_schema(CpuTimes, base_schema=CpuTimesBaseSchema) +CpuStatsSchema = class_schema(CpuStats, base_schema=DataClassSchema) diff --git a/platypush/schemas/system/_disk/__init__.py b/platypush/schemas/system/_disk/__init__.py new file mode 100644 index 000000000..7f8ac1b69 --- /dev/null +++ b/platypush/schemas/system/_disk/__init__.py @@ -0,0 +1,5 @@ +from ._model import Disk +from ._schemas import DiskSchema + + +__all__ = ["Disk", "DiskSchema"] diff --git a/platypush/schemas/system/_disk/_base.py b/platypush/schemas/system/_disk/_base.py new file mode 100644 index 000000000..35826e7d8 --- /dev/null +++ b/platypush/schemas/system/_disk/_base.py @@ -0,0 +1,22 @@ +from marshmallow import pre_load + +from platypush.schemas.dataclasses import DataClassSchema + + +class DiskBaseSchema(DataClassSchema): + """ + Base schema for disk stats. + """ + + @pre_load + def pre_load(self, data: dict, **_) -> dict: + # Convert read/write/busy times from milliseconds to seconds + for attr in ['read_time', 'write_time', 'busy_time']: + if data.get(attr) is not None: + data[attr] /= 1000 + + # Normalize the percentage between 0 and 1 + if data.get('percent') is not None: + data['percent'] /= 100 + + return data diff --git a/platypush/schemas/system/_disk/_model.py b/platypush/schemas/system/_disk/_model.py new file mode 100644 index 000000000..8cfd6a199 --- /dev/null +++ b/platypush/schemas/system/_disk/_model.py @@ -0,0 +1,129 @@ +from dataclasses import dataclass, field +from typing import Optional + +from .._base import percent_field + + +@dataclass +class Disk: + """ + Disk data class. + """ + + device: str = field( + metadata={ + 'metadata': { + 'description': 'Path/identifier of the disk/partition', + 'example': '/dev/sda1', + } + } + ) + + mountpoint: Optional[str] = field( + metadata={ + 'metadata': { + 'description': 'Where the disk is mounted', + 'example': '/home', + } + } + ) + + fstype: Optional[str] = field( + metadata={ + 'metadata': { + 'description': 'Filesystem type', + 'example': 'ext4', + } + } + ) + + opts: Optional[str] = field( + metadata={ + 'metadata': { + 'description': 'Extra mount options passed to the partition', + 'example': 'rw,relatime,fmask=0022,dmask=0022,utf8', + } + } + ) + + total: int = field( + metadata={ + 'metadata': { + 'description': 'Total available space, in bytes', + } + } + ) + + used: int = field( + metadata={ + 'metadata': { + 'description': 'Used disk space, in bytes', + } + } + ) + + free: int = field( + metadata={ + 'metadata': { + 'description': 'Free disk space, in bytes', + } + } + ) + + read_count: int = field( + metadata={ + 'metadata': { + 'description': 'Number of recorded read operations', + } + } + ) + + write_count: int = field( + metadata={ + 'metadata': { + 'description': 'Number of recorded write operations', + } + } + ) + + read_bytes: int = field( + metadata={ + 'metadata': { + 'description': 'Number of read bytes', + } + } + ) + + write_bytes: int = field( + metadata={ + 'metadata': { + 'description': 'Number of written bytes', + } + } + ) + + read_time: float = field( + metadata={ + 'metadata': { + 'description': 'Time spent reading, in seconds', + } + } + ) + + write_time: float = field( + metadata={ + 'metadata': { + 'description': 'Time spent writing, in seconds', + } + } + ) + + busy_time: float = field( + metadata={ + 'metadata': { + 'description': 'Total disk busy time, in seconds', + } + } + ) + + percent: float = percent_field() diff --git a/platypush/schemas/system/_disk/_schemas.py b/platypush/schemas/system/_disk/_schemas.py new file mode 100644 index 000000000..564910ae4 --- /dev/null +++ b/platypush/schemas/system/_disk/_schemas.py @@ -0,0 +1,7 @@ +from marshmallow_dataclass import class_schema + +from ._base import DiskBaseSchema +from ._model import Disk + + +DiskSchema = class_schema(Disk, base_schema=DiskBaseSchema) diff --git a/platypush/schemas/system/_memory/__init__.py b/platypush/schemas/system/_memory/__init__.py new file mode 100644 index 000000000..c98dc5a75 --- /dev/null +++ b/platypush/schemas/system/_memory/__init__.py @@ -0,0 +1,5 @@ +from ._model import MemoryStats, SwapStats +from ._schemas import MemoryStatsSchema, SwapStatsSchema + + +__all__ = ["MemoryStats", "MemoryStatsSchema", "SwapStats", "SwapStatsSchema"] diff --git a/platypush/schemas/system/_memory/_base.py b/platypush/schemas/system/_memory/_base.py new file mode 100644 index 000000000..73bf1c514 --- /dev/null +++ b/platypush/schemas/system/_memory/_base.py @@ -0,0 +1,16 @@ +from marshmallow import pre_load + +from platypush.schemas.dataclasses import DataClassSchema + + +class MemoryStatsBaseSchema(DataClassSchema): + """ + Base schema for memory stats. + """ + + @pre_load + def pre_load(self, data: dict, **_) -> dict: + # Normalize the percentage between 0 and 1 + if data.get('percent') is not None: + data['percent'] /= 100 + return data diff --git a/platypush/schemas/system/_memory/_model.py b/platypush/schemas/system/_memory/_model.py new file mode 100644 index 000000000..7de7f66da --- /dev/null +++ b/platypush/schemas/system/_memory/_model.py @@ -0,0 +1,117 @@ +from dataclasses import dataclass, field + +from .._base import percent_field + + +@dataclass +class MemoryStats: + """ + Memory stats data class. + """ + + total: int = field( + metadata={ + 'metadata': { + 'description': 'Total available memory, in bytes', + } + } + ) + + available: int = field( + metadata={ + 'metadata': { + 'description': 'Available memory, in bytes', + } + } + ) + + used: int = field( + metadata={ + 'metadata': { + 'description': 'Used memory, in bytes', + } + } + ) + + free: int = field( + metadata={ + 'metadata': { + 'description': 'Free memory, in bytes', + } + } + ) + + active: int = field( + metadata={ + 'metadata': { + 'description': 'Size of the active memory, in bytes', + } + } + ) + + inactive: int = field( + metadata={ + 'metadata': { + 'description': 'Size of the inactive memory, in bytes', + } + } + ) + + buffers: int = field( + metadata={ + 'metadata': { + 'description': 'Size of the buffered memory, in bytes', + } + } + ) + + cached: int = field( + metadata={ + 'metadata': { + 'description': 'Size of the cached memory, in bytes', + } + } + ) + + shared: int = field( + metadata={ + 'metadata': { + 'description': 'Size of the shared memory, in bytes', + } + } + ) + + percent: float = percent_field() + + +@dataclass +class SwapStats: + """ + Swap memory stats data class. + """ + + total: int = field( + metadata={ + 'metadata': { + 'description': 'Total available memory, in bytes', + } + } + ) + + used: int = field( + metadata={ + 'metadata': { + 'description': 'Used memory, in bytes', + } + } + ) + + free: int = field( + metadata={ + 'metadata': { + 'description': 'Free memory, in bytes', + } + } + ) + + percent: float = percent_field() diff --git a/platypush/schemas/system/_memory/_schemas.py b/platypush/schemas/system/_memory/_schemas.py new file mode 100644 index 000000000..9d7bc21df --- /dev/null +++ b/platypush/schemas/system/_memory/_schemas.py @@ -0,0 +1,8 @@ +from marshmallow_dataclass import class_schema + +from ._base import MemoryStatsBaseSchema +from ._model import MemoryStats, SwapStats + + +MemoryStatsSchema = class_schema(MemoryStats, base_schema=MemoryStatsBaseSchema) +SwapStatsSchema = class_schema(SwapStats, base_schema=MemoryStatsBaseSchema) diff --git a/platypush/schemas/system/_model.py b/platypush/schemas/system/_model.py new file mode 100644 index 000000000..89bd7261f --- /dev/null +++ b/platypush/schemas/system/_model.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass +from typing import List + +from ._cpu import Cpu +from ._disk import Disk +from ._memory import MemoryStats, SwapStats +from ._network import NetworkInterface + + +@dataclass +class SystemInfo: + """ + Aggregate system info dataclass. + """ + + cpu: Cpu + memory: MemoryStats + swap: SwapStats + disks: List[Disk] + network: List[NetworkInterface] diff --git a/platypush/schemas/system/_network/__init__.py b/platypush/schemas/system/_network/__init__.py new file mode 100644 index 000000000..969c95498 --- /dev/null +++ b/platypush/schemas/system/_network/__init__.py @@ -0,0 +1,8 @@ +from ._model import NetworkInterface +from ._schemas import NetworkInterfaceSchema + + +__all__ = [ + "NetworkInterface", + "NetworkInterfaceSchema", +] diff --git a/platypush/schemas/system/_network/_base.py b/platypush/schemas/system/_network/_base.py new file mode 100644 index 000000000..b0e2da2b9 --- /dev/null +++ b/platypush/schemas/system/_network/_base.py @@ -0,0 +1,22 @@ +from marshmallow import pre_load + +from platypush.schemas.dataclasses import DataClassSchema + + +class NetworkInterfaceBaseSchema(DataClassSchema): + """ + Base schema for network interface stats. + """ + + @pre_load + def pre_load(self, data: dict, **_) -> dict: + for in_attr, out_attr in { + 'errin': 'errors_in', + 'errout': 'errors_out', + 'dropin': 'drop_in', + 'dropout': 'drop_out', + }.items(): + if in_attr in data: + data[out_attr] = data.pop(in_attr) + + return data diff --git a/platypush/schemas/system/_network/_model.py b/platypush/schemas/system/_network/_model.py new file mode 100644 index 000000000..98db4e4bb --- /dev/null +++ b/platypush/schemas/system/_network/_model.py @@ -0,0 +1,82 @@ +from dataclasses import dataclass, field +from typing import Optional + + +@dataclass +class NetworkInterface: + """ + Network interface statistics data class. + """ + + interface: Optional[str] = field( + metadata={ + 'metadata': { + 'description': 'Network interface identifier', + 'example': 'eth0', + } + } + ) + + bytes_sent: int = field( + metadata={ + 'metadata': { + 'description': 'Number of bytes sent', + } + } + ) + + bytes_recv: int = field( + metadata={ + 'metadata': { + 'description': 'Number of bytes received', + } + } + ) + + packets_sent: int = field( + metadata={ + 'metadata': { + 'description': 'Number of packets sent', + } + } + ) + + packets_recv: int = field( + metadata={ + 'metadata': { + 'description': 'Number of packets received', + } + } + ) + + errors_in: int = field( + metadata={ + 'metadata': { + 'description': 'Number of errors on incoming traffic', + }, + } + ) + + errors_out: int = field( + metadata={ + 'metadata': { + 'description': 'Number of errors on outgoing traffic', + }, + } + ) + + drop_in: int = field( + metadata={ + 'metadata': { + 'description': 'Number of packets dropped on incoming traffic', + }, + } + ) + + drop_out: int = field( + metadata={ + 'metadata': { + 'description': 'Number of packets dropped on outgoing traffic', + }, + } + ) diff --git a/platypush/schemas/system/_network/_schemas.py b/platypush/schemas/system/_network/_schemas.py new file mode 100644 index 000000000..e960b111b --- /dev/null +++ b/platypush/schemas/system/_network/_schemas.py @@ -0,0 +1,9 @@ +from marshmallow_dataclass import class_schema + +from ._base import NetworkInterfaceBaseSchema +from ._model import NetworkInterface + + +NetworkInterfaceSchema = class_schema( + NetworkInterface, base_schema=NetworkInterfaceBaseSchema +) diff --git a/platypush/schemas/system/_schemas.py b/platypush/schemas/system/_schemas.py new file mode 100644 index 000000000..0bd2992fc --- /dev/null +++ b/platypush/schemas/system/_schemas.py @@ -0,0 +1,8 @@ +from marshmallow_dataclass import class_schema + +from platypush.schemas.dataclasses import DataClassSchema + +from ._model import SystemInfo + + +SystemInfoSchema = class_schema(SystemInfo, base_schema=DataClassSchema)