First commit

This commit is contained in:
2025-12-17 23:14:15 +01:00
commit eba1e1fcb2
13 changed files with 1294 additions and 0 deletions

178
collectors/zfs_pools.py Normal file
View File

@@ -0,0 +1,178 @@
#!/usr/bin/env python3
import subprocess
from typing import Dict, Any, List
import json
import shutil
import os
# Default update interval in seconds
DEFAULT_INTERVAL = 300 # 5 minutes
def get_zfs_pools() -> List[Dict[str, Any]]:
"""Get information about ZFS pools."""
try:
# Get list of pools
pools = subprocess.check_output(['zpool', 'list', '-H', '-o', 'name,size,alloc,free,health,readonly,dedup,altroot']).decode().strip().split('\n')
pool_info = []
for pool in pools:
if not pool: # Skip empty lines
continue
name, size, alloc, free, health, readonly, dedup, altroot = pool.split('\t')
# Get detailed pool status
status = subprocess.check_output(['zpool', 'status', name]).decode()
# Get pool properties
properties = subprocess.check_output(['zpool', 'get', 'all', name]).decode()
pool_info.append({
'name': name,
'size': size,
'allocated': alloc,
'free': free,
'health': health,
'readonly': readonly == 'on',
'dedup': dedup,
'altroot': altroot,
'status': status,
'properties': properties
})
return pool_info
except subprocess.SubprocessError as e:
print(f"Error getting ZFS pool information: {e}")
return []
def convert_size_to_bytes(size_str: str) -> int:
"""Convert ZFS size string to bytes."""
units = {
'B': 1,
'K': 1024,
'M': 1024**2,
'G': 1024**3,
'T': 1024**4,
'P': 1024**5
}
try:
number = float(size_str[:-1])
unit = size_str[-1].upper()
return int(number * units[unit])
except (ValueError, KeyError):
return 0
def collect_metrics() -> Dict[str, Any]:
"""Collect ZFS pool metrics. Skips cleanly if ZFS is unavailable."""
metrics = {"entities": []}
# Check binary availability
zpool_path = shutil.which('zpool')
zfs_path = shutil.which('zfs')
if not zpool_path or not zfs_path:
# Skip gracefully when binaries are missing
return {"entities": []}
# Check device node (required for libzfs operations)
if not os.path.exists('/dev/zfs'):
# Skip if device not present in container/host
return {"entities": []}
pools = get_zfs_pools()
def fmt_size(bytes_value: int):
tb = 1024**4
gb = 1024**3
if bytes_value >= tb:
return round(bytes_value / tb, 2), 'TB'
return round(bytes_value / gb, 2), 'GB'
for pool in pools:
# Pool health status
metrics['entities'].append({
'sensor_id': f'zfs_pool_{pool["name"]}_health',
'name': f'ZFS Pool {pool["name"]} Health',
'value': pool['health'],
'state_class': 'measurement',
'unit_of_measurement': '',
'device_class': 'enum',
'icon': 'mdi:database-check',
'attributes': {
'friendly_name': f'ZFS Pool {pool["name"]} Health Status',
'readonly': pool['readonly'],
'dedup': pool['dedup'],
'altroot': pool['altroot']
}
})
# Pool size
size_bytes = convert_size_to_bytes(pool['size'])
size_value, size_unit = fmt_size(size_bytes)
metrics['entities'].append({
'sensor_id': f'zfs_pool_{pool["name"]}_size',
'name': f'ZFS Pool {pool["name"]} Size',
'value': str(size_value),
'state_class': 'measurement',
'unit_of_measurement': size_unit,
'device_class': 'data_size',
'icon': 'mdi:database',
'attributes': {
'friendly_name': f'ZFS Pool {pool["name"]} Total Size'
}
})
# Pool allocated space
alloc_bytes = convert_size_to_bytes(pool['allocated'])
alloc_value, alloc_unit = fmt_size(alloc_bytes)
metrics['entities'].append({
'sensor_id': f'zfs_pool_{pool["name"]}_allocated',
'name': f'ZFS Pool {pool["name"]} Allocated',
'value': str(alloc_value),
'state_class': 'measurement',
'unit_of_measurement': alloc_unit,
'device_class': 'data_size',
'icon': 'mdi:database-minus',
'attributes': {
'friendly_name': f'ZFS Pool {pool["name"]} Allocated Space'
}
})
# Pool free space
free_bytes = convert_size_to_bytes(pool['free'])
free_value, free_unit = fmt_size(free_bytes)
metrics['entities'].append({
'sensor_id': f'zfs_pool_{pool["name"]}_free',
'name': f'ZFS Pool {pool["name"]} Free',
'value': str(free_value),
'state_class': 'measurement',
'unit_of_measurement': free_unit,
'device_class': 'data_size',
'icon': 'mdi:database-plus',
'attributes': {
'friendly_name': f'ZFS Pool {pool["name"]} Free Space'
}
})
# Pool usage percentage
usage_percent = (alloc_bytes / size_bytes * 100) if size_bytes > 0 else 0
metrics['entities'].append({
'sensor_id': f'zfs_pool_{pool["name"]}_usage',
'name': f'ZFS Pool {pool["name"]} Usage',
'value': str(round(usage_percent, 1)),
'state_class': 'measurement',
'unit_of_measurement': '%',
'device_class': 'power_factor',
'icon': 'mdi:chart-donut',
'attributes': {
'friendly_name': f'ZFS Pool {pool["name"]} Usage Percentage'
}
})
return metrics
if __name__ == "__main__":
# Example usage
metrics = collect_metrics()
print(json.dumps(metrics, indent=2))