@cryptotaxi247 / netdata-1 / commits / 164d13c68

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