- Repository
- Munin (contrib)
- Last change
- 2021-10-28
- Graph Categories
- Family
- auto
- Capabilities
- Keywords
- Language
- Python (3.x)
- License
- GPL-2.0-only
- Authors
nginx_unit
Name
nginx_unit - monitor NGINX Unit applications
Applicable Systems
Systems with NGINX Unit running. See https://unit.nginx.org/ for more information.
Configuration
No configuration is required for this plugin.
Author
Kim B. Heino b@bbbs.net
License
GPLv2
Magic Markers
#%# family=auto
#%# capabilities=autoconf
#!/usr/bin/env python3
"""Munin plugin to monitor NGINX Unit applications.
=head1 NAME
nginx_unit - monitor NGINX Unit applications
=head1 APPLICABLE SYSTEMS
Systems with NGINX Unit running. See https://unit.nginx.org/ for more
information.
=head1 CONFIGURATION
No configuration is required for this plugin.
=head1 AUTHOR
Kim B. Heino <b@bbbs.net>
=head1 LICENSE
GPLv2
=head1 MAGIC MARKERS
#%# family=auto
#%# capabilities=autoconf
=cut
"""
import os
import re
import subprocess
import sys
import unicodedata
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 run_binary(arg):
"""Run binary and return output."""
try:
return subprocess.run(arg, stdout=subprocess.PIPE, check=False,
encoding='utf-8', errors='ignore').stdout
except FileNotFoundError:
return ''
def parse_time(value):
"""Parse "dd-hh:mm:ss", "hh:mm:ss" and "mm:ss" to seconds."""
days = hours = '0'
if '-' in value:
days, value = value.split('-', 1)
if value.count(':') == 2:
hours, value = value.split(':', 1)
minutes, seconds = value.split(':')
return (int(days) * 86400 + int(hours) * 3600 + int(minutes) * 60 +
int(seconds))
def find_apps():
"""Return dict of found applications."""
apps = {}
ps_lines = run_binary(['ps', '-eo', '%cpu,etime,rss,command']).splitlines()
for line in ps_lines:
appmatch = re.match(r' *([.0-9]+) +([-:0-9]+) +([0-9]+) '
r'unit: "(.*)" application', line)
if not appmatch:
continue
cpu = float(appmatch.group(1))
age = parse_time(appmatch.group(2))
memory = int(appmatch.group(3)) * 1024
appname = appmatch.group(4)
if appname in apps:
apps[appname]['count'] += 1
apps[appname]['cpu'] += cpu
apps[appname]['age'] += age
apps[appname]['memory'] += memory
else:
apps[appname] = {
'count': 1,
'cpu': cpu,
'age': age,
'memory': memory,
}
return apps
def config(apps):
"""Print plugin config."""
print('multigraph nginx_unit_process')
print('graph_title Unit application processes')
print('graph_info NGINX Unit application process counts.')
print('graph_category appserver')
print('graph_vlabel processes')
print('graph_args --lower-limit 0')
print('graph_scale no')
for app in sorted(apps):
safe = safename(app)
print(f'{safe}.label {app} processes')
print(f'{safe}.draw AREASTACK')
print('multigraph nginx_unit_cpu')
print('graph_title Unit application average CPU usage')
print('graph_info NGINX Unit application average CPU usage per process.')
print('graph_category appserver')
print('graph_vlabel %')
print('graph_args --lower-limit 0')
print('graph_scale no')
for app in sorted(apps):
safe = safename(app)
print(f'{safe}.label {app} CPU')
print('multigraph nginx_unit_age')
print('graph_title Unit application average age')
print('graph_info NGINX Unit application average age per process.')
print('graph_category appserver')
print('graph_vlabel seconds')
print('graph_args --lower-limit 0')
for app in sorted(apps):
safe = safename(app)
print(f'{safe}.label {app} age')
print('multigraph nginx_unit_memory')
print('graph_title Unit application average memory')
print('graph_info NGINX Unit application average memory per process.')
print('graph_category appserver')
print('graph_vlabel bytes')
print('graph_args --lower-limit 0 --base 1024')
for app in sorted(apps):
safe = safename(app)
print(f'{safe}.label {app} memory')
print('multigraph nginx_unit_total_memory')
print('graph_title Unit application total memory')
print('graph_info NGINX Unit application total memory.')
print('graph_category appserver')
print('graph_vlabel bytes')
print('graph_args --lower-limit 0 --base 1024')
for app in sorted(apps):
safe = safename(app)
print(f'{safe}.label {app} memory')
print(f'{safe}.draw AREASTACK')
if os.environ.get('MUNIN_CAP_DIRTYCONFIG') == '1':
fetch(apps)
def fetch(apps):
"""Print values."""
print('multigraph nginx_unit_process')
for app, values in apps.items():
safe = safename(app)
print(f'{safe}.value {values["count"]}')
print('multigraph nginx_unit_cpu')
for app, values in apps.items():
safe = safename(app)
print(f'{safe}.value {values["cpu"] / values["count"]}')
print('multigraph nginx_unit_age')
for app, values in apps.items():
safe = safename(app)
print(f'{safe}.value {values["age"] / values["count"]}')
print('multigraph nginx_unit_memory')
for app, values in apps.items():
safe = safename(app)
print(f'{safe}.value {values["memory"] / values["count"]}')
print('multigraph nginx_unit_total_memory')
for app, values in apps.items():
safe = safename(app)
print(f'{safe}.value {values["memory"]}')
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == 'autoconf':
print('yes' if find_apps() else 'no (NGINX Unit is not running)')
elif len(sys.argv) > 1 and sys.argv[1] == 'config':
config(find_apps())
else:
fetch(find_apps())