Add multi-port support per host
Chris committed
Feb 20, 2018 at 11:52 UTC
164d13c681931f84394534e2a8c19b5ab4f2a20f
1 file changed
+70
-53
python.d/portcheck.chart.py
+70
-53
@@ -15,13 +15,12 @@ ORDER = ['latency', 'error']
15
CHARTS = {
16
'latency': {
17
'options': [None, 'TCP connect latency', 'ms', 'latency', 'portcheck.latency', 'line'],
18
- 'lines': [
19
- ['tcp_connect_time', 'connect', 'absolute', 100, 1000]
20
- ]},
18
+ 'lines': []
19
+ },
20
'error': {
21
'options': [None, 'Portcheck error code', 'code', 'error', 'portcheck.error', 'line'],
22
'lines': [
24
- ['tcp_error', 'error', 'absolute']
23
+
24
]}
25
}
26
@@ -29,6 +28,9 @@ SOCKET_FAILED = -1
28
CONNECTION_FAILED = -2
29
CONNECTION_TIMED_OUT = -3
30
31
+TCP_DIMENSION_CONNECT_PREFIX = 'tcp_connect_'
32
+TCP_DIMENSION_ERROR_PREFIX = 'tcp_error_'
33
+
34
35
class Service(SimpleService):
36
def __init__(self, configuration=None, name=None):
@@ -36,28 +38,37 @@ class Service(SimpleService):
38
self.order = ORDER
39
self.definitions = CHARTS
40
self.chart_name = ""
39
- self._sock = None
40
- self.__socket_config = None
41
self.host = self.configuration.get('host', None)
42
- self.port = self.configuration.get('port', None)
43
- self.update_every = self.configuration.get('update every', 3)
44
- self.timeout = self.configuration.get('timeout', 5)
42
+ self.ports = self.configuration.get('ports', None)
43
+ self.update_every = self.configuration.get('update every', 1)
44
+ self.timeout = self.configuration.get('timeout', 3)
45
46
def check(self):
47
"""
48
Parse configuration, check if configuration is available, and dynamically create chart lines data
49
:return: boolean
50
"""
51
- if self.host is None or self.port is None:
52
- self.error("Host and/or port missing")
51
+ if self.host is None or self.ports is None:
52
+ self.error("Host and/or ports missing")
53
+ return False
54
+ if not isinstance(self.ports, list):
55
+ self.error('"ports" is not defined as a list. Specify a PyYaml compatible list.')
56
return False
54
- else:
57
+
58
+ for port in self.ports:
59
+ if not isinstance(port, int):
60
+ self.error("{port} is not an integer. Disabling plugin.".format(port=port))
61
+ return False
62
self.debug("Enabled portcheck: {host}:{port}, update every {update}s, timeout: {timeout}s".format(
56
- host=self.host, port=self.port, update=self.update_every, timeout=self.timeout
63
+ host=self.host, port=port, update=self.update_every, timeout=self.timeout
64
))
58
- # We will accept any configuration, even if initial connection fails (a service might be down from
59
- # the beginning)
60
- return True
65
+ self.definitions['latency']['lines'].append(
66
+ [TCP_DIMENSION_CONNECT_PREFIX + str(port), port, 'absolute', 100, 1000])
67
+ self.definitions['error']['lines'].append(
68
+ [TCP_DIMENSION_ERROR_PREFIX + str(port), port, 'absolute'])
69
+ # We will accept any (valid-ish) configuration, even if initial connection fails (a service might be down from
70
+ # the beginning)
71
+ return True
72
73
def _get_data(self):
74
"""
@@ -66,73 +77,79 @@ class Service(SimpleService):
77
"""
78
data = dict()
79
69
- for res in socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM):
70
- # use first working socket
71
- if self._create_socket(res):
72
- self._connect2socket(data)
73
- self._disconnect()
74
- return data
80
+ for port in self.ports:
81
+ success = False
82
+ for socket_config in socket.getaddrinfo(self.host, port, socket.AF_UNSPEC, socket.SOCK_STREAM):
83
+ # use first working socket
84
+ sock = self._create_socket(socket_config)
85
+ if sock is not None:
86
+ self._connect2socket(data, socket_config, sock)
87
+ self._disconnect(sock)
88
+ success = True
89
+ break
90
+
91
+ # We could not connect
92
+ if not success:
93
+ data[TCP_DIMENSION_CONNECT_PREFIX + str(port)] = 0
94
+ data[TCP_DIMENSION_ERROR_PREFIX + str(port)] = SOCKET_FAILED
95
76
- # We could not connect
77
- data['tcp_connect_time'] = 0
78
- data['tcp_error'] = SOCKET_FAILED
96
return data
97
81
- def _create_socket(self, res=None):
82
- af, sock_type, proto, canon_name, sa = res
98
+ def _create_socket(self, socket_config):
99
+ af, sock_type, proto, canon_name, sa = socket_config
100
try:
101
self.debug('Creating socket to "{address}", port {port}'.format(address=sa[0], port=sa[1]))
85
- self._sock = socket.socket(af, sock_type, proto)
86
- self.__socket_config = res
87
- self._sock.settimeout(self.timeout)
88
- return True
102
+ sock = socket.socket(af, sock_type, proto)
103
+ sock.settimeout(self.timeout)
104
+ return sock
105
except socket.error as error:
90
- self.error('Failed to create socket "{address}", port {port}, error: {error}'.format(
106
+ self.debug('Failed to create socket "{address}", port {port}, error: {error}'.format(
107
address=sa[0], port=sa[1], error=error
108
))
93
- return False
109
+ return None
110
95
- def _connect2socket(self, data):
111
+ def _connect2socket(self, data, socket_config, sock):
112
"""
113
Connect to a socket, passing the result of getaddrinfo()
114
:return: dict
115
"""
100
- data['tcp_connect_time'] = 0
101
- data['tcp_error'] = 0
116
103
- af, sock_type, proto, canon_name, sa = self.__socket_config
117
+ af, sock_type, proto, canon_name, sa = socket_config
118
+ port = str(sa[1])
119
+ data[TCP_DIMENSION_CONNECT_PREFIX + port] = 0
120
+ data[TCP_DIMENSION_ERROR_PREFIX + port] = 0
121
try:
105
- self.debug('connecting socket to "{address}", port {port}'.format(address=sa[0], port=sa[1]))
122
+ self.debug('Connecting socket to "{address}", port {port}'.format(address=sa[0], port=port))
123
start = time.time()
107
- self._sock.connect(sa)
124
+ sock.connect(sa)
125
diff = time.time() - start
109
- self.debug('connected to "{address}", port {port}, latency {latency}'.format(
110
- address=sa[0], port=sa[1], latency=diff
126
+ self.debug('Connected to "{address}", port {port}, latency {latency}'.format(
127
+ address=sa[0], port=port, latency=diff
128
))
129
# we will set it at least 0.1 ms. 0.0 would mean failed connection (handy for 3rd-party-APIs)
113
- data['tcp_connect_time'] = max(round(diff * 10000), 1)
130
+ data[TCP_DIMENSION_CONNECT_PREFIX + port] = max(round(diff * 10000), 1)
131
132
except socket.timeout as error:
116
- self.error('Socket timed out on "{address}", port {port}, error: {error}'.format(
117
- address=sa[0], port=sa[1], error=error
133
+ self.debug('Socket timed out on "{address}", port {port}, error: {error}'.format(
134
+ address=sa[0], port=port, error=error
135
))
119
- data['tcp_error'] = CONNECTION_TIMED_OUT
136
+ data[TCP_DIMENSION_ERROR_PREFIX + port] = CONNECTION_TIMED_OUT
137
138
except socket.error as error:
122
- self.error('Failed to connect to "{address}", port {port}, error: {error}'.format(
123
- address=sa[0], port=sa[1], error=error
139
+ self.debug('Failed to connect to "{address}", port {port}, error: {error}'.format(
140
+ address=sa[0], port=port, error=error
141
))
125
- data['tcp_error'] = CONNECTION_FAILED
142
+ data[TCP_DIMENSION_ERROR_PREFIX + port] = CONNECTION_FAILED
143
127
- def _disconnect(self):
144
+ def _disconnect(self, sock):
145
"""
146
Close socket connection
147
:return:
148
"""
132
- if self._sock is not None:
149
+ if sock is not None:
150
try:
134
- self.debug('closing socket')
135
- self._sock.shutdown(2) # 0 - read, 1 - write, 2 - all
136
- self._sock.close()
151
+ self.debug('Closing socket')
152
+ sock.shutdown(2) # 0 - read, 1 - write, 2 - all
153
+ sock.close()
154
except Exception:
155
pass