]> git.ipfire.org Git - collecty.git/blame - src/collecty/plugins/conntrack.py
graphs: Justify all legends
[collecty.git] / src / collecty / plugins / conntrack.py
CommitLineData
f37913e8 1#!/usr/bin/python3
0ec1854a
MT
2###############################################################################
3# #
4# collecty - A system statistics collection daemon for IPFire #
5# Copyright (C) 2015 IPFire development team #
6# #
7# This program is free software: you can redistribute it and/or modify #
8# it under the terms of the GNU General Public License as published by #
9# the Free Software Foundation, either version 3 of the License, or #
10# (at your option) any later version. #
11# #
12# This program is distributed in the hope that it will be useful, #
13# but WITHOUT ANY WARRANTY; without even the implied warranty of #
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15# GNU General Public License for more details. #
16# #
17# You should have received a copy of the GNU General Public License #
18# along with this program. If not, see <http://www.gnu.org/licenses/>. #
19# #
20###############################################################################
21
22import os
23
f37913e8 24from . import base
0ec1854a 25
03ba5630 26from ..colours import *
0308c0f3 27
0ec1854a
MT
28CONNTRACK_FILE = "/proc/net/nf_conntrack"
29
30class ConntrackTable(object):
31 _layer3_protocols = (
32 "ipv6",
33 "ipv4",
34 "other",
35 )
36
37 _layer4_protocols = (
38 "dccp",
39 "icmp",
40 "igmp",
41 "sctp",
42 "tcp",
43 "udp",
44 "udplite",
45 "other",
46 )
47
48 _stateful_layer4_protocols = {
49 "dccp" : (
50 "CLOSEREQ",
51 "CLOSING",
52 "IGNORE",
53 "INVALID",
54 "NONE",
55 "OPEN",
56 "PARTOPEN",
57 "REQUEST",
58 "RESPOND",
59 "TIME_WAIT",
60 ),
61 "sctp" : (
62 "CLOSED",
63 "COOKIE_ECHOED",
64 "COOKIE_WAIT",
65 "ESTABLISHED",
66 "NONE",
67 "SHUTDOWN_ACK_SENT",
68 "SHUTDOWN_RECD",
69 "SHUTDOWN_SENT",
70 ),
71 "tcp" : (
72 "CLOSE",
73 "CLOSE_WAIT",
74 "ESTABLISHED",
75 "FIN_WAIT",
76 "LAST_ACK",
77 "NONE",
78 "SYN_RECV",
79 "SYN_SENT",
80 "SYN_SENT2",
81 "TIME_WAIT",
82 ),
83 }
84
85 def __init__(self, filename):
86 with open(filename) as f:
87 self.layer3_protocols = {}
88 for proto in self._layer3_protocols:
89 self.layer3_protocols[proto] = 0
90
91 self.layer4_protocols = {}
92 for proto in self._layer4_protocols:
93 self.layer4_protocols[proto] = 0
94
95 self.protocol_states = {}
96 for proto, states in self._stateful_layer4_protocols.items():
97 self.protocol_states[proto] = dict((state, 0) for state in states)
98
99 for line in f.readlines():
100 line = line.split()
101
102 # Layer 3 protocol
103 layer3_protocol = line[0]
104
105 try:
106 self.layer3_protocols[layer3_protocol] += 1
107 except KeyError:
108 self.layer3_protocols["other"] += 1
109
110 # Layer 4 protocol
111 layer4_protocol = line[2]
112
113 try:
114 self.layer4_protocols[layer4_protocol] += 1
115 except KeyError:
116 self.layer4_protocols["other"] += 1
117 layer4_protocol = "other"
118
119 # Count connection states
f37913e8 120 if layer4_protocol in self.protocol_states:
0ec1854a
MT
121 state = line[5]
122
123 try:
124 self.protocol_states[layer4_protocol][state] += 1
125 except KeyError:
126 pass
127
128
0308c0f3
MT
129class ConntrackLayer3ProtocolsGraphTemplate(base.GraphTemplate):
130 name = "conntrack-layer3-protocols"
131
f998b6cb 132 _protocols = ConntrackTable._layer3_protocols
0308c0f3
MT
133
134 protocol_colours = {
03ba5630
MT
135 "ipv6" : COLOUR_IPV6,
136 "ipv4" : COLOUR_IPV4,
137 "other" : COLOUR_IPVX,
0308c0f3
MT
138 }
139
76c12e91
MT
140 def get_objects(self, *args):
141 return [
142 self.plugin.get_object("layer3-protocols"),
143 ]
cd8bba0b 144
f998b6cb
MT
145 @property
146 def protocols(self):
147 # Order the protocols by standard deviation which will give us cleaner graphs
148 # http://stackoverflow.com/questions/13958409/how-to-graph-rrd-stackable-data-by-standard-deviation-to-maximize-readability
149 stddev = self.object.get_stddev()
150
151 protos = {}
152 for p in self._protocols:
153 protos[p] = stddev.get(p)
154
155 return sorted(protos, key=protos.get)
156
fa43c345
MT
157 @property
158 def protocol_descriptions(self):
159 _ = self.locale.translate
160
161 return {
162 "ipv6" : _("IPv6"),
163 "ipv4" : _("IPv4"),
164 "other" : _("Other"),
165 }
0308c0f3
MT
166
167 @property
168 def graph_title(self):
fa43c345 169 _ = self.locale.translate
0308c0f3
MT
170 return _("Connections by Layer 3 Protocols")
171
172 @property
173 def graph_vertical_label(self):
fa43c345 174 _ = self.locale.translate
0308c0f3
MT
175 return _("Number of open connections")
176
cd8bba0b
MT
177 @property
178 def rrd_defs(self):
179 return []
0308c0f3
MT
180
181 @property
182 def rrd_graph(self):
fa43c345 183 _ = self.locale.translate
0308c0f3
MT
184 args = []
185
f998b6cb 186 for proto in self.protocols:
9e7f57b5
MT
187 colour = self.protocol_colours.get(proto, COLOUR_OTHER)
188 description = self.protocol_descriptions.get(proto, proto)
189
190 args += [
191 "AREA:%s%s:%-15s:STACK" % (proto, colour, description),
192 "GPRINT:%s_cur:%-6s %%8.0lf" % (proto, _("Now")),
193 "GPRINT:%s_avg:%-6s %%8.0lf" % (proto, _("Avg")),
194 "GPRINT:%s_min:%-6s %%8.0lf" % (proto, _("Min")),
195 "GPRINT:%s_max:%-6s %%8.0lf\\l" % (proto, _("Max")),
0308c0f3
MT
196 ]
197
198 return args
199
200 @property
201 def rrd_graph_args(self):
202 return [
203 "--legend-direction=bottomup",
204 ]
205
206
207class ConntrackLayer4ProtocolsGraphTemplate(ConntrackLayer3ProtocolsGraphTemplate):
208 name = "conntrack-layer4-protocols"
209
210 protocol_colours = {
03ba5630
MT
211 "tcp" : COLOUR_TCP,
212 "udp" : COLOUR_UDP,
213 "icmp" : COLOUR_ICMP,
214 "igmp" : COLOUR_IGMP,
215 "udplite" : COLOUR_UDPLITE,
216 "sctp" : COLOUR_SCTP,
217 "dccp" : COLOUR_DCCP,
0308c0f3
MT
218 }
219
fa43c345
MT
220 @property
221 def protocol_descriptions(self):
222 _ = self.locale.translate
223
224 return {
225 "tcp" : _("TCP"),
226 "udp" : _("UDP"),
227 "icmp" : _("ICMP"),
228 "igmp" : _("IGMP"),
229 "udplite" : _("UDP Lite"),
230 "sctp" : _("SCTP"),
231 "dccp" : _("DCCP"),
232 "other" : _("Other"),
233 }
0308c0f3
MT
234
235 protocol_sortorder = {
236 "tcp" : 1,
237 "udp" : 2,
238 "icmp" : 3,
239 "igmp" : 4,
240 "udplite" : 5,
241 "sctp" : 6,
242 "dccp" : 7,
243 }
244
76c12e91
MT
245 def get_objects(self, *args):
246 return [
247 self.plugin.get_object("layer4-protocols"),
248 ]
cd8bba0b 249
0308c0f3
MT
250 @property
251 def graph_title(self):
fa43c345 252 _ = self.locale.translate
0308c0f3
MT
253 return _("Connections by IP Protocols")
254
255 @property
f998b6cb 256 def _protocols(self):
0308c0f3
MT
257 return sorted(ConntrackTable._layer4_protocols,
258 key=lambda x: self.protocol_sortorder.get(x, 99))
259
0308c0f3
MT
260
261class ConntrackProtocolWithStatesGraphTemplate(base.GraphTemplate):
262 name = "conntrack-protocol-states"
263
264 lower_limit = 0
265
0308c0f3
MT
266 states_descriptions = {
267 "dccp" : {},
268 "sctp" : {},
269 "tcp" : {},
270 }
271
272 states_sortorder = {
273 "dccp" : {
274 "CLOSEREQ" : 0,
275 "CLOSING" : 0,
276 "IGNORE" : 0,
277 "INVALID" : 0,
278 "NONE" : 0,
279 "OPEN" : 0,
280 "PARTOPEN" : 0,
281 "REQUEST" : 0,
282 "RESPOND" : 0,
283 "TIME_WAIT" : 0,
284 },
285 "sctp" : {
286 "CLOSED" : 0,
287 "COOKIE_ECHOED" : 0,
288 "COOKIE_WAIT" : 0,
289 "ESTABLISHED" : 0,
290 "NONE" : 0,
291 "SHUTDOWN_ACK_SENT" : 0,
292 "SHUTDOWN_RECD" : 0,
293 "SHUTDOWN_SENT" : 0,
294 },
295 "tcp" : {
296 "CLOSE" : 9,
297 "CLOSE_WAIT" : 8,
298 "ESTABLISHED" : 1,
299 "FIN_WAIT" : 6,
300 "LAST_ACK" : 7,
301 "NONE" : 10,
302 "SYN_RECV" : 2,
303 "SYN_SENT" : 3,
304 "SYN_SENT2" : 4,
305 "TIME_WAIT" : 5,
306 },
307 }
308
309 @property
310 def graph_title(self):
fa43c345 311 _ = self.locale.translate
0308c0f3
MT
312 return _("Protocol States of all %s connections") % self.protocol.upper()
313
314 @property
315 def graph_vertical_label(self):
fa43c345 316 _ = self.locale.translate
0308c0f3
MT
317 return _("Number of open connections")
318
319 @property
320 def protocol(self):
321 return self.object.protocol
322
323 @property
324 def states(self):
325 return sorted(ConntrackTable._stateful_layer4_protocols[self.protocol],
326 key=lambda x: self.states_sortorder[self.protocol].get(x, 99))
327
328 @property
329 def rrd_graph(self):
fa43c345 330 _ = self.locale.translate
0308c0f3
MT
331 args = []
332
333 for state in reversed(self.states):
334 i = {
03ba5630 335 "colour" : COLOURS_PROTOCOL_STATES.get(state, BLACK),
0308c0f3
MT
336 "description" : self.states_descriptions[self.protocol].get(state, state),
337 "proto" : self.protocol,
338 "state" : state,
339
340 "legend_min" : "%10s\: %%8.0lf" % _("Minimum"),
341 "legend_max" : "%10s\: %%8.0lf" % _("Maximum"),
342 "legend_avg" : "%10s\: %%8.0lf" % _("Average"),
343 "legend_cur" : "%10s\: %%8.0lf" % _("Current"),
344 }
345
cd8bba0b 346 args += self.object.make_rrd_defs(state) + [
0308c0f3 347 "AREA:%(state)s%(colour)s:%(description)-15s:STACK" % i,
0308c0f3 348 "GPRINT:%(state)s_cur:%(legend_cur)s" % i,
0308c0f3 349 "GPRINT:%(state)s_avg:%(legend_avg)s" % i,
0308c0f3 350 "GPRINT:%(state)s_min:%(legend_min)s" % i,
8ec1299b 351 "GPRINT:%(state)s_max:%(legend_max)s" % i,
0308c0f3
MT
352 ]
353
354 return args
355
356 @property
357 def rrd_graph_args(self):
358 return [
359 "--legend-direction=bottomup",
360 ]
361
362
0ec1854a
MT
363class ConntrackObject(base.Object):
364 protocol = None
365
366 def init(self, conntrack_table):
367 self.conntrack_table = conntrack_table
368
369 @property
370 def id(self):
371 return self.protocol
372
373
374class ConntrackLayer3ProtocolsObject(ConntrackObject):
375 protocols = ConntrackTable._layer3_protocols
376
377 rrd_schema = [
378 "DS:%s:GAUGE:0:U" % p for p in protocols
379 ]
380
381 @property
382 def id(self):
383 return "layer3-protocols"
384
385 def collect(self):
386 results = []
387
388 for proto in self.protocols:
389 r = self.conntrack_table.layer3_protocols.get(proto, 0)
390 results.append("%s" % r)
391
f648421a 392 return results
0ec1854a
MT
393
394
395class ConntrackLayer4ProtocolsObject(ConntrackObject):
396 protocols = ConntrackTable._layer4_protocols
397
398 rrd_schema = [
399 "DS:%s:GAUGE:0:U" % p for p in protocols
400 ]
401
402 @property
403 def id(self):
404 return "layer4-protocols"
405
406 def collect(self):
407 results = []
408
409 for proto in self.protocols:
410 r = self.conntrack_table.layer4_protocols.get(proto, 0)
411 results.append("%s" % r)
412
f648421a 413 return results
0ec1854a
MT
414
415
416class ConntrackProtocolWithStatesObject(ConntrackObject):
417 def init(self, conntrack_table, protocol):
418 ConntrackObject.init(self, conntrack_table)
419 self.protocol = protocol
420
b54c3e02
MT
421 def __repr__(self):
422 return "<%s %s>" % (self.__class__.__name__, self.protocol)
423
0ec1854a
MT
424 @property
425 def states(self):
426 return ConntrackTable._stateful_layer4_protocols.get(self.protocol)
427
428 @property
429 def rrd_schema(self):
430 return ["DS:%s:GAUGE:0:U" % state for state in self.states]
431
432 def get_states(self):
433 results = []
434
435 for state in self.states:
436 r = self.conntrack_table.protocol_states[self.protocol].get(state, 0)
437 results.append("%s" % r)
438
439 return results
440
441 def collect(self):
f648421a 442 return self.get_states()
0ec1854a
MT
443
444
445class ConntrackPlugin(base.Plugin):
446 name = "conntrack"
447 description = "Conntrack Plugin"
448
0308c0f3
MT
449 templates = [
450 ConntrackLayer3ProtocolsGraphTemplate,
451 ConntrackLayer4ProtocolsGraphTemplate,
452 ConntrackProtocolWithStatesGraphTemplate,
453 ]
0ec1854a
MT
454
455 @property
456 def objects(self):
457 ct = self.get_conntrack_table()
458
459 if ct:
460 yield ConntrackLayer3ProtocolsObject(self, ct)
461 yield ConntrackLayer4ProtocolsObject(self, ct)
462
463 for protocol in ConntrackTable._stateful_layer4_protocols:
464 yield ConntrackProtocolWithStatesObject(self, ct, protocol)
465
466 def get_conntrack_table(self):
467 if not os.path.exists(CONNTRACK_FILE):
468 return
469
470 return ConntrackTable(CONNTRACK_FILE)