uwsgi.chart.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. # -*- coding: utf-8 -*-
  2. # Description: uwsgi netdata python.d module
  3. # Author: Robbert Segeren (robbert-ef)
  4. # SPDX-License-Identifier: GPL-3.0-or-later
  5. import json
  6. from copy import deepcopy
  7. from bases.FrameworkServices.SocketService import SocketService
  8. # default module values (can be overridden per job in `config`)
  9. # update_every = 2
  10. priority = 60000
  11. retries = 60
  12. ORDER = [
  13. 'requests',
  14. 'tx',
  15. 'avg_rt',
  16. 'memory_rss',
  17. 'memory_vsz',
  18. 'exceptions',
  19. 'harakiri',
  20. 'respawn',
  21. ]
  22. DYNAMIC_CHARTS = [
  23. 'requests',
  24. 'tx',
  25. 'avg_rt',
  26. 'memory_rss',
  27. 'memory_vsz',
  28. ]
  29. # NOTE: lines are created dynamically in `check()` method
  30. CHARTS = {
  31. 'requests': {
  32. 'options': [None, 'Requests', 'requests/s', 'requests', 'uwsgi.requests', 'stacked'],
  33. 'lines': [
  34. ['requests', 'requests', 'incremental']
  35. ]
  36. },
  37. 'tx': {
  38. 'options': [None, 'Transmitted data', 'KB/s', 'requests', 'uwsgi.tx', 'stacked'],
  39. 'lines': [
  40. ['tx', 'tx', 'incremental']
  41. ]
  42. },
  43. 'avg_rt': {
  44. 'options': [None, 'Average request time', 'ms', 'requests', 'uwsgi.avg_rt', 'line'],
  45. 'lines': [
  46. ['avg_rt', 'avg_rt', 'absolute']
  47. ]
  48. },
  49. 'memory_rss': {
  50. 'options': [None, 'RSS (Resident Set Size)', 'MB', 'memory', 'uwsgi.memory_rss', 'stacked'],
  51. 'lines': [
  52. ['memory_rss', 'memory_rss', 'absolute', 1, 1024 * 1024]
  53. ]
  54. },
  55. 'memory_vsz': {
  56. 'options': [None, 'VSZ (Virtual Memory Size)', 'MB', 'memory', 'uwsgi.memory_vsz', 'stacked'],
  57. 'lines': [
  58. ['memory_vsz', 'memory_vsz', 'absolute', 1, 1024 * 1024]
  59. ]
  60. },
  61. 'exceptions': {
  62. 'options': [None, 'Exceptions', 'exceptions', 'exceptions', 'uwsgi.exceptions', 'line'],
  63. 'lines': [
  64. ['exceptions', 'exceptions', 'incremental']
  65. ]
  66. },
  67. 'harakiri': {
  68. 'options': [None, 'Harakiris', 'harakiris', 'harakiris', 'uwsgi.harakiris', 'line'],
  69. 'lines': [
  70. ['harakiri_count', 'harakiris', 'incremental']
  71. ]
  72. },
  73. 'respawn': {
  74. 'options': [None, 'Respawns', 'respawns', 'respawns', 'uwsgi.respawns', 'line'],
  75. 'lines': [
  76. ['respawn_count', 'respawns', 'incremental']
  77. ]
  78. },
  79. }
  80. class Service(SocketService):
  81. def __init__(self, configuration=None, name=None):
  82. super(Service, self).__init__(configuration=configuration, name=name)
  83. self.url = self.configuration.get('host', 'localhost')
  84. self.port = self.configuration.get('port', 1717)
  85. self.order = ORDER
  86. self.definitions = deepcopy(CHARTS)
  87. # Clear dynamic dimensions, these are added during `_get_data()` to allow adding workers at run-time
  88. for chart in DYNAMIC_CHARTS:
  89. self.definitions[chart]['lines'] = []
  90. self.last_result = {}
  91. self.workers = []
  92. def read_data(self):
  93. """
  94. Read data from socket and parse as JSON.
  95. :return: (dict) stats
  96. """
  97. raw_data = self._get_raw_data()
  98. if not raw_data:
  99. return None
  100. try:
  101. return json.loads(raw_data)
  102. except ValueError as err:
  103. self.error(err)
  104. return None
  105. def check(self):
  106. """
  107. Parse configuration and check if we can read data.
  108. :return: boolean
  109. """
  110. self._parse_config()
  111. return bool(self.read_data())
  112. def add_worker_dimensions(self, key):
  113. """
  114. Helper to add dimensions for a worker.
  115. :param key: (int or str) worker identifier
  116. :return:
  117. """
  118. for chart in DYNAMIC_CHARTS:
  119. for line in CHARTS[chart]['lines']:
  120. dimension_id = '{}_{}'.format(line[0], key)
  121. dimension_name = str(key)
  122. dimension = [dimension_id, dimension_name] + line[2:]
  123. self.charts[chart].add_dimension(dimension)
  124. @staticmethod
  125. def _check_raw_data(data):
  126. # The server will close the connection when it's done sending
  127. # data, so just keep looping until that happens.
  128. return False
  129. def _get_data(self):
  130. """
  131. Read data from socket
  132. :return: dict
  133. """
  134. stats = self.read_data()
  135. if not stats:
  136. return None
  137. result = {
  138. 'exceptions': 0,
  139. 'harakiri_count': 0,
  140. 'respawn_count': 0,
  141. }
  142. for worker in stats['workers']:
  143. key = worker['pid']
  144. # Add dimensions for new workers
  145. if key not in self.workers:
  146. self.add_worker_dimensions(key)
  147. self.workers.append(key)
  148. result['requests_{}'.format(key)] = worker['requests']
  149. result['tx_{}'.format(key)] = worker['tx']
  150. result['avg_rt_{}'.format(key)] = worker['avg_rt']
  151. # avg_rt is not reset by uwsgi, so reset here
  152. if self.last_result.get('requests_{}'.format(key)) == worker['requests']:
  153. result['avg_rt_{}'.format(key)] = 0
  154. result['memory_rss_{}'.format(key)] = worker['rss']
  155. result['memory_vsz_{}'.format(key)] = worker['vsz']
  156. result['exceptions'] += worker['exceptions']
  157. result['harakiri_count'] += worker['harakiri_count']
  158. result['respawn_count'] += worker['respawn_count']
  159. self.last_result = result
  160. return result