portcheck.chart.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. # -*- coding: utf-8 -*-
  2. # Description: simple port check netdata python.d module
  3. # Original Author: ccremer (github.com/ccremer)
  4. # SPDX-License-Identifier: GPL-3.0-or-later
  5. import socket
  6. try:
  7. from time import monotonic as time
  8. except ImportError:
  9. from time import time
  10. from bases.FrameworkServices.SimpleService import SimpleService
  11. PORT_LATENCY = 'connect'
  12. PORT_SUCCESS = 'success'
  13. PORT_TIMEOUT = 'timeout'
  14. PORT_FAILED = 'no_connection'
  15. ORDER = ['latency', 'status']
  16. CHARTS = {
  17. 'latency': {
  18. 'options': [None, 'TCP connect latency', 'milliseconds', 'latency', 'portcheck.latency', 'line'],
  19. 'lines': [
  20. [PORT_LATENCY, 'connect', 'absolute', 100, 1000]
  21. ]
  22. },
  23. 'status': {
  24. 'options': [None, 'Portcheck status', 'boolean', 'status', 'portcheck.status', 'line'],
  25. 'lines': [
  26. [PORT_SUCCESS, 'success', 'absolute'],
  27. [PORT_TIMEOUT, 'timeout', 'absolute'],
  28. [PORT_FAILED, 'no connection', 'absolute']
  29. ]
  30. }
  31. }
  32. # Not deriving from SocketService, too much is different
  33. class Service(SimpleService):
  34. def __init__(self, configuration=None, name=None):
  35. SimpleService.__init__(self, configuration=configuration, name=name)
  36. self.order = ORDER
  37. self.definitions = CHARTS
  38. self.host = self.configuration.get('host')
  39. self.port = self.configuration.get('port')
  40. self.timeout = self.configuration.get('timeout', 1)
  41. def check(self):
  42. """
  43. Parse configuration, check if configuration is available, and dynamically create chart lines data
  44. :return: boolean
  45. """
  46. if self.host is None or self.port is None:
  47. self.error('Host or port missing')
  48. return False
  49. if not isinstance(self.port, int):
  50. self.error('"port" is not an integer. Specify a numerical value, not service name.')
  51. return False
  52. self.debug('Enabled portcheck: {host}:{port}, update every {update}s, timeout: {timeout}s'.format(
  53. host=self.host, port=self.port, update=self.update_every, timeout=self.timeout
  54. ))
  55. # We will accept any (valid-ish) configuration, even if initial connection fails (a service might be down from
  56. # the beginning)
  57. return True
  58. def _get_data(self):
  59. """
  60. Get data from socket
  61. :return: dict
  62. """
  63. data = dict()
  64. data[PORT_SUCCESS] = 0
  65. data[PORT_TIMEOUT] = 0
  66. data[PORT_FAILED] = 0
  67. success = False
  68. try:
  69. for socket_config in socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM):
  70. # use first working socket
  71. sock = self._create_socket(socket_config)
  72. if sock is not None:
  73. self._connect2socket(data, socket_config, sock)
  74. self._disconnect(sock)
  75. success = True
  76. break
  77. except socket.gaierror as error:
  78. self.debug('Failed to connect to "{host}:{port}", error: {error}'.format(
  79. host=self.host, port=self.port, error=error
  80. ))
  81. # We could not connect
  82. if not success:
  83. data[PORT_FAILED] = 1
  84. return data
  85. def _create_socket(self, socket_config):
  86. af, sock_type, proto, _, sa = socket_config
  87. try:
  88. self.debug('Creating socket to "{address}", port {port}'.format(address=sa[0], port=sa[1]))
  89. sock = socket.socket(af, sock_type, proto)
  90. sock.settimeout(self.timeout)
  91. return sock
  92. except socket.error as error:
  93. self.debug('Failed to create socket "{address}", port {port}, error: {error}'.format(
  94. address=sa[0], port=sa[1], error=error
  95. ))
  96. return None
  97. def _connect2socket(self, data, socket_config, sock):
  98. """
  99. Connect to a socket, passing the result of getaddrinfo()
  100. :return: dict
  101. """
  102. _, _, _, _, sa = socket_config
  103. port = str(sa[1])
  104. try:
  105. self.debug('Connecting socket to "{address}", port {port}'.format(address=sa[0], port=port))
  106. start = time()
  107. sock.connect(sa)
  108. diff = time() - start
  109. self.debug('Connected to "{address}", port {port}, latency {latency}'.format(
  110. address=sa[0], port=port, latency=diff
  111. ))
  112. # we will set it at least 0.1 ms. 0.0 would mean failed connection (handy for 3rd-party-APIs)
  113. data[PORT_LATENCY] = max(round(diff * 10000), 0)
  114. data[PORT_SUCCESS] = 1
  115. except socket.timeout as error:
  116. self.debug('Socket timed out on "{address}", port {port}, error: {error}'.format(
  117. address=sa[0], port=port, error=error
  118. ))
  119. data[PORT_TIMEOUT] = 1
  120. except socket.error as error:
  121. self.debug('Failed to connect to "{address}", port {port}, error: {error}'.format(
  122. address=sa[0], port=port, error=error
  123. ))
  124. data[PORT_FAILED] = 1
  125. def _disconnect(self, sock):
  126. """
  127. Close socket connection
  128. :return:
  129. """
  130. if sock is not None:
  131. try:
  132. self.debug('Closing socket')
  133. sock.shutdown(2) # 0 - read, 1 - write, 2 - all
  134. sock.close()
  135. except socket.error:
  136. pass