Merged `network_stats` into `NetworkInterface` model.

This commit is contained in:
Fabio Manganiello 2023-04-22 17:16:55 +02:00
parent f4036be52b
commit 374f936c1f
Signed by: blacklight
GPG Key ID: D90FBA7F76362774
6 changed files with 106 additions and 66 deletions

View File

@ -91,12 +91,39 @@
</div>
</div>
<div class="child head" :class="{expanded: !areAddressesCollapsed}"
@click.stop="areAddressesCollapsed = !areAddressesCollapsed">
<div class="col-11 label">Addresses</div>
<div class="col-1 collapse-toggler pull-right">
<i class="fas"
:class="{'fa-chevron-down': areAddressesCollapsed, 'fa-chevron-up': !areAddressesCollapsed}" />
<div class="child" v-if="value.speed">
<div class="col-s-12 col-m-6 label">
<div class="name">Speed</div>
</div>
<div class="value">
<div class="name" v-text="value.speed + ' Mbps'" />
</div>
</div>
<div class="child" v-if="value.mtu">
<div class="col-s-12 col-m-6 label">
<div class="name">MTU</div>
</div>
<div class="value">
<div class="name" v-text="value.mtu" />
</div>
</div>
<div class="child" v-if="value.flags?.length">
<div class="col-s-12 col-m-6 label">
<div class="name">Flags</div>
</div>
<div class="value">
<div class="name" v-text="value.flags.join(', ')" />
</div>
</div>
<div class="child head" :class="{expanded: !areAddressesCollapsed}"
@click.stop="areAddressesCollapsed = !areAddressesCollapsed">
<div class="col-11 label">Addresses</div>
<div class="col-1 collapse-toggler pull-right">
<i class="fas"
:class="{'fa-chevron-down': areAddressesCollapsed, 'fa-chevron-up': !areAddressesCollapsed}" />
</div>
</div>

View File

@ -3,6 +3,7 @@ from sqlalchemy import Column, Float, ForeignKey, Integer, JSON, String
from platypush.common.db import Base
from . import Entity
from .devices import Device
if 'cpu' not in Base.metadata:
@ -179,7 +180,7 @@ if 'disk' not in Base.metadata:
if 'network_interface' not in Base.metadata:
class NetworkInterface(Entity):
class NetworkInterface(Device):
"""
``NetworkInterface`` ORM model.
"""
@ -187,7 +188,7 @@ if 'network_interface' not in Base.metadata:
__tablename__ = 'network_interface'
id = Column(
Integer, ForeignKey(Entity.id, ondelete='CASCADE'), primary_key=True
Integer, ForeignKey(Device.id, ondelete='CASCADE'), primary_key=True
)
bytes_sent = Column(Integer)
@ -199,6 +200,10 @@ if 'network_interface' not in Base.metadata:
drop_in = Column(Integer)
drop_out = Column(Integer)
addresses = Column(JSON)
speed = Column(Integer)
mtu = Column(Integer)
duplex = Column(String)
flags = Column(JSON)
__mapper_args__ = {
'polymorphic_identity': __tablename__,

View File

@ -28,23 +28,6 @@ class SensorResponse(SystemResponse):
pass
class NetworkInterfaceStatsResponse(NetworkResponse):
def __init__(
self, nic: str, is_up: bool, duplex: str, speed: int, mtu: int, *args, **kwargs
):
super().__init__(
*args,
output={
'nic': nic,
'is_up': is_up,
'duplex': duplex,
'speed': speed,
'mtu': mtu,
},
**kwargs
)
class SensorTemperatureResponse(SensorResponse):
def __init__(
self,
@ -184,11 +167,6 @@ class SystemResponseList(SystemResponse):
super().__init__(output=[r.output for r in responses], *args, **kwargs)
class NetworkResponseList(NetworkResponse, SystemResponseList):
def __init__(self, responses: List[NetworkResponse], *args, **kwargs):
super().__init__(responses=responses, *args, **kwargs)
class SensorResponseList(SensorResponse, SystemResponseList):
def __init__(self, responses: List[SensorResponse], *args, **kwargs):
super().__init__(responses=responses, *args, **kwargs)

View File

@ -21,8 +21,6 @@ from platypush.entities.system import (
SwapStats as SwapStatsModel,
)
from platypush.message.response.system import (
NetworkResponseList,
NetworkInterfaceStatsResponse,
SensorTemperatureResponse,
SensorResponseList,
SensorFanResponse,
@ -256,15 +254,18 @@ class SystemPlugin(SensorPlugin, EntityManager):
def _network_info(self) -> List[NetworkInterface]:
addrs = psutil.net_if_addrs()
stats = psutil.net_if_stats()
return NetworkInterfaceSchema().load( # type: ignore
[
{
'interface': interface,
'addresses': addrs.get(interface, []),
**stats._asdict(),
**(stats[interface]._asdict() if stats.get(interface) else {}),
**info._asdict(),
}
for interface, stats in psutil.net_io_counters(pernic=True).items()
if any(bool(val) for val in stats._asdict().values())
for interface, info in psutil.net_io_counters(pernic=True).items()
if any(bool(val) for val in info._asdict().values())
],
many=True,
)
@ -331,37 +332,6 @@ class SystemPlugin(SensorPlugin, EntityManager):
many=True,
)
@action
def net_stats(
self, nic: Optional[str] = None
) -> Union[NetworkInterfaceStatsResponse, NetworkResponseList]:
"""
Get stats about the network interfaces.
:param nic: Select the stats for a specific network device (e.g. 'eth0'). Default: get stats for all NICs.
:return: :class:`platypush.message.response.system.NetworkInterfaceStatsResponse` or list of
:class:`platypush.message.response.system.NetworkInterfaceStatsResponse`.
"""
stats = psutil.net_if_stats()
def _expand_stats(_nic, _stats):
return NetworkInterfaceStatsResponse(
nic=_nic,
is_up=_stats.isup,
duplex=_stats.duplex.name,
speed=_stats.speed,
mtu=_stats.mtu,
)
if nic:
stats = [addr for name, addr in stats.items() if name == nic]
assert stats, 'No such network interface: {}'.format(nic)
return _expand_stats(nic, stats[0])
return NetworkResponseList(
[_expand_stats(nic, addr) for nic, addr in stats.items()]
)
@action
def sensors_temperature(
self, sensor: Optional[str] = None, fahrenheit: bool = False
@ -706,6 +676,7 @@ class SystemPlugin(SensorPlugin, EntityManager):
NetworkInterfaceModel(
id=f'system:network_interface:{nic["interface"]}',
name=nic.pop('interface'),
reachable=nic.pop('is_up'),
**nic,
)
for nic in entities['network']

View File

@ -1,3 +1,4 @@
from enum import Enum
from socket import AddressFamily
from marshmallow import pre_load
@ -20,6 +21,7 @@ class NetworkInterfaceBaseSchema(SystemBaseSchema):
'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)
@ -32,4 +34,10 @@ class NetworkInterfaceBaseSchema(SystemBaseSchema):
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,6 +2,8 @@ from dataclasses import dataclass, field
from socket import AddressFamily
from typing import List, Optional
from marshmallow.validate import OneOf
@dataclass
class NetworkInterface:
@ -82,6 +84,55 @@ class NetworkInterface:
}
)
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={