Added different intervals for collectors and made them configurable.
This commit is contained in:
parent
85154fcd60
commit
6e6f22f5d4
146
README.md
146
README.md
@ -1,79 +1,115 @@
|
|||||||
# system2mqtt
|
# system2mqtt
|
||||||
|
|
||||||
Ein System zur Überwachung von Hosts durch Sammeln von Metriken und Senden via MQTT an Home Assistant.
|
A system for monitoring hosts by collecting metrics and sending them to Home Assistant via MQTT.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Modulare Struktur für Metrik-Sammler
|
- Modular structure for metric collectors
|
||||||
- Einfache Erweiterbarkeit durch neue Sammler
|
- Easy extensibility through new collectors
|
||||||
- Automatische Erkennung in Home Assistant
|
- Automatic discovery in Home Assistant
|
||||||
- Verschlüsselte MQTT-Kommunikation
|
- Encrypted MQTT communication
|
||||||
- Detaillierte Geräteinformationen in Home Assistant
|
- Detailed device information in Home Assistant
|
||||||
|
- Individual update intervals per collector
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Repository klonen:
|
1. Clone the repository:
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/yourusername/system2mqtt.git
|
git clone https://github.com/yourusername/system2mqtt.git
|
||||||
cd system2mqtt
|
cd system2mqtt
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Python-Abhängigkeiten installieren:
|
2. Install Python dependencies:
|
||||||
```bash
|
```bash
|
||||||
python3 -m venv .venv
|
python3 -m venv .venv
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Konfiguration anpassen:
|
3. Configure the system:
|
||||||
```yaml
|
```yaml
|
||||||
mqtt:
|
mqtt:
|
||||||
host: "mqtt.example.com" # MQTT Broker Adresse
|
host: "mqtt.example.com" # MQTT Broker Address
|
||||||
port: 1883 # MQTT Port
|
port: 1883 # MQTT Port
|
||||||
username: "your_username"
|
username: "your_username"
|
||||||
password: "your_password"
|
password: "your_password"
|
||||||
client_id: "system2mqtt"
|
client_id: "system2mqtt"
|
||||||
discovery_prefix: "homeassistant" # Home Assistant Discovery Prefix
|
discovery_prefix: "homeassistant" # Home Assistant Discovery Prefix
|
||||||
|
|
||||||
|
collectors:
|
||||||
|
# Default interval for all collectors (in seconds)
|
||||||
|
default_interval: 60
|
||||||
|
|
||||||
|
# Specific intervals for individual collectors
|
||||||
|
intervals:
|
||||||
|
zfs_pools: 300 # ZFS Pools every 5 minutes
|
||||||
|
cpu_temperature: 30 # CPU Temperature every 30 seconds
|
||||||
|
system_metrics: 60 # System Metrics every minute
|
||||||
```
|
```
|
||||||
|
|
||||||
## Verwendung
|
## Usage
|
||||||
|
|
||||||
Das System wird über das `run.sh` Skript gesteuert:
|
The system is controlled via the `run.sh` script:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Starten des Systems
|
# Start the system
|
||||||
./run.sh start
|
./run.sh start
|
||||||
|
|
||||||
# Stoppen des Systems
|
# Stop the system
|
||||||
./run.sh stop
|
./run.sh stop
|
||||||
|
|
||||||
# MQTT Topics aufräumen
|
# Clean up MQTT topics
|
||||||
./run.sh cleanup
|
./run.sh cleanup
|
||||||
```
|
```
|
||||||
|
|
||||||
## Sammler
|
## Collectors
|
||||||
|
|
||||||
### System Metrics
|
### System Metrics
|
||||||
|
|
||||||
Sammelt grundlegende Systemmetriken:
|
Collects basic system metrics:
|
||||||
- Last Boot Zeit
|
- Last Boot Time
|
||||||
- Load Average (1, 5, 15 Minuten)
|
- Load Average (1, 5, 15 minutes)
|
||||||
- Speichernutzung (Gesamt, Verfügbar, Verwendet)
|
- Memory Usage (Total, Available, Used)
|
||||||
- Swap-Nutzung (Gesamt, Verfügbar, Verwendet)
|
- Swap Usage (Total, Available, Used)
|
||||||
- CPU-Auslastung
|
- CPU Usage
|
||||||
- Speicherauslastung
|
- Memory Usage
|
||||||
- Swap-Auslastung
|
- Swap Usage
|
||||||
|
|
||||||
|
Default Update Interval: 60 seconds
|
||||||
|
|
||||||
### CPU Temperature
|
### CPU Temperature
|
||||||
|
|
||||||
Sammelt CPU-Temperaturdaten:
|
Collects CPU temperature data:
|
||||||
- Unterstützt Linux und FreeBSD
|
- Supports Linux and FreeBSD
|
||||||
- Automatische Erkennung des Betriebssystems
|
- Automatic OS detection
|
||||||
- Korrekte Einheit (°C) und Device Class (temperature)
|
- Correct unit (°C) and device class (temperature)
|
||||||
|
|
||||||
## Datenformat
|
Default Update Interval: 30 seconds
|
||||||
|
|
||||||
Das Datenaustauschformat ist versioniert und folgt den Home Assistant Spezifikationen. Jeder Collector gibt ein JSON-Objekt zurück mit folgender Struktur:
|
### ZFS Pools
|
||||||
|
|
||||||
|
Collects information about ZFS pools:
|
||||||
|
- Pool Health
|
||||||
|
- Total Size
|
||||||
|
- Used Space
|
||||||
|
- Free Space
|
||||||
|
- Usage Percentage
|
||||||
|
- Additional Attributes (readonly, dedup, altroot)
|
||||||
|
|
||||||
|
Default Update Interval: 300 seconds (5 minutes)
|
||||||
|
|
||||||
|
## Update Intervals
|
||||||
|
|
||||||
|
Each collector has a predefined default update interval that can be overridden in the configuration file:
|
||||||
|
|
||||||
|
1. Default intervals are defined in the collector files
|
||||||
|
2. These intervals can be customized per collector in `config.yaml`
|
||||||
|
3. If no specific interval is defined in the configuration, the collector's default interval is used
|
||||||
|
4. If no default interval is defined in the collector, the global `default_interval` from the configuration is used
|
||||||
|
|
||||||
|
## Data Format
|
||||||
|
|
||||||
|
The data exchange format is versioned and follows Home Assistant specifications. Each collector returns a JSON object with the following structure:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -81,46 +117,44 @@ Das Datenaustauschformat ist versioniert und folgt den Home Assistant Spezifikat
|
|||||||
{
|
{
|
||||||
"sensor_id": "unique_sensor_id",
|
"sensor_id": "unique_sensor_id",
|
||||||
"name": "Sensor Name",
|
"name": "Sensor Name",
|
||||||
"value": "Sensor Value",
|
"value": "sensor_value",
|
||||||
"state_class": "measurement|total|total_increasing",
|
"state_class": "measurement",
|
||||||
"unit_of_measurement": "Unit",
|
"unit_of_measurement": "unit",
|
||||||
"device_class": "temperature|humidity|pressure|...",
|
"device_class": "device_class",
|
||||||
"icon": "mdi:icon-name",
|
"icon": "mdi:icon",
|
||||||
"entity_category": "diagnostic|config|system",
|
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"friendly_name": "Friendly Name",
|
"friendly_name": "Friendly Name",
|
||||||
"source": "Data Source",
|
"additional_attributes": "values"
|
||||||
"additional_info": "Additional Information"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Felder
|
### Fields
|
||||||
|
|
||||||
- `sensor_id`: Eindeutige ID für den Sensor (wird für MQTT-Topics verwendet)
|
- `sensor_id`: Unique ID for the sensor (used for MQTT topics)
|
||||||
- `name`: Anzeigename des Sensors
|
- `name`: Display name of the sensor
|
||||||
- `value`: Aktueller Wert des Sensors
|
- `value`: Current value of the sensor
|
||||||
- `state_class`: Art der Messung (measurement, total, total_increasing)
|
- `state_class`: Type of measurement (measurement, total, total_increasing)
|
||||||
- `unit_of_measurement`: Einheit der Messung
|
- `unit_of_measurement`: Unit of measurement
|
||||||
- `device_class`: Art des Sensors (temperature, humidity, pressure, etc.)
|
- `device_class`: Type of sensor (temperature, humidity, pressure, etc.)
|
||||||
- `icon`: Material Design Icon Name (mdi:...)
|
- `icon`: Material Design Icon name (mdi:...)
|
||||||
- `entity_category`: Kategorie des Sensors (diagnostic, config, system)
|
- `entity_category`: Category of the sensor (diagnostic, config, system)
|
||||||
- `attributes`: Zusätzliche Informationen als Key-Value-Paare
|
- `attributes`: Additional information as key-value pairs
|
||||||
|
|
||||||
### Versionierung
|
### Versioning
|
||||||
|
|
||||||
Das Format ist versioniert, um zukünftige Erweiterungen zu ermöglichen. Die aktuelle Version ist 1.0.
|
The format is versioned to allow for future extensions. The current version is 1.0.
|
||||||
|
|
||||||
## Home Assistant Integration
|
## Home Assistant Integration
|
||||||
|
|
||||||
Das System nutzt die MQTT Discovery-Funktion von Home Assistant. Die Sensoren werden automatisch erkannt und erscheinen in Home Assistant mit:
|
The system uses Home Assistant's MQTT Discovery feature. Sensors are automatically detected and appear in Home Assistant with:
|
||||||
- Korrektem Namen und Icon
|
- Correct name and icon
|
||||||
- Aktuellen Werten
|
- Current values
|
||||||
- Historischen Daten
|
- Historical data
|
||||||
- Detaillierten Geräteinformationen
|
- Detailed device information
|
||||||
|
|
||||||
## Lizenz
|
## License
|
||||||
|
|
||||||
MIT License
|
MIT License
|
@ -7,6 +7,9 @@ import glob
|
|||||||
from typing import Dict, Any, Optional, List, Tuple
|
from typing import Dict, Any, Optional, List, Tuple
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
# Default update interval in seconds
|
||||||
|
DEFAULT_INTERVAL = 30 # 30 seconds
|
||||||
|
|
||||||
def get_temperature_linux_coretemp() -> List[Tuple[float, str]]:
|
def get_temperature_linux_coretemp() -> List[Tuple[float, str]]:
|
||||||
"""Get CPU temperatures using coretemp module."""
|
"""Get CPU temperatures using coretemp module."""
|
||||||
temps = []
|
temps = []
|
||||||
|
@ -5,6 +5,9 @@ import time
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
# Default update interval in seconds
|
||||||
|
DEFAULT_INTERVAL = 60 # 1 minute
|
||||||
|
|
||||||
def collect_metrics() -> Dict[str, Any]:
|
def collect_metrics() -> Dict[str, Any]:
|
||||||
"""Collect system metrics and return them in the required format."""
|
"""Collect system metrics and return them in the required format."""
|
||||||
# Get system metrics
|
# Get system metrics
|
||||||
|
@ -4,6 +4,9 @@ import subprocess
|
|||||||
from typing import Dict, Any, List
|
from typing import Dict, Any, List
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
# Default update interval in seconds
|
||||||
|
DEFAULT_INTERVAL = 300 # 5 minutes
|
||||||
|
|
||||||
def get_zfs_pools() -> List[Dict[str, Any]]:
|
def get_zfs_pools() -> List[Dict[str, Any]]:
|
||||||
"""Get information about ZFS pools."""
|
"""Get information about ZFS pools."""
|
||||||
try:
|
try:
|
||||||
|
@ -1,8 +1,54 @@
|
|||||||
|
# MQTT Configuration
|
||||||
mqtt:
|
mqtt:
|
||||||
|
# MQTT Broker Address
|
||||||
host: "mqtt.example.com"
|
host: "mqtt.example.com"
|
||||||
|
|
||||||
|
# MQTT Port (Default: 1883 for unencrypted, 8883 for TLS)
|
||||||
port: 1883
|
port: 1883
|
||||||
username: "system2mqtt"
|
|
||||||
password: "your_secure_password"
|
# MQTT Username
|
||||||
|
username: "your_username"
|
||||||
|
|
||||||
|
# MQTT Password
|
||||||
|
password: "your_password"
|
||||||
|
|
||||||
|
# MQTT Client ID (will be extended with hostname)
|
||||||
client_id: "system2mqtt_{hostname}"
|
client_id: "system2mqtt_{hostname}"
|
||||||
|
|
||||||
|
# Home Assistant Discovery Prefix
|
||||||
discovery_prefix: "homeassistant"
|
discovery_prefix: "homeassistant"
|
||||||
|
|
||||||
|
# MQTT State Prefix for sensors
|
||||||
state_prefix: "system2mqtt"
|
state_prefix: "system2mqtt"
|
||||||
|
|
||||||
|
# Collector Configuration
|
||||||
|
collectors:
|
||||||
|
# Default interval for all collectors (in seconds)
|
||||||
|
# Used when no specific interval is defined
|
||||||
|
default_interval: 60
|
||||||
|
|
||||||
|
# Specific intervals for individual collectors
|
||||||
|
# These override the collector's default intervals
|
||||||
|
intervals:
|
||||||
|
# ZFS Pools are updated every 5 minutes
|
||||||
|
zfs_pools: 300
|
||||||
|
|
||||||
|
# CPU Temperature is updated every 30 seconds
|
||||||
|
cpu_temperature: 30
|
||||||
|
|
||||||
|
# System Metrics are updated every minute
|
||||||
|
system_metrics: 60
|
||||||
|
|
||||||
|
# Notes:
|
||||||
|
# 1. The default intervals for collectors are:
|
||||||
|
# - zfs_pools: 300 seconds (5 minutes)
|
||||||
|
# - cpu_temperature: 30 seconds
|
||||||
|
# - system_metrics: 60 seconds (1 minute)
|
||||||
|
#
|
||||||
|
# 2. These intervals can be overridden here
|
||||||
|
#
|
||||||
|
# 3. If no specific interval is defined, the collector's
|
||||||
|
# default interval will be used
|
||||||
|
#
|
||||||
|
# 4. If no default interval is defined in the collector,
|
||||||
|
# the global default_interval will be used
|
@ -8,9 +8,10 @@ import time
|
|||||||
import yaml
|
import yaml
|
||||||
import platform
|
import platform
|
||||||
import paho.mqtt.client as mqtt
|
import paho.mqtt.client as mqtt
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any, List
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import glob
|
import glob
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
class System2MQTT:
|
class System2MQTT:
|
||||||
def __init__(self, config_path: str = "config.yaml"):
|
def __init__(self, config_path: str = "config.yaml"):
|
||||||
@ -20,6 +21,7 @@ class System2MQTT:
|
|||||||
self.connected = False
|
self.connected = False
|
||||||
self.device_info = self._get_device_info()
|
self.device_info = self._get_device_info()
|
||||||
self.collectors = self._load_collectors()
|
self.collectors = self._load_collectors()
|
||||||
|
self.last_run = {} # Speichert den Zeitpunkt des letzten Laufs für jeden Sammler
|
||||||
|
|
||||||
def _load_config(self, config_path: str) -> Dict[str, Any]:
|
def _load_config(self, config_path: str) -> Dict[str, Any]:
|
||||||
"""Load configuration from YAML file."""
|
"""Load configuration from YAML file."""
|
||||||
@ -74,7 +76,7 @@ class System2MQTT:
|
|||||||
"""Generate state topic from sensor_id."""
|
"""Generate state topic from sensor_id."""
|
||||||
return f"system2mqtt/{self.hostname}/{sensor_id}/state"
|
return f"system2mqtt/{self.hostname}/{sensor_id}/state"
|
||||||
|
|
||||||
def _load_collectors(self) -> list:
|
def _load_collectors(self) -> List[Dict[str, Any]]:
|
||||||
"""Load all collector modules from the collectors directory."""
|
"""Load all collector modules from the collectors directory."""
|
||||||
collectors = []
|
collectors = []
|
||||||
collector_dir = os.path.join(os.path.dirname(__file__), 'collectors')
|
collector_dir = os.path.join(os.path.dirname(__file__), 'collectors')
|
||||||
@ -92,8 +94,19 @@ class System2MQTT:
|
|||||||
spec.loader.exec_module(module)
|
spec.loader.exec_module(module)
|
||||||
|
|
||||||
if hasattr(module, 'collect_metrics'):
|
if hasattr(module, 'collect_metrics'):
|
||||||
collectors.append(module)
|
# Get interval from config or use collector's default
|
||||||
print(f"Loaded collector: {module_name}")
|
default_interval = getattr(module, 'DEFAULT_INTERVAL', self.config['collectors']['default_interval'])
|
||||||
|
interval = self.config['collectors']['intervals'].get(
|
||||||
|
module_name,
|
||||||
|
default_interval
|
||||||
|
)
|
||||||
|
|
||||||
|
collectors.append({
|
||||||
|
'module': module,
|
||||||
|
'name': module_name,
|
||||||
|
'interval': interval
|
||||||
|
})
|
||||||
|
print(f"Loaded collector: {module_name} (interval: {interval}s)")
|
||||||
|
|
||||||
return collectors
|
return collectors
|
||||||
|
|
||||||
@ -144,14 +157,30 @@ class System2MQTT:
|
|||||||
retain=True
|
retain=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def should_run_collector(self, collector: Dict[str, Any]) -> bool:
|
||||||
|
"""Check if a collector should run based on its interval."""
|
||||||
|
now = datetime.now()
|
||||||
|
last_run = self.last_run.get(collector['name'])
|
||||||
|
|
||||||
|
if last_run is None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
interval = timedelta(seconds=collector['interval'])
|
||||||
|
return (now - last_run) >= interval
|
||||||
|
|
||||||
def collect_and_publish(self):
|
def collect_and_publish(self):
|
||||||
"""Collect metrics from all collectors and publish them."""
|
"""Collect metrics from all collectors and publish them."""
|
||||||
for collector in self.collectors:
|
for collector in self.collectors:
|
||||||
|
if not self.should_run_collector(collector):
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = collector.collect_metrics()
|
data = collector['module'].collect_metrics()
|
||||||
self.process_collector_data(data)
|
self.process_collector_data(data)
|
||||||
|
self.last_run[collector['name']] = datetime.now()
|
||||||
|
print(f"Updated {collector['name']} metrics")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error collecting metrics from {collector.__name__}: {e}")
|
print(f"Error collecting metrics from {collector['name']}: {e}")
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"""Connect to MQTT broker."""
|
"""Connect to MQTT broker."""
|
||||||
@ -179,10 +208,11 @@ def main():
|
|||||||
# Initial collection
|
# Initial collection
|
||||||
system2mqtt.collect_and_publish()
|
system2mqtt.collect_and_publish()
|
||||||
|
|
||||||
# Keep collecting metrics every 60 seconds
|
# Main loop - check every second if any collector needs to run
|
||||||
while True:
|
while True:
|
||||||
time.sleep(60)
|
|
||||||
system2mqtt.collect_and_publish()
|
system2mqtt.collect_and_publish()
|
||||||
|
time.sleep(1) # Check every second
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nShutting down...")
|
print("\nShutting down...")
|
||||||
finally:
|
finally:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user