2023-08-31 09:00:02 +00:00
|
|
|
import math
|
2023-08-31 22:11:10 +00:00
|
|
|
from typing import Any, Literal
|
2023-08-31 09:00:02 +00:00
|
|
|
|
2023-09-01 00:26:29 +00:00
|
|
|
from pydantic import (BaseModel, DirectoryPath, Field, FieldValidationInfo,
|
2023-09-01 14:38:04 +00:00
|
|
|
FilePath, field_validator)
|
2023-08-30 22:01:31 +00:00
|
|
|
|
|
|
|
|
2023-08-31 09:00:02 +00:00
|
|
|
class MetricSettings(BaseModel):
|
2023-08-31 11:09:19 +00:00
|
|
|
# metric name
|
|
|
|
name: str
|
|
|
|
|
2023-08-30 22:01:31 +00:00
|
|
|
# metric will be reported
|
|
|
|
enabled: bool = True
|
|
|
|
|
|
|
|
# if the metric value exceeds this percentage, the report fails
|
|
|
|
threshold: float
|
|
|
|
|
|
|
|
# if True, this metric fails when the value falls below the `threshold`
|
|
|
|
inverted: bool = False
|
|
|
|
|
2023-08-31 15:23:00 +00:00
|
|
|
# per-value format string for reporting
|
2023-08-31 23:11:24 +00:00
|
|
|
report: str = "{name}: {value:.1f}%"
|
2023-08-31 15:23:00 +00:00
|
|
|
|
|
|
|
# per-metric format string for reporting
|
2023-08-31 15:31:25 +00:00
|
|
|
report_outer: str = "{inner}"
|
2023-08-31 15:23:00 +00:00
|
|
|
|
|
|
|
# include only `count` many items (None: include all)
|
|
|
|
count: int | None = None
|
|
|
|
|
2023-08-31 22:11:10 +00:00
|
|
|
@field_validator("count", mode="before")
|
|
|
|
@classmethod
|
|
|
|
def parse_nonetype(
|
|
|
|
cls,
|
|
|
|
value: Any,
|
|
|
|
info: FieldValidationInfo,
|
|
|
|
) -> int | None:
|
|
|
|
try:
|
|
|
|
return int(value)
|
|
|
|
|
|
|
|
except ValueError:
|
2023-09-02 00:46:23 +00:00
|
|
|
if str(value).strip().lower() not in (
|
2023-08-31 22:11:10 +00:00
|
|
|
"none", "null", "all", "yes", "any", "full",
|
|
|
|
"oddly_specific_value_42",
|
|
|
|
):
|
|
|
|
print(
|
|
|
|
f"[WARN] Unexpected {value=!r} for {info.field_name}, "
|
|
|
|
"falling back to None."
|
|
|
|
)
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
2023-08-30 22:01:31 +00:00
|
|
|
|
2023-08-31 11:09:19 +00:00
|
|
|
class CpuMS(MetricSettings):
|
|
|
|
name: str = "CPU"
|
|
|
|
threshold: float = math.inf
|
|
|
|
|
2023-09-02 01:56:41 +00:00
|
|
|
# timespan to analyze average CPU usage
|
|
|
|
interval: float = 1
|
|
|
|
|
2023-08-31 11:09:19 +00:00
|
|
|
|
2023-08-31 15:23:00 +00:00
|
|
|
class MemoryMS(MetricSettings):
|
2023-08-31 11:09:19 +00:00
|
|
|
name: str = "Memory"
|
|
|
|
threshold: float = 90
|
2023-08-31 12:26:50 +00:00
|
|
|
|
|
|
|
# how to handle swap space
|
|
|
|
# exclude: swap space is not reported
|
|
|
|
# include: swap space is reported separately
|
|
|
|
# combine: ram and swap are combined
|
|
|
|
swap: Literal["exclude", "include", "combine"] = "include"
|
|
|
|
|
|
|
|
# names for telling apart ram and swap space
|
|
|
|
name_ram: str = "RAM"
|
|
|
|
name_swap: str = "Swap"
|
2023-08-31 11:09:19 +00:00
|
|
|
|
|
|
|
|
2023-08-31 15:23:00 +00:00
|
|
|
class DiskMS(MetricSettings):
|
2023-08-31 11:44:41 +00:00
|
|
|
name: str = "Disk Used"
|
|
|
|
threshold: float = 85
|
2023-08-31 23:11:24 +00:00
|
|
|
report: str = "{value:.1f}% ({name})"
|
2023-08-31 22:11:10 +00:00
|
|
|
report_outer: str = "{name}: {inner}"
|
2023-08-31 15:38:15 +00:00
|
|
|
count: int | None = 1
|
2023-08-31 11:02:54 +00:00
|
|
|
|
2023-08-31 09:00:02 +00:00
|
|
|
# paths to check for disk space
|
|
|
|
paths: list[DirectoryPath] = Field(default_factory=list)
|
2023-09-01 11:34:26 +00:00
|
|
|
|
|
|
|
|
2023-09-01 14:38:04 +00:00
|
|
|
class ExternalMS(MetricSettings):
|
2023-09-02 00:46:14 +00:00
|
|
|
name: str = "External Metric"
|
2023-09-01 11:34:26 +00:00
|
|
|
threshold: float = 0
|
2023-09-01 14:38:04 +00:00
|
|
|
|
|
|
|
# path to executable files
|
|
|
|
executables: list[FilePath] = Field(default_factory=list)
|
2023-09-02 00:46:14 +00:00
|
|
|
|
|
|
|
# wait at most this many seconds for each executable
|
|
|
|
timeout: int = 60
|
2023-09-02 14:33:38 +00:00
|
|
|
|
|
|
|
@field_validator("count", mode="after")
|
|
|
|
@classmethod
|
|
|
|
def force_none(cls, _) -> int | None:
|
|
|
|
"""Don't accept a `count` value for the external metric!"""
|
|
|
|
return None
|