check_systemd_timer: parse cli output instead, more perfdta
This commit is contained in:
parent
d32092c62f
commit
efc3c010f9
|
@ -2,18 +2,61 @@
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
import traceback
|
import traceback
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
import humanize
|
from dateutil import tz
|
||||||
|
|
||||||
from checker import nagios
|
from checker import nagios
|
||||||
|
from checker.humanfriendly import parse_systemctl_time_delta
|
||||||
from checker.result import quit_check
|
from checker.result import quit_check
|
||||||
|
|
||||||
sys.path.insert(0, "/usr/lib/python3/dist-packages")
|
sys.path.insert(0, "/usr/lib/python3/dist-packages")
|
||||||
import dbus
|
import dbus
|
||||||
|
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import humanfriendly
|
||||||
|
|
||||||
|
SYSTEMCTL_TIMERS_RE = re.compile(
|
||||||
|
r'^([A-Za-z]*\s[0-9]{4}-[0-9]{2}-[0-9]{2}\s*[0-9]{2}:[0-9]{2}:[0-9]{2}\s[A-Z]*)\s*(([0-9]*[a-z]*\s)*left)\s*([A-Za-z]*\s[0-9]{4}-[0-9]{2}-[0-9]{2}\s[0-9]{2}:[0-9]{2}:[0-9]{2}\s[A-Z]*)\s*([0-9A-Za-z\s]*\sago)\s*([A-Za-z\-_]*.timer)\s*([A-Za-z\-_]*.service)')
|
||||||
|
|
||||||
|
|
||||||
|
def get_next_elapse(timer_name):
|
||||||
|
try:
|
||||||
|
output = subprocess.check_output(["systemctl", "list-timers", "--all"], universal_newlines=True)
|
||||||
|
lines = output.split('\n')
|
||||||
|
for line in lines:
|
||||||
|
if timer_name in line:
|
||||||
|
parts = re.search(SYSTEMCTL_TIMERS_RE, line)
|
||||||
|
|
||||||
|
try:
|
||||||
|
datetime_object = datetime.strptime(parts.group(1), '%a %Y-%m-%d %H:%M:%S %Z')
|
||||||
|
except ValueError as e:
|
||||||
|
return None, e
|
||||||
|
|
||||||
|
time_left = parse_systemctl_time_delta(parts.group(2))
|
||||||
|
if isinstance(time_left, humanfriendly.InvalidTimespan):
|
||||||
|
return None, humanfriendly.InvalidTimespan
|
||||||
|
|
||||||
|
time_passed = parse_systemctl_time_delta(parts.group(5))
|
||||||
|
if isinstance(time_passed, humanfriendly.InvalidTimespan):
|
||||||
|
return None, humanfriendly.InvalidTimespan
|
||||||
|
|
||||||
|
timer_info = {
|
||||||
|
'next': datetime_object,
|
||||||
|
'left': time_left,
|
||||||
|
'last': parts.group(4),
|
||||||
|
'passed': time_passed,
|
||||||
|
'unit': parts.group(6),
|
||||||
|
'activates': parts.group(7)
|
||||||
|
}
|
||||||
|
return timer_info, None
|
||||||
|
return None, ValueError('Timer not found')
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
return None, e
|
||||||
|
|
||||||
|
|
||||||
def check_timer(timer_name):
|
def check_timer(timer_name):
|
||||||
if not timer_name.endswith('.timer'):
|
if not timer_name.endswith('.timer'):
|
||||||
|
@ -28,31 +71,32 @@ def check_timer(timer_name):
|
||||||
timer_properties = dbus.Interface(timer_unit, 'org.freedesktop.DBus.Properties')
|
timer_properties = dbus.Interface(timer_unit, 'org.freedesktop.DBus.Properties')
|
||||||
active_state = timer_properties.Get('org.freedesktop.systemd1.Unit', 'ActiveState')
|
active_state = timer_properties.Get('org.freedesktop.systemd1.Unit', 'ActiveState')
|
||||||
if active_state == 'active':
|
if active_state == 'active':
|
||||||
next_elapse = timer_properties.Get('org.freedesktop.systemd1.Timer', 'NextElapseUSecRealtime')
|
next_elapse, err = get_next_elapse(timer_name)
|
||||||
|
if err:
|
||||||
|
quit_check(f'{err}', nagios.STATE_UNKNOWN)
|
||||||
|
|
||||||
try:
|
print(next_elapse)
|
||||||
local_timezone_offset_seconds = time.localtime().tm_gmtoff
|
|
||||||
local_timezone_offset = timedelta(seconds=local_timezone_offset_seconds)
|
|
||||||
local_datetime = datetime.utcfromtimestamp(int(next_elapse) / 1e6) + local_timezone_offset
|
|
||||||
next_elapse_str = local_datetime.strftime("%m-%d-%Y %H:%M")
|
|
||||||
except ValueError as v_err:
|
|
||||||
if 'is out of range' in repr(v_err):
|
|
||||||
# This occurs whenever the timer resets.
|
|
||||||
# "year 586524 is out of range"
|
|
||||||
print(f'OK - {timer_name} has triggered.')
|
|
||||||
sys.exit(nagios.STATE_OK)
|
|
||||||
|
|
||||||
current_time = time.time() * 1e6 # convert current time to microseconds
|
if next_elapse['left'] < 0 or next_elapse['passed'] < 0:
|
||||||
remaining_time_sec = int((next_elapse - current_time) / 1e6) # convert remaining time to seconds
|
quit_check(f'Timer is negative? Left: {next_elapse["left"]}. Passed: {next_elapse["passed"]}', nagios.STATE_UNKNOWN)
|
||||||
remaining_time_human = str(humanize.naturaltime(datetime.now() + timedelta(seconds=remaining_time_sec))).strip(' from now')
|
|
||||||
|
local_tz = tz.tzlocal()
|
||||||
|
next_elapse_str = next_elapse['next'].replace(tzinfo=local_tz).strftime('%a %Y-%m-%d %H:%M %Z')
|
||||||
|
remaining_time_human = humanfriendly.format_timespan(next_elapse['left'])
|
||||||
|
passed_time_human = humanfriendly.format_timespan(next_elapse['passed'])
|
||||||
perfdata_dict = {
|
perfdata_dict = {
|
||||||
'remaining_time': {
|
'remaining_time': {
|
||||||
'value': remaining_time_sec,
|
'value': int(next_elapse['left']),
|
||||||
|
'unit': 's',
|
||||||
|
'min': 0
|
||||||
|
},
|
||||||
|
'passed_time': {
|
||||||
|
'value': int(next_elapse['passed']),
|
||||||
'unit': 's',
|
'unit': 's',
|
||||||
'min': 0
|
'min': 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
quit_check(f'{timer_name} is active. Trigger time: {next_elapse_str}. Remaining time: {remaining_time_human}.', nagios.STATE_OK, perfdata_dict)
|
quit_check(f'{timer_name} is active. Trigger time: {next_elapse_str}. Remaining time: {remaining_time_human}. Time since last trigger: {passed_time_human}.', nagios.STATE_OK, perfdata_dict)
|
||||||
else:
|
else:
|
||||||
quit_check(f'{timer_name} is not active.', nagios.STATE_CRIT)
|
quit_check(f'{timer_name} is not active.', nagios.STATE_CRIT)
|
||||||
except dbus.exceptions.DBusException:
|
except dbus.exceptions.DBusException:
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
import humanfriendly
|
||||||
|
|
||||||
|
SYSTEMCTL_TIMESPAN_RE = re.compile(r'([0-9]+((\s[a-z]*)|([a-z]+)))')
|
||||||
|
|
||||||
|
|
||||||
|
def parse_systemctl_time_delta(time_str: str):
|
||||||
|
"""
|
||||||
|
https://humanfriendly.readthedocs.io/en/latest/api.html?highlight=parse_timespan#humanfriendly.parse_timespan
|
||||||
|
"""
|
||||||
|
time_str = time_str.replace(' left', '').replace(' ago', '')
|
||||||
|
spans = []
|
||||||
|
for part in re.findall(SYSTEMCTL_TIMESPAN_RE, time_str):
|
||||||
|
delta = part[0]
|
||||||
|
if 'months' in delta:
|
||||||
|
# humanfriendly does not support "months" so we convert it to weeks
|
||||||
|
num = int(delta.split(' ')[0])
|
||||||
|
delta = f'{num * 4} weeks'
|
||||||
|
try:
|
||||||
|
spans.append(humanfriendly.parse_timespan(delta))
|
||||||
|
except humanfriendly.InvalidTimespan as e:
|
||||||
|
return e
|
||||||
|
return sum(spans)
|
|
@ -10,4 +10,5 @@ cf_speedtest==0.1.7
|
||||||
zfslib==0.11.0
|
zfslib==0.11.0
|
||||||
hurry.filesize==0.9
|
hurry.filesize==0.9
|
||||||
dateparser==1.2.0
|
dateparser==1.2.0
|
||||||
humanize==4.9.0
|
humanize==4.9.0
|
||||||
|
humanfriendly==10.0
|
Loading…
Reference in New Issue