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.
This commit is contained in:
Fabio Manganiello 2023-04-21 00:45:15 +02:00
parent 44b8fd4b34
commit 98a300c4b1
Signed by: blacklight
GPG key ID: D90FBA7F76362774
26 changed files with 1006 additions and 595 deletions

View file

@ -0,0 +1,167 @@
<template>
<div class="entity network-interface-container">
<div class="head" @click.stop="isCollapsed = !isCollapsed">
<div class="col-1 icon">
<EntityIcon
:entity="value"
:loading="loading"
:error="error" />
</div>
<div class="col-10 label">
<div class="name" v-text="value.name" />
</div>
<div class="col-1 collapse-toggler" @click.stop="isCollapsed = !isCollapsed">
<i class="fas"
:class="{'fa-chevron-down': isCollapsed, 'fa-chevron-up': !isCollapsed}" />
</div>
</div>
<div class="body children attributes fade-in" v-if="!isCollapsed">
<div class="child" v-if="value.bytes_sent">
<div class="col-s-12 col-m-6 label">
<div class="name">Bytes sent</div>
</div>
<div class="value">
<div class="name" v-text="convertSize(value.bytes_sent)" />
</div>
</div>
<div class="child" v-if="value.bytes_recv">
<div class="col-s-12 col-m-6 label">
<div class="name">Bytes received</div>
</div>
<div class="value">
<div class="name" v-text="convertSize(value.bytes_recv)" />
</div>
</div>
<div class="child" v-if="value.packets_sent">
<div class="col-s-12 col-m-6 label">
<div class="name">Packets sent</div>
</div>
<div class="value">
<div class="name" v-text="value.packets_sent" />
</div>
</div>
<div class="child" v-if="value.packets_recv">
<div class="col-s-12 col-m-6 label">
<div class="name">Packets received</div>
</div>
<div class="value">
<div class="name" v-text="value.packets_recv" />
</div>
</div>
<div class="child" v-if="value.errors_in">
<div class="col-s-12 col-m-6 label">
<div class="name">Inbound errors</div>
</div>
<div class="value">
<div class="name" v-text="value.errors_in" />
</div>
</div>
<div class="child" v-if="value.errors_out">
<div class="col-s-12 col-m-6 label">
<div class="name">Outbound errors</div>
</div>
<div class="value">
<div class="name" v-text="value.errors_out" />
</div>
</div>
<div class="child" v-if="value.drop_in">
<div class="col-s-12 col-m-6 label">
<div class="name">Dropped inbound packets</div>
</div>
<div class="value">
<div class="name" v-text="value.drop_in" />
</div>
</div>
<div class="child" v-if="value.drop_out">
<div class="col-s-12 col-m-6 label">
<div class="name">Dropped outbound packets</div>
</div>
<div class="value">
<div class="name" v-text="value.drop_out" />
</div>
</div>
</div>
</div>
</template>
<script>
import EntityMixin from "./EntityMixin"
import EntityIcon from "./EntityIcon"
export default {
name: 'NetworkInterface',
components: {EntityIcon},
mixins: [EntityMixin],
data() {
return {
isCollapsed: true,
}
},
}
</script>
<style lang="scss" scoped>
@import "common";
.entity {
.head {
padding: 0.25em;
.icon {
margin-right: 1em;
}
}
}
.collapse-toggler {
display: flex;
align-items: center;
flex: 1;
min-height: 3em;
cursor: pointer;
&:hover {
color: $default-hover-fg;
}
}
.attributes .child {
margin: 0 -0.5em;
padding: 0.5em 1em;
&:not(:last-child) {
border-bottom: 1px solid $border-color-1;
}
&:hover {
cursor: initial;
}
.label {
font-weight: bold;
@include from($tablet) {
@extend .col-m-6;
}
}
.value {
font-size: 0.95em;
text-align: right;
@include from($tablet) {
@extend .col-m-6;
}
}
}
</style>

View file

