@cryptotaxi247 / netdata-1 / commits / a4327d68d

Rewrite of portcheck plugin. Each error code has its own dimension, which simplifies alarms.

Chris committed Feb 24, 2018 at 21:40 UTC a4327d68d09b03881faf546025bae727222209aa
4 files changed +60 -70
conf.d/python.d/portcheck.conf
+3 -5
@@ -52,11 +52,9 @@
52 # update_every: 1 # [optional] the JOB's data collection frequency
53 # priority: 60000 # [optional] the JOB's order on the dashboard
54 # retries: 60 # [optional] the JOB's number of restoration attempts
55 -# timeout: 3 # [optional] the socket timeout when connecting
56 -# host: 'dns or ip' # [required] the remote host address in either IPv4, IPv6 or as DNS name
57 -# ports: # [required] the port number(s) to check (list is checked sequentially)
58 -# - 22 # at least one port number is required (do not specify well-known names e.g. 'ssh')
59 -# - 8080 # multiple ports are supported. they will be grouped in one chart.
55 +# timeout: 1 # [optional] the socket timeout when connecting
56 +# host: 'dns or ip' # [required] the remote host address in either IPv4, IPv6 or as DNS name.
57 +# port: 22 # [required] the port number to check. Specify an integer, not service name.
58
59 # You just have been warned about possible portscan blocking. The portcheck plugin is meant for simple use cases.
60 # For monitoring large networks, consider a real service monitoring tool.
python.d/README.md
+8 -10
@@ -1553,16 +1553,14 @@ Module monitors one or more remote ports per host.
1553 Following charts are drawn per host:
1554
1555 1. **Latency** ms
1556 - * For each port: TCP socket opening and closing time when connecting.
1557 - Displays latency in 0.1 ms resolution and 0.1ms will be the minimum.
1558 - If it is 0.0ms, the connection failed with error code >0 (useful for API)
1559 -
1560 -2. **Error code** int
1561 - * For each port: One of:
1562 - - 0: Connection successful
1563 - - 1: Could not create socket (dns name not resolved?)
1564 - - 2: Connection refused (port not listening or blocked)
1565 - - 3: Connection timed out (host unreachable?)
1556 + * TCP socket opening and closing time when connecting.
1557 + Displays latency in 0.1 ms resolution. If the connection failed, the value is missing.
1558 +
1559 +2. **Error** int
1560 + * Connection successful: value = 1 otherwise 0
1561 + * Could not create socket: possible DNS problems (value = 1, otherwise 0)
1562 + * Connection refused: port not listening or blocked (value = 1, otherwise 0)
1563 + * Connection timed out: host or port unreachable (value = 1, otherwise 0)
1564
1565
1566 ### configuration
python.d/portcheck.chart.py
+45 -51
@@ -10,30 +10,30 @@ from bases.FrameworkServices.SimpleService import SimpleService
10 priority = 60000
11 retries = 60
12
13 +PORT_CONNECT = 'connect'
14 +
15 +PORT_SUCCESS = 'success'
16 +PORT_TIMEOUT = 'timeout'
17 +PORT_FAILED = 'failed'
18 +
19 ORDER = ['latency', 'error']
20
21 CHARTS = {
22 'latency': {
23 'options': [None, 'TCP connect latency', 'ms', 'latency', 'portcheck.latency', 'line'],
18 - 'lines': []
24 + 'lines': [
25 + [PORT_CONNECT, 'connect', 'absolute', 100, 1000]
26 + ]
27 },
28 'error': {
29 'options': [None, 'Portcheck error code', 'code', 'error', 'portcheck.error', 'line'],
30 'lines': [
23 -
31 + [PORT_SUCCESS, 'success', 'absolute'],
32 + [PORT_TIMEOUT, 'timeout', 'absolute'],
33 + [PORT_FAILED, 'failed', 'absolute']
34 ]}
35 }
36
27 -# The higher the error code, the "more severe" it is.
28 -# We use steps of 5, which allows future fine-grained error codes, should we need it (fill the blanks where
29 -# appropriate).
30 -CONNECTION_FAILED = 5
31 -CONNECTION_TIMED_OUT = 10
32 -SOCKET_FAILED = 15
33 -
34 -TCP_DIMENSION_CONNECT_PREFIX = 'tcp_connect_'
35 -TCP_DIMENSION_ERROR_PREFIX = 'tcp_error_'
36 -
37
38 class Service(SimpleService):
39 def __init__(self, configuration=None, name=None):
@@ -42,7 +42,7 @@ class Service(SimpleService):
42 self.definitions = CHARTS
43 self.chart_name = ""
44 self.host = self.configuration.get('host', None)
45 - self.ports = self.configuration.get('ports', None)
45 + self.port = self.configuration.get('port', None)
46 self.timeout = self.configuration.get('timeout', 1)
47
48 def check(self):
@@ -50,24 +50,16 @@ class Service(SimpleService):
50 Parse configuration, check if configuration is available, and dynamically create chart lines data
51 :return: boolean
52 """
53 - if self.host is None or self.ports is None:
54 - self.error("Host and/or ports missing")
53 + if self.host is None or self.port is None:
54 + self.error("Host or port missing")
55 return False
56 - if not isinstance(self.ports, list):
57 - self.error('"ports" is not defined as a list. Specify a PyYaml compatible list.')
56 + if not isinstance(self.port, int):
57 + self.error('"port" is not an integer. Specify a numerical value, not service name.')
58 return False
59
60 - for port in self.ports:
61 - if not isinstance(port, int):
62 - self.error("{port} is not an integer. Disabling plugin.".format(port=port))
63 - return False
64 - self.debug("Enabled portcheck: {host}:{port}, update every {update}s, timeout: {timeout}s".format(
65 - host=self.host, port=port, update=self.update_every, timeout=self.timeout
66 - ))
67 - self.definitions['latency']['lines'].append(
68 - [TCP_DIMENSION_CONNECT_PREFIX + str(port), port, 'absolute', 100, 1000])
69 - self.definitions['error']['lines'].append(
70 - [TCP_DIMENSION_ERROR_PREFIX + str(port), port, 'absolute'])
60 + self.info("Enabled portcheck: {host}:{port}, update every {update}s, timeout: {timeout}s".format(
61 + host=self.host, port=self.port, update=self.update_every, timeout=self.timeout
62 + ))
63 # We will accept any (valid-ish) configuration, even if initial connection fails (a service might be down from
64 # the beginning)
65 return True
@@ -79,25 +71,28 @@ class Service(SimpleService):
71 """
72 data = dict()
73
82 - for port in self.ports:
83 - success = False
84 - try:
85 - for socket_config in socket.getaddrinfo(self.host, port, socket.AF_UNSPEC, socket.SOCK_STREAM):
86 - # use first working socket
87 - sock = self._create_socket(socket_config)
88 - if sock is not None:
89 - self._connect2socket(data, socket_config, sock)
90 - self._disconnect(sock)
91 - success = True
92 - break
93 - except socket.gaierror:
94 - success = False
95 - pass
74 + data[PORT_SUCCESS] = 0
75 + data[PORT_TIMEOUT] = 0
76 + data[PORT_FAILED] = 0
77 +
78 + success = False
79 + try:
80 + for socket_config in socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM):
81 + # use first working socket
82 + sock = self._create_socket(socket_config)
83 + if sock is not None:
84 + self._connect2socket(data, socket_config, sock)
85 + self._disconnect(sock)
86 + success = True
87 + break
88 + except socket.gaierror as error:
89 + self.debug('Failed to connect to "{host}:{port}", error: {error}'.format(
90 + host=self.host, port=self.port, error=error
91 + ))
92
97 - # We could not connect
98 - if not success:
99 - data[TCP_DIMENSION_CONNECT_PREFIX + str(port)] = 0
100 - data[TCP_DIMENSION_ERROR_PREFIX + str(port)] = SOCKET_FAILED
93 + # We could not connect
94 + if not success:
95 + data[PORT_FAILED] = 1
96
97 return data
98
@@ -122,8 +117,6 @@ class Service(SimpleService):
117
118 af, sock_type, proto, canon_name, sa = socket_config
119 port = str(sa[1])
125 - data[TCP_DIMENSION_CONNECT_PREFIX + port] = 0
126 - data[TCP_DIMENSION_ERROR_PREFIX + port] = 0
120 try:
121 self.debug('Connecting socket to "{address}", port {port}'.format(address=sa[0], port=port))
122 start = time.time()
@@ -133,19 +126,20 @@ class Service(SimpleService):
126 address=sa[0], port=port, latency=diff
127 ))
128 # we will set it at least 0.1 ms. 0.0 would mean failed connection (handy for 3rd-party-APIs)
136 - data[TCP_DIMENSION_CONNECT_PREFIX + port] = max(round(diff * 10000), 1)
129 + data[PORT_CONNECT] = max(round(diff * 10000), 1)
130 + data[PORT_SUCCESS] = 1
131
132 except socket.timeout as error:
133 self.debug('Socket timed out on "{address}", port {port}, error: {error}'.format(
134 address=sa[0], port=port, error=error
135 ))
142 - data[TCP_DIMENSION_ERROR_PREFIX + port] = CONNECTION_TIMED_OUT
136 + data[PORT_TIMEOUT] = 1
137
138 except socket.error as error:
139 self.debug('Failed to connect to "{address}", port {port}, error: {error}'.format(
140 address=sa[0], port=port, error=error
141 ))
148 - data[TCP_DIMENSION_ERROR_PREFIX + port] = CONNECTION_FAILED
142 + data[PORT_FAILED] = 1
143
144 def _disconnect(self, sock):
145 """
web/dashboard_info.js
+4 -4
@@ -1733,10 +1733,10 @@ netdataDashboard.context = {
1733 },
1734
1735 'portcheck.error': {
1736 - valueRange: "[0, 3]",
1737 - info: 'The <code>Error</code> is returned by the plugin when it could not connect to the host/port. It is one of: ' +
1738 - '<code>0</code> (connection successful), <code>5</code> (port closed), <code>10</code> (host or port timeout), <code>15</code> (hostname lookup failed). ' +
1739 - 'The error code is most useful for 3rd-party apps and alarms.'
1736 + valueRange: "[0, 1]",
1737 + info: 'The <code>error</code> codes are returned by the plugin when it could verify the availability of the service. ' +
1738 + 'Each error dimension will have a value of <code>1</code> if triggered. Dimension <code>success</code> is always <code>1</code> on successful connects.' +
1739 + 'The error code is most useful for alarms and third-party apps.'
1740 },
1741
1742 // ------------------------------------------------------------------------