Projet

Général

Profil

Paste
Télécharger au format
Statistiques
| Branche: | Révision:

root / plugins / mysql / mysql_audit @ 10ca1256

Historique | Voir | Annoter | Télécharger (4,43 ko)

1 10ca1256 pcy
#!/usr/bin/env python3
2
# -*- python -*-
3
4
"""
5
=head1 INTRODUCTION
6
7
Plugin to monitor the MySQL audit log connection count
8
9
=head1 APPLICABLE SYSTEMS
10
11
MySQL needs to be configured manually in order to enable the audit log. This
12
can be done as follows:
13
14
=over 2
15
16
      plugin-load = server_audit=server_audit.so
17
      server_audit_events = connect
18
      server_audit_output_type=file
19
      server_audit_file_path = /var/log/mysql/audit.log
20
      server_audit_file_rotate_size = 512M
21
      server_audit_logging = ON
22
      server_audit_file_rotations = 5
23
24
=back
25
26
=head1 INSTALLATION
27
28
Place in /etc/munin/plugins/ (or link it there using ln -s)
29
30
=head1 CONFIGURATION
31
32
Add this to your /etc/munin/plugin-conf.d/munin-node:
33
34
=over 2
35
36
      [mysql_audit]
37
      user mysql
38
      env.logfile /var/log/mysql/audit.log
39
      env.sorted 1 # sort output (if not: unsorted)
40
      env.toplist 10 # only the top 10 (if unset: all of them). implies sorted
41
42
=back
43
44
=head1 HISTORY
45
46
2017-11-03: v 1.0 Bert Van de Poel <bert@bhack.net>: created
47
2020-07-19: v 1.1 pcy <pcy@ulyssis.org>: added config options
48
49
=head1 USAGE
50
51
Parameters understood:
52
53
  config   (required)
54
  autoconf (optional - used by munin-config)
55
56
=head1 MAGIC MARKERS
57
58
#%# family=auto
59
#%# capabilities=autoconf
60
"""
61
62
63
from datetime import datetime, timedelta, timezone
64
import operator
65
import os
66
import struct
67
import sys
68
69
70
def weakbool(x):
71
    return x.lower().strip() in {'true', 'yes', 'y', '1'}
72
73
74
logfile = os.getenv('logfile', '/var/log/mysql/audit.log')
75
toplist = int(os.getenv('toplist', '0'))
76
sortlist = weakbool(os.getenv('sorted', 'N')) or toplist > 0
77
78
STATEFILE = os.getenv('MUNIN_STATEFILE')
79
80
81
def loadstate():
82
    if not os.path.isfile(STATEFILE):
83
        return None
84
85
    with open(STATEFILE, 'rb') as f:
86
        tstamp = struct.unpack('d', f.read())[0]
87
        return datetime.fromtimestamp(tstamp, tz=timezone.utc)
88
89
90
def savestate(state):
91
    with open(STATEFILE, 'wb') as f:
92
        f.write(struct.pack('d', state.timestamp()))
93
94
95
def reverse_lines(filename, BUFSIZE=4096):
96
    with open(filename, "r") as f:
97
        f.seek(0, 2)
98
        p = f.tell()
99
        remainder = ""
100
        while True:
101
            sz = min(BUFSIZE, p)
102
            p -= sz
103
            f.seek(p)
104
            buf = f.read(sz) + remainder
105
            if '\n' not in buf:
106
                remainder = buf
107
            else:
108
                i = buf.index('\n')
109
                for L in buf[i + 1:].split("\n")[::-1]:
110
                    yield L
111
                remainder = buf[:i]
112
            if p == 0:
113
                break
114
        yield remainder
115
116
117
def get_data(do_save=True):
118
    occurrences = {}
119
    begin = datetime.now(timezone.utc)
120
    begin_local = datetime.now()
121
122
    state = loadstate()
123
    if state is None:
124
        # need to do something here to prevent reading indefinitely
125
        state = begin - timedelta(minutes=5)
126
127
    for line in reverse_lines(logfile):
128
        if ',CONNECT,' not in line:
129
            continue
130
131
        splitted = line.split(',')
132
        key = splitted[2]
133
        date = datetime.strptime(splitted[0], '%Y%m%d %H:%M:%S')
134
        # hack to add timezone data to the datetime
135
        date = begin + (date - begin_local)
136
137
        if date < state:
138
            break
139
140
        occurrences[key] = occurrences.get(key, 0) + 1
141
142
    if do_save:
143
        savestate(begin)
144
145
    return occurrences
146
147
148
def autoconf():
149
    print("no (logfile not found)" if os.path.isfile(logfile) else "yes")
150
151
152
def configure():
153
    print('graph_title MySQL Audit connect count')
154
    print('graph_vlabel Connections')
155
    print('graph_category mysql')
156
157
    occurrences = get_data(False)
158
    occitems = occurrences.items()
159
    occitems = sorted(occitems, key=operator.itemgetter(1 if sortlist else 0),
160
                      reverse=sortlist)
161
    if toplist > 0:
162
        occitems = occitems[:toplist]
163
164
    for key, value in occitems:
165
        print('{}.label {}'.format(key.lower(), key))
166
        print('{}.type GAUGE'.format(key.lower()))
167
        print('{}.draw AREASTACK'.format(key.lower()))
168
169
170
def fetch():
171
    occurrences = get_data()
172
    occitems = occurrences.items()
173
    occitems = sorted(occitems, key=operator.itemgetter(1 if sortlist else 0),
174
                      reverse=sortlist)
175
    if toplist > 0:
176
        occitems = occitems[:toplist]
177
178
    for key, value in occitems:
179
        print('{}.value {}'.format(key, value))
180
181
182
if len(sys.argv) == 2 and sys.argv[1] == "autoconf":
183
    autoconf()
184
elif len(sys.argv) == 2 and sys.argv[1] == "config":
185
    configure()
186
else:
187
    fetch()
188
189
# flake8: noqa: E265