mirror of
https://github.com/munin-monitoring/contrib.git
synced 2025-07-21 18:41:03 +00:00
[plugins/mysql/mysql_audit] Added plugin for monitoring the audit log
Shows the number of active over time
This commit is contained in:
parent
271e21d34e
commit
10ca12569a
1 changed files with 189 additions and 0 deletions
189
plugins/mysql/mysql_audit
Executable file
189
plugins/mysql/mysql_audit
Executable file
|
@ -0,0 +1,189 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- python -*-
|
||||
|
||||
"""
|
||||
=head1 INTRODUCTION
|
||||
|
||||
Plugin to monitor the MySQL audit log connection count
|
||||
|
||||
=head1 APPLICABLE SYSTEMS
|
||||
|
||||
MySQL needs to be configured manually in order to enable the audit log. This
|
||||
can be done as follows:
|
||||
|
||||
=over 2
|
||||
|
||||
plugin-load = server_audit=server_audit.so
|
||||
server_audit_events = connect
|
||||
server_audit_output_type=file
|
||||
server_audit_file_path = /var/log/mysql/audit.log
|
||||
server_audit_file_rotate_size = 512M
|
||||
server_audit_logging = ON
|
||||
server_audit_file_rotations = 5
|
||||
|
||||
=back
|
||||
|
||||
=head1 INSTALLATION
|
||||
|
||||
Place in /etc/munin/plugins/ (or link it there using ln -s)
|
||||
|
||||
=head1 CONFIGURATION
|
||||
|
||||
Add this to your /etc/munin/plugin-conf.d/munin-node:
|
||||
|
||||
=over 2
|
||||
|
||||
[mysql_audit]
|
||||
user mysql
|
||||
env.logfile /var/log/mysql/audit.log
|
||||
env.sorted 1 # sort output (if not: unsorted)
|
||||
env.toplist 10 # only the top 10 (if unset: all of them). implies sorted
|
||||
|
||||
=back
|
||||
|
||||
=head1 HISTORY
|
||||
|
||||
2017-11-03: v 1.0 Bert Van de Poel <bert@bhack.net>: created
|
||||
2020-07-19: v 1.1 pcy <pcy@ulyssis.org>: added config options
|
||||
|
||||
=head1 USAGE
|
||||
|
||||
Parameters understood:
|
||||
|
||||
config (required)
|
||||
autoconf (optional - used by munin-config)
|
||||
|
||||
=head1 MAGIC MARKERS
|
||||
|
||||
#%# family=auto
|
||||
#%# capabilities=autoconf
|
||||
"""
|
||||
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import operator
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
def weakbool(x):
|
||||
return x.lower().strip() in {'true', 'yes', 'y', '1'}
|
||||
|
||||
|
||||
logfile = os.getenv('logfile', '/var/log/mysql/audit.log')
|
||||
toplist = int(os.getenv('toplist', '0'))
|
||||
sortlist = weakbool(os.getenv('sorted', 'N')) or toplist > 0
|
||||
|
||||
STATEFILE = os.getenv('MUNIN_STATEFILE')
|
||||
|
||||
|
||||
def loadstate():
|
||||
if not os.path.isfile(STATEFILE):
|
||||
return None
|
||||
|
||||
with open(STATEFILE, 'rb') as f:
|
||||
tstamp = struct.unpack('d', f.read())[0]
|
||||
return datetime.fromtimestamp(tstamp, tz=timezone.utc)
|
||||
|
||||
|
||||
def savestate(state):
|
||||
with open(STATEFILE, 'wb') as f:
|
||||
f.write(struct.pack('d', state.timestamp()))
|
||||
|
||||
|
||||
def reverse_lines(filename, BUFSIZE=4096):
|
||||
with open(filename, "r") as f:
|
||||
f.seek(0, 2)
|
||||
p = f.tell()
|
||||
remainder = ""
|
||||
while True:
|
||||
sz = min(BUFSIZE, p)
|
||||
p -= sz
|
||||
f.seek(p)
|
||||
buf = f.read(sz) + remainder
|
||||
if '\n' not in buf:
|
||||
remainder = buf
|
||||
else:
|
||||
i = buf.index('\n')
|
||||
for L in buf[i + 1:].split("\n")[::-1]:
|
||||
yield L
|
||||
remainder = buf[:i]
|
||||
if p == 0:
|
||||
break
|
||||
yield remainder
|
||||
|
||||
|
||||
def get_data(do_save=True):
|
||||
occurrences = {}
|
||||
begin = datetime.now(timezone.utc)
|
||||
begin_local = datetime.now()
|
||||
|
||||
state = loadstate()
|
||||
if state is None:
|
||||
# need to do something here to prevent reading indefinitely
|
||||
state = begin - timedelta(minutes=5)
|
||||
|
||||
for line in reverse_lines(logfile):
|
||||
if ',CONNECT,' not in line:
|
||||
continue
|
||||
|
||||
splitted = line.split(',')
|
||||
key = splitted[2]
|
||||
date = datetime.strptime(splitted[0], '%Y%m%d %H:%M:%S')
|
||||
# hack to add timezone data to the datetime
|
||||
date = begin + (date - begin_local)
|
||||
|
||||
if date < state:
|
||||
break
|
||||
|
||||
occurrences[key] = occurrences.get(key, 0) + 1
|
||||
|
||||
if do_save:
|
||||
savestate(begin)
|
||||
|
||||
return occurrences
|
||||
|
||||
|
||||
def autoconf():
|
||||
print("no (logfile not found)" if os.path.isfile(logfile) else "yes")
|
||||
|
||||
|
||||
def configure():
|
||||
print('graph_title MySQL Audit connect count')
|
||||
print('graph_vlabel Connections')
|
||||
print('graph_category mysql')
|
||||
|
||||
occurrences = get_data(False)
|
||||
occitems = occurrences.items()
|
||||
occitems = sorted(occitems, key=operator.itemgetter(1 if sortlist else 0),
|
||||
reverse=sortlist)
|
||||
if toplist > 0:
|
||||
occitems = occitems[:toplist]
|
||||
|
||||
for key, value in occitems:
|
||||
print('{}.label {}'.format(key.lower(), key))
|
||||
print('{}.type GAUGE'.format(key.lower()))
|
||||
print('{}.draw AREASTACK'.format(key.lower()))
|
||||
|
||||
|
||||
def fetch():
|
||||
occurrences = get_data()
|
||||
occitems = occurrences.items()
|
||||
occitems = sorted(occitems, key=operator.itemgetter(1 if sortlist else 0),
|
||||
reverse=sortlist)
|
||||
if toplist > 0:
|
||||
occitems = occitems[:toplist]
|
||||
|
||||
for key, value in occitems:
|
||||
print('{}.value {}'.format(key, value))
|
||||
|
||||
|
||||
if len(sys.argv) == 2 and sys.argv[1] == "autoconf":
|
||||
autoconf()
|
||||
elif len(sys.argv) == 2 and sys.argv[1] == "config":
|
||||
configure()
|
||||
else:
|
||||
fetch()
|
||||
|
||||
# flake8: noqa: E265
|
Loading…
Add table
Add a link
Reference in a new issue