[#344] Removed marshmallow_dataclass dependency from system plugin.

This commit is contained in:
Fabio Manganiello 2024-09-25 23:55:32 +02:00
parent 63d9c1e348
commit a7f03a1af9
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
35 changed files with 1327 additions and 1185 deletions

View file

@ -154,6 +154,8 @@ class SystemPlugin(SensorPlugin, EntityManager):
@staticmethod @staticmethod
def _cpu_frequency_avg() -> CpuFrequency: def _cpu_frequency_avg() -> CpuFrequency:
# Dummy call to ensure the CPU frequency is updated
psutil.cpu_freq(percpu=False)
return CpuFrequencySchema().load(psutil.cpu_freq(percpu=False)) # type: ignore return CpuFrequencySchema().load(psutil.cpu_freq(percpu=False)) # type: ignore
@staticmethod @staticmethod

View file

@ -1,4 +1,3 @@
from dataclasses import field
from datetime import date, datetime from datetime import date, datetime
from uuid import UUID from uuid import UUID
@ -18,8 +17,7 @@ def percent_field(**kwargs):
""" """
Field used to model percentage float fields between 0 and 1. Field used to model percentage float fields between 0 and 1.
""" """
return field( return fields.Float(
default_factory=float,
metadata={ metadata={
'validate': Range(min=0, max=1), 'validate': Range(min=0, max=1),
**kwargs, **kwargs,

View file

@ -5,7 +5,7 @@ from platypush.schemas.dataclasses import DataClassSchema
class SystemBaseSchema(DataClassSchema): class SystemBaseSchema(DataClassSchema):
""" """
Base schema for system info. Base schema for system information.
""" """
@pre_load @pre_load

View file

@ -1,20 +0,0 @@
from enum import Enum
from marshmallow import pre_load
from .._base import SystemBaseSchema
class BatteryBaseSchema(SystemBaseSchema):
"""
Base schema for system battery sensors.
"""
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
percent = data.pop('percent', data.pop('value', None))
seconds_left = data.pop('secsleft', data.pop('seconds_left', None))
data['value'] = percent / 100 if percent is not None else None
data['seconds_left'] = None if isinstance(seconds_left, Enum) else seconds_left
return data

View file

@ -1,39 +1,13 @@
from dataclasses import dataclass, field from dataclasses import dataclass
from typing import Optional from typing import Optional
from platypush.schemas.dataclasses import percent_field
@dataclass @dataclass
class Battery: class Battery:
""" """
System battery sensor wrapper. System battery sensor representation.
""" """
seconds_left: Optional[float] = field( seconds_left: Optional[float] = None
metadata={ power_plugged: Optional[bool] = None
'metadata': { value: Optional[float] = None
'description': 'High threshold for the temperature sensor, in Celsius',
'example': 75,
}
}
)
power_plugged: Optional[bool] = field(
metadata={
'metadata': {
'description': 'Whether the battery is plugged in or not',
'example': False,
}
}
)
value: Optional[float] = percent_field(
metadata={
'metadata': {
'description': 'Current charge left, as a percentage value '
'between 0 and 1',
'example': 0.5,
}
}
)

View file

@ -1,7 +1,45 @@
from marshmallow_dataclass import class_schema from enum import Enum
from ._base import BatteryBaseSchema from marshmallow import fields, pre_load
from ._model import Battery
from platypush.schemas.dataclasses import percent_field
from .._base import SystemBaseSchema
BatterySchema = class_schema(Battery, base_schema=BatteryBaseSchema) class BatterySchema(SystemBaseSchema):
"""
System battery sensor schema.
"""
seconds_left = fields.Float(
allow_none=True,
metadata={
'description': 'Number of seconds left before the battery runs out',
'example': 7200,
},
)
power_plugged = fields.Boolean(
metadata={
'description': 'Whether the battery is currently plugged in',
'example': False,
}
)
value = percent_field(
allow_none=True,
metadata={
'description': 'Current battery charge level, as a percentage between 0 and 1',
'example': 0.5,
},
)
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
percent = data.pop('percent', data.pop('value', None))
seconds_left = data.pop('secsleft', data.pop('seconds_left', None))
data['value'] = percent / 100 if percent is not None else None
data['seconds_left'] = None if isinstance(seconds_left, Enum) else seconds_left
return data

View file

@ -1,39 +0,0 @@
from marshmallow import pre_load
from .._base import SystemBaseSchema
class ConnectionBaseSchema(SystemBaseSchema):
"""
Base schema for connections.
"""
@pre_load
def pre_load(self, data, **_) -> dict:
data = super().pre_load(data)
addr_mapping = {
'laddr': ('local_address', 'local_port'),
'raddr': ('remote_address', 'remote_port'),
}
# Parse laddr/raddr attributes
for ext_attr, (addr_attr, port_attr) in addr_mapping.items():
value = data.pop(ext_attr, None)
if not value:
data[addr_attr] = data[port_attr] = None
elif isinstance(value, tuple):
data[addr_attr], data[port_attr] = value
elif isinstance(value, str):
data[addr_attr] = value
data[port_attr] = None
# Handle enum values
for attr in ['type', 'family']:
value = data.pop(attr, None)
if value is not None:
data[attr] = value.name
if data.get('status') == 'NONE':
data['status'] = None
return data

View file

@ -1,4 +1,4 @@
from dataclasses import dataclass, field from dataclasses import dataclass
from socket import AddressFamily, SocketKind from socket import AddressFamily, SocketKind
from typing import Optional from typing import Optional
@ -9,88 +9,12 @@ class Connection:
Network/UNIX socket data class. Network/UNIX socket data class.
""" """
fd: int = field( fd: int
metadata={ family: AddressFamily
'metadata': { type: SocketKind
'description': 'File descriptor', local_address: str
'example': 3, local_port: Optional[int] = None
}, remote_address: Optional[str] = None
} remote_port: Optional[int] = None
) status: Optional[str] = None
pid: Optional[int] = None
family: AddressFamily = field(
metadata={
'metadata': {
'description': 'Socket family',
'example': AddressFamily.AF_INET.name,
}
}
)
type: SocketKind = field(
metadata={
'metadata': {
'description': 'Socket type',
'example': SocketKind.SOCK_STREAM.name,
}
}
)
local_address: str = field(
metadata={
'metadata': {
'description': 'Local address, as an IP address for network '
'connections and a socket path for a UNIX socket',
'example': '192.168.1.2',
}
}
)
local_port: Optional[int] = field(
metadata={
'metadata': {
'description': 'Local port, if this is a TCP/UDP connection, '
'otherwise null',
'example': 12345,
}
}
)
remote_address: Optional[str] = field(
metadata={
'metadata': {
'description': 'Remote address, if this is a network '
'connection, otherwise null',
'example': '192.168.1.1',
}
}
)
remote_port: Optional[int] = field(
metadata={
'metadata': {
'description': 'Local port, if this is a TCP/UDP connection, '
'otherwise null',
'example': 443,
}
}
)
status: Optional[str] = field(
metadata={
'metadata': {
'description': 'Connection status, if this is a network '
'connection, otherise null',
'example': 'ESTABLISHED',
}
}
)
pid: Optional[int] = field(
metadata={
'metadata': {
'description': 'ID of the process that owns the connection',
'example': 4321,
}
}
)

View file

@ -1,7 +1,111 @@
from marshmallow_dataclass import class_schema from marshmallow import fields, pre_load
from ._base import ConnectionBaseSchema from .._base import SystemBaseSchema
from ._model import Connection
ConnectionSchema = class_schema(Connection, base_schema=ConnectionBaseSchema) class ConnectionSchema(SystemBaseSchema):
"""
Base schema for connections.
"""
fd = fields.Int(
required=True,
metadata={
'description': 'File descriptor number.',
'example': 3,
},
)
family = fields.String(
allow_none=True,
metadata={
'description': 'Address family.',
'example': 'AF_INET',
},
)
type = fields.String(
allow_none=True,
metadata={
'description': 'Socket type.',
'example': 'SOCK_STREAM',
},
)
local_address = fields.String(
allow_none=True,
metadata={
'description': 'Local address.',
'example': '192.168.1.3',
},
)
local_port = fields.Int(
allow_none=True,
metadata={
'description': 'Local port.',
'example': 1234,
},
)
remote_address = fields.String(
allow_none=True,
metadata={
'description': 'Remote address.',
'example': '192.168.1.4',
},
)
remote_port = fields.Int(
allow_none=True,
metadata={
'description': 'Remote port.',
'example': 5678,
},
)
status = fields.String(
allow_none=True,
metadata={
'description': 'Connection status.',
'example': 'ESTABLISHED',
},
)
pid = fields.Int(
allow_none=True,
metadata={
'description': 'ID of the process that owns the connection.',
'example': 1234,
},
)
@pre_load
def pre_load(self, data, **_) -> dict:
data = super().pre_load(data)
addr_mapping = {
'laddr': ('local_address', 'local_port'),
'raddr': ('remote_address', 'remote_port'),
}
# Parse laddr/raddr attributes
for ext_attr, (addr_attr, port_attr) in addr_mapping.items():
value = data.pop(ext_attr, None)
if not value:
data[addr_attr] = data[port_attr] = None
elif isinstance(value, tuple):
data[addr_attr], data[port_attr] = value
elif isinstance(value, str):
data[addr_attr] = value
data[port_attr] = None
# Handle enum values
for attr in ['type', 'family']:
value = data.pop(attr, None)
if value is not None:
data[attr] = value.name
if data.get('status') == 'NONE':
data['status'] = None
return data

View file

@ -1,5 +1,11 @@
from ._model import Cpu, CpuFrequency, CpuInfo, CpuStats, CpuTimes from ._model import Cpu, CpuFrequency, CpuInfo, CpuStats, CpuTimes
from ._schemas import CpuFrequencySchema, CpuInfoSchema, CpuStatsSchema, CpuTimesSchema from ._schemas import (
CpuFrequencySchema,
CpuInfoSchema,
CpuSchema,
CpuStatsSchema,
CpuTimesSchema,
)
__all__ = [ __all__ = [
@ -8,6 +14,7 @@ __all__ = [
"CpuFrequencySchema", "CpuFrequencySchema",
"CpuInfo", "CpuInfo",
"CpuInfoSchema", "CpuInfoSchema",
"CpuSchema",
"CpuStats", "CpuStats",
"CpuStatsSchema", "CpuStatsSchema",
"CpuTimes", "CpuTimes",

View file

@ -1,56 +0,0 @@
from marshmallow import pre_load
from .._base import SystemBaseSchema
class CpuInfoBaseSchema(SystemBaseSchema):
"""
Base schema for CPU info.
"""
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
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]
for key, value in data.items():
if key.endswith("_cache_size") and isinstance(value, str):
tokens = value.split(" ")
unit = None
if len(tokens) > 1:
unit = tokens[1]
value = int(tokens[0])
if unit == "KiB":
value *= 1024
elif unit == "MiB":
value *= 1024 * 1024
elif unit == "GiB":
value *= 1024 * 1024 * 1024
data[key] = value
return data
class CpuTimesBaseSchema(SystemBaseSchema):
"""
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].
"""
data = super().pre_load(data)
return {
key: value / 100.0
for key, value in (
data if isinstance(data, dict) else data._asdict()
).items()
}

View file

@ -1,8 +1,6 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
from platypush.schemas.dataclasses import percent_field
@dataclass @dataclass
class CpuInfo: class CpuInfo:
@ -10,117 +8,18 @@ class CpuInfo:
CPU info data class. CPU info data class.
""" """
architecture: Optional[str] = field( bits: int
metadata={ cores: int
'data_key': 'arch_string_raw', architecture: Optional[str] = None
'metadata': { vendor: Optional[str] = None
'description': 'CPU architecture', brand: Optional[str] = None
'example': 'x86_64', frequency_advertised: Optional[float] = None
}, frequency_actual: Optional[float] = None
} flags: List[str] = field(default_factory=list)
) l1_instruction_cache_size: Optional[float] = None
l1_data_cache_size: Optional[float] = None
bits: int = field( l2_cache_size: Optional[float] = None
metadata={ l3_cache_size: Optional[float] = None
'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[float] = field(
metadata={
'metadata': {
'description': 'Advertised CPU frequency, in Hz',
'example': 2400000000,
}
}
)
frequency_actual: Optional[float] = 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[float] = field(
metadata={
'metadata': {
'description': 'Size of the L1 instruction cache, in bytes',
'example': 65536,
}
}
)
l1_data_cache_size: Optional[float] = field(
metadata={
'metadata': {
'description': 'Size of the L1 data cache, in bytes',
'example': 65536,
}
}
)
l2_cache_size: Optional[float] = field(
metadata={
'metadata': {
'description': 'Size of the L2 cache, in bytes',
'example': 524288,
}
}
)
l3_cache_size: Optional[float] = field(
metadata={
'metadata': {
'description': 'Size of the L2 cache, in bytes',
'example': 4194304,
}
}
)
@dataclass @dataclass
@ -129,16 +28,16 @@ class CpuTimes:
CPU times data class. CPU times data class.
""" """
user: Optional[float] = percent_field() user: Optional[float] = None
nice: Optional[float] = percent_field() nice: Optional[float] = None
system: Optional[float] = percent_field() system: Optional[float] = None
idle: Optional[float] = percent_field() idle: Optional[float] = None
iowait: Optional[float] = percent_field() iowait: Optional[float] = None
irq: Optional[float] = percent_field() irq: Optional[float] = None
softirq: Optional[float] = percent_field() softirq: Optional[float] = None
steal: Optional[float] = percent_field() steal: Optional[float] = None
guest: Optional[float] = percent_field() guest: Optional[float] = None
guest_nice: Optional[float] = percent_field() guest_nice: Optional[float] = None
@dataclass @dataclass
@ -175,4 +74,4 @@ class Cpu:
frequency: CpuFrequency frequency: CpuFrequency
stats: CpuStats stats: CpuStats
load_avg: Tuple[float, float, float] load_avg: Tuple[float, float, float]
percent: float = percent_field() percent: float

View file

@ -1,11 +1,312 @@
from marshmallow_dataclass import class_schema from marshmallow import fields, pre_load
from platypush.schemas.dataclasses import percent_field
from .._base import SystemBaseSchema from .._base import SystemBaseSchema
from ._base import CpuInfoBaseSchema, CpuTimesBaseSchema
from ._model import CpuFrequency, CpuInfo, CpuStats, CpuTimes
CpuFrequencySchema = class_schema(CpuFrequency, base_schema=SystemBaseSchema) class CpuInfoSchema(SystemBaseSchema):
CpuInfoSchema = class_schema(CpuInfo, base_schema=CpuInfoBaseSchema) """
CpuTimesSchema = class_schema(CpuTimes, base_schema=CpuTimesBaseSchema) Base schema for CPU info.
CpuStatsSchema = class_schema(CpuStats, base_schema=SystemBaseSchema) """
architecture = fields.String(
data_key='arch_string_raw',
metadata={
'description': 'CPU architecture.',
'example': 'x86_64',
},
)
bits = fields.Int(
metadata={
'description': 'CPU architecture bits.',
'example': 64,
}
)
cores = fields.Int(
data_key='count',
metadata={
'description': 'Number of CPU cores.',
'example': 4,
},
)
vendor = fields.String(
data_key='vendor_id_raw',
metadata={
'description': 'CPU vendor.',
'example': 'GenuineIntel',
},
)
brand = fields.String(
data_key='brand_raw',
metadata={
'description': 'CPU brand.',
'example': 'Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz',
},
)
frequency_advertised = fields.Float(
metadata={
'description': 'CPU advertised frequency.',
'example': 2800000000.0,
}
)
frequency_actual = fields.Float(
metadata={
'description': 'CPU actual frequency.',
'example': 2650000000.0,
}
)
flags = fields.List(
fields.String(),
metadata={
'description': 'CPU flags.',
'example': ['fpu', 'vme', 'de', 'pse', 'tsc', 'msr', 'pae'],
},
)
l1_instruction_cache_size = fields.Int(
metadata={
'description': 'L1 instruction cache size.',
'example': 65536,
}
)
l1_data_cache_size = fields.Int(
metadata={
'description': 'L1 data cache size.',
'example': 65536,
}
)
l2_cache_size = fields.Int(
metadata={
'description': 'L2 cache size.',
'example': 524288,
}
)
l3_cache_size = fields.Int(
metadata={
'description': 'L3 cache size.',
'example': 6291456,
}
)
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
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]
for key, value in data.items():
if key.endswith("_cache_size") and isinstance(value, str):
tokens = value.split(" ")
unit = None
if len(tokens) > 1:
unit = tokens[1]
value = int(tokens[0])
if unit == "KiB":
value *= 1024
elif unit == "MiB":
value *= 1024 * 1024
elif unit == "GiB":
value *= 1024 * 1024 * 1024
data[key] = value
return data
class CpuTimesSchema(SystemBaseSchema):
"""
Base schema for CPU times.
"""
user = percent_field(
metadata={
'description': 'Time spent in user mode, '
'as a percentage between 0 and 1 of the total CPU time.',
'example': 0.0,
}
)
nice = percent_field(
metadata={
'description': 'Time spent in nice mode, '
'as a percentage between 0 and 1 of the total CPU time.',
'example': 0.0,
}
)
system = percent_field(
metadata={
'description': 'Time spent in system mode, '
'as a percentage between 0 and 1 of the total CPU time.',
'example': 0.0,
}
)
idle = percent_field(
metadata={
'description': 'Time spent in idle mode, '
'as a percentage between 0 and 1 of the total CPU time.',
'example': 0.0,
}
)
iowait = percent_field(
metadata={
'description': 'Time spent in I/O wait mode, '
'as a percentage between 0 and 1 of the total CPU time.',
'example': 0.0,
}
)
irq = percent_field(
metadata={
'description': 'Time spent in IRQ mode, '
'as a percentage between 0 and 1 of the total CPU time.',
'example': 0.0,
}
)
softirq = percent_field(
metadata={
'description': 'Time spent in soft IRQ mode, '
'as a percentage between 0 and 1 of the total CPU time.',
'example': 0.0,
}
)
steal = percent_field(
metadata={
'description': 'Time spent in steal mode, '
'as a percentage between 0 and 1 of the total CPU time.',
'example': 0.0,
}
)
guest = percent_field(
metadata={
'description': 'Time spent in guest mode, '
'as a percentage between 0 and 1 of the total CPU time.',
'example': 0.0,
}
)
guest_nice = percent_field(
metadata={
'description': 'Time spent in guest nice mode, '
'as a percentage between 0 and 1 of the total CPU time.',
'example': 0.0,
}
)
@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].
"""
data = super().pre_load(data)
return {
key: value / 100.0
for key, value in (
data if isinstance(data, dict) else data._asdict()
).items()
}
class CpuFrequencySchema(SystemBaseSchema):
"""
Base schema for CPU frequency.
"""
current = fields.Float(
metadata={
'description': 'Current CPU frequency.',
'example': 2800000000.0,
}
)
min = fields.Float(
metadata={
'description': 'Minimum CPU frequency.',
'example': 800000000.0,
}
)
max = fields.Float(
metadata={
'description': 'Maximum CPU frequency.',
'example': 2800000000.0,
}
)
class CpuStatsSchema(SystemBaseSchema):
"""
Base schema for CPU stats.
"""
ctx_switches = fields.Int(
metadata={
'description': 'Number of context switches.',
'example': 1234,
}
)
interrupts = fields.Int(
metadata={
'description': 'Number of interrupts.',
'example': 1234,
}
)
soft_interrupts = fields.Int(
metadata={
'description': 'Number of soft interrupts.',
'example': 1234,
}
)
syscalls = fields.Int(
metadata={
'description': 'Number of system calls.',
'example': 1234,
}
)
class CpuSchema(SystemBaseSchema):
"""
Base schema for CPU results.
"""
info = fields.Nested(CpuInfoSchema)
times = fields.Nested(CpuTimesSchema)
frequency = fields.Nested(CpuFrequencySchema)
stats = fields.Nested(CpuStatsSchema)
load_avg = fields.Tuple(
[fields.Float(), fields.Float(), fields.Float()],
metadata={
'description': 'CPU load average.',
'example': (0.0, 0.0, 0.0),
},
)
percent = percent_field(
metadata={
'description': 'CPU usage percentage, as a value between 0 and 1.',
'example': 0.0,
}
)

View file

@ -1,24 +0,0 @@
from marshmallow import pre_load
from .._base import SystemBaseSchema
class DiskBaseSchema(SystemBaseSchema):
"""
Base schema for disk stats.
"""
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
# 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

View file

@ -1,8 +1,6 @@
from dataclasses import dataclass, field from dataclasses import dataclass
from typing import Optional from typing import Optional
from platypush.schemas.dataclasses import percent_field
@dataclass @dataclass
class Disk: class Disk:
@ -10,120 +8,18 @@ class Disk:
Disk data class. Disk data class.
""" """
device: str = field( device: str
metadata={ mountpoint: Optional[str] = None
'metadata': { fstype: Optional[str] = None
'description': 'Path/identifier of the disk/partition', opts: Optional[str] = None
'example': '/dev/sda1', total: Optional[int] = None
} used: Optional[int] = None
} free: Optional[int] = None
) read_count: Optional[int] = None
write_count: Optional[int] = None
mountpoint: Optional[str] = field( read_bytes: Optional[int] = None
metadata={ write_bytes: Optional[int] = None
'metadata': { read_time: Optional[float] = None
'description': 'Where the disk is mounted', write_time: Optional[float] = None
'example': '/home', busy_time: Optional[float] = None
} percent: Optional[float] = None
}
)
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: Optional[int] = field(
metadata={
'metadata': {
'description': 'Number of recorded read operations',
}
}
)
write_count: Optional[int] = field(
metadata={
'metadata': {
'description': 'Number of recorded write operations',
}
}
)
read_bytes: Optional[int] = field(
metadata={
'metadata': {
'description': 'Number of read bytes',
}
}
)
write_bytes: Optional[int] = field(
metadata={
'metadata': {
'description': 'Number of written bytes',
}
}
)
read_time: Optional[float] = field(
metadata={
'metadata': {
'description': 'Time spent reading, in seconds',
}
}
)
write_time: Optional[float] = field(
metadata={
'metadata': {
'description': 'Time spent writing, in seconds',
}
}
)
busy_time: Optional[float] = field(
metadata={
'metadata': {
'description': 'Total disk busy time, in seconds',
}
}
)
percent: float = percent_field()

View file

@ -1,7 +1,131 @@
from marshmallow_dataclass import class_schema from marshmallow import fields, pre_load
from ._base import DiskBaseSchema from platypush.schemas.dataclasses import percent_field
from ._model import Disk from .._base import SystemBaseSchema
DiskSchema = class_schema(Disk, base_schema=DiskBaseSchema) class DiskSchema(SystemBaseSchema):
"""
Base schema for disk stats.
"""
device = fields.String(
required=True,
metadata={
'description': 'Path/identifier of the disk/partition',
'example': '/dev/sda1',
},
)
mountpoint = fields.String(
metadata={
'description': 'Mountpoint of the disk/partition',
'example': '/mnt/data',
}
)
fstype = fields.String(
metadata={
'description': 'Filesystem type',
'example': 'ext4',
}
)
opts = fields.String( # type: ignore
metadata={
'description': 'Mount options',
'example': 'rw,relatime',
}
)
total = fields.Integer(
metadata={
'description': 'Total disk space in bytes',
'example': 1024**3,
}
)
used = fields.Integer(
metadata={
'description': 'Used disk space in bytes',
'example': 1024**2,
}
)
free = fields.Integer(
metadata={
'description': 'Free disk space in bytes',
'example': (1024**3) - (1024**2),
}
)
read_count = fields.Integer(
metadata={
'description': 'Number of read operations',
'example': 100,
}
)
write_count = fields.Integer(
metadata={
'description': 'Number of write operations',
'example': 50,
}
)
read_bytes = fields.Integer(
metadata={
'description': 'Number of bytes read',
'example': 1024**3,
}
)
write_bytes = fields.Integer(
metadata={
'description': 'Number of bytes written',
'example': 1024**2,
}
)
read_time = fields.Float(
metadata={
'description': 'Time spent reading in seconds',
'example': 10.5,
}
)
write_time = fields.Float(
metadata={
'description': 'Time spent writing in seconds',
'example': 5.5,
}
)
busy_time = fields.Float(
metadata={
'description': 'Time spent doing I/Os in seconds',
'example': 20.5,
}
)
percent = percent_field(
metadata={
'description': 'Percentage of disk space used, normalized between 0 and 1',
'example': 0.1,
}
)
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
# 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

View file

@ -1,15 +0,0 @@
from marshmallow import pre_load
from .._base import SystemBaseSchema
class FanBaseSchema(SystemBaseSchema):
"""
Base schema for system fan sensors.
"""
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
data['value'] = data.pop('current', data.pop('value', None))
return data

View file

@ -1,4 +1,5 @@
from dataclasses import dataclass, field from dataclasses import dataclass
from typing import Optional
@dataclass @dataclass
@ -7,29 +8,6 @@ class Fan:
System fan sensor data class. System fan sensor data class.
""" """
id: str = field( id: str
metadata={ label: Optional[str] = None
'metadata': { value: Optional[float] = None
'description': 'Unique ID for the sensor',
'example': 'acpi_1',
}
}
)
label: str = field(
metadata={
'metadata': {
'description': 'Name of the sensor',
'example': 'CPU',
}
}
)
value: float = field(
metadata={
'metadata': {
'description': 'Current fan speed, in RPM',
'example': 3000,
}
}
)

View file

@ -1,7 +1,39 @@
from marshmallow_dataclass import class_schema from marshmallow import fields, pre_load
from ._base import FanBaseSchema from .._base import SystemBaseSchema
from ._model import Fan
FanSchema = class_schema(Fan, base_schema=FanBaseSchema) class FanSchema(SystemBaseSchema):
"""
Base schema for system fan sensors.
"""
id = fields.String(
required=True,
metadata={
'description': 'Unique ID for the sensor',
'example': 'acpi_1',
},
)
label = fields.String(
allow_none=True,
metadata={
'description': 'Label for the sensor',
'example': 'CPU Fan',
},
)
value = fields.Float(
allow_none=True,
metadata={
'description': 'Current fan speed in RPM',
'example': 1200,
},
)
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
data['value'] = data.pop('current', data.pop('value', None))
return data

View file

@ -1,18 +0,0 @@
from marshmallow import pre_load
from .._base import SystemBaseSchema
class MemoryStatsBaseSchema(SystemBaseSchema):
"""
Base schema for memory stats.
"""
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
# Normalize the percentage between 0 and 1
if data.get('percent') is not None:
data['percent'] /= 100
return data

View file

@ -1,6 +1,4 @@
from dataclasses import dataclass, field from dataclasses import dataclass
from platypush.schemas.dataclasses import percent_field
@dataclass @dataclass
@ -9,79 +7,16 @@ class MemoryStats:
Memory stats data class. Memory stats data class.
""" """
total: int = field( total: int
metadata={ available: int
'metadata': { used: int
'description': 'Total available memory, in bytes', free: int
} active: int
} inactive: int
) buffers: int
cached: int
available: int = field( shared: int
metadata={ percent: float
'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 @dataclass
@ -90,28 +25,7 @@ class SwapStats:
Swap memory stats data class. Swap memory stats data class.
""" """
total: int = field( total: int
metadata={ used: int
'metadata': { free: int
'description': 'Total available memory, in bytes', percent: float
}
}
)
used: int = field(
metadata={
'metadata': {
'description': 'Used memory, in bytes',
}
}
)
free: int = field(
metadata={
'metadata': {
'description': 'Free memory, in bytes',
}
}
)
percent: float = percent_field()

View file

@ -1,8 +1,133 @@
from marshmallow_dataclass import class_schema from marshmallow import fields, pre_load
from ._base import MemoryStatsBaseSchema from platypush.schemas.dataclasses import percent_field
from ._model import MemoryStats, SwapStats
from .._base import SystemBaseSchema
MemoryStatsSchema = class_schema(MemoryStats, base_schema=MemoryStatsBaseSchema) class MemoryStatsSchema(SystemBaseSchema):
SwapStatsSchema = class_schema(SwapStats, base_schema=MemoryStatsBaseSchema) """
Base schema for memory stats.
"""
total = fields.Integer(
metadata={
'description': 'Total memory available in bytes.',
'example': 8589934592,
}
)
available = fields.Integer(
metadata={
'description': 'Memory available in bytes.',
'example': 2147483648,
}
)
used = fields.Integer(
metadata={
'description': 'Memory used in bytes.',
'example': 6442450944,
}
)
free = fields.Integer(
metadata={
'description': 'Memory free in bytes.',
'example': 2147483648,
}
)
active = fields.Integer(
metadata={
'description': 'Memory active in bytes.',
'example': 4294967296,
}
)
inactive = fields.Integer(
metadata={
'description': 'Memory inactive in bytes.',
'example': 2147483648,
}
)
buffers = fields.Integer(
metadata={
'description': 'Memory buffers in bytes.',
'example': 2147483648,
}
)
cached = fields.Integer(
metadata={
'description': 'Memory cached in bytes.',
'example': 2147483648,
}
)
shared = fields.Integer(
metadata={
'description': 'Memory shared in bytes.',
'example': 3221225472,
}
)
percent = percent_field(
metadata={
'description': 'Memory usage percentage between 0 and 1.',
'example': 0.75,
}
)
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
# Normalize the percentage between 0 and 1
if data.get('percent') is not None:
data['percent'] /= 100
return data
class SwapStatsSchema(SystemBaseSchema):
"""
Base schema for swap stats.
"""
total = fields.Integer(
metadata={
'description': 'Total memory available in bytes.',
'example': 8589934592,
}
)
used = fields.Integer(
metadata={
'description': 'Memory used in bytes.',
'example': 6442450944,
}
)
free = fields.Integer(
metadata={
'description': 'Memory free in bytes.',
'example': 2147483648,
}
)
percent = percent_field(
metadata={
'description': 'Memory usage percentage between 0 and 1.',
'example': 0.75,
}
)
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
# Normalize the percentage between 0 and 1
if data.get('percent') is not None:
data['percent'] /= 100
return data

View file

@ -1,43 +0,0 @@
from enum import Enum
from socket import AddressFamily
from marshmallow import pre_load
from .._base import SystemBaseSchema
class NetworkInterfaceBaseSchema(SystemBaseSchema):
"""
Base schema for network interface stats.
"""
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
# Custom attribute mappings
for in_attr, out_attr in {
'errin': 'errors_in',
'errout': 'errors_out',
'dropin': 'drop_in',
'dropout': 'drop_out',
'isup': 'is_up',
}.items():
if in_attr in data:
data[out_attr] = data.pop(in_attr)
# Serialize enum values
for i, addr in enumerate(data.get('addresses', [])):
if hasattr(addr, '_asdict'):
addr = addr._asdict()
if isinstance(addr.get('family'), AddressFamily):
addr['family'] = addr['family'].name
data['addresses'][i] = addr
if isinstance(data.get('duplex'), Enum):
data['duplex'] = data['duplex'].name.split('_')[-1]
# Split the flags string
data['flags'] = data.get('flags', '').split(',')
return data

View file

@ -2,154 +2,6 @@ from dataclasses import dataclass, field
from socket import AddressFamily from socket import AddressFamily
from typing import List, Optional from typing import List, Optional
from marshmallow.validate import OneOf
@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',
},
}
)
is_up: bool = field(
metadata={
'metadata': {
'description': 'Whether the interface is active',
'example': True,
},
}
)
speed: int = field(
metadata={
'metadata': {
'description': 'Interface reported speed in Mbps',
'example': 10000,
},
}
)
mtu: int = field(
metadata={
'metadata': {
'description': 'Interface maximum transmission unit expressed '
'in bytes',
'example': 65535,
},
}
)
duplex: str = field(
metadata={
'validate': OneOf(['FULL', 'HALF', 'UNKNOWN']),
'metadata': {
'description': 'Interface duplex configuration. Can be FULL, '
'HALF or UNKNOWN',
'example': 'FULL',
},
}
)
flags: List[str] = field(
default_factory=list,
metadata={
'metadata': {
'description': 'List of flags associated to the interface',
'example': ['up', 'broadcast', 'running'],
}
},
)
addresses: List['NetworkInterfaceAddress'] = field(
default_factory=list,
metadata={
'metadata': {
'description': 'List of addresses associated to the interface',
'example': [
{
'family': AddressFamily.AF_INET.name,
'address': '192.168.1.2',
'netmask': '255.255.255.0',
'broadcast': '192.168.1.255',
}
],
},
},
)
@dataclass @dataclass
class NetworkInterfaceAddress: class NetworkInterfaceAddress:
@ -157,38 +9,30 @@ class NetworkInterfaceAddress:
Network interface address data class. Network interface address data class.
""" """
family: AddressFamily = field( family: Optional[AddressFamily] = None
metadata={ address: Optional[str] = None
'metadata': { netmask: Optional[str] = None
'description': 'Address family', broadcast: Optional[str] = None
'example': AddressFamily.AF_INET.name,
}
}
)
address: Optional[str] = field(
metadata={
'metadata': {
'description': 'IPv4 or IPv6 address of the interface',
'example': '192.168.1.2',
}
}
)
netmask: Optional[str] = field( @dataclass
metadata={ class NetworkInterface:
'metadata': { """
'description': 'Netmask for the interface address', Network interface statistics data class.
'example': '255.255.255.0', """
}
}
)
broadcast: Optional[str] = field( interface: Optional[str] = None
metadata={ bytes_sent: int = 0
'metadata': { bytes_recv: int = 0
'description': 'Broadcast address for the interface', packets_sent: int = 0
'example': '192.168.1.255', packets_recv: int = 0
} errors_in: int = 0
} errors_out: int = 0
) drop_in: int = 0
drop_out: int = 0
is_up: bool = False
speed: int = 0
mtu: int = 0
duplex: Optional[str] = None
flags: List[str] = field(default_factory=list)
addresses: List[NetworkInterfaceAddress] = field(default_factory=list)

View file

@ -1,9 +1,207 @@
from marshmallow_dataclass import class_schema from enum import Enum
from socket import AddressFamily
from ._base import NetworkInterfaceBaseSchema from marshmallow import fields, pre_load
from ._model import NetworkInterface from marshmallow.validate import OneOf
from .._base import SystemBaseSchema
NetworkInterfaceSchema = class_schema( class NetworkInterfaceAddressSchema(SystemBaseSchema):
NetworkInterface, base_schema=NetworkInterfaceBaseSchema """
) Schema for network interface address.
"""
family = fields.String(
allow_none=True,
metadata={
'description': 'The address family.',
'example': 'AF_INET',
},
)
address = fields.String(
allow_none=True,
metadata={
'description': 'The IP address associated with the interface.',
'example': '192.168.1.4',
},
)
netmask = fields.String(
allow_none=True,
metadata={
'description': 'The netmask associated with the interface.',
'example': '255.255.255.0',
},
)
broadcast = fields.String(
allow_none=True,
metadata={
'description': 'The broadcast address associated with the interface.',
'example': '192.168.1.255',
},
)
class NetworkInterfaceSchema(SystemBaseSchema):
"""
Schema for network interface stats.
"""
interface = fields.String(
allow_none=True,
metadata={
'description': 'The name of the network interface.',
'example': 'eth0',
},
)
bytes_sent = fields.Integer(
missing=0,
metadata={
'description': 'The number of bytes sent.',
'example': 123456,
},
)
bytes_recv = fields.Integer(
missing=0,
metadata={
'description': 'The number of bytes received.',
'example': 654321,
},
)
packets_sent = fields.Integer(
missing=0,
metadata={
'description': 'The number of packets sent.',
'example': 123,
},
)
packets_recv = fields.Integer(
missing=0,
metadata={
'description': 'The number of packets received.',
'example': 321,
},
)
errors_in = fields.Integer(
missing=0,
metadata={
'description': 'The number of errors on the input side.',
'example': 10,
},
)
errors_out = fields.Integer(
missing=0,
metadata={
'description': 'The number of errors on the output side.',
'example': 5,
},
)
drop_in = fields.Integer(
missing=0,
metadata={
'description': 'The number of dropped packets on the input side.',
'example': 1,
},
)
drop_out = fields.Integer(
missing=0,
metadata={
'description': 'The number of dropped packets on the output side.',
'example': 2,
},
)
is_up = fields.Boolean(
metadata={
'description': 'Whether the interface is up.',
'example': True,
},
)
speed = fields.Integer(
metadata={
'description': 'The advertised speed of the interface in Mbps.',
'example': 1000,
},
)
mtu = fields.Integer(
metadata={
'description': 'The maximum transmission unit of the interface in bytes.',
'example': 1500,
},
)
duplex = fields.String(
validate=OneOf(['FULL', 'HALF', 'UNKNOWN']),
metadata={
'description': 'Interface duplex configuration. Can be FULL, '
'HALF or UNKNOWN',
'example': 'FULL',
},
)
flags = fields.List(
fields.String(),
metadata={
'description': 'A list of flags associated with the interface.',
'example': ['up', 'broadcast', 'running', 'multicast'],
},
)
addresses = fields.List(
fields.Nested(NetworkInterfaceAddressSchema),
metadata={
'description': 'A list of addresses associated with the interface.',
'example': [
{
'family': 'AF_INET',
'address': '192.168.1.4',
'netmask': '255.255.255.0',
'broadcast': '192.168.1.255',
},
],
},
)
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
# Custom attribute mappings
for in_attr, out_attr in {
'errin': 'errors_in',
'errout': 'errors_out',
'dropin': 'drop_in',
'dropout': 'drop_out',
'isup': 'is_up',
}.items():
if in_attr in data:
data[out_attr] = data.pop(in_attr)
# Serialize enum values
for i, addr in enumerate(data.get('addresses', [])):
if hasattr(addr, '_asdict'):
addr = addr._asdict()
if isinstance(addr.get('family'), AddressFamily):
addr['family'] = addr['family'].name
data['addresses'][i] = addr
if isinstance(data.get('duplex'), Enum):
data['duplex'] = data['duplex'].name.split('_')[-1]
# Split the flags string
data['flags'] = data.get('flags', '').split(',')
return data

View file

@ -1,28 +0,0 @@
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

View file

@ -2,8 +2,6 @@ from dataclasses import dataclass, field
from datetime import datetime from datetime import datetime
from typing import List, Optional from typing import List, Optional
from platypush.schemas.dataclasses import percent_field
@dataclass @dataclass
class Process: class Process:
@ -11,100 +9,14 @@ class Process:
System process data class. System process data class.
""" """
pid: int = field( pid: int
metadata={ name: str
'metadata': { parent_pid: Optional[int] = None
'description': 'Process PID', username: Optional[str] = None
'example': 12345, command_line: List[str] = field(default_factory=list)
} status: Optional[str] = None
} terminal: Optional[str] = None
) current_directory: Optional[str] = None
started: Optional[datetime] = None
name: str = field( cpu_percent: float = 0
metadata={ memory_percent: float = 0
'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,
}
}
)

View file

@ -1,7 +1,124 @@
from marshmallow_dataclass import class_schema from datetime import datetime
from ._base import ProcessBaseSchema from dateutil.tz import gettz
from ._model import Process from marshmallow import fields, pre_load
from platypush.schemas import DateTime
from platypush.schemas.dataclasses import percent_field
from .._base import SystemBaseSchema
ProcessSchema = class_schema(Process, base_schema=ProcessBaseSchema) class ProcessSchema(SystemBaseSchema):
"""
Schema for system processes.
"""
pid = fields.Int(
missing=-1,
metadata={
'description': 'The process ID.',
'example': 1234,
},
)
name = fields.String(
allow_none=True,
metadata={
'description': 'The name of the process.',
'example': 'python',
},
)
parent_pid = fields.Int(
allow_none=True,
metadata={
'description': 'The parent process ID.',
'example': 1000,
},
)
username = fields.String(
allow_none=True,
metadata={
'description': 'The username of the process owner.',
'example': 'root',
},
)
command_line = fields.List(
fields.String(),
allow_none=True,
metadata={
'description': 'The command line arguments of the process.',
'example': ['python', 'script.py'],
},
)
status = fields.String(
allow_none=True,
metadata={
'description': 'The status of the process.',
'example': 'running',
},
)
terminal = fields.String(
allow_none=True,
metadata={
'description': 'The terminal of the process.',
'example': 'tty1',
},
)
current_directory = fields.String(
allow_none=True,
metadata={
'description': 'The current working directory of the process.',
'example': '/home/user',
},
)
started = DateTime(
allow_none=True,
metadata={
'description': 'The timestamp when the process was started.',
'example': '2021-01-01T00:00:00+00:00',
},
)
cpu_percent = percent_field(
missing=0,
metadata={
'description': 'The CPU usage percentage of the process, in the range [0, 1].',
'example': 0.5,
},
)
memory_percent = percent_field(
missing=0,
metadata={
'description': 'The memory usage percentage of the process, in the range [0, 1].',
'example': 0.5,
},
)
@pre_load
def pre_load(self, data, **_) -> dict:
import psutil
if hasattr(data, 'as_dict'):
try:
data = data.as_dict()
except psutil.NoSuchProcess:
return {}
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

View file

@ -1,8 +1,25 @@
from marshmallow_dataclass import class_schema from marshmallow import fields
from platypush.schemas.dataclasses import DataClassSchema from ._base import SystemBaseSchema
from ._battery import BatterySchema
from ._model import SystemInfo from ._cpu import CpuSchema
from ._disk import DiskSchema
from ._fan import FanSchema
from ._memory import MemoryStatsSchema, SwapStatsSchema
from ._network import NetworkInterfaceSchema
from ._temperature import TemperatureSchema
SystemInfoSchema = class_schema(SystemInfo, base_schema=DataClassSchema) class SystemInfoSchema(SystemBaseSchema):
"""
Schema for system info.
"""
cpu = fields.Nested(CpuSchema)
memory = fields.Nested(MemoryStatsSchema)
swap = fields.Nested(SwapStatsSchema)
disks = fields.List(fields.Nested(DiskSchema))
network = fields.List(fields.Nested(NetworkInterfaceSchema))
temperature = fields.List(fields.Nested(TemperatureSchema))
fans = fields.List(fields.Nested(FanSchema))
battery = fields.Nested(BatterySchema)

View file

@ -1,15 +0,0 @@
from marshmallow import pre_load
from .._base import SystemBaseSchema
class TemperatureBaseSchema(SystemBaseSchema):
"""
Base schema for system temperature sensors.
"""
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
data['value'] = data.pop('current', data.pop('value', None))
return data

View file

@ -1,54 +1,15 @@
from dataclasses import dataclass, field from dataclasses import dataclass
from typing import Optional from typing import Optional
@dataclass @dataclass
class Temperature: class Temperature:
""" """
System temperature sensor wrapper. System temperature sensor data class.
""" """
id: str = field( id: str
metadata={ label: Optional[str] = None
'metadata': { value: Optional[float] = None
'description': 'Unique ID for the sensor', high: Optional[float] = None
'example': 'acpi_1', critical: Optional[float] = None
}
}
)
label: str = field(
metadata={
'metadata': {
'description': 'Name of the sensor',
'example': 'CPU',
}
}
)
value: float = field(
metadata={
'metadata': {
'description': 'Current temperature value, in Celsius',
'example': 55,
}
}
)
high: Optional[float] = field(
metadata={
'metadata': {
'description': 'High threshold for the temperature sensor, in Celsius',
'example': 75,
}
}
)
critical: Optional[float] = field(
metadata={
'metadata': {
'description': 'Critical threshold for the temperature sensor, in Celsius',
'example': 95,
}
}
)

View file

@ -1,7 +1,54 @@
from marshmallow_dataclass import class_schema from marshmallow import fields, pre_load
from ._base import TemperatureBaseSchema from .._base import SystemBaseSchema
from ._model import Temperature
TemperatureSchema = class_schema(Temperature, base_schema=TemperatureBaseSchema) class TemperatureSchema(SystemBaseSchema):
"""
Schema for system temperature sensors.
"""
id = fields.String(
metadata={
'description': 'The unique identifier of the temperature sensor.',
'example': 'acpi_1',
}
)
label = fields.String(
allow_none=True,
metadata={
'description': 'The label of the temperature sensor.',
'example': 'CPU Temperature',
},
)
value = fields.Float(
allow_none=True,
metadata={
'description': 'The current temperature value of the sensor, in degrees Celsius.',
'example': 56.0,
},
)
high = fields.Float(
allow_none=True,
metadata={
'description': 'The high temperature threshold of the sensor, in degrees Celsius.',
'example': 90.0,
},
)
critical = fields.Float(
allow_none=True,
metadata={
'description': 'The critical temperature threshold of the sensor, in degrees Celsius.',
'example': 100.0,
},
)
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
data['value'] = data.pop('current', data.pop('value', None))
return data

View file

@ -1,22 +0,0 @@
from datetime import datetime
from dateutil.tz import gettz
from marshmallow import pre_load
from .._base import SystemBaseSchema
class UserBaseSchema(SystemBaseSchema):
"""
Base schema for system users.
"""
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
started_ts = data.pop('started', None)
if started_ts is not None:
data['started'] = datetime.fromtimestamp(started_ts).replace(tzinfo=gettz())
data['username'] = data.pop('name', data.pop('username', None))
return data

View file

@ -1,4 +1,4 @@
from dataclasses import dataclass, field from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
@ -9,37 +9,7 @@ class User:
System user wrapper. System user wrapper.
""" """
username: str = field( username: str
metadata={ terminal: Optional[str] = None
'metadata': { started: Optional[datetime] = None
'description': 'Username', pid: Optional[int] = None
'example': 'root',
}
}
)
terminal: Optional[str] = field(
metadata={
'metadata': {
'description': 'Identifier of the terminal the user is connected to',
'example': 'pts/1',
}
}
)
started: Optional[datetime] = field(
metadata={
'metadata': {
'description': 'When the user session started',
}
}
)
pid: Optional[int] = field(
metadata={
'metadata': {
'description': 'PID of the process that holds the session',
'example': 12345,
}
}
)

View file

@ -1,7 +1,47 @@
from marshmallow_dataclass import class_schema from datetime import datetime
from ._base import UserBaseSchema from dateutil.tz import gettz
from ._model import User from marshmallow import fields, pre_load
from platypush.schemas import DateTime
from .._base import SystemBaseSchema
UserSchema = class_schema(User, base_schema=UserBaseSchema) class UserSchema(SystemBaseSchema):
"""
Schema for system users.
"""
username = fields.String(
required=True,
metadata={'description': 'The username of the user.', 'example': 'johndoe'},
)
terminal = fields.String(
metadata={
'description': 'The terminal the user is currently using.',
'example': 'tty1',
}
)
started = DateTime(
metadata={
'description': 'The timestamp when the user session started.',
'example': '2021-01-01T00:00:00+00:00',
}
)
pid = fields.Integer(
metadata={'description': 'The PID of the user session.', 'example': 1234}
)
@pre_load
def pre_load(self, data: dict, **_) -> dict:
data = super().pre_load(data)
started_ts = data.pop('started', None)
if started_ts is not None:
data['started'] = datetime.fromtimestamp(started_ts).replace(tzinfo=gettz())
data['username'] = data.pop('name', data.pop('username', None))
return data