├── .gitignore ├── README.md ├── sensors.conf ├── sensors.py ├── template_sensors_4.4.xml ├── template_sensors_5.0.xml ├── template_sensors_6.0.xml └── template_sensors_6.4.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zabbix-sensors 2 | 3 | Zabbix template & scripts to discover & monitor Linux sensors 4 | 5 | ## Features 6 | 7 | - Low-level discovery of sensors: temperature (with thresholds), fans, voltage and power 8 | - Triggers on temperature, fans and voltage (detect stopped fan, adjustable) 9 | - Data is gathered once as a single JSON and all other items are `Dependent` - extracted from raw JSON 10 | - All data is gathered directly from `SysFS` - no `lm-sensors` needed to function 11 | 12 | ## Usage 13 | 14 | - Put _sensors.conf_ in _/etc/zabbix/zabbix_agentd.d_ folder 15 | - Put _sensors.py_ in _/etc/zabbix/scripts_ folder (or in any other, but then you'll need to adjust _sensors.conf_) 16 | - Import & link template 17 | 18 | ## Requirements 19 | 20 | - Python3 21 | 22 | ## Macros 23 | - `{$SENSORS_FAN_LOW}`: Low fan speed sensor threshold 24 | - `{$SENSORS_TEMP_CRIT}`: Crit value for temp sensors 25 | - `{$SENSORS_TEMP_HIGH}`: High value for temp sensors 26 | - `{$SENSORS_TEMP_HYST}`: Hysteresis for temp sensors to make sure that trigger is not firing when value oscillates over threshold and back 27 | - `{$SENSORS_VOLTAGE_HIGH}`: Voltage high threshold 28 | - `{$SENSORS_VOLTAGE_LOW}`: Voltage low threshold 29 | 30 | ## Update 2023-06 31 | 32 | - Script was rewritten to gather data directly from `sysfs` instead of using `sensors` binary 33 | - Updated templates for `6.0` and `6.4` 34 | 35 | ## Update 2020-12 36 | 37 | - Script was rewritten from scratch to make use of new `sensors` argument `-j` to export in JSON format. If it's not supported then it'll fall back to parse raw text output of `-u` - this will stick for some time for backwards compatibility 38 | - Move to Python3 39 | -------------------------------------------------------------------------------- /sensors.conf: -------------------------------------------------------------------------------- 1 | UserParameter=sensors,/etc/zabbix/scripts/sensors.py -------------------------------------------------------------------------------- /sensors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import errno 4 | import json 5 | import re 6 | import os 7 | import sys 8 | import glob 9 | 10 | DIR = "/sys/class/hwmon" 11 | 12 | 13 | def read(fn): 14 | try: 15 | with open(fn) as f: 16 | return f.read() 17 | except OSError as e: 18 | # in some cases nouveau might return EINVAL when GPU is not in use 19 | # We are defaulting to 0 when the value cannot be read 20 | # https://github.com/torvalds/linux/blob/v6.9/drivers/gpu/drm/nouveau/nouveau_hwmon.c#L379 21 | if e.errno == errno.EINVAL: 22 | return '0' 23 | else: 24 | raise 25 | 26 | 27 | def read_parse(fn): 28 | x = read(fn).strip() 29 | try: 30 | return int(x) 31 | except ValueError: 32 | return x 33 | 34 | 35 | def list_hwmon(): 36 | return sorted([f for f in os.listdir(DIR) if f.startswith("hwmon")]) 37 | 38 | 39 | def process_sensors(path): 40 | r = {} 41 | for fn in os.listdir(path): 42 | m = re.match(r"^(fan|in|temp|power)(\d+)_(.*)$", fn) 43 | if not m: 44 | continue 45 | 46 | t, i, rd = m.group(1, 2, 3) 47 | 48 | sens = f"{t}{i}" 49 | if sens not in r: 50 | r[sens] = {"sensor_type": t} 51 | 52 | r[sens][rd] = read_parse(f"{path}/{fn}") 53 | 54 | return r 55 | 56 | 57 | def process_hwmon(n): 58 | path = f"{DIR}/{n}" 59 | if not os.path.exists(f"{path}/name"): 60 | return None, None 61 | 62 | name = read(f"{path}/name").strip() 63 | 64 | blockdev = False 65 | if os.path.isdir(f"{path}/device/block"): 66 | blockdev = os.path.basename(glob.glob(f"{path}/device/block/*")[0]) 67 | 68 | if glob.glob(f"{path}/device/nvme*"): 69 | blockdev = os.path.basename(glob.glob(f"{path}/device/nvme*")[0]) 70 | 71 | raw_devlinks = glob.glob("/dev/disk/by-id/*") 72 | devlinks = list(filter(lambda x: not re.search("^nvme-eui|^nvme-nvme|^wwn-0x|^scsi-[0-9]", os.path.basename(x)), raw_devlinks)) 73 | if blockdev: 74 | for devlink in devlinks: 75 | if os.path.islink(devlink) and blockdev==os.path.basename(os.readlink(devlink)): 76 | name = os.path.basename(devlink) 77 | break 78 | 79 | return name, process_sensors(path) 80 | 81 | 82 | def main(): 83 | r = {} 84 | 85 | for hwm in list_hwmon(): 86 | try: 87 | name, sensors = process_hwmon(hwm) 88 | except Exception as e: 89 | sys.stderr.write(f"Failure to process {hwm}: {e}\n") 90 | continue 91 | if not sensors: 92 | continue 93 | 94 | r[name] = sensors 95 | 96 | print(json.dumps(r, indent=2, sort_keys=True)) 97 | 98 | 99 | if __name__ == "__main__": 100 | main() 101 | -------------------------------------------------------------------------------- /template_sensors_4.4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.4 4 | 2023-12-21T08:18:32Z 5 | 6 | 7 | Templates 8 | 9 | 10 | 11 | 450 | 451 | 452 | -------------------------------------------------------------------------------- /template_sensors_5.0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5.0 4 | 2023-12-08T05:12:50Z 5 | 6 | 7 | Templates 8 | 9 | 10 | 11 | 439 | 440 | 441 | -------------------------------------------------------------------------------- /template_sensors_6.0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6.0 4 | 2023-08-06T11:45:42Z 5 | 6 | 7 | 7df96b18c230490a9a0a9e2307226338 8 | Templates 9 | 10 | 11 | 12 | 413 | 414 | 415 | -------------------------------------------------------------------------------- /template_sensors_6.4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6.4 4 | 5 | 6 | 7df96b18c230490a9a0a9e2307226338 7 | Templates 8 | 9 | 10 | 11 | 412 | 413 | 414 | --------------------------------------------------------------------------------