Added memory stats entities.

This commit is contained in:
Fabio Manganiello 2023-04-19 01:31:11 +02:00
parent 0073239a40
commit 4ebfbf3851
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
7 changed files with 426 additions and 95 deletions

View file

@ -0,0 +1,182 @@
<template>
<div class="entity memory-stats-container">
<div class="head" @click.stop="isCollapsed = !isCollapsed">
<div class="col-1 icon">
<EntityIcon
:entity="value"
:loading="loading"
:error="error" />
</div>
<div class="col-8 label">
<div class="name" v-text="value.name" />
</div>
<div class="col-2 value" v-text="Math.round(value.percent * 100, 1) + '%'" />
<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.total != null">
<div class="col-s-12 col-m-6 label">
<div class="name">Total</div>
</div>
<div class="value">
<div class="name" v-text="convertSize(value.total)" />
</div>
</div>
<div class="child" v-if="value.available != null">
<div class="col-s-12 col-m-6 label">
<div class="name">Available</div>
</div>
<div class="value">
<div class="name" v-text="convertSize(value.available)" />
</div>
</div>
<div class="child" v-if="value.used != null">
<div class="col-s-12 col-m-6 label">
<div class="name">Used</div>
</div>
<div class="value">
<div class="name" v-text="convertSize(value.used)" />
</div>
</div>
<div class="child" v-if="value.free != null">
<div class="col-s-12 col-m-6 label">
<div class="name">Free</div>
</div>
<div class="value">
<div class="name" v-text="convertSize(value.free)" />
</div>
</div>
<div class="child" v-if="value.active != null">
<div class="col-s-12 col-m-6 label">
<div class="name">Active</div>
</div>
<div class="value">
<div class="name" v-text="convertSize(value.active)" />
</div>
</div>
<div class="child" v-if="value.inactive != null">
<div class="col-s-12 col-m-6 label">
<div class="name">Inactive</div>
</div>
<div class="value">
<div class="name" v-text="convertSize(value.inactive)" />
</div>
</div>
<div class="child" v-if="value.buffers != null">
<div class="col-s-12 col-m-6 label">
<div class="name">Buffers</div>
</div>
<div class="value">
<div class="name" v-text="convertSize(value.buffers)" />
</div>
</div>
<div class="child" v-if="value.cached != null">
<div class="col-s-12 col-m-6 label">
<div class="name">Cached</div>
</div>
<div class="value">
<div class="name" v-text="convertSize(value.cached)" />
</div>
</div>
<div class="child" v-if="value.shared != null">
<div class="col-s-12 col-m-6 label">
<div class="name">Shared</div>
</div>
<div class="value">
<div class="name" v-text="convertSize(value.shared)" />
</div>
</div>
</div>
</div>
</template>
<script>
import EntityMixin from "./EntityMixin"
import EntityIcon from "./EntityIcon"
export default {
name: 'MemoryStats',
components: {EntityIcon},
mixins: [EntityMixin],
data() {
return {
isCollapsed: true,
}
},
}
</script>
<style lang="scss" scoped>
@import "common";
.entity {
.head {
padding: 0.25em;
.icon {
margin-right: 1em;
}
.value {
text-align: right;
}
}
}
.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

@ -0,0 +1 @@
MemoryStats.vue

View file

@ -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": { "current_sensor": {
"name": "Sensor", "name": "Sensor",
"name_plural": "Sensors", "name_plural": "Sensors",

View file

