mirror of
https://github.com/yavook/kiwi-simple-metrics.git
synced 2024-11-24 08:23:01 +00:00
improved aggregation by refactoring metrics._report
ReportData mutable, Report immutable class Report has builtin `aggregate`
This commit is contained in:
parent
b14a573b24
commit
8b532829a5
5 changed files with 117 additions and 107 deletions
|
@ -4,25 +4,56 @@ from typing import Self
|
||||||
from ..settings import MetricSettings
|
from ..settings import MetricSettings
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(slots=True, kw_only=True)
|
||||||
|
class ReportData:
|
||||||
|
name: str
|
||||||
|
value: float
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_free_total(cls, *, name: str, free: float, total: float) -> Self:
|
||||||
|
return cls(
|
||||||
|
name=name,
|
||||||
|
value=(total - free) / total * 100,
|
||||||
|
)
|
||||||
|
|
||||||
|
def report(self, settings: MetricSettings) -> "Report":
|
||||||
|
result = settings.report.format(
|
||||||
|
name=self.name,
|
||||||
|
value=self.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
return Report(result, failed=(
|
||||||
|
self.value > settings.threshold and not settings.inverted
|
||||||
|
or self.value < settings.threshold and settings.inverted
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True, frozen=True)
|
@dataclass(slots=True, frozen=True)
|
||||||
class Report:
|
class Report:
|
||||||
result: str
|
result: str
|
||||||
failed: bool = field(default=False, kw_only=True)
|
failed: bool = field(default=False, kw_only=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def new(
|
def aggregate(
|
||||||
cls, *,
|
cls, *,
|
||||||
settings: MetricSettings,
|
settings: MetricSettings,
|
||||||
name: str,
|
data: list[ReportData],
|
||||||
value: float,
|
|
||||||
) -> Self:
|
) -> Self:
|
||||||
result = settings.report.format(name=name, value=value)
|
reports = [
|
||||||
|
data.report(settings)
|
||||||
|
for data in data
|
||||||
|
]
|
||||||
|
|
||||||
if (
|
return cls(
|
||||||
value > settings.threshold and not settings.inverted
|
settings.report_outer.format(
|
||||||
or value < settings.threshold and settings.inverted
|
name=settings.name,
|
||||||
):
|
inner=", ".join(
|
||||||
return cls(result, failed=True)
|
report.result
|
||||||
|
for report in reports[:settings.count]
|
||||||
else:
|
),
|
||||||
return cls(result)
|
),
|
||||||
|
failed=any(
|
||||||
|
report.failed
|
||||||
|
for report in reports
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
from ..settings import SETTINGS
|
from ..settings import SETTINGS
|
||||||
from ._report import Report
|
from ._report import Report, ReportData
|
||||||
|
|
||||||
|
|
||||||
|
def _hwdata() -> ReportData:
|
||||||
|
return ReportData(
|
||||||
|
name=SETTINGS.cpu.name,
|
||||||
|
value=psutil.cpu_percent(interval=1),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def cpu() -> Report | None:
|
def cpu() -> Report | None:
|
||||||
if not SETTINGS.cpu.enabled:
|
if not SETTINGS.cpu.enabled:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
value = psutil.cpu_percent(interval=1)
|
data = _hwdata()
|
||||||
return Report.new(
|
|
||||||
settings=SETTINGS.cpu,
|
return data.report(SETTINGS.cpu)
|
||||||
name=SETTINGS.cpu.name,
|
|
||||||
value=value,
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,43 +1,32 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from ..settings import SETTINGS
|
from ..settings import SETTINGS
|
||||||
from ._report import Report
|
from ._report import Report, ReportData
|
||||||
|
|
||||||
|
|
||||||
|
def _hwdata() -> list[ReportData]:
|
||||||
|
def get_path_statvfs(path: os.PathLike) -> dict[str, float]:
|
||||||
|
sv = os.statvfs(path)
|
||||||
|
return {
|
||||||
|
"free": sv.f_bavail,
|
||||||
|
"total": sv.f_blocks,
|
||||||
|
}
|
||||||
|
|
||||||
|
return sorted([
|
||||||
|
ReportData.from_free_total(
|
||||||
|
name=str(path),
|
||||||
|
**get_path_statvfs(path),
|
||||||
|
) for path in SETTINGS.disk.paths
|
||||||
|
], key=lambda d: d.value, reverse=True)
|
||||||
|
|
||||||
|
|
||||||
def disk() -> Report | None:
|
def disk() -> Report | None:
|
||||||
if not SETTINGS.disk.enabled:
|
if not SETTINGS.disk.enabled:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def path_to_used_percent(path: os.PathLike) -> float:
|
data = _hwdata()
|
||||||
try:
|
|
||||||
sv = os.statvfs(path)
|
|
||||||
return (1 - sv.f_bavail / sv.f_blocks) * 100
|
|
||||||
except ZeroDivisionError:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
data = sorted([
|
return Report.aggregate(
|
||||||
(str(path), path_to_used_percent(path))
|
|
||||||
for path in SETTINGS.disk.paths
|
|
||||||
], key=lambda d: d[1], reverse=True)
|
|
||||||
|
|
||||||
reports = [Report.new(
|
|
||||||
settings=SETTINGS.disk,
|
settings=SETTINGS.disk,
|
||||||
name=path,
|
data=data,
|
||||||
value=percent,
|
|
||||||
) for path, percent in data]
|
|
||||||
|
|
||||||
report_inner = ", ".join(
|
|
||||||
report.result
|
|
||||||
for report in reports[:SETTINGS.disk.count]
|
|
||||||
)
|
|
||||||
|
|
||||||
return Report(
|
|
||||||
SETTINGS.disk.report_outer.format(
|
|
||||||
name=SETTINGS.disk.name,
|
|
||||||
inner=report_inner,
|
|
||||||
),
|
|
||||||
failed=any(
|
|
||||||
report.failed
|
|
||||||
for report in reports
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,56 +1,43 @@
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
from ..settings import SETTINGS
|
from ..settings import SETTINGS
|
||||||
from ._report import Report
|
from ._report import Report, ReportData
|
||||||
|
|
||||||
|
|
||||||
|
def _hwdata() -> list[ReportData]:
|
||||||
|
vmem = psutil.virtual_memory()
|
||||||
|
swap = psutil.swap_memory()
|
||||||
|
|
||||||
|
if SETTINGS.memory.swap == "exclude":
|
||||||
|
return [ReportData(
|
||||||
|
name=SETTINGS.memory.name_ram,
|
||||||
|
value=vmem.percent,
|
||||||
|
)]
|
||||||
|
|
||||||
|
elif SETTINGS.memory.swap == "combine":
|
||||||
|
return [ReportData.from_free_total(
|
||||||
|
name=SETTINGS.memory.name,
|
||||||
|
free=vmem.available + swap.free,
|
||||||
|
total=vmem.total + swap.total,
|
||||||
|
)]
|
||||||
|
|
||||||
|
else: # SETTINGS.memory.swap == "include"
|
||||||
|
return [ReportData(
|
||||||
|
name=SETTINGS.memory.name_ram,
|
||||||
|
value=vmem.percent,
|
||||||
|
), ReportData(
|
||||||
|
name=SETTINGS.memory.name_swap,
|
||||||
|
value=swap.percent,
|
||||||
|
)]
|
||||||
|
|
||||||
|
|
||||||
def memory() -> Report | None:
|
def memory() -> Report | None:
|
||||||
if not SETTINGS.memory.enabled:
|
if not SETTINGS.memory.enabled:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_used_percent(free: float, total: float) -> float:
|
data = _hwdata()
|
||||||
return (total - free) / total * 100
|
|
||||||
|
|
||||||
vmem = psutil.virtual_memory()
|
return Report.aggregate(
|
||||||
swap = psutil.swap_memory()
|
|
||||||
|
|
||||||
if SETTINGS.memory.swap == "exclude":
|
|
||||||
data = {
|
|
||||||
SETTINGS.memory.name_ram: vmem.percent,
|
|
||||||
}
|
|
||||||
|
|
||||||
elif SETTINGS.memory.swap == "combine":
|
|
||||||
data = {
|
|
||||||
SETTINGS.memory.name: get_used_percent(
|
|
||||||
vmem.available + swap.free,
|
|
||||||
vmem.total + swap.total,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else: # SETTINGS.memory.swap == "include"
|
|
||||||
data = {
|
|
||||||
SETTINGS.memory.name_ram: vmem.percent,
|
|
||||||
SETTINGS.memory.name_swap: swap.percent,
|
|
||||||
}
|
|
||||||
|
|
||||||
reports = [Report.new(
|
|
||||||
settings=SETTINGS.memory,
|
settings=SETTINGS.memory,
|
||||||
name=name,
|
data=data,
|
||||||
value=value,
|
|
||||||
) for name, value in data.items()]
|
|
||||||
|
|
||||||
report_inner = ", ".join(
|
|
||||||
report.result
|
|
||||||
for report in reports
|
|
||||||
)
|
|
||||||
|
|
||||||
return Report(
|
|
||||||
SETTINGS.memory.report_outer.format(
|
|
||||||
name=SETTINGS.memory.name,
|
|
||||||
inner=report_inner,
|
|
||||||
),
|
|
||||||
failed=any(
|
|
||||||
report.failed
|
|
||||||
for report in reports
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,27 +12,28 @@ class MetricSettings(BaseModel):
|
||||||
# metric will be reported
|
# metric will be reported
|
||||||
enabled: bool = True
|
enabled: bool = True
|
||||||
|
|
||||||
# format string to report the metric
|
|
||||||
report: str = "{name}: {value:.2f}%"
|
|
||||||
|
|
||||||
# if the metric value exceeds this percentage, the report fails
|
# if the metric value exceeds this percentage, the report fails
|
||||||
threshold: float
|
threshold: float
|
||||||
|
|
||||||
# if True, this metric fails when the value falls below the `threshold`
|
# if True, this metric fails when the value falls below the `threshold`
|
||||||
inverted: bool = False
|
inverted: bool = False
|
||||||
|
|
||||||
|
# per-value format string for reporting
|
||||||
|
report: str = "{name}: {value:.2f}%"
|
||||||
|
|
||||||
|
# per-metric format string for reporting
|
||||||
|
report_outer: str = "{name}: [{inner}]"
|
||||||
|
|
||||||
|
# include only `count` many items (None: include all)
|
||||||
|
count: int | None = None
|
||||||
|
|
||||||
|
|
||||||
class CpuMS(MetricSettings):
|
class CpuMS(MetricSettings):
|
||||||
name: str = "CPU"
|
name: str = "CPU"
|
||||||
threshold: float = math.inf
|
threshold: float = math.inf
|
||||||
|
|
||||||
|
|
||||||
class MultiMS(MetricSettings):
|
class MemoryMS(MetricSettings):
|
||||||
# outer format string for reporting
|
|
||||||
report_outer: str = "{name}: [{inner}]"
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryMS(MultiMS):
|
|
||||||
name: str = "Memory"
|
name: str = "Memory"
|
||||||
threshold: float = 90
|
threshold: float = 90
|
||||||
report_outer: str = "{inner}"
|
report_outer: str = "{inner}"
|
||||||
|
@ -48,16 +49,14 @@ class MemoryMS(MultiMS):
|
||||||
name_swap: str = "Swap"
|
name_swap: str = "Swap"
|
||||||
|
|
||||||
|
|
||||||
class DiskMS(MultiMS):
|
class DiskMS(MetricSettings):
|
||||||
name: str = "Disk Used"
|
name: str = "Disk Used"
|
||||||
threshold: float = 85
|
threshold: float = 85
|
||||||
|
count: int = 1
|
||||||
|
|
||||||
# paths to check for disk space
|
# paths to check for disk space
|
||||||
paths: list[DirectoryPath] = Field(default_factory=list)
|
paths: list[DirectoryPath] = Field(default_factory=list)
|
||||||
|
|
||||||
# include only `count` many of the paths with the least free space
|
|
||||||
count: int = 1
|
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
|
|
Loading…
Reference in a new issue