From e6c47a3a4e1e4d2aac998a0198f6d476b30c042e Mon Sep 17 00:00:00 2001 From: "Kim B. Heino" Date: Tue, 26 Oct 2021 12:38:39 +0300 Subject: [PATCH] unit: add new plugin to monitor nginx unit app server This multigraph plugin does basic monitoring of unit's applications. See: https://unit.nginx.org/ --- plugins/unit/unit | 192 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100755 plugins/unit/unit diff --git a/plugins/unit/unit b/plugins/unit/unit new file mode 100755 index 00000000..c389be41 --- /dev/null +++ b/plugins/unit/unit @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 + +"""Munin plugin to monitor NGINX Unit applications. + +=head1 NAME + +unit - monitor NGINX Unit applications + +=head1 APPLICABLE SYSTEMS + +Systems with NGINX Unit running. + +=head1 CONFIGURATION + +No configuration is required for this plugin. + +=head1 AUTHOR + +Kim B. Heino + +=head1 LICENSE + +GPLv2 + +=head1 MAGIC MARKERS + + #%# family=auto + #%# capabilities=autoconf + +=cut +""" + +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 unit 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)) # KiB + 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 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 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 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') + print('graph_scale no') + for app in sorted(apps): + safe = safename(app) + print(f'{safe}.label {app} age') + + print('multigraph 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 MiB') + print('graph_args --lower-limit 0 --base 1024') + print('graph_scale no') + for app in sorted(apps): + safe = safename(app) + print(f'{safe}.label {app} memory') + + print('multigraph 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 MiB') + print('graph_args --lower-limit 0 --base 1024') + print('graph_scale no') + for app in sorted(apps): + safe = safename(app) + print(f'{safe}.label {app} memory') + print(f'{safe}.draw AREASTACK') + + +def fetch(apps): + """Print values.""" + print('multigraph unit_process') + for app, values in apps.items(): + safe = safename(app) + print(f'{safe}.value {values["count"]}') + + print('multigraph unit_cpu') + for app, values in apps.items(): + safe = safename(app) + print(f'{safe}.value {values["cpu"] / values["count"]}') + + print('multigraph unit_age') + for app, values in apps.items(): + safe = safename(app) + print(f'{safe}.value {values["age"] / values["count"]}') + + print('multigraph unit_memory') + for app, values in apps.items(): + safe = safename(app) + print(f'{safe}.value {values["memory"] / values["count"] / 1024}') + + print('multigraph unit_total_memory') + for app, values in apps.items(): + safe = safename(app) + print(f'{safe}.value {values["memory"] / 1024}') + + +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())