tomcat.chart.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. # -*- coding: utf-8 -*-
  2. # Description: tomcat netdata python.d module
  3. # Author: Pawel Krupa (paulfantom)
  4. # Author: Wei He (Wing924)
  5. # SPDX-License-Identifier: GPL-3.0-or-later
  6. import re
  7. import xml.etree.ElementTree as ET
  8. from bases.FrameworkServices.UrlService import UrlService
  9. MiB = 1 << 20
  10. # Regex fix for Tomcat single quote XML attributes
  11. # affecting Tomcat < 8.5.24 & 9.0.2 running with Java > 9
  12. # cf. https://bz.apache.org/bugzilla/show_bug.cgi?id=61603
  13. single_quote_regex = re.compile(r"='([^']+)'([^']+)''")
  14. ORDER = [
  15. 'accesses',
  16. 'bandwidth',
  17. 'processing_time',
  18. 'threads',
  19. 'jvm',
  20. 'jvm_eden',
  21. 'jvm_survivor',
  22. 'jvm_tenured',
  23. ]
  24. CHARTS = {
  25. 'accesses': {
  26. 'options': [None, 'Requests', 'requests/s', 'statistics', 'tomcat.accesses', 'area'],
  27. 'lines': [
  28. ['requestCount', 'accesses', 'incremental'],
  29. ['errorCount', 'errors', 'incremental'],
  30. ]
  31. },
  32. 'bandwidth': {
  33. 'options': [None, 'Bandwidth', 'KiB/s', 'statistics', 'tomcat.bandwidth', 'area'],
  34. 'lines': [
  35. ['bytesSent', 'sent', 'incremental', 1, 1024],
  36. ['bytesReceived', 'received', 'incremental', 1, 1024],
  37. ]
  38. },
  39. 'processing_time': {
  40. 'options': [None, 'processing time', 'seconds', 'statistics', 'tomcat.processing_time', 'area'],
  41. 'lines': [
  42. ['processingTime', 'processing time', 'incremental', 1, 1000]
  43. ]
  44. },
  45. 'threads': {
  46. 'options': [None, 'Threads', 'current threads', 'statistics', 'tomcat.threads', 'area'],
  47. 'lines': [
  48. ['currentThreadCount', 'current', 'absolute'],
  49. ['currentThreadsBusy', 'busy', 'absolute']
  50. ]
  51. },
  52. 'jvm': {
  53. 'options': [None, 'JVM Memory Pool Usage', 'MiB', 'memory', 'tomcat.jvm', 'stacked'],
  54. 'lines': [
  55. ['free', 'free', 'absolute', 1, MiB],
  56. ['eden_used', 'eden', 'absolute', 1, MiB],
  57. ['survivor_used', 'survivor', 'absolute', 1, MiB],
  58. ['tenured_used', 'tenured', 'absolute', 1, MiB],
  59. ['code_cache_used', 'code cache', 'absolute', 1, MiB],
  60. ['compressed_used', 'compressed', 'absolute', 1, MiB],
  61. ['metaspace_used', 'metaspace', 'absolute', 1, MiB],
  62. ]
  63. },
  64. 'jvm_eden': {
  65. 'options': [None, 'Eden Memory Usage', 'MiB', 'memory', 'tomcat.jvm_eden', 'area'],
  66. 'lines': [
  67. ['eden_used', 'used', 'absolute', 1, MiB],
  68. ['eden_committed', 'committed', 'absolute', 1, MiB],
  69. ['eden_max', 'max', 'absolute', 1, MiB]
  70. ]
  71. },
  72. 'jvm_survivor': {
  73. 'options': [None, 'Survivor Memory Usage', 'MiB', 'memory', 'tomcat.jvm_survivor', 'area'],
  74. 'lines': [
  75. ['survivor_used', 'used', 'absolute', 1, MiB],
  76. ['survivor_committed', 'committed', 'absolute', 1, MiB],
  77. ['survivor_max', 'max', 'absolute', 1, MiB],
  78. ]
  79. },
  80. 'jvm_tenured': {
  81. 'options': [None, 'Tenured Memory Usage', 'MiB', 'memory', 'tomcat.jvm_tenured', 'area'],
  82. 'lines': [
  83. ['tenured_used', 'used', 'absolute', 1, MiB],
  84. ['tenured_committed', 'committed', 'absolute', 1, MiB],
  85. ['tenured_max', 'max', 'absolute', 1, MiB]
  86. ]
  87. }
  88. }
  89. class Service(UrlService):
  90. def __init__(self, configuration=None, name=None):
  91. UrlService.__init__(self, configuration=configuration, name=name)
  92. self.order = ORDER
  93. self.definitions = CHARTS
  94. self.url = self.configuration.get('url', 'http://127.0.0.1:8080/manager/status?XML=true')
  95. self.connector_name = self.configuration.get('connector_name', None)
  96. self.parse = self.xml_parse
  97. def xml_parse(self, data):
  98. try:
  99. return ET.fromstring(data)
  100. except ET.ParseError:
  101. self.debug('%s is not a valid XML page. Please add "?XML=true" to tomcat status page.' % self.url)
  102. return None
  103. def xml_single_quote_fix_parse(self, data):
  104. data = single_quote_regex.sub(r"='\g<1>\g<2>'", data)
  105. return self.xml_parse(data)
  106. def check(self):
  107. self._manager = self._build_manager()
  108. raw_data = self._get_raw_data()
  109. if not raw_data:
  110. return False
  111. if single_quote_regex.search(raw_data):
  112. self.warning('Tomcat status page is returning invalid single quote XML, please consider upgrading '
  113. 'your Tomcat installation. See https://bz.apache.org/bugzilla/show_bug.cgi?id=61603')
  114. self.parse = self.xml_single_quote_fix_parse
  115. return self.parse(raw_data) is not None
  116. def _get_data(self):
  117. """
  118. Format data received from http request
  119. :return: dict
  120. """
  121. data = None
  122. raw_data = self._get_raw_data()
  123. if raw_data:
  124. xml = self.parse(raw_data)
  125. if xml is None:
  126. return None
  127. data = {}
  128. jvm = xml.find('jvm')
  129. connector = None
  130. if self.connector_name:
  131. for conn in xml.findall('connector'):
  132. if self.connector_name in conn.get('name'):
  133. connector = conn
  134. break
  135. else:
  136. connector = xml.find('connector')
  137. memory = jvm.find('memory')
  138. data['free'] = memory.get('free')
  139. data['total'] = memory.get('total')
  140. for pool in jvm.findall('memorypool'):
  141. name = pool.get('name')
  142. if 'Eden Space' in name:
  143. data['eden_used'] = pool.get('usageUsed')
  144. data['eden_committed'] = pool.get('usageCommitted')
  145. data['eden_max'] = pool.get('usageMax')
  146. elif 'Survivor Space' in name:
  147. data['survivor_used'] = pool.get('usageUsed')
  148. data['survivor_committed'] = pool.get('usageCommitted')
  149. data['survivor_max'] = pool.get('usageMax')
  150. elif 'Tenured Gen' in name or 'Old Gen' in name:
  151. data['tenured_used'] = pool.get('usageUsed')
  152. data['tenured_committed'] = pool.get('usageCommitted')
  153. data['tenured_max'] = pool.get('usageMax')
  154. elif name == 'Code Cache':
  155. data['code_cache_used'] = pool.get('usageUsed')
  156. data['code_cache_committed'] = pool.get('usageCommitted')
  157. data['code_cache_max'] = pool.get('usageMax')
  158. elif name == 'Compressed':
  159. data['compressed_used'] = pool.get('usageUsed')
  160. data['compressed_committed'] = pool.get('usageCommitted')
  161. data['compressed_max'] = pool.get('usageMax')
  162. elif name == 'Metaspace':
  163. data['metaspace_used'] = pool.get('usageUsed')
  164. data['metaspace_committed'] = pool.get('usageCommitted')
  165. data['metaspace_max'] = pool.get('usageMax')
  166. if connector is not None:
  167. thread_info = connector.find('threadInfo')
  168. data['currentThreadsBusy'] = thread_info.get('currentThreadsBusy')
  169. data['currentThreadCount'] = thread_info.get('currentThreadCount')
  170. request_info = connector.find('requestInfo')
  171. data['processingTime'] = request_info.get('processingTime')
  172. data['requestCount'] = request_info.get('requestCount')
  173. data['errorCount'] = request_info.get('errorCount')
  174. data['bytesReceived'] = request_info.get('bytesReceived')
  175. data['bytesSent'] = request_info.get('bytesSent')
  176. return data or None