@ -71,6 +71,14 @@
} }
}, },
"network_interface": {
"name": "System",
"name_plural": "System",
"icon": {
"class": "fas fa-ethernet"
}
},
"current_sensor": { "current_sensor": {
"name": "Sensor", "name": "Sensor",
"name_plural": "Sensors", "name_plural": "Sensors",

View file

@ -175,3 +175,30 @@ if 'disk' not in Base.metadata:
__mapper_args__ = { __mapper_args__ = {
'polymorphic_identity': __tablename__, '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__,
}

View file

@ -28,38 +28,6 @@ class SensorResponse(SystemResponse):
pass 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): class NetworkConnectionResponse(NetworkResponse):
# noinspection PyShadowingBuiltins # noinspection PyShadowingBuiltins
def __init__( def __init__(

View file

@ -18,10 +18,10 @@ from platypush.entities.system import (
CpuTimes as CpuTimesModel, CpuTimes as CpuTimesModel,
Disk as DiskModel, Disk as DiskModel,
MemoryStats as MemoryStatsModel, MemoryStats as MemoryStatsModel,
NetworkInterface as NetworkInterfaceModel,
SwapStats as SwapStatsModel, SwapStats as SwapStatsModel,
) )
from platypush.message.response.system import ( from platypush.message.response.system import (
NetworkIoCountersResponse,
NetworkResponseList, NetworkResponseList,
NetworkConnectionResponse, NetworkConnectionResponse,
NetworkAddressResponse, NetworkAddressResponse,
@ -50,6 +50,8 @@ from platypush.schemas.system import (
DiskSchema, DiskSchema,
MemoryStats, MemoryStats,
MemoryStatsSchema, MemoryStatsSchema,
NetworkInterface,
NetworkInterfaceSchema,
SwapStats, SwapStats,
SwapStatsSchema, SwapStatsSchema,
SystemInfoSchema, SystemInfoSchema,
@ -257,47 +259,44 @@ class SystemPlugin(SensorPlugin, EntityManager):
""" """
return DiskSchema().dump(self._disk_info(), many=True) return DiskSchema().dump(self._disk_info(), many=True)
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 @action
def net_io_counters( def net_io_counters(self, per_nic: bool = False):
self, nic: Optional[str] = None, per_nic: bool = False
) -> Union[NetworkIoCountersResponse, NetworkResponseList]:
""" """
Get the I/O counters stats for the network interfaces. Get the information and statistics 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 grouped by interface (default: False).
:param per_nic: Return the stats broken down per interface (default: False). :return: If ``per_nic=False``:
:return: :class:`platypush.message.response.system.NetIoCountersResponse` or list of
:class:`platypush.message.response.system.NetIoCountersResponse`. .. 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)
""" """
def _expand_response(_nic, _stats): if per_nic:
return NetworkIoCountersResponse( return NetworkInterfaceSchema().dump(self._net_io_counters(), many=True)
bytes_sent=_stats.bytes_sent, return NetworkInterfaceSchema().dump(self._net_io_counters_avg())
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()]
)
@action @action
def net_connections( def net_connections(
@ -688,6 +687,7 @@ class SystemPlugin(SensorPlugin, EntityManager):
'memory': self._mem_virtual(), 'memory': self._mem_virtual(),
'swap': self._mem_swap(), 'swap': self._mem_swap(),
'disks': self._disk_info(), 'disks': self._disk_info(),
'network': self._net_io_counters(),
} }
) )
@ -772,6 +772,14 @@ class SystemPlugin(SensorPlugin, EntityManager):
) )
for disk in entities['disks'] for disk in entities['disks']
], ],
*[
NetworkInterfaceModel(
id=f'system:network_interface:{nic["interface"]}',
name=nic.pop('interface'),
**nic,
)
for nic in entities['network']
],
] ]

View file

@ -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)

View file

@ -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",
]

View file

@ -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,
},
)

View file

@ -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",
]

View file

@ -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()
}

View file

@ -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()

View file

@ -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)

View file

@ -0,0 +1,5 @@
from ._model import Disk
from ._schemas import DiskSchema
__all__ = ["Disk", "DiskSchema"]

View file

@ -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

View file

@ -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()

View file

@ -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)

View file

@ -0,0 +1,5 @@
from ._model import MemoryStats, SwapStats
from ._schemas import MemoryStatsSchema, SwapStatsSchema
__all__ = ["MemoryStats", "MemoryStatsSchema", "SwapStats", "SwapStatsSchema"]

View file

@ -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

View file

@ -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()

View file

@ -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)

View file

@ -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]

View file

@ -0,0 +1,8 @@
from ._model import NetworkInterface
from ._schemas import NetworkInterfaceSchema
__all__ = [
"NetworkInterface",
"NetworkInterfaceSchema",
]

View file

@ -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

View file

@ -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',
},
}
)

View file

@ -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
)

View file

@ -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)