@ -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 from platypush.common.db import Base
@ -88,3 +88,55 @@ if 'cpu_stats' not in Base.metadata:
__mapper_args__ = { __mapper_args__ = {
'polymorphic_identity': __tablename__, '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__,
}

View file

@ -28,66 +28,6 @@ class SensorResponse(SystemResponse):
pass 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): class DiskPartitionResponse(DiskResponse):
def __init__( def __init__(
self, self,

View file

@ -13,10 +13,10 @@ from platypush.entities.system import (
CpuInfo as CpuInfoModel, CpuInfo as CpuInfoModel,
CpuStats as CpuStatsModel, CpuStats as CpuStatsModel,
CpuTimes as CpuTimesModel, CpuTimes as CpuTimesModel,
MemoryStats as MemoryStatsModel,
SwapStats as SwapStatsModel,
) )
from platypush.message.response.system import ( from platypush.message.response.system import (
VirtualMemoryUsageResponse,
SwapMemoryUsageResponse,
DiskResponseList, DiskResponseList,
DiskPartitionResponse, DiskPartitionResponse,
DiskUsageResponse, DiskUsageResponse,
@ -46,6 +46,10 @@ from platypush.schemas.system import (
CpuStatsSchema, CpuStatsSchema,
CpuTimes, CpuTimes,
CpuTimesSchema, CpuTimesSchema,
MemoryStats,
MemoryStatsSchema,
SwapStats,
SwapStatsSchema,
SystemInfoSchema, SystemInfoSchema,
) )
@ -203,45 +207,35 @@ class SystemPlugin(SensorPlugin, EntityManager):
return psutil.getloadavg() return psutil.getloadavg()
@action def _mem_virtual(self) -> MemoryStats:
def mem_virtual(self) -> VirtualMemoryUsageResponse:
"""
Get the current virtual memory usage stats.
:return: list of :class:`platypush.message.response.system.VirtualMemoryUsageResponse`
"""
import psutil import psutil
mem = psutil.virtual_memory() return MemoryStatsSchema().load(
return VirtualMemoryUsageResponse( psutil.virtual_memory()._asdict()
total=mem.total, ) # type: ignore
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,
)
@action @action
def mem_swap(self) -> SwapMemoryUsageResponse: def mem_virtual(self) -> dict:
""" """
Get the current virtual memory usage stats. 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 import psutil
mem = psutil.swap_memory() return SwapStatsSchema().load(psutil.swap_memory()._asdict()) # type: ignore
return SwapMemoryUsageResponse(
total=mem.total, @action
percent=mem.percent, def mem_swap(self) -> dict:
used=mem.used, """
free=mem.free, Get the current swap memory usage stats.
sin=mem.sin,
sout=mem.sout, :return: .. schema:: system.SwapStatsSchema
) """
return SwapStatsSchema().dump(self._mem_swap()) # type: ignore
@action @action
def disk_partitions(self) -> DiskResponseList: def disk_partitions(self) -> DiskResponseList:
@ -793,11 +787,13 @@ class SystemPlugin(SensorPlugin, EntityManager):
'cpu': { 'cpu': {
'frequency': self._cpu_frequency_avg(), 'frequency': self._cpu_frequency_avg(),
'info': self._cpu_info, 'info': self._cpu_info,
'load_avg': self.load_avg().output, 'load_avg': self.load_avg().output, # type: ignore
'stats': self._cpu_stats(), 'stats': self._cpu_stats(),
'times': self._cpu_times_avg(), 'times': self._cpu_times_avg(),
'percent': self.cpu_percent().output / 100.0, # type: ignore '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'], value=cpu['percent'],
), ),
], ],
) ),
MemoryStatsModel(
id='system:memory',
name='Memory',
**entities['memory'],
),
SwapStatsModel(
id='system:swap',
name='Swap',
**entities['swap'],
),
] ]

View file

@ -9,6 +9,9 @@ from platypush.schemas.dataclasses import DataClassSchema
def percent_field(**kwargs): def percent_field(**kwargs):
"""
Field used to model percentage float fields between 0 and 1.
"""
return field( return field(
default_factory=float, default_factory=float,
metadata={ metadata={
@ -33,6 +36,19 @@ class CpuInfoBaseSchema(DataClassSchema):
return data 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): class CpuTimesBaseSchema(DataClassSchema):
""" """
Base schema for CPU times. Base schema for CPU times.
@ -226,6 +242,120 @@ class CpuData:
percent: float = percent_field() 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 @dataclass
class SystemInfo: class SystemInfo:
""" """
@ -233,10 +363,14 @@ class SystemInfo:
""" """
cpu: CpuData cpu: CpuData
memory: MemoryStats
swap: SwapStats
CpuFrequencySchema = class_schema(CpuFrequency, base_schema=DataClassSchema) CpuFrequencySchema = class_schema(CpuFrequency, base_schema=DataClassSchema)
CpuInfoSchema = class_schema(CpuInfo, base_schema=CpuInfoBaseSchema) CpuInfoSchema = class_schema(CpuInfo, base_schema=CpuInfoBaseSchema)
CpuTimesSchema = class_schema(CpuTimes, base_schema=CpuTimesBaseSchema) CpuTimesSchema = class_schema(CpuTimes, base_schema=CpuTimesBaseSchema)
CpuStatsSchema = class_schema(CpuStats, base_schema=DataClassSchema) 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) SystemInfoSchema = class_schema(SystemInfo, base_schema=DataClassSchema)