|
@@ -4,24 +4,45 @@ import os
|
4
|
4
|
import re
|
5
|
5
|
import shlex
|
6
|
6
|
import sys
|
|
7
|
+import time
|
7
|
8
|
|
8
|
|
-CONFIG="""\
|
9
|
|
-multigraph rspamd_actions
|
|
9
|
+def avg_scantime(scantimes):
|
|
10
|
+ total = [float(t) for t in scantimes if t]
|
|
11
|
+ return sum(total) / len(total)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+def storage2name(name):
|
|
15
|
+ return re.sub('[\.\- ]', '_', name)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+CONFIGS = {
|
|
19
|
+ 'actions': [
|
|
20
|
+ lambda data: '''\
|
10
|
21
|
graph_title Rspamd actions
|
11
|
22
|
graph_vlabel mails
|
12
|
23
|
graph_args --base 1000 -l 0
|
13
|
24
|
graph_category antispam
|
14
|
25
|
graph_scale yes
|
15
|
|
-reject.label Rejection
|
16
|
|
-soft_reject.label Soft rejection
|
17
|
|
-rewrite_subject.label Rewrite subject
|
18
|
|
-add_header.label Set headers
|
19
|
|
-greylist.label Graylist
|
20
|
|
-no_action.label No action
|
21
|
26
|
scanned.label Scanned/total
|
22
|
27
|
learned.label Learned
|
23
|
|
-
|
24
|
|
-multigraph rspamd_result
|
|
28
|
+%s''' % (
|
|
29
|
+ '\n'.join(['%s.label %s' % (action.replace(' ', '_'),
|
|
30
|
+ action.title())
|
|
31
|
+ for action in data['actions']])
|
|
32
|
+ ),
|
|
33
|
+ lambda data: '''\
|
|
34
|
+scanned.value %d
|
|
35
|
+learned.value %d
|
|
36
|
+%s''' % (
|
|
37
|
+ data['scanned'],
|
|
38
|
+ data['learned'],
|
|
39
|
+ '\n'.join(['%s.value %d' % (action.replace(' ', '_'),
|
|
40
|
+ value)
|
|
41
|
+ for action, value in data['actions'].items()])
|
|
42
|
+ ),
|
|
43
|
+ ],
|
|
44
|
+ 'results': [
|
|
45
|
+ '''\
|
25
|
46
|
graph_title Rspamd results
|
26
|
47
|
graph_vlabel mails
|
27
|
48
|
graph_args --base 1000 -l 0
|
|
@@ -29,117 +50,166 @@ graph_category antispam
|
29
|
50
|
graph_scale yes
|
30
|
51
|
spam.label Spams
|
31
|
52
|
ham.label Ham
|
32
|
|
-
|
33
|
|
-multigraph rspamd_scantime
|
|
53
|
+''',
|
|
54
|
+ lambda data: '''\
|
|
55
|
+spam.value %d
|
|
56
|
+ham.value %d''' % (data['spam_count'], data['ham_count']),
|
|
57
|
+ ],
|
|
58
|
+ 'scantime': [
|
|
59
|
+ '''\
|
34
|
60
|
graph_title Rspamd scantime
|
35
|
61
|
graph_args --base 1000 -l 0
|
36
|
62
|
graph_category antispam
|
37
|
63
|
graph_scale yes
|
38
|
64
|
graph_vlabel seconds
|
39
|
65
|
scantime.label Average scantime
|
40
|
|
-
|
41
|
|
-multigraph rspamd_uptime
|
|
66
|
+''',
|
|
67
|
+ lambda data: '''\
|
|
68
|
+scantime.value %f''' % avg_scantime(data['scan_times']),
|
|
69
|
+ ],
|
|
70
|
+ 'uptime': [
|
|
71
|
+ '''\
|
42
|
72
|
graph_title Rspamd uptime
|
43
|
73
|
graph_args --base 1000 -l 0
|
44
|
74
|
graph_category antispam
|
45
|
75
|
graph_scale no
|
46
|
76
|
graph_vlabel uptime in days
|
47
|
77
|
uptime.label uptime
|
48
|
|
-uptime.label uptime
|
49
|
|
-
|
50
|
|
-multigraph rspamd_memory
|
|
78
|
+''',
|
|
79
|
+ lambda data: 'uptime.value %f' % (data['uptime']/(3600*24)),
|
|
80
|
+ ],
|
|
81
|
+ 'memory_usage': [
|
|
82
|
+ '''\
|
51
|
83
|
graph_title Rspamd memory usage
|
52
|
84
|
graph_args --base 1024 -l 0
|
53
|
85
|
graph_category antispam
|
54
|
86
|
graph_scale yes
|
55
|
87
|
graph_vlabel bytes
|
56
|
88
|
memory.label Memory usage
|
57
|
|
-
|
58
|
|
-multigraph rspamd_connections
|
|
89
|
+''',
|
|
90
|
+ lambda data: 'memory.value %d' % data['bytes_allocated'],
|
|
91
|
+ ],
|
|
92
|
+ 'memory_stats': [
|
|
93
|
+ '''\
|
|
94
|
+graph_title Rspamd memory stats
|
|
95
|
+graph_args --base 1000 -l 0
|
|
96
|
+graph_category antispam
|
|
97
|
+graph_scale yes
|
|
98
|
+pools_allocated.label Pools allocated
|
|
99
|
+pools_freed.label Pools freed
|
|
100
|
+chunks_allocated.label Chunks allocated
|
|
101
|
+chunks_freed.label Chunks freed
|
|
102
|
+shared_chunks_allocated.label Shared chunks allocated
|
|
103
|
+''',
|
|
104
|
+ lambda data: '''\
|
|
105
|
+pools_allocated.value %d
|
|
106
|
+pools_freed.value %d
|
|
107
|
+chunks_allocated.value %d
|
|
108
|
+chunks_freed.value %d
|
|
109
|
+shared_chunks_allocated.value %d''' % (data['pools_allocated'], data['pools_freed'],
|
|
110
|
+ data['chunks_allocated'], data['chunks_freed'],
|
|
111
|
+ data['shared_chunks_allocated']),
|
|
112
|
+ ],
|
|
113
|
+ 'connections': [
|
|
114
|
+ '''\
|
59
|
115
|
graph_title Rspamd connections
|
60
|
116
|
graph_args --base 1000 -l 0
|
61
|
117
|
graph_category antispam
|
62
|
118
|
graph_scale yes
|
63
|
119
|
graph_vlabel connections
|
64
|
120
|
connections.label opened connections
|
65
|
|
-
|
66
|
|
-multigraph rspamd_ctrl_connections
|
|
121
|
+''',
|
|
122
|
+ lambda data: 'connections.value %d' % data['connections'],
|
|
123
|
+ ],
|
|
124
|
+ 'ctrl_connections': [
|
|
125
|
+ '''\
|
67
|
126
|
graph_title Rspamd control connections count
|
68
|
127
|
graph_args --base 1000 -l 0
|
69
|
128
|
graph_category antispam
|
70
|
129
|
graph_scale yes
|
71
|
130
|
graph_vlabel connections
|
72
|
131
|
connections.label control connections count
|
73
|
|
-"""
|
74
|
|
-
|
75
|
|
-def storage2name(name):
|
76
|
|
- return re.sub('[\.\- ]', '_', name)
|
77
|
|
-
|
78
|
|
-rspamc='/usr/bin/rspamc'
|
79
|
|
-# TODO check env for rspamc path
|
80
|
|
-cmd = shlex.join([rspamc, '--json', 'stat'])
|
81
|
|
-
|
82
|
|
-with os.popen(cmd, mode='r') as pipe:
|
83
|
|
- data = json.loads(pipe.read())
|
84
|
|
-
|
85
|
|
-
|
86
|
|
-if len(sys.argv) > 1 and sys.argv[1] == 'config':
|
87
|
|
- print(CONFIG)
|
88
|
|
- print('''multigraph rspamd_statfiles
|
|
132
|
+''',
|
|
133
|
+ lambda data: 'connections.value %d' % data['control_connections'],
|
|
134
|
+ ],
|
|
135
|
+ 'statfiles': [
|
|
136
|
+ lambda data:'''\
|
89
|
137
|
graph_title rspamd bayes statfiles
|
90
|
138
|
graph_args --base 1000 -l 0
|
91
|
139
|
graph_scale yes
|
92
|
|
-graph_vlabel learned mails''')
|
93
|
|
- for stat in data['statfiles']:
|
94
|
|
- sym = stat['symbol']
|
95
|
|
- print(f'learned_{sym}.label {sym} learned')
|
96
|
|
- print(f'users_{sym}.label {sym} users')
|
97
|
|
- print()
|
98
|
|
-
|
99
|
|
- print('''multigraph rspamd_fuzzy_hash
|
|
140
|
+graph_vlabel learned mails
|
|
141
|
+%s''' % (
|
|
142
|
+ '\n'.join(['''learned_{sym}.label {sym} learned
|
|
143
|
+users_{sym}.label {sym} users'''.format(sym=stat['symbol'])
|
|
144
|
+ for stat in data['statfiles']])
|
|
145
|
+ ),
|
|
146
|
+ lambda data: '\n'.join('''\
|
|
147
|
+learned_{sym}.value {lv}
|
|
148
|
+users_{sym}.value {uv}'''.format(sym=stat['symbol'], lv=stat['revision'], uv=stat['users']) for stat in data['statfiles']),
|
|
149
|
+ ],
|
|
150
|
+ 'fuzzy_hashes': [
|
|
151
|
+ lambda data:'''\
|
100
|
152
|
graph_title rspamd fuzzy hases storage
|
101
|
153
|
graph_args --base 1000 -l 0
|
102
|
154
|
graph_scale yes
|
103
|
|
-graph_vlabel hashes''')
|
104
|
|
- for storage in data['fuzzy_hashes']:
|
105
|
|
- storage_name = storage2name(storage)
|
106
|
|
- print(f'{storage_name}.label {storage}')
|
107
|
|
- exit(0)
|
108
|
|
-
|
109
|
|
-print('multigraph rspamd_actions')
|
110
|
|
-for action, value in data['actions'].items():
|
111
|
|
- print('%s.value %d' % (action.replace(' ', '_'), value))
|
112
|
|
-print('scanned.value %d' % data['scanned'])
|
113
|
|
-print('learned.value %d' % data['learned'])
|
114
|
|
-
|
115
|
|
-print('multigraph rspamd_result')
|
116
|
|
-print('spam.value %d' % data['spam_count'])
|
117
|
|
-print('ham.value %d' % data['ham_count'])
|
118
|
|
-
|
119
|
|
-print('multigraph rspamd_scantime')
|
120
|
|
-scantimes = [float(t) for t in data['scan_times'] if t]
|
121
|
|
-scantime = sum(scantimes) / len(scantimes)
|
122
|
|
-print('scantime.value %f' % scantime)
|
123
|
|
-
|
124
|
|
-print('multigraph rspamd_uptime')
|
125
|
|
-print('uptime.value %f' % (data['uptime']/(3600*24)))
|
126
|
|
-
|
127
|
|
-print('multigraph rspamd_memory')
|
128
|
|
-print('memory.value %d' % data['bytes_allocated'])
|
129
|
|
-
|
130
|
|
-print('multigraph rspamd_connections')
|
131
|
|
-print('connections.value %d' % data['connections'])
|
132
|
|
-
|
133
|
|
-print('multigraph rspamd_ctrl_connections')
|
134
|
|
-print('connections.value %d' % data['control_connections'])
|
135
|
|
-
|
136
|
|
-print('multigraph rspamd_statfiles')
|
137
|
|
-for stat in data['statfiles']:
|
138
|
|
- sym = stat['symbol']
|
139
|
|
- print(f'learned_{sym}.value %d' % stat['revision'])
|
140
|
|
- print(f'users_{sym}.value %d' % stat['users'])
|
141
|
|
-
|
142
|
|
-print('multigraph rspamd_fuzzy_hash')
|
143
|
|
-for storage, count in data['fuzzy_hashes'].items():
|
144
|
|
- storage_name = storage2name(storage)
|
145
|
|
- print(f'{storage_name}.value %d' % count)
|
|
155
|
+graph_vlabel hashes
|
|
156
|
+%s''' % (
|
|
157
|
+ '\n'.join(['%s.label %s' % (storage2name(storage), storage)
|
|
158
|
+ for storage in data['fuzzy_hashes']])
|
|
159
|
+ ),
|
|
160
|
+ lambda data: '\n'.join(['%s.value %d' % (storage2name(storage), count)
|
|
161
|
+ for storage, count in data['fuzzy_hashes'].items()]),
|
|
162
|
+ ],
|
|
163
|
+}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+def main(argv, environ):
|
|
167
|
+ # TODO check env for rspamc path
|
|
168
|
+ rspamc = environ.get('rspamc', '/usr/bin/rspamc')
|
|
169
|
+ cache_file = environ.get('cache_file', '/tmp/munin_rspamd.cache')
|
|
170
|
+ cache_time = int(environ.get('cache_time', 60 * 4))
|
|
171
|
+
|
|
172
|
+ rspamc_cmd = shlex.join([rspamc, '--json', 'stat'])
|
|
173
|
+
|
|
174
|
+ data = None
|
|
175
|
+ if os.path.exists(cache_file):
|
|
176
|
+ cache_stat = os.stat(cache_file)
|
|
177
|
+ if cache_stat.st_mtime > time.time() - cache_time:
|
|
178
|
+ with open(cache_file, 'r') as cache:
|
|
179
|
+ try:
|
|
180
|
+ data = json.loads(cache.read())
|
|
181
|
+ except json.JSONDecodeError:
|
|
182
|
+ pass
|
|
183
|
+
|
|
184
|
+ if data is None:
|
|
185
|
+ with open(cache_file, 'w') as cache:
|
|
186
|
+ with os.popen(rspamc_cmd, mode='r') as pipe:
|
|
187
|
+ data = pipe.read()
|
|
188
|
+ cache.write(data)
|
|
189
|
+ data = json.loads(data)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+ spl = argv[0].split('_')[1:]
|
|
193
|
+ if len(spl) == 0:
|
|
194
|
+ raise NameError('Valid commands are : %s' % (','.join(CONFIGS.keys())))
|
|
195
|
+ cmd = '_'.join(spl)
|
|
196
|
+ if cmd not in CONFIGS:
|
|
197
|
+ raise NameError('Unknown command %r valid commands are : %s' % (cmd, ','.join(CONFIGS.keys())))
|
|
198
|
+
|
|
199
|
+ CONFIG = CONFIGS[cmd][0]
|
|
200
|
+ ACT = CONFIGS[cmd][1]
|
|
201
|
+
|
|
202
|
+ if len(argv) > 1 and argv[1] == 'config':
|
|
203
|
+ if not isinstance(CONFIG, str):
|
|
204
|
+ CONFIG = CONFIG(data)
|
|
205
|
+ return CONFIG
|
|
206
|
+ else:
|
|
207
|
+ return ACT(data)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+if __name__ == '__main__':
|
|
211
|
+ try:
|
|
212
|
+ print(main(sys.argv[0], os.environ))
|
|
213
|
+ except Exception as expt:
|
|
214
|
+ print(expt)
|
|
215
|
+
|