diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/MemoryStats.vue b/platypush/backend/http/webapp/src/components/panels/Entities/MemoryStats.vue new file mode 100644 index 00000000..b013df2c --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/MemoryStats.vue @@ -0,0 +1,182 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/SwapStats.vue b/platypush/backend/http/webapp/src/components/panels/Entities/SwapStats.vue new file mode 120000 index 00000000..fedbe17e --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/SwapStats.vue @@ -0,0 +1 @@ +MemoryStats.vue \ No newline at end of file diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/meta.json b/platypush/backend/http/webapp/src/components/panels/Entities/meta.json index cbaa8bf8..49e45a60 100644 --- a/platypush/backend/http/webapp/src/components/panels/Entities/meta.json +++ b/platypush/backend/http/webapp/src/components/panels/Entities/meta.json @@ -47,6 +47,22 @@ } }, + "memory_stats": { + "name": "System", + "name_plural": "System", + "icon": { + "class": "fas fa-memory" + } + }, + + "swap_stats": { + "name": "System", + "name_plural": "System", + "icon": { + "class": "fas fa-memory" + } + }, + "current_sensor": { "name": "Sensor", "name_plural": "Sensors", diff --git a/platypush/entities/system.py b/platypush/entities/system.py index 60525a20..687f81ac 100644 --- a/platypush/entities/system.py +++ b/platypush/entities/system.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, ForeignKey, Integer, JSON, String +from sqlalchemy import Column, Float, ForeignKey, Integer, JSON, String from platypush.common.db import Base @@ -88,3 +88,55 @@ if 'cpu_stats' not in Base.metadata: __mapper_args__ = { 'polymorphic_identity': __tablename__, } + + +if 'memory_stats' not in Base.metadata: + + class MemoryStats(Entity): + """ + ``MemoryStats`` ORM model. + """ + + __tablename__ = 'memory_stats' + + id = Column( + Integer, ForeignKey(Entity.id, ondelete='CASCADE'), primary_key=True + ) + + total = Column(Integer) + available = Column(Integer) + used = Column(Integer) + free = Column(Integer) + active = Column(Integer) + inactive = Column(Integer) + buffers = Column(Integer) + cached = Column(Integer) + shared = Column(Integer) + percent = Column(Float) + + __mapper_args__ = { + 'polymorphic_identity': __tablename__, + } + + +if 'swap_stats' not in Base.metadata: + + class SwapStats(Entity): + """ + ``SwapStats`` ORM model. + """ + + __tablename__ = 'swap_stats' + + id = Column( + Integer, ForeignKey(Entity.id, ondelete='CASCADE'), primary_key=True + ) + + total = Column(Integer) + used = Column(Integer) + free = Column(Integer) + percent = Column(Float) + + __mapper_args__ = { + 'polymorphic_identity': __tablename__, + } diff --git a/platypush/message/response/system/__init__.py b/platypush/message/response/system/__init__.py index 45c7d51f..ac9b4157 100644 --- a/platypush/message/response/system/__init__.py +++ b/platypush/message/response/system/__init__.py @@ -28,66 +28,6 @@ class SensorResponse(SystemResponse): pass -class VirtualMemoryUsageResponse(MemoryResponse): - def __init__( - self, - total: int, - available: int, - percent: float, - used: int, - free: int, - active: int, - inactive: int, - buffers: int, - cached: int, - shared: int, - *args, - **kwargs - ): - super().__init__( - *args, - output={ - 'total': total, - 'available': available, - 'percent': percent, - 'used': used, - 'free': free, - 'active': active, - 'inactive': inactive, - 'buffers': buffers, - 'cached': cached, - 'shared': shared, - }, - **kwargs - ) - - -class SwapMemoryUsageResponse(MemoryResponse): - def __init__( - self, - total: int, - percent: float, - used: int, - free: int, - sin: int, - sout: int, - *args, - **kwargs - ): - super().__init__( - *args, - output={ - 'total': total, - 'percent': percent, - 'used': used, - 'free': free, - 'sin': sin, - 'sout': sout, - }, - **kwargs - ) - - class DiskPartitionResponse(DiskResponse): def __init__( self, diff --git a/platypush/plugins/system/__init__.py b/platypush/plugins/system/__init__.py index 20e5392e..dc455936 100644 --- a/platypush/plugins/system/__init__.py +++ b/platypush/plugins/system/__init__.py @@ -13,10 +13,10 @@ from platypush.entities.system import ( CpuInfo as CpuInfoModel, CpuStats as CpuStatsModel, CpuTimes as CpuTimesModel, + MemoryStats as MemoryStatsModel, + SwapStats as SwapStatsModel, ) from platypush.message.response.system import ( - VirtualMemoryUsageResponse, - SwapMemoryUsageResponse, DiskResponseList, DiskPartitionResponse, DiskUsageResponse, @@ -46,6 +46,10 @@ from platypush.schemas.system import ( CpuStatsSchema, CpuTimes, CpuTimesSchema, + MemoryStats, + MemoryStatsSchema, + SwapStats, + SwapStatsSchema, SystemInfoSchema, ) @@ -203,45 +207,35 @@ class SystemPlugin(SensorPlugin, EntityManager): return psutil.getloadavg() - @action - def mem_virtual(self) -> VirtualMemoryUsageResponse: - """ - Get the current virtual memory usage stats. - :return: list of :class:`platypush.message.response.system.VirtualMemoryUsageResponse` - """ + def _mem_virtual(self) -> MemoryStats: import psutil - mem = psutil.virtual_memory() - return VirtualMemoryUsageResponse( - total=mem.total, - available=mem.available, - percent=mem.percent, - used=mem.used, - free=mem.free, - active=mem.active, - inactive=mem.inactive, - buffers=mem.buffers, - cached=mem.cached, - shared=mem.shared, - ) + return MemoryStatsSchema().load( + psutil.virtual_memory()._asdict() + ) # type: ignore @action - def mem_swap(self) -> SwapMemoryUsageResponse: + def mem_virtual(self) -> dict: """ Get the current virtual memory usage stats. - :return: list of :class:`platypush.message.response.system.SwapMemoryUsageResponse` + + :return: .. schema:: system.MemoryStatsSchema """ + return MemoryStatsSchema().dump(self._mem_virtual()) # type: ignore + + def _mem_swap(self) -> SwapStats: import psutil - mem = psutil.swap_memory() - return SwapMemoryUsageResponse( - total=mem.total, - percent=mem.percent, - used=mem.used, - free=mem.free, - sin=mem.sin, - sout=mem.sout, - ) + return SwapStatsSchema().load(psutil.swap_memory()._asdict()) # type: ignore + + @action + def mem_swap(self) -> dict: + """ + Get the current swap memory usage stats. + + :return: .. schema:: system.SwapStatsSchema + """ + return SwapStatsSchema().dump(self._mem_swap()) # type: ignore @action def disk_partitions(self) -> DiskResponseList: @@ -793,11 +787,13 @@ class SystemPlugin(SensorPlugin, EntityManager): 'cpu': { 'frequency': self._cpu_frequency_avg(), 'info': self._cpu_info, - 'load_avg': self.load_avg().output, + 'load_avg': self.load_avg().output, # type: ignore 'stats': self._cpu_stats(), 'times': self._cpu_times_avg(), 'percent': self.cpu_percent().output / 100.0, # type: ignore }, + 'memory': self._mem_virtual(), + 'swap': self._mem_swap(), } ) @@ -867,7 +863,17 @@ class SystemPlugin(SensorPlugin, EntityManager): value=cpu['percent'], ), ], - ) + ), + MemoryStatsModel( + id='system:memory', + name='Memory', + **entities['memory'], + ), + SwapStatsModel( + id='system:swap', + name='Swap', + **entities['swap'], + ), ] diff --git a/platypush/schemas/system.py b/platypush/schemas/system.py index fb496382..927272c1 100644 --- a/platypush/schemas/system.py +++ b/platypush/schemas/system.py @@ -9,6 +9,9 @@ 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={ @@ -33,6 +36,19 @@ class CpuInfoBaseSchema(DataClassSchema): 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. @@ -226,6 +242,120 @@ class CpuData: 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 SystemInfo: """ @@ -233,10 +363,14 @@ class SystemInfo: """ cpu: CpuData + memory: MemoryStats + swap: SwapStats 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) +MemoryStatsSchema = class_schema(MemoryStats, base_schema=MemoryStatsBaseSchema) +SwapStatsSchema = class_schema(SwapStats, base_schema=MemoryStatsBaseSchema) SystemInfoSchema = class_schema(SystemInfo, base_schema=DataClassSchema)