systemd_status: add support to monitor user services

Also add support to ignore specific services.
This commit is contained in:
Kim B. Heino 2023-10-16 11:54:14 +03:00
parent 4abd859e6b
commit d93503664b
1 changed files with 61 additions and 10 deletions

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3
# pylint: disable=invalid-name
# pylint: enable=invalid-name
# pylint: disable=consider-using-f-string
"""Munin plugin to monitor systemd service status.
@ -20,6 +21,21 @@ No configuration is required for this plugin.
Warning level for systemd "failed" state is set to 0:0. If any of the services
enters "failed" state, Munin will emit warning.
Single service can be ignored by configuring it's value to "0":
[systemd_status]
env.fwupd_refresh_service 0
Normally this plugin monitors global system services. User services can be
monitored by manually adding softlink and config:
ln -s /usr/share/munin/plugins/systemd_status \
/etc/munin/plugins/systemd_status_b
[systemd_status_b]
user b
env.monitor_user yes
=head1 AUTHOR
Kim B. Heino <b@bbbs.net>
@ -38,9 +54,11 @@ GPLv2
"""
import os
import pwd
import re
import subprocess
import sys
import unicodedata
STATES = (
@ -55,10 +73,26 @@ STATES = (
'mounted',
)
USER = os.environ.get('monitor_user') in ('yes', '1')
def safename(name):
"""Return safe variable name."""
# Convert ä->a as isalpha('ä') is true
value = unicodedata.normalize('NFKD', name)
value = value.encode('ASCII', 'ignore').decode('utf-8')
# Remove non-alphanumeric chars
return ''.join(char.lower() if char.isalnum() else '_' for char in value)
def config():
"""Autoconfig values."""
print('graph_title systemd services')
if USER:
print('graph_title systemd services for {}'.format(
pwd.getpwuid(os.geteuid())[0]))
else:
print('graph_title systemd services')
print('graph_vlabel Services')
print('graph_category processes')
print('graph_args --base 1000 --lower-limit 0')
@ -75,33 +109,50 @@ def fetch():
"""Print runtime values."""
# Get data
try:
# deb9/py3.5 doesn't have encoding parameter in subprocess
output = subprocess.check_output(['/bin/systemctl', 'list-units'])
output = subprocess.check_output(['systemctl', 'list-units'] +
(['--user'] if USER else []),
encoding='utf-8')
except (OSError, subprocess.CalledProcessError):
return
output = output.decode('utf-8', 'ignore')
# Parse data
states = {state: 0 for state in STATES}
for line in output.splitlines():
token = line.split()
if token and len(token[0]) < 3: # Skip failed-bullet
token = token[1:]
if len(token) < 4:
continue
if len(token[0]) < 3: # Skip failed-bullet
token = token[1:]
if token[0].endswith('.scope'):
service = token[0]
state = token[3]
if service.endswith('.scope'):
continue # Ignore scopes
if re.match(r'user.*@\d+\.service', token[0]):
if re.match(r'user.*@\d+\.service', service):
continue # These fail randomly in older systemd
if token[3] in states:
states[token[3]] = states[token[3]] + 1
if state in states:
value = 1
if state == 'failed':
value = int(os.environ.get(safename(service), 1))
states[state] += value
# Output
for state in STATES:
print('{}.value {}'.format(state, states[state]))
def fix_dbus():
"""Fix DBus address for user service."""
if not USER:
return
euid = '/{}/'.format(os.geteuid())
if euid in os.environ.get('DBUS_SESSION_BUS_ADDRESS', ''):
return
os.putenv('DBUS_SESSION_BUS_ADDRESS',
'unix:path=/run/user{}bus'.format(euid))
if __name__ == '__main__':
fix_dbus()
if len(sys.argv) > 1 and sys.argv[1] == 'autoconf':
print('yes' if os.path.exists('/run/systemd/system') else
'no (systemd is not running)')