diff options
Diffstat (limited to 'collectors/python.d.plugin/portcheck')
-rw-r--r-- | collectors/python.d.plugin/portcheck/Makefile.inc | 13 | ||||
-rw-r--r-- | collectors/python.d.plugin/portcheck/README.md | 52 | ||||
-rw-r--r-- | collectors/python.d.plugin/portcheck/portcheck.chart.py | 157 | ||||
-rw-r--r-- | collectors/python.d.plugin/portcheck/portcheck.conf | 74 |
4 files changed, 296 insertions, 0 deletions
diff --git a/collectors/python.d.plugin/portcheck/Makefile.inc b/collectors/python.d.plugin/portcheck/Makefile.inc new file mode 100644 index 0000000..76763f0 --- /dev/null +++ b/collectors/python.d.plugin/portcheck/Makefile.inc @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +# THIS IS NOT A COMPLETE Makefile +# IT IS INCLUDED BY ITS PARENT'S Makefile.am +# IT IS REQUIRED TO REFERENCE ALL FILES RELATIVE TO THE PARENT + +# install these files +dist_python_DATA += portcheck/portcheck.chart.py +dist_pythonconfig_DATA += portcheck/portcheck.conf + +# do not install these files, but include them in the distribution +dist_noinst_DATA += portcheck/README.md portcheck/Makefile.inc + diff --git a/collectors/python.d.plugin/portcheck/README.md b/collectors/python.d.plugin/portcheck/README.md new file mode 100644 index 0000000..35521b2 --- /dev/null +++ b/collectors/python.d.plugin/portcheck/README.md @@ -0,0 +1,52 @@ +<!-- +title: "TCP endpoint monitoring with Netdata" +custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/python.d.plugin/portcheck/README.md +sidebar_label: "TCP endpoints" +--> + +# TCP endpoint monitoring with Netdata + +Monitors TCP endpoint availability and response time. + +Following charts are drawn per host: + +1. **Latency** ms + + - Time required to connect to a TCP port. + Displays latency in 0.1 ms resolution. If the connection failed, the value is missing. + +2. **Status** boolean + + - Connection successful + - Could not create socket: possible DNS problems + - Connection refused: port not listening or blocked + - Connection timed out: host or port unreachable + +## Configuration + +Edit the `python.d/portcheck.conf` configuration file using `edit-config` from the Netdata [config +directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`. + +```bash +cd /etc/netdata # Replace this path with your Netdata config directory, if different +sudo ./edit-config python.d/portcheck.conf +``` + +```yaml +server: + host: 'dns or ip' # required + port: 22 # required + timeout: 1 # optional + update_every: 1 # optional +``` + +### notes + +- The error chart is intended for alarms, badges or for access via API. +- A system/service/firewall might block Netdata's access if a portscan or + similar is detected. +- Currently, the accuracy of the latency is low and should be used as reference only. + +--- + +[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fcollectors%2Fpython.d.plugin%2Fportcheck%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>) diff --git a/collectors/python.d.plugin/portcheck/portcheck.chart.py b/collectors/python.d.plugin/portcheck/portcheck.chart.py new file mode 100644 index 0000000..818ac76 --- /dev/null +++ b/collectors/python.d.plugin/portcheck/portcheck.chart.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- +# Description: simple port check netdata python.d module +# Original Author: ccremer (github.com/ccremer) +# SPDX-License-Identifier: GPL-3.0-or-later + +import socket + +try: + from time import monotonic as time +except ImportError: + from time import time + +from bases.FrameworkServices.SimpleService import SimpleService + +PORT_LATENCY = 'connect' + +PORT_SUCCESS = 'success' +PORT_TIMEOUT = 'timeout' +PORT_FAILED = 'no_connection' + +ORDER = ['latency', 'status'] + +CHARTS = { + 'latency': { + 'options': [None, 'TCP connect latency', 'milliseconds', 'latency', 'portcheck.latency', 'line'], + 'lines': [ + [PORT_LATENCY, 'connect', 'absolute', 100, 1000] + ] + }, + 'status': { + 'options': [None, 'Portcheck status', 'boolean', 'status', 'portcheck.status', 'line'], + 'lines': [ + [PORT_SUCCESS, 'success', 'absolute'], + [PORT_TIMEOUT, 'timeout', 'absolute'], + [PORT_FAILED, 'no connection', 'absolute'] + ] + } +} + + +# Not deriving from SocketService, too much is different +class Service(SimpleService): + def __init__(self, configuration=None, name=None): + SimpleService.__init__(self, configuration=configuration, name=name) + self.order = ORDER + self.definitions = CHARTS + self.host = self.configuration.get('host') + self.port = self.configuration.get('port') + self.timeout = self.configuration.get('timeout', 1) + + def check(self): + """ + Parse configuration, check if configuration is available, and dynamically create chart lines data + :return: boolean + """ + if self.host is None or self.port is None: + self.error('Host or port missing') + return False + if not isinstance(self.port, int): + self.error('"port" is not an integer. Specify a numerical value, not service name.') + return False + + self.debug('Enabled portcheck: {host}:{port}, update every {update}s, timeout: {timeout}s'.format( + host=self.host, port=self.port, update=self.update_every, timeout=self.timeout + )) + # We will accept any (valid-ish) configuration, even if initial connection fails (a service might be down from + # the beginning) + return True + + def _get_data(self): + """ + Get data from socket + :return: dict + """ + data = dict() + data[PORT_SUCCESS] = 0 + data[PORT_TIMEOUT] = 0 + data[PORT_FAILED] = 0 + + success = False + try: + for socket_config in socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM): + # use first working socket + sock = self._create_socket(socket_config) + if sock is not None: + self._connect2socket(data, socket_config, sock) + self._disconnect(sock) + success = True + break + except socket.gaierror as error: + self.debug('Failed to connect to "{host}:{port}", error: {error}'.format( + host=self.host, port=self.port, error=error + )) + + # We could not connect + if not success: + data[PORT_FAILED] = 1 + + return data + + def _create_socket(self, socket_config): + af, sock_type, proto, _, sa = socket_config + try: + self.debug('Creating socket to "{address}", port {port}'.format(address=sa[0], port=sa[1])) + sock = socket.socket(af, sock_type, proto) + sock.settimeout(self.timeout) + return sock + except socket.error as error: + self.debug('Failed to create socket "{address}", port {port}, error: {error}'.format( + address=sa[0], port=sa[1], error=error + )) + return None + + def _connect2socket(self, data, socket_config, sock): + """ + Connect to a socket, passing the result of getaddrinfo() + :return: dict + """ + + _, _, _, _, sa = socket_config + port = str(sa[1]) + try: + self.debug('Connecting socket to "{address}", port {port}'.format(address=sa[0], port=port)) + start = time() + sock.connect(sa) + diff = time() - start + self.debug('Connected to "{address}", port {port}, latency {latency}'.format( + address=sa[0], port=port, latency=diff + )) + # we will set it at least 0.1 ms. 0.0 would mean failed connection (handy for 3rd-party-APIs) + data[PORT_LATENCY] = max(round(diff * 10000), 0) + data[PORT_SUCCESS] = 1 + + except socket.timeout as error: + self.debug('Socket timed out on "{address}", port {port}, error: {error}'.format( + address=sa[0], port=port, error=error + )) + data[PORT_TIMEOUT] = 1 + + except socket.error as error: + self.debug('Failed to connect to "{address}", port {port}, error: {error}'.format( + address=sa[0], port=port, error=error + )) + data[PORT_FAILED] = 1 + + def _disconnect(self, sock): + """ + Close socket connection + :return: + """ + if sock is not None: + try: + self.debug('Closing socket') + sock.shutdown(2) # 0 - read, 1 - write, 2 - all + sock.close() + except socket.error: + pass diff --git a/collectors/python.d.plugin/portcheck/portcheck.conf b/collectors/python.d.plugin/portcheck/portcheck.conf new file mode 100644 index 0000000..df67824 --- /dev/null +++ b/collectors/python.d.plugin/portcheck/portcheck.conf @@ -0,0 +1,74 @@ +# netdata python.d.plugin configuration for portcheck +# +# This file is in YaML format. Generally the format is: +# +# name: value +# +# There are 2 sections: +# - global variables +# - one or more JOBS +# +# JOBS allow you to collect values from multiple sources. +# Each source will have its own set of charts. +# +# JOB parameters have to be indented (using spaces only, example below). + +# ---------------------------------------------------------------------- +# Global Variables +# These variables set the defaults for all JOBs, however each JOB +# may define its own, overriding the defaults. + +# update_every sets the default data collection frequency. +# If unset, the python.d.plugin default is used. +# update_every: 1 + +# priority controls the order of charts at the netdata dashboard. +# Lower numbers move the charts towards the top of the page. +# If unset, the default for python.d.plugin is used. +# priority: 60000 + +# penalty indicates whether to apply penalty to update_every in case of failures. +# Penalty will increase every 5 failed updates in a row. Maximum penalty is 10 minutes. +# penalty: yes + +# chart_cleanup sets the default chart cleanup interval in iterations. +# A chart is marked as obsolete if it has not been updated +# 'chart_cleanup' iterations in a row. +# They will be hidden immediately (not offered to dashboard viewer, +# streamed upstream and archived to backends) and deleted one hour +# later (configurable from netdata.conf). +# -- For this plugin, cleanup MUST be disabled, otherwise we lose latency chart +chart_cleanup: 0 + +# Autodetection and retries do not work for this plugin + +# ---------------------------------------------------------------------- +# JOBS (data collection sources) +# +# The default JOBS share the same *name*. JOBS with the same name +# are mutually exclusive. Only one of them will be allowed running at +# any time. This allows autodetection to try several alternatives and +# pick the one that works. +# +# Any number of jobs is supported. +# +# ------------------------------- +# ATTENTION: Any valid configuration will be accepted, even if initial connection fails! +# ------------------------------- +# +# There is intentionally no default config for 'localhost' + +# job_name: +# name: myname # [optional] the JOB's name as it will appear at the +# # dashboard (by default is the job_name) +# # JOBs sharing a name are mutually exclusive +# update_every: 1 # [optional] the JOB's data collection frequency +# priority: 60000 # [optional] the JOB's order on the dashboard +# penalty: yes # the JOB's penalty +# timeout: 1 # [optional] the socket timeout when connecting +# host: 'dns or ip' # [required] the remote host address in either IPv4, IPv6 or as DNS name. +# port: 22 # [required] the port number to check. Specify an integer, not service name. + +# You just have been warned about possible portscan blocking. The portcheck plugin is meant for simple use cases. +# Currently, the accuracy of the latency is low and should be used as reference only. + |