Révision e6c47a3a
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 | ||
|---|---|---|
| 1 |
#!/usr/bin/env python3 |
|
| 2 |
|
|
| 3 |
"""Munin plugin to monitor NGINX Unit applications. |
|
| 4 |
|
|
| 5 |
=head1 NAME |
|
| 6 |
|
|
| 7 |
unit - monitor NGINX Unit applications |
|
| 8 |
|
|
| 9 |
=head1 APPLICABLE SYSTEMS |
|
| 10 |
|
|
| 11 |
Systems with NGINX Unit running. |
|
| 12 |
|
|
| 13 |
=head1 CONFIGURATION |
|
| 14 |
|
|
| 15 |
No configuration is required for this plugin. |
|
| 16 |
|
|
| 17 |
=head1 AUTHOR |
|
| 18 |
|
|
| 19 |
Kim B. Heino <b@bbbs.net> |
|
| 20 |
|
|
| 21 |
=head1 LICENSE |
|
| 22 |
|
|
| 23 |
GPLv2 |
|
| 24 |
|
|
| 25 |
=head1 MAGIC MARKERS |
|
| 26 |
|
|
| 27 |
#%# family=auto |
|
| 28 |
#%# capabilities=autoconf |
|
| 29 |
|
|
| 30 |
=cut |
|
| 31 |
""" |
|
| 32 |
|
|
| 33 |
import re |
|
| 34 |
import subprocess |
|
| 35 |
import sys |
|
| 36 |
import unicodedata |
|
| 37 |
|
|
| 38 |
|
|
| 39 |
def safename(name): |
|
| 40 |
"""Return safe variable name.""" |
|
| 41 |
# Convert ä->a as isalpha('ä') is true
|
|
| 42 |
value = unicodedata.normalize('NFKD', name)
|
|
| 43 |
value = value.encode('ASCII', 'ignore').decode('utf-8')
|
|
| 44 |
|
|
| 45 |
# Remove non-alphanumeric chars |
|
| 46 |
return ''.join(char.lower() if char.isalnum() else '_' for char in value) |
|
| 47 |
|
|
| 48 |
|
|
| 49 |
def run_binary(arg): |
|
| 50 |
"""Run binary and return output.""" |
|
| 51 |
try: |
|
| 52 |
return subprocess.run(arg, stdout=subprocess.PIPE, check=False, |
|
| 53 |
encoding='utf-8', errors='ignore').stdout |
|
| 54 |
except FileNotFoundError: |
|
| 55 |
return '' |
|
| 56 |
|
|
| 57 |
|
|
| 58 |
def parse_time(value): |
|
| 59 |
"""Parse "dd-hh:mm:ss", "hh:mm:ss" and "mm:ss" to seconds.""" |
|
| 60 |
days = hours = '0' |
|
| 61 |
if '-' in value: |
|
| 62 |
days, value = value.split('-', 1)
|
|
| 63 |
if value.count(':') == 2:
|
|
| 64 |
hours, value = value.split(':', 1)
|
|
| 65 |
minutes, seconds = value.split(':')
|
|
| 66 |
return (int(days) * 86400 + int(hours) * 3600 + int(minutes) * 60 + |
|
| 67 |
int(seconds)) |
|
| 68 |
|
|
| 69 |
|
|
| 70 |
def find_apps(): |
|
| 71 |
"""Return dict of found unit applications.""" |
|
| 72 |
apps = {}
|
|
| 73 |
ps_lines = run_binary(['ps', '-eo', '%cpu,etime,rss,command']).splitlines() |
|
| 74 |
for line in ps_lines: |
|
| 75 |
appmatch = re.match(r' *([.0-9]+) +([-:0-9]+) +([0-9]+) ' |
|
| 76 |
r'unit: "(.*)" application', line) |
|
| 77 |
if not appmatch: |
|
| 78 |
continue |
|
| 79 |
cpu = float(appmatch.group(1)) |
|
| 80 |
age = parse_time(appmatch.group(2)) |
|
| 81 |
memory = int(appmatch.group(3)) # KiB |
|
| 82 |
appname = appmatch.group(4) |
|
| 83 |
if appname in apps: |
|
| 84 |
apps[appname]['count'] += 1 |
|
| 85 |
apps[appname]['cpu'] += cpu |
|
| 86 |
apps[appname]['age'] += age |
|
| 87 |
apps[appname]['memory'] += memory |
|
| 88 |
else: |
|
| 89 |
apps[appname] = {
|
|
| 90 |
'count': 1, |
|
| 91 |
'cpu': cpu, |
|
| 92 |
'age': age, |
|
| 93 |
'memory': memory, |
|
| 94 |
} |
|
| 95 |
return apps |
|
| 96 |
|
|
| 97 |
|
|
| 98 |
def config(apps): |
|
| 99 |
"""Print plugin config.""" |
|
| 100 |
print('multigraph unit_process')
|
|
| 101 |
print('graph_title Unit application processes')
|
|
| 102 |
print('graph_info NGINX Unit application process counts.')
|
|
| 103 |
print('graph_category appserver')
|
|
| 104 |
print('graph_vlabel processes')
|
|
| 105 |
print('graph_args --lower-limit 0')
|
|
| 106 |
print('graph_scale no')
|
|
| 107 |
for app in sorted(apps): |
|
| 108 |
safe = safename(app) |
|
| 109 |
print(f'{safe}.label {app} processes')
|
|
| 110 |
print(f'{safe}.draw AREASTACK')
|
|
| 111 |
|
|
| 112 |
print('multigraph unit_cpu')
|
|
| 113 |
print('graph_title Unit application average CPU usage')
|
|
| 114 |
print('graph_info NGINX Unit application average CPU usage per process.')
|
|
| 115 |
print('graph_category appserver')
|
|
| 116 |
print('graph_vlabel %')
|
|
| 117 |
print('graph_args --lower-limit 0')
|
|
| 118 |
print('graph_scale no')
|
|
| 119 |
for app in sorted(apps): |
|
| 120 |
safe = safename(app) |
|
| 121 |
print(f'{safe}.label {app} CPU')
|
|
| 122 |
|
|
| 123 |
print('multigraph unit_age')
|
|
| 124 |
print('graph_title Unit application average age')
|
|
| 125 |
print('graph_info NGINX Unit application average age per process.')
|
|
| 126 |
print('graph_category appserver')
|
|
| 127 |
print('graph_vlabel seconds')
|
|
| 128 |
print('graph_args --lower-limit 0')
|
|
| 129 |
print('graph_scale no')
|
|
| 130 |
for app in sorted(apps): |
|
| 131 |
safe = safename(app) |
|
| 132 |
print(f'{safe}.label {app} age')
|
|
| 133 |
|
|
| 134 |
print('multigraph unit_memory')
|
|
| 135 |
print('graph_title Unit application average memory')
|
|
| 136 |
print('graph_info NGINX Unit application average memory per process.')
|
|
| 137 |
print('graph_category appserver')
|
|
| 138 |
print('graph_vlabel MiB')
|
|
| 139 |
print('graph_args --lower-limit 0 --base 1024')
|
|
| 140 |
print('graph_scale no')
|
|
| 141 |
for app in sorted(apps): |
|
| 142 |
safe = safename(app) |
|
| 143 |
print(f'{safe}.label {app} memory')
|
|
| 144 |
|
|
| 145 |
print('multigraph unit_total_memory')
|
|
| 146 |
print('graph_title Unit application total memory')
|
|
| 147 |
print('graph_info NGINX Unit application total memory.')
|
|
| 148 |
print('graph_category appserver')
|
|
| 149 |
print('graph_vlabel MiB')
|
|
| 150 |
print('graph_args --lower-limit 0 --base 1024')
|
|
| 151 |
print('graph_scale no')
|
|
| 152 |
for app in sorted(apps): |
|
| 153 |
safe = safename(app) |
|
| 154 |
print(f'{safe}.label {app} memory')
|
|
| 155 |
print(f'{safe}.draw AREASTACK')
|
|
| 156 |
|
|
| 157 |
|
|
| 158 |
def fetch(apps): |
|
| 159 |
"""Print values.""" |
|
| 160 |
print('multigraph unit_process')
|
|
| 161 |
for app, values in apps.items(): |
|
| 162 |
safe = safename(app) |
|
| 163 |
print(f'{safe}.value {values["count"]}')
|
|
| 164 |
|
|
| 165 |
print('multigraph unit_cpu')
|
|
| 166 |
for app, values in apps.items(): |
|
| 167 |
safe = safename(app) |
|
| 168 |
print(f'{safe}.value {values["cpu"] / values["count"]}')
|
|
| 169 |
|
|
| 170 |
print('multigraph unit_age')
|
|
| 171 |
for app, values in apps.items(): |
|
| 172 |
safe = safename(app) |
|
| 173 |
print(f'{safe}.value {values["age"] / values["count"]}')
|
|
| 174 |
|
|
| 175 |
print('multigraph unit_memory')
|
|
| 176 |
for app, values in apps.items(): |
|
| 177 |
safe = safename(app) |
|
| 178 |
print(f'{safe}.value {values["memory"] / values["count"] / 1024}')
|
|
| 179 |
|
|
| 180 |
print('multigraph unit_total_memory')
|
|
| 181 |
for app, values in apps.items(): |
|
| 182 |
safe = safename(app) |
|
| 183 |
print(f'{safe}.value {values["memory"] / 1024}')
|
|
| 184 |
|
|
| 185 |
|
|
| 186 |
if __name__ == '__main__': |
|
| 187 |
if len(sys.argv) > 1 and sys.argv[1] == 'autoconf': |
|
| 188 |
print('yes' if find_apps() else 'no (NGINX Unit is not running)')
|
|
| 189 |
elif len(sys.argv) > 1 and sys.argv[1] == 'config': |
|
| 190 |
config(find_apps()) |
|
| 191 |
else: |
|
| 192 |
fetch(find_apps()) |
|
Formats disponibles : Unified diff