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 untrusted user: blacklight
GPG key ID: D90FBA7F76362774
6 changed files with 106 additions and 66 deletions

View file

@ -91,12 +91,39 @@
</div> </div>
</div> </div>
<div class="child head" :class="{expanded: !areAddressesCollapsed}" <div class="child" v-if="value.speed">
@click.stop="areAddressesCollapsed = !areAddressesCollapsed"> <div class="col-s-12 col-m-6 label">
<div class="col-11 label">Addresses</div> <div class="name">Speed</div>
<div class="col-1 collapse-toggler pull-right"> </div>
<i class="fas" <div class="value">
:class="{'fa-chevron-down': areAddressesCollapsed, 'fa-chevron-up': !areAddressesCollapsed}" /> <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>
</div> </div>

View file

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

View file

@ -28,23 +28,6 @@ class SensorResponse(SystemResponse):
pass 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): class SensorTemperatureResponse(SensorResponse):
def __init__( def __init__(
self, self,
@ -184,11 +167,6 @@ class SystemResponseList(SystemResponse):
super().__init__(output=[r.output for r in responses], *args, **kwargs) 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): class SensorResponseList(SensorResponse, SystemResponseList):
def __init__(self, responses: List[SensorResponse], *args, **kwargs): def __init__(self, responses: List[SensorResponse], *args, **kwargs):
super().__init__(responses=responses, *args, **kwargs) super().__init__(responses=responses, *args, **kwargs)

View file

@ -21,8 +21,6 @@ from platypush.entities.system import (
SwapStats as SwapStatsModel, SwapStats as SwapStatsModel,
) )
from platypush.message.response.system import ( from platypush.message.response.system import (
NetworkResponseList,
NetworkInterfaceStatsResponse,
SensorTemperatureResponse, SensorTemperatureResponse,
SensorResponseList, SensorResponseList,
SensorFanResponse, SensorFanResponse,
@ -256,15 +254,18 @@ class SystemPlugin(SensorPlugin, EntityManager):
def _network_info(self) -> List[NetworkInterface]: def _network_info(self) -> List[NetworkInterface]:
addrs = psutil.net_if_addrs() addrs = psutil.net_if_addrs()
stats = psutil.net_if_stats()
return NetworkInterfaceSchema().load( # type: ignore return NetworkInterfaceSchema().load( # type: ignore
[ [
{ {
'interface': interface, 'interface': interface,
'addresses': addrs.get(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() for interface, info in psutil.net_io_counters(pernic=True).items()
if any(bool(val) for val in stats._asdict().values()) if any(bool(val) for val in info._asdict().values())
], ],
many=True, many=True,
) )
@ -331,37 +332,6 @@ class SystemPlugin(SensorPlugin, EntityManager):
many=True, 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 @action
def sensors_temperature( def sensors_temperature(
self, sensor: Optional[str] = None, fahrenheit: bool = False self, sensor: Optional[str] = None, fahrenheit: bool = False
@ -706,6 +676,7 @@ class SystemPlugin(SensorPlugin, EntityManager):
NetworkInterfaceModel( NetworkInterfaceModel(
id=f'system:network_interface:{nic["interface"]}', id=f'system:network_interface:{nic["interface"]}',
name=nic.pop('interface'), name=nic.pop('interface'),
reachable=nic.pop('is_up'),
**nic, **nic,
) )
for nic in entities['network'] for nic in entities['network']

View file

@ -1,3 +1,4 @@
from enum import Enum
from socket import AddressFamily from socket import AddressFamily
from marshmallow import pre_load from marshmallow import pre_load
@ -20,6 +21,7 @@ class NetworkInterfaceBaseSchema(SystemBaseSchema):
'errout': 'errors_out', 'errout': 'errors_out',
'dropin': 'drop_in', 'dropin': 'drop_in',
'dropout': 'drop_out', 'dropout': 'drop_out',
'isup': 'is_up',
}.items(): }.items():
if in_attr in data: if in_attr in data:
data[out_attr] = data.pop(in_attr) data[out_attr] = data.pop(in_attr)
@ -32,4 +34,10 @@ class NetworkInterfaceBaseSchema(SystemBaseSchema):
addr['family'] = addr['family'].name addr['family'] = addr['family'].name
data['addresses'][i] = addr 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 return data

View file

@ -2,6 +2,8 @@ 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 @dataclass
class NetworkInterface: 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( addresses: List['NetworkInterfaceAddress'] = field(
default_factory=list, default_factory=list,
metadata={ metadata={