Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <br> if update is "Available", will trigger WARNING <br> 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 |



Expand Down
75 changes: 69 additions & 6 deletions check_synology.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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())
Comment thread
wernerfred marked this conversation as resolved.
Outdated

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
Expand Down Expand Up @@ -225,15 +238,25 @@ def exitCode():
continue

storage_used_percent = int(storage_used * 100 / storage_size)
storage_free_percent = 100 - storage_used_percent

Comment thread
wernerfred marked this conversation as resolved.
Outdated
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'
Comment thread
wernerfred marked this conversation as resolved.
Outdated
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'
Comment thread
wernerfred marked this conversation as resolved.
Outdated

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()

Expand Down Expand Up @@ -302,3 +325,43 @@ 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
RAID_STATUS_NAMES = {
"1": "Normal", "2": "Repairing", "3": "Migrating", "4": "Expanding",
"5": "Deleting", "6": "Creating", "7": "RaidSyncing",
"8": "RaidParityChecking", "9": "RaidAssembling", "10": "Canceling",
"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"
status_name = RAID_STATUS_NAMES.get(raid_status, "Unknown")
output += ' - raid status: [' + storage_name + '] status=' + raid_status + ' (' + status_name + ')'
perfdata += ' "' + storage_name + '"=' + raid_status
print('%s%s %s' % (state, output, perfdata))
exitCode()