diff --git a/README.md b/README.md
index 39bec85..b0cc334 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,7 @@ Custom timeouts (`-t`) and retries (`-r`) can be specified by using `-t` and `-r
| storage | Detects and checks all disks (free, total, %) | if more used than w/c in % |
| update | Shows the current DSM version and if DSM update is available | if update is "Unavailable", will trigger OK
if update is "Available", will trigger WARNING
otherwise: UNKNOWN |
| status | Shows model, s/n, temp and status of system, fan, cpu fan and power supply | if temp higher than w/c in °C |
+| raid | Shows raid volume status for every storage pool | WARNING for intermediate states (Repairing/Migrating/Expanding/Deleting/Creating/RaidSyncing/RaidParityChecking/RaidAssembling/Canceling); CRITICAL for Degrade and Crashed; OK for Normal |
diff --git a/check_synology.py b/check_synology.py
index fe72ae5..97b7c7b 100755
--- a/check_synology.py
+++ b/check_synology.py
@@ -15,9 +15,11 @@
parser.add_argument("username", help="the snmp user name", type=str)
parser.add_argument("authkey", help="the auth key", type=str)
parser.add_argument("privkey", help="the priv key", type=str)
-parser.add_argument("mode", help="the mode", type=str, choices=["load", "memory", "disk", "storage", "update", "status"])
-parser.add_argument("-w", help="warning value for selected mode", type=int)
-parser.add_argument("-c", help="critical value for selected mode", type=int)
+parser.add_argument("mode", help="the mode", type=str, choices=["load", "memory", "disk", "storage", "update", "status", "raid"])
+parser.add_argument("-w", help="warning value for selected mode (storage mode: PERC_USED, warn if used > N)", type=int)
+parser.add_argument("-c", help="critical value for selected mode (storage mode: PERC_USED, crit if used > N)", type=int)
+parser.add_argument("--warning-free", help="storage mode only: PERC_FREE, warn if free < N (matches check_disk -w convention; takes precedence over -w if both set)", type=str, default=None, dest="warning_free")
+parser.add_argument("--critical-free", help="storage mode only: PERC_FREE, crit if free < N (matches check_disk -c convention; takes precedence over -c if both set)", type=str, default=None, dest="critical_free")
parser.add_argument("-p", help="the snmp port", type=int, dest="port", default=161)
parser.add_argument("-e", help="SNMP privacy protocol encryption", type=str, default="AES128", choices=["AES128", "DES"])
parser.add_argument("-t", help="timeout for snmp connection", type=int, default=10)
@@ -32,6 +34,17 @@
mode = args.mode
warning = args.w
critical = args.c
+
+def _parse_pct(s):
+ """Strip optional '%' and whitespace, convert to int. None → None."""
+ if s is None:
+ return None
+ return int(str(s).strip().rstrip('%').strip())
+
+warning_free = _parse_pct(args.warning_free)
+critical_free = _parse_pct(args.critical_free)
+if (warning_free is not None and warning is not None) or (critical_free is not None and critical is not None):
+ print("check_synology: both -w/-c and --warning-free/--critical-free given; using free thresholds", file=sys.stderr)
priv_protocol = args.e
snmp_timeout = args.t
snmp_retries = args.r
@@ -225,15 +238,25 @@ def exitCode():
continue
storage_used_percent = int(storage_used * 100 / storage_size)
+ storage_free_percent = 100 - storage_used_percent
- if warning and warning < int(storage_used_percent):
+ # Free-percent (PERC_FREE) wins; falls back to upstream -w/-c PERC_USED.
+ if warning_free is not None:
+ if storage_free_percent < warning_free and state != 'CRITICAL':
+ state = 'WARNING'
+ elif warning and warning < storage_used_percent:
if state != 'CRITICAL':
state = 'WARNING'
- if critical and critical < int(storage_used_percent):
+
+ if critical_free is not None:
+ if storage_free_percent < critical_free:
+ state = 'CRITICAL'
+ elif critical and critical < storage_used_percent:
state = 'CRITICAL'
output += ' - free space: ' + storage_name + ' ' + str(storage_free) + ' GB (' + str(storage_used) + ' GB of ' + str(storage_size) + ' GB used, ' + str(storage_used_percent) + '%)'
- perfdata += storage_name + '=' + str(storage_used) + 'c '
+ # Perfdata format: value;warn;crit;min;max — max=storage_size enables disk_forecast (.max series in Graphite).
+ perfdata += storage_name + '=' + str(storage_used) + 'c;;;0;' + str(storage_size) + ' '
print('%s%s %s' % (state, output, perfdata))
exitCode()
@@ -302,3 +325,36 @@ def exitCode():
# 3. Respond with textual and perfdata output and propagate exit code.
print(state + ' - Model: %s, S/N: %s, System Temperature: %s C, System Status: %s, System Fan: %s, CPU Fan: %s, Powersupply : %s' % (status_model, status_serial, status_temperature, status_system, status_system_fan, status_cpu_fan, status_power) + ' | system_temp=%sc' % status_temperature)
exitCode()
+
+if mode == 'raid':
+ output = ''
+ perfdata = '|'
+ # Synology raid status values (from synology MIB).
+ # 1 = Normal — the raid functions normally.
+ # 2 = Repairing
+ # 3 = Migrating
+ # 4 = Expanding
+ # 5 = Deleting
+ # 6 = Creating
+ # 7 = RaidSyncing
+ # 8 = RaidParityChecking
+ # 9 = RaidAssembling
+ # 10 = Canceling
+ # 11 = Degrade — a tolerable disk failure has occurred (still actionable).
+ # 12 = Crashed — raid is in read-only state.
+ WARNING_STATUSES = {"2", "3", "4", "5", "6", "7", "8", "9", "10"}
+ CRITICAL_STATUSES = {"11", "12"} # 11 = Degrade, 12 = Crashed
+ for item in snmpwalk('1.3.6.1.4.1.6574.3.1.1.1'):
+ i = item.oid_index or item.oid.split('.')[-1]
+ storage_name = snmpget('1.3.6.1.4.1.6574.3.1.1.2.' + str(i))
+ raid_status = str(snmpget('1.3.6.1.4.1.6574.3.1.1.3.' + str(i)))
+ if raid_status in CRITICAL_STATUSES:
+ state = "CRITICAL"
+ elif raid_status in WARNING_STATUSES and state != "CRITICAL":
+ state = "WARNING"
+ elif raid_status != "1" and state == "OK":
+ state = "UNKNOWN"
+ output += ' - raid status: [' + storage_name + '] status=' + raid_status
+ perfdata += ' "' + storage_name + '"=' + raid_status
+ print('%s%s %s' % (state, output, perfdata))
+ exitCode()