@cryptotaxi247 / netdata-1 / commits / 76527f40e

Port Tomcat collector to Go (#18220)

* initial files commit * followup * init.go * working implementation * test file and changes * checklist changes for tomcat collector port to Go * remove edge case with regex matching * fix listeners tomcat entry * simplify and improve charts --------- Co-authored-by: ilyam8 <ilya@netdata.cloud>

Fotis Voutsas committed Jul 25, 2024 at 01:40 UTC 76527f40ecd8f734713fc82a052f41b8f3a0c82a
24 files changed +1327 -731
CMakeLists.txt
-2
@@ -2788,7 +2788,6 @@ install(FILES
2788 src/collectors/python.d.plugin/samba/samba.conf
2789 src/collectors/python.d.plugin/spigotmc/spigotmc.conf
2790 src/collectors/python.d.plugin/squid/squid.conf
2791 - src/collectors/python.d.plugin/tomcat/tomcat.conf
2791 src/collectors/python.d.plugin/tor/tor.conf
2792 src/collectors/python.d.plugin/traefik/traefik.conf
2793 src/collectors/python.d.plugin/uwsgi/uwsgi.conf
@@ -2824,7 +2823,6 @@ install(FILES
2823 src/collectors/python.d.plugin/samba/samba.chart.py
2824 src/collectors/python.d.plugin/spigotmc/spigotmc.chart.py
2825 src/collectors/python.d.plugin/squid/squid.chart.py
2827 - src/collectors/python.d.plugin/tomcat/tomcat.chart.py
2826 src/collectors/python.d.plugin/tor/tor.chart.py
2827 src/collectors/python.d.plugin/traefik/traefik.chart.py
2828 src/collectors/python.d.plugin/uwsgi/uwsgi.chart.py
src/collectors/python.d.plugin/python.d.conf
+1 -1
@@ -53,7 +53,6 @@ go_expvar: no
53 # spigotmc: yes
54 # squid: yes
55 # traefik: yes
56 -# tomcat: yes
56 # tor: yes
57 # uwsgi: yes
58 # varnish: yes
@@ -82,4 +81,5 @@ postgres: no # Removed (replaced with go.d/postgres).
81 proxysql: no # Removed (replaced with go.d/proxysql).
82 redis: no # Removed (replaced with go.d/redis).
83 sensors: no # Removed (replaced with go.d/sensors).
84 +tomcat: no # Removed (replaced with go.d/tomcat)
85 puppet: no # Removed (replaced with go.d/puppet).
src/collectors/python.d.plugin/tomcat/README.md deleted
-1
@@ -1 +0,0 @@
1 -integrations/tomcat.md
\ No newline at end of file
src/collectors/python.d.plugin/tomcat/integrations/tomcat.md deleted
-236
@@ -1,236 +0,0 @@
1 -<!--startmeta
2 -custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/python.d.plugin/tomcat/README.md"
3 -meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/python.d.plugin/tomcat/metadata.yaml"
4 -sidebar_label: "Tomcat"
5 -learn_status: "Published"
6 -learn_rel_path: "Collecting Metrics/Web Servers and Web Proxies"
7 -most_popular: False
8 -message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE"
9 -endmeta-->
10 -
11 -# Tomcat
12 -
13 -
14 -<img src="https://netdata.cloud/img/tomcat.svg" width="150"/>
15 -
16 -
17 -Plugin: python.d.plugin
18 -Module: tomcat
19 -
20 -<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
21 -
22 -## Overview
23 -
24 -This collector monitors Tomcat metrics about bandwidth, processing time, threads and more.
25 -
26 -
27 -It parses the information provided by the http endpoint of the `/manager/status` in XML format
28 -
29 -
30 -This collector is supported on all platforms.
31 -
32 -This collector supports collecting metrics from multiple instances of this integration, including remote instances.
33 -
34 -You need to provide the username and the password, to access the webserver's status page. Create a seperate user with read only rights for this particular endpoint
35 -
36 -### Default Behavior
37 -
38 -#### Auto-Detection
39 -
40 -If the Netdata Agent and the Tomcat webserver are in the same host, without configuration, module attempts to connect to http://localhost:8080/manager/status?XML=true, without any credentials. So it will probably fail.
41 -
42 -#### Limits
43 -
44 -This module is not supporting SSL communication. If you want a Netdata Agent to monitor a Tomcat deployment, you shouldnt try to monitor it via public network (public internet). Credentials are passed by Netdata in an unsecure port
45 -
46 -#### Performance Impact
47 -
48 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
49 -
50 -
51 -## Metrics
52 -
53 -Metrics grouped by *scope*.
54 -
55 -The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels.
56 -
57 -
58 -
59 -### Per Tomcat instance
60 -
61 -These metrics refer to the entire monitored application.
62 -
63 -This scope has no labels.
64 -
65 -Metrics:
66 -
67 -| Metric | Dimensions | Unit |
68 -|:------|:----------|:----|
69 -| tomcat.accesses | accesses, errors | requests/s |
70 -| tomcat.bandwidth | sent, received | KiB/s |
71 -| tomcat.processing_time | processing time | seconds |
72 -| tomcat.threads | current, busy | current threads |
73 -| tomcat.jvm | free, eden, survivor, tenured, code cache, compressed, metaspace | MiB |
74 -| tomcat.jvm_eden | used, committed, max | MiB |
75 -| tomcat.jvm_survivor | used, committed, max | MiB |
76 -| tomcat.jvm_tenured | used, committed, max | MiB |
77 -
78 -
79 -
80 -## Alerts
81 -
82 -There are no alerts configured by default for this integration.
83 -
84 -
85 -## Setup
86 -
87 -### Prerequisites
88 -
89 -#### Create a read-only `netdata` user, to monitor the `/status` endpoint.
90 -
91 -This is necessary for configuring the collector.
92 -
93 -
94 -### Configuration
95 -
96 -#### File
97 -
98 -The configuration file name for this integration is `python.d/tomcat.conf`.
99 -
100 -
101 -You can edit the configuration file using the `edit-config` script from the
102 -Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory).
103 -
104 -```bash
105 -cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
106 -sudo ./edit-config python.d/tomcat.conf
107 -```
108 -#### Options
109 -
110 -There are 2 sections:
111 -
112 -* Global variables
113 -* One or more JOBS that can define multiple different instances to monitor.
114 -
115 -The following options can be defined globally: priority, penalty, autodetection_retry, update_every, but can also be defined per JOB to override the global values.Additionally, the following collapsed table contains all the options that can be configured inside a JOB definition.
116 -
117 -Every configuration JOB starts with a `job_name` value which will appear in the dashboard, unless a `name` parameter is specified.
118 -
119 -
120 -<details open><summary>Config options per job</summary>
121 -
122 -| Name | Description | Default | Required |
123 -|:----|:-----------|:-------|:--------:|
124 -| update_every | Sets the default data collection frequency. | 5 | no |
125 -| priority | Controls the order of charts at the netdata dashboard. | 60000 | no |
126 -| autodetection_retry | Sets the job re-check interval in seconds. | 0 | no |
127 -| penalty | Indicates whether to apply penalty to update_every in case of failures. | yes | no |
128 -| url | The URL of the Tomcat server's status endpoint. Always add the suffix ?XML=true. | no | yes |
129 -| user | A valid user with read permission to access the /manager/status endpoint of the server. Required if the endpoint is password protected | no | no |
130 -| pass | A valid password for the user in question. Required if the endpoint is password protected | no | no |
131 -| connector_name | The connector component that communicates with a web connector via the AJP protocol, e.g ajp-bio-8009 | | no |
132 -
133 -</details>
134 -
135 -#### Examples
136 -
137 -##### Basic
138 -
139 -A basic example configuration
140 -
141 -```yaml
142 -localhost:
143 - name : 'local'
144 - url : 'http://localhost:8080/manager/status?XML=true'
145 -
146 -```
147 -##### Using an IPv4 endpoint
148 -
149 -A typical configuration using an IPv4 endpoint
150 -
151 -<details open><summary>Config</summary>
152 -
153 -```yaml
154 -local_ipv4:
155 - name : 'local'
156 - url : 'http://127.0.0.1:8080/manager/status?XML=true'
157 -
158 -```
159 -</details>
160 -
161 -##### Using an IPv6 endpoint
162 -
163 -A typical configuration using an IPv6 endpoint
164 -
165 -<details open><summary>Config</summary>
166 -
167 -```yaml
168 -local_ipv6:
169 - name : 'local'
170 - url : 'http://[::1]:8080/manager/status?XML=true'
171 -
172 -```
173 -</details>
174 -
175 -
176 -
177 -## Troubleshooting
178 -
179 -### Debug Mode
180 -
181 -To troubleshoot issues with the `tomcat` collector, run the `python.d.plugin` with the debug option enabled. The output
182 -should give you clues as to why the collector isn't working.
183 -
184 -- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on
185 - your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`.
186 -
187 - ```bash
188 - cd /usr/libexec/netdata/plugins.d/
189 - ```
190 -
191 -- Switch to the `netdata` user.
192 -
193 - ```bash
194 - sudo -u netdata -s
195 - ```
196 -
197 -- Run the `python.d.plugin` to debug the collector:
198 -
199 - ```bash
200 - ./python.d.plugin tomcat debug trace
201 - ```
202 -
203 -### Getting Logs
204 -
205 -If you're encountering problems with the `tomcat` collector, follow these steps to retrieve logs and identify potential issues:
206 -
207 -- **Run the command** specific to your system (systemd, non-systemd, or Docker container).
208 -- **Examine the output** for any warnings or error messages that might indicate issues. These messages should provide clues about the root cause of the problem.
209 -
210 -#### System with systemd
211 -
212 -Use the following command to view logs generated since the last Netdata service restart:
213 -
214 -```bash
215 -journalctl _SYSTEMD_INVOCATION_ID="$(systemctl show --value --property=InvocationID netdata)" --namespace=netdata --grep tomcat
216 -```
217 -
218 -#### System without systemd
219 -
220 -Locate the collector log file, typically at `/var/log/netdata/collector.log`, and use `grep` to filter for collector's name:
221 -
222 -```bash
223 -grep tomcat /var/log/netdata/collector.log
224 -```
225 -
226 -**Note**: This method shows logs from all restarts. Focus on the **latest entries** for troubleshooting current issues.
227 -
228 -#### Docker Container
229 -
230 -If your Netdata runs in a Docker container named "netdata" (replace if different), use this command:
231 -
232 -```bash
233 -docker logs netdata 2>&1 | grep tomcat
234 -```
235 -
236 -
src/collectors/python.d.plugin/tomcat/metadata.yaml deleted
-200
@@ -1,200 +0,0 @@
1 -plugin_name: python.d.plugin
2 -modules:
3 - - meta:
4 - plugin_name: python.d.plugin
5 - module_name: tomcat
6 - monitored_instance:
7 - name: Tomcat
8 - link: "https://tomcat.apache.org/"
9 - categories:
10 - - data-collection.web-servers-and-web-proxies
11 - icon_filename: "tomcat.svg"
12 - related_resources:
13 - integrations:
14 - list: []
15 - info_provided_to_referring_integrations:
16 - description: ""
17 - keywords:
18 - - apache
19 - - tomcat
20 - - webserver
21 - - websocket
22 - - jakarta
23 - - javaEE
24 - most_popular: false
25 - overview:
26 - data_collection:
27 - metrics_description: |
28 - This collector monitors Tomcat metrics about bandwidth, processing time, threads and more.
29 - method_description: |
30 - It parses the information provided by the http endpoint of the `/manager/status` in XML format
31 - supported_platforms:
32 - include: []
33 - exclude: []
34 - multi_instance: true
35 - additional_permissions:
36 - description: "You need to provide the username and the password, to access the webserver's status page. Create a seperate user with read only rights for this particular endpoint"
37 - default_behavior:
38 - auto_detection:
39 - description: "If the Netdata Agent and the Tomcat webserver are in the same host, without configuration, module attempts to connect to http://localhost:8080/manager/status?XML=true, without any credentials. So it will probably fail."
40 - limits:
41 - description: "This module is not supporting SSL communication. If you want a Netdata Agent to monitor a Tomcat deployment, you shouldnt try to monitor it via public network (public internet). Credentials are passed by Netdata in an unsecure port"
42 - performance_impact:
43 - description: ""
44 - setup:
45 - prerequisites:
46 - list:
47 - - title: Create a read-only `netdata` user, to monitor the `/status` endpoint.
48 - description: This is necessary for configuring the collector.
49 - configuration:
50 - file:
51 - name: "python.d/tomcat.conf"
52 - options:
53 - description: |
54 - There are 2 sections:
55 -
56 - * Global variables
57 - * One or more JOBS that can define multiple different instances to monitor.
58 -
59 - The following options can be defined globally: priority, penalty, autodetection_retry, update_every, but can also be defined per JOB to override the global values.Additionally, the following collapsed table contains all the options that can be configured inside a JOB definition.
60 -
61 - Every configuration JOB starts with a `job_name` value which will appear in the dashboard, unless a `name` parameter is specified.
62 - folding:
63 - title: "Config options per job"
64 - enabled: true
65 - list:
66 - - name: update_every
67 - description: Sets the default data collection frequency.
68 - default_value: 5
69 - required: false
70 - - name: priority
71 - description: Controls the order of charts at the netdata dashboard.
72 - default_value: 60000
73 - required: false
74 - - name: autodetection_retry
75 - description: Sets the job re-check interval in seconds.
76 - default_value: 0
77 - required: false
78 - - name: penalty
79 - description: Indicates whether to apply penalty to update_every in case of failures.
80 - default_value: yes
81 - required: false
82 - - name: url
83 - description: The URL of the Tomcat server's status endpoint. Always add the suffix ?XML=true.
84 - default_value: no
85 - required: true
86 - - name: user
87 - description: A valid user with read permission to access the /manager/status endpoint of the server. Required if the endpoint is password protected
88 - default_value: no
89 - required: false
90 - - name: pass
91 - description: A valid password for the user in question. Required if the endpoint is password protected
92 - default_value: no
93 - required: false
94 - - name: connector_name
95 - description: The connector component that communicates with a web connector via the AJP protocol, e.g ajp-bio-8009
96 - default_value: ""
97 - required: false
98 - examples:
99 - folding:
100 - enabled: true
101 - title: "Config"
102 - list:
103 - - name: Basic
104 - folding:
105 - enabled: false
106 - description: A basic example configuration
107 - config: |
108 - localhost:
109 - name : 'local'
110 - url : 'http://localhost:8080/manager/status?XML=true'
111 - - name: Using an IPv4 endpoint
112 - description: A typical configuration using an IPv4 endpoint
113 - config: |
114 - local_ipv4:
115 - name : 'local'
116 - url : 'http://127.0.0.1:8080/manager/status?XML=true'
117 - - name: Using an IPv6 endpoint
118 - description: A typical configuration using an IPv6 endpoint
119 - config: |
120 - local_ipv6:
121 - name : 'local'
122 - url : 'http://[::1]:8080/manager/status?XML=true'
123 - troubleshooting:
124 - problems:
125 - list: []
126 - alerts: []
127 - metrics:
128 - folding:
129 - title: Metrics
130 - enabled: false
131 - description: ""
132 - availability: []
133 - scopes:
134 - - name: global
135 - description: "These metrics refer to the entire monitored application."
136 - labels: []
137 - metrics:
138 - - name: tomcat.accesses
139 - description: Requests
140 - unit: "requests/s"
141 - chart_type: area
142 - dimensions:
143 - - name: accesses
144 - - name: errors
145 - - name: tomcat.bandwidth
146 - description: Bandwidth
147 - unit: "KiB/s"
148 - chart_type: area
149 - dimensions:
150 - - name: sent
151 - - name: received
152 - - name: tomcat.processing_time
153 - description: processing time
154 - unit: "seconds"
155 - chart_type: area
156 - dimensions:
157 - - name: processing time
158 - - name: tomcat.threads
159 - description: Threads
160 - unit: "current threads"
161 - chart_type: area
162 - dimensions:
163 - - name: current
164 - - name: busy
165 - - name: tomcat.jvm
166 - description: JVM Memory Pool Usage
167 - unit: "MiB"
168 - chart_type: stacked
169 - dimensions:
170 - - name: free
171 - - name: eden
172 - - name: survivor
173 - - name: tenured
174 - - name: code cache
175 - - name: compressed
176 - - name: metaspace
177 - - name: tomcat.jvm_eden
178 - description: Eden Memory Usage
179 - unit: "MiB"
180 - chart_type: area
181 - dimensions:
182 - - name: used
183 - - name: committed
184 - - name: max
185 - - name: tomcat.jvm_survivor
186 - description: Survivor Memory Usage
187 - unit: "MiB"
188 - chart_type: area
189 - dimensions:
190 - - name: used
191 - - name: committed
192 - - name: max
193 - - name: tomcat.jvm_tenured
194 - description: Tenured Memory Usage
195 - unit: "MiB"
196 - chart_type: area
197 - dimensions:
198 - - name: used
199 - - name: committed
200 - - name: max
src/collectors/python.d.plugin/tomcat/tomcat.chart.py deleted
-199
@@ -1,199 +0,0 @@
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 -
7 -import re
8 -import xml.etree.ElementTree as ET
9 -
10 -from bases.FrameworkServices.UrlService import UrlService
11 -
12 -MiB = 1 << 20
13 -
14 -# Regex fix for Tomcat single quote XML attributes
15 -# affecting Tomcat < 8.5.24 & 9.0.2 running with Java > 9
16 -# cf. https://bz.apache.org/bugzilla/show_bug.cgi?id=61603
17 -single_quote_regex = re.compile(r"='([^']+)'([^']+)''")
18 -
19 -ORDER = [
20 - 'accesses',
21 - 'bandwidth',
22 - 'processing_time',
23 - 'threads',
24 - 'jvm',
25 - 'jvm_eden',
26 - 'jvm_survivor',
27 - 'jvm_tenured',
28 -]
29 -
30 -CHARTS = {
31 - 'accesses': {
32 - 'options': [None, 'Requests', 'requests/s', 'statistics', 'tomcat.accesses', 'area'],
33 - 'lines': [
34 - ['requestCount', 'accesses', 'incremental'],
35 - ['errorCount', 'errors', 'incremental'],
36 - ]
37 - },
38 - 'bandwidth': {
39 - 'options': [None, 'Bandwidth', 'KiB/s', 'statistics', 'tomcat.bandwidth', 'area'],
40 - 'lines': [
41 - ['bytesSent', 'sent', 'incremental', 1, 1024],
42 - ['bytesReceived', 'received', 'incremental', 1, 1024],
43 - ]
44 - },
45 - 'processing_time': {
46 - 'options': [None, 'processing time', 'seconds', 'statistics', 'tomcat.processing_time', 'area'],
47 - 'lines': [
48 - ['processingTime', 'processing time', 'incremental', 1, 1000]
49 - ]
50 - },
51 - 'threads': {
52 - 'options': [None, 'Threads', 'current threads', 'statistics', 'tomcat.threads', 'area'],
53 - 'lines': [
54 - ['currentThreadCount', 'current', 'absolute'],
55 - ['currentThreadsBusy', 'busy', 'absolute']
56 - ]
57 - },
58 - 'jvm': {
59 - 'options': [None, 'JVM Memory Pool Usage', 'MiB', 'memory', 'tomcat.jvm', 'stacked'],
60 - 'lines': [
61 - ['free', 'free', 'absolute', 1, MiB],
62 - ['eden_used', 'eden', 'absolute', 1, MiB],
63 - ['survivor_used', 'survivor', 'absolute', 1, MiB],
64 - ['tenured_used', 'tenured', 'absolute', 1, MiB],
65 - ['code_cache_used', 'code cache', 'absolute', 1, MiB],
66 - ['compressed_used', 'compressed', 'absolute', 1, MiB],
67 - ['metaspace_used', 'metaspace', 'absolute', 1, MiB],
68 - ]
69 - },
70 - 'jvm_eden': {
71 - 'options': [None, 'Eden Memory Usage', 'MiB', 'memory', 'tomcat.jvm_eden', 'area'],
72 - 'lines': [
73 - ['eden_used', 'used', 'absolute', 1, MiB],
74 - ['eden_committed', 'committed', 'absolute', 1, MiB],
75 - ['eden_max', 'max', 'absolute', 1, MiB]
76 - ]
77 - },
78 - 'jvm_survivor': {
79 - 'options': [None, 'Survivor Memory Usage', 'MiB', 'memory', 'tomcat.jvm_survivor', 'area'],
80 - 'lines': [
81 - ['survivor_used', 'used', 'absolute', 1, MiB],
82 - ['survivor_committed', 'committed', 'absolute', 1, MiB],
83 - ['survivor_max', 'max', 'absolute', 1, MiB],
84 - ]
85 - },
86 - 'jvm_tenured': {
87 - 'options': [None, 'Tenured Memory Usage', 'MiB', 'memory', 'tomcat.jvm_tenured', 'area'],
88 - 'lines': [
89 - ['tenured_used', 'used', 'absolute', 1, MiB],
90 - ['tenured_committed', 'committed', 'absolute', 1, MiB],
91 - ['tenured_max', 'max', 'absolute', 1, MiB]
92 - ]
93 - }
94 -}
95 -
96 -
97 -class Service(UrlService):
98 - def __init__(self, configuration=None, name=None):
99 - UrlService.__init__(self, configuration=configuration, name=name)
100 - self.order = ORDER
101 - self.definitions = CHARTS
102 - self.url = self.configuration.get('url', 'http://127.0.0.1:8080/manager/status?XML=true')
103 - self.connector_name = self.configuration.get('connector_name', None)
104 - self.parse = self.xml_parse
105 -
106 - def xml_parse(self, data):
107 - try:
108 - return ET.fromstring(data)
109 - except ET.ParseError:
110 - self.debug('%s is not a valid XML page. Please add "?XML=true" to tomcat status page.' % self.url)
111 - return None
112 -
113 - def xml_single_quote_fix_parse(self, data):
114 - data = single_quote_regex.sub(r"='\g<1>\g<2>'", data)
115 - return self.xml_parse(data)
116 -
117 - def check(self):
118 - self._manager = self._build_manager()
119 -
120 - raw_data = self._get_raw_data()
121 - if not raw_data:
122 - return False
123 -
124 - if single_quote_regex.search(raw_data):
125 - self.warning('Tomcat status page is returning invalid single quote XML, please consider upgrading '
126 - 'your Tomcat installation. See https://bz.apache.org/bugzilla/show_bug.cgi?id=61603')
127 - self.parse = self.xml_single_quote_fix_parse
128 -
129 - return self.parse(raw_data) is not None
130 -
131 - def _get_data(self):
132 - """
133 - Format data received from http request
134 - :return: dict
135 - """
136 - data = None
137 - raw_data = self._get_raw_data()
138 - if raw_data:
139 - xml = self.parse(raw_data)
140 - if xml is None:
141 - return None
142 -
143 - data = {}
144 -
145 - jvm = xml.find('jvm')
146 -
147 - connector = None
148 - if self.connector_name:
149 - for conn in xml.findall('connector'):
150 - if self.connector_name in conn.get('name'):
151 - connector = conn
152 - break
153 - else:
154 - connector = xml.find('connector')
155 -
156 - memory = jvm.find('memory')
157 - data['free'] = memory.get('free')
158 - data['total'] = memory.get('total')
159 -
160 - for pool in jvm.findall('memorypool'):
161 - name = pool.get('name')
162 - if 'Eden Space' in name:
163 - data['eden_used'] = pool.get('usageUsed')
164 - data['eden_committed'] = pool.get('usageCommitted')
165 - data['eden_max'] = pool.get('usageMax')
166 - elif 'Survivor Space' in name:
167 - data['survivor_used'] = pool.get('usageUsed')
168 - data['survivor_committed'] = pool.get('usageCommitted')
169 - data['survivor_max'] = pool.get('usageMax')
170 - elif 'Tenured Gen' in name or 'Old Gen' in name:
171 - data['tenured_used'] = pool.get('usageUsed')
172 - data['tenured_committed'] = pool.get('usageCommitted')
173 - data['tenured_max'] = pool.get('usageMax')
174 - elif name == 'Code Cache':
175 - data['code_cache_used'] = pool.get('usageUsed')
176 - data['code_cache_committed'] = pool.get('usageCommitted')
177 - data['code_cache_max'] = pool.get('usageMax')
178 - elif name == 'Compressed':
179 - data['compressed_used'] = pool.get('usageUsed')
180 - data['compressed_committed'] = pool.get('usageCommitted')
181 - data['compressed_max'] = pool.get('usageMax')
182 - elif name == 'Metaspace':
183 - data['metaspace_used'] = pool.get('usageUsed')
184 - data['metaspace_committed'] = pool.get('usageCommitted')
185 - data['metaspace_max'] = pool.get('usageMax')
186 -
187 - if connector is not None:
188 - thread_info = connector.find('threadInfo')
189 - data['currentThreadsBusy'] = thread_info.get('currentThreadsBusy')
190 - data['currentThreadCount'] = thread_info.get('currentThreadCount')
191 -
192 - request_info = connector.find('requestInfo')
193 - data['processingTime'] = request_info.get('processingTime')
194 - data['requestCount'] = request_info.get('requestCount')
195 - data['errorCount'] = request_info.get('errorCount')
196 - data['bytesReceived'] = request_info.get('bytesReceived')
197 - data['bytesSent'] = request_info.get('bytesSent')
198 -
199 - return data or None
src/collectors/python.d.plugin/tomcat/tomcat.conf deleted
-89
@@ -1,89 +0,0 @@
1 -# netdata python.d.plugin configuration for tomcat
2 -#
3 -# This file is in YaML format. Generally the format is:
4 -#
5 -# name: value
6 -#
7 -# There are 2 sections:
8 -# - global variables
9 -# - one or more JOBS
10 -#
11 -# JOBS allow you to collect values from multiple sources.
12 -# Each source will have its own set of charts.
13 -#
14 -# JOB parameters have to be indented (using spaces only, example below).
15 -
16 -# ----------------------------------------------------------------------
17 -# Global Variables
18 -# These variables set the defaults for all JOBs, however each JOB
19 -# may define its own, overriding the defaults.
20 -
21 -# update_every sets the default data collection frequency.
22 -# If unset, the python.d.plugin default is used.
23 -# update_every: 1
24 -
25 -# priority controls the order of charts at the netdata dashboard.
26 -# Lower numbers move the charts towards the top of the page.
27 -# If unset, the default for python.d.plugin is used.
28 -# priority: 60000
29 -
30 -# penalty indicates whether to apply penalty to update_every in case of failures.
31 -# Penalty will increase every 5 failed updates in a row. Maximum penalty is 10 minutes.
32 -# penalty: yes
33 -
34 -# autodetection_retry sets the job re-check interval in seconds.
35 -# The job is not deleted if check fails.
36 -# Attempts to start the job are made once every autodetection_retry.
37 -# This feature is disabled by default.
38 -# autodetection_retry: 0
39 -
40 -# ----------------------------------------------------------------------
41 -# JOBS (data collection sources)
42 -#
43 -# The default JOBS share the same *name*. JOBS with the same name
44 -# are mutually exclusive. Only one of them will be allowed running at
45 -# any time. This allows autodetection to try several alternatives and
46 -# pick the one that works.
47 -#
48 -# Any number of jobs is supported.
49 -#
50 -# All python.d.plugin JOBS (for all its modules) support a set of
51 -# predefined parameters. These are:
52 -#
53 -# job_name:
54 -# name: myname # the JOB's name as it will appear at the
55 -# # dashboard (by default is the job_name)
56 -# # JOBs sharing a name are mutually exclusive
57 -# update_every: 1 # the JOB's data collection frequency
58 -# priority: 60000 # the JOB's order on the dashboard
59 -# penalty: yes # the JOB's penalty
60 -# autodetection_retry: 0 # the JOB's re-check interval in seconds
61 -#
62 -# Additionally to the above, tomcat also supports the following:
63 -#
64 -# url: 'URL' # the URL to fetch nginx's status stats
65 -#
66 -# if the URL is password protected, the following are supported:
67 -#
68 -# user: 'username'
69 -# pass: 'password'
70 -#
71 -# if you have multiple connectors, the following are supported:
72 -#
73 -# connector_name: 'ajp-bio-8009' # default is null, which use first connector in status XML
74 -#
75 -# ----------------------------------------------------------------------
76 -# AUTO-DETECTION JOBS
77 -# only one of them will run (they have the same name)
78 -
79 -localhost:
80 - name : 'local'
81 - url : 'http://localhost:8080/manager/status?XML=true'
82 -
83 -localipv4:
84 - name : 'local'
85 - url : 'http://127.0.0.1:8080/manager/status?XML=true'
86 -
87 -localipv6:
88 - name : 'local'
89 - url : 'http://[::1]:8080/manager/status?XML=true'
src/go/plugin/go.d/README.md
+1
@@ -132,6 +132,7 @@ see the appropriate collector readme.
132 | [supervisord](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/supervisord) | Supervisor |
133 | [systemdunits](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/systemdunits) | Systemd unit state |
134 | [tengine](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/tengine) | Tengine |
135 +| [tomcat](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/tomcat) | Tomcat |
136 | [traefik](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/traefik) | Traefik |
137 | [upsd](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/upsd) | UPSd (Nut) |
138 | [unbound](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/unbound) | Unbound |
src/go/plugin/go.d/config/go.d.conf
+1
@@ -96,6 +96,7 @@ modules:
96 # supervisord: yes
97 # systemdunits: yes
98 # tengine: yes
99 +# tomcat: yes
100 # traefik: yes
101 # upsd: yes
102 # unbound: yes
src/go/plugin/go.d/config/go.d/sd/docker.conf
+7
@@ -66,6 +66,8 @@ classify:
66 expr: '{{ or (eq .PrivatePort "6379") (match "sp" .Image "redis redis:* */redis */redis:*") }}'
67 - tags: "tengine"
68 expr: '{{ match "sp" .Image "*/tengine */tengine:*" }}'
69 + - tags: "tomcat"
70 + expr: '{{ match "sp" .Image "tomcat tomcat:* */tomcat */tomcat:*" }}'
71 - tags: "vernemq"
72 expr: '{{ match "sp" .Image "*/vernemq */vernemq:*" }}'
73 - tags: "zookeeper"
@@ -201,6 +203,11 @@ compose:
203 module: tengine
204 name: docker_{{.Name}}
205 url: http://{{.Address}}/us
206 + - selector: "tomcat"
207 + template: |
208 + module: tomcat
209 + name: docker_{{.Name}}
210 + url: http://{{.Address}}
211 - selector: "vernemq"
212 template: |
213 module: vernemq
src/go/plugin/go.d/config/go.d/sd/net_listeners.conf
+5 -3
@@ -106,6 +106,8 @@ classify:
106 expr: '{{ and (eq .Port "11334") (eq .Comm "rspamd") }}'
107 - tags: "supervisord"
108 expr: '{{ and (eq .Port "9001") (eq .Comm "supervisord") }}'
109 + - tags: "tomcat"
110 + expr: '{{ and (eq .Port "8080") (glob .Cmdline "*tomcat*") }}'
111 - tags: "traefik"
112 expr: '{{ and (eq .Port "80" "8080") (eq .Comm "traefik") }}'
113 - tags: "unbound"
@@ -417,11 +419,11 @@ compose:
419 module: traefik
420 name: local
421 url: http://{{.Address}}/metrics
420 - - selector: "traefik"
422 + - selector: "tomcat"
423 template: |
422 - module: traefik
424 + module: tomcat
425 name: local
424 - url: http://{{.Address}}/metrics
426 + url: http://{{.Address}}
427 - selector: "unbound"
428 template: |
429 module: unbound
src/go/plugin/go.d/config/go.d/tomcat.conf new
+6
@@ -0,0 +1,6 @@
1 +## All available configuration options, their descriptions and default values:
2 +## https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/tomcat#readme
3 +
4 +#jobs:
5 +# - name: local
6 +# url: http://127.0.0.1:8080
src/go/plugin/go.d/modules/init.go
+1
@@ -88,6 +88,7 @@ import (
88 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/supervisord"
89 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/systemdunits"
90 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/tengine"
91 + _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/tomcat"
92 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/traefik"
93 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/unbound"
94 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/upsd"
src/go/plugin/go.d/modules/tomcat/charts.go new
+196
@@ -0,0 +1,196 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package tomcat
4 +
5 +import (
6 + "fmt"
7 + "strings"
8 +
9 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
10 +)
11 +
12 +const (
13 + prioConnectorRequestsCount = module.Priority + iota
14 + prioConnectorRequestsBandwidth
15 + prioConnectorRequestsProcessingTime
16 + prioConnectorRequestsErrors
17 +
18 + prioConnectorRequestThreads
19 +
20 + prioJvmMemoryUsage
21 +
22 + prioJvmMemoryPoolMemoryUsage
23 +)
24 +
25 +var (
26 + defaultCharts = module.Charts{
27 + jvmMemoryUsageChart.Copy(),
28 + }
29 +
30 + jvmMemoryUsageChart = module.Chart{
31 + ID: "jvm_memory_usage",
32 + Title: "JVM Memory Usage",
33 + Units: "bytes",
34 + Fam: "memory",
35 + Ctx: "tomcat.jvm_memory_usage",
36 + Type: module.Stacked,
37 + Priority: prioJvmMemoryUsage,
38 + Dims: module.Dims{
39 + {ID: "jvm_memory_free", Name: "free"},
40 + {ID: "jvm_memory_used", Name: "used"},
41 + },
42 + }
43 +)
44 +
45 +var (
46 + connectorChartsTmpl = module.Charts{
47 + connectorRequestsCountChartTmpl.Copy(),
48 + connectorRequestsBandwidthChartTmpl.Copy(),
49 + connectorRequestsProcessingTimeChartTmpl.Copy(),
50 + connectorRequestsErrorsChartTmpl.Copy(),
51 + connectorRequestThreadsChartTmpl.Copy(),
52 + }
53 +
54 + connectorRequestsCountChartTmpl = module.Chart{
55 + ID: "connector_%_requests",
56 + Title: "Connector Requests",
57 + Units: "requests/s",
58 + Fam: "requests",
59 + Ctx: "tomcat.connector_requests",
60 + Type: module.Line,
61 + Priority: prioConnectorRequestsCount,
62 + Dims: module.Dims{
63 + {ID: "connector_%s_request_info_request_count", Name: "requests", Algo: module.Incremental},
64 + },
65 + }
66 + connectorRequestsBandwidthChartTmpl = module.Chart{
67 + ID: "connector_%s_requests_bandwidth",
68 + Title: "Connector Requests Bandwidth",
69 + Units: "bytes/s",
70 + Fam: "requests",
71 + Ctx: "tomcat.connector_bandwidth",
72 + Type: module.Area,
73 + Priority: prioConnectorRequestsBandwidth,
74 + Dims: module.Dims{
75 + {ID: "connector_%s_request_info_bytes_received", Name: "received", Algo: module.Incremental},
76 + {ID: "connector_%s_request_info_bytes_sent", Name: "sent", Mul: -1, Algo: module.Incremental},
77 + },
78 + }
79 + connectorRequestsProcessingTimeChartTmpl = module.Chart{
80 + ID: "connector_%_requests_processing_time",
81 + Title: "Connector Requests Processing Time",
82 + Units: "milliseconds",
83 + Fam: "requests",
84 + Ctx: "tomcat.connector_requests_processing_time",
85 + Type: module.Line,
86 + Priority: prioConnectorRequestsProcessingTime,
87 + Dims: module.Dims{
88 + {ID: "connector_%s_request_info_processing_time", Name: "processing_time", Algo: module.Incremental},
89 + },
90 + }
91 + connectorRequestsErrorsChartTmpl = module.Chart{
92 + ID: "connector_%_errors",
93 + Title: "Connector Errors",
94 + Units: "errors/s",
95 + Fam: "requests",
96 + Ctx: "tomcat.connector_errors",
97 + Type: module.Line,
98 + Priority: prioConnectorRequestsErrors,
99 + Dims: module.Dims{
100 + {ID: "connector_%s_request_info_error_count", Name: "errors", Algo: module.Incremental},
101 + },
102 + }
103 +
104 + connectorRequestThreadsChartTmpl = module.Chart{
105 + ID: "connector_%s_request_threads",
106 + Title: "Connector Request Threads",
107 + Units: "threads",
108 + Fam: "threads",
109 + Ctx: "tomcat.connector_request_threads",
110 + Type: module.Stacked,
111 + Priority: prioConnectorRequestThreads,
112 + Dims: module.Dims{
113 + {ID: "connector_%s_thread_info_idle", Name: "idle"},
114 + {ID: "connector_%s_thread_info_busy", Name: "busy"},
115 + },
116 + }
117 +)
118 +
119 +var (
120 + jvmMemoryPoolChartsTmpl = module.Charts{
121 + jvmMemoryPoolMemoryUsageChartTmpl.Copy(),
122 + }
123 +
124 + jvmMemoryPoolMemoryUsageChartTmpl = module.Chart{
125 + ID: "jvm_mem_pool_%s_memory_usage",
126 + Title: "JVM Mem Pool Memory Usage",
127 + Units: "bytes",
128 + Fam: "memory",
129 + Ctx: "tomcat.jvm_mem_pool_memory_usage",
130 + Type: module.Area,
131 + Priority: prioJvmMemoryPoolMemoryUsage,
132 + Dims: module.Dims{
133 + {ID: "jvm_memorypool_%s_commited", Name: "commited"},
134 + {ID: "jvm_memorypool_%s_used", Name: "used"},
135 + {ID: "jvm_memorypool_%s_max", Name: "max"},
136 + },
137 + }
138 +)
139 +
140 +func (t *Tomcat) addConnectorCharts(name string) {
141 + charts := connectorChartsTmpl.Copy()
142 +
143 + for _, chart := range *charts {
144 + chart.ID = fmt.Sprintf(chart.ID, cleanName(name))
145 + chart.Labels = []module.Label{
146 + {Key: "connector_name", Value: strings.Trim(name, "\"")},
147 + }
148 + for _, dim := range chart.Dims {
149 + dim.ID = fmt.Sprintf(dim.ID, cleanName(name))
150 + }
151 + }
152 +
153 + if err := t.Charts().Add(*charts...); err != nil {
154 + t.Warning(err)
155 + }
156 +}
157 +
158 +func (t *Tomcat) addMemPoolCharts(name, typ string) {
159 + name = strings.ReplaceAll(name, "'", "")
160 +
161 + charts := jvmMemoryPoolChartsTmpl.Copy()
162 +
163 + for _, chart := range *charts {
164 + chart.ID = fmt.Sprintf(chart.ID, cleanName(name))
165 + chart.Labels = []module.Label{
166 + {Key: "mempool_name", Value: name},
167 + {Key: "mempool_type", Value: typ},
168 + }
169 + for _, dim := range chart.Dims {
170 + dim.ID = fmt.Sprintf(dim.ID, cleanName(name))
171 + }
172 + }
173 +
174 + if err := t.Charts().Add(*charts...); err != nil {
175 + t.Warning(err)
176 + }
177 +}
178 +
179 +func (t *Tomcat) removeConnectorCharts(name string) {
180 + px := fmt.Sprintf("connector_%s_", cleanName(name))
181 + t.removeCharts(px)
182 +}
183 +
184 +func (t *Tomcat) removeMemoryPoolCharts(name string) {
185 + px := fmt.Sprintf("jvm_mem_pool_%s_", cleanName(name))
186 + t.removeCharts(px)
187 +}
188 +
189 +func (t *Tomcat) removeCharts(prefix string) {
190 + for _, chart := range *t.Charts() {
191 + if strings.HasPrefix(chart.ID, prefix) {
192 + chart.MarkRemove()
193 + chart.MarkNotCreated()
194 + }
195 + }
196 +}
src/go/plugin/go.d/modules/tomcat/collect.go new
+130
@@ -0,0 +1,130 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package tomcat
4 +
5 +import (
6 + "encoding/xml"
7 + "errors"
8 + "fmt"
9 + "io"
10 + "net/http"
11 + "net/url"
12 + "strings"
13 +
14 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm"
15 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
16 +)
17 +
18 +var (
19 + urlPathServerStatus = "/manager/status"
20 + urlQueryServerStatus = url.Values{"XML": {"true"}}.Encode()
21 +)
22 +
23 +func (t *Tomcat) collect() (map[string]int64, error) {
24 + mx, err := t.collectServerStatus()
25 + if err != nil {
26 + return nil, err
27 + }
28 +
29 + return mx, nil
30 +}
31 +
32 +func (t *Tomcat) collectServerStatus() (map[string]int64, error) {
33 + resp, err := t.queryServerStatus()
34 + if err != nil {
35 + return nil, err
36 + }
37 +
38 + if len(resp.Connectors) == 0 {
39 + return nil, errors.New("unexpected response: not tomcat server status data")
40 + }
41 +
42 + seenConns, seenPools := make(map[string]bool), make(map[string]bool)
43 +
44 + for i, v := range resp.Connectors {
45 + resp.Connectors[i].STMKey = cleanName(v.Name)
46 + ti := &resp.Connectors[i].ThreadInfo
47 + ti.CurrentThreadsIdle = ti.CurrentThreadCount - ti.CurrentThreadsBusy
48 +
49 + seenConns[v.Name] = true
50 + if !t.seenConnectors[v.Name] {
51 + t.seenConnectors[v.Name] = true
52 + t.addConnectorCharts(v.Name)
53 + }
54 + }
55 +
56 + for i, v := range resp.JVM.MemoryPools {
57 + resp.JVM.MemoryPools[i].STMKey = cleanName(v.Name)
58 +
59 + seenPools[v.Name] = true
60 + if !t.seenMemPools[v.Name] {
61 + t.seenMemPools[v.Name] = true
62 + t.addMemPoolCharts(v.Name, v.Type)
63 + }
64 + }
65 +
66 + for name := range t.seenConnectors {
67 + if !seenConns[name] {
68 + delete(t.seenConnectors, name)
69 + t.removeConnectorCharts(name)
70 + }
71 + }
72 +
73 + for name := range t.seenMemPools {
74 + if !seenPools[name] {
75 + delete(t.seenMemPools, name)
76 + t.removeMemoryPoolCharts(name)
77 + }
78 + }
79 +
80 + resp.JVM.Memory.Used = resp.JVM.Memory.Total - resp.JVM.Memory.Free
81 +
82 + return stm.ToMap(resp), nil
83 +}
84 +
85 +func cleanName(name string) string {
86 + r := strings.NewReplacer(" ", "_", ".", "_", "\"", "", "'", "")
87 + return strings.ToLower(r.Replace(name))
88 +}
89 +
90 +func (t *Tomcat) queryServerStatus() (*serverStatusResponse, error) {
91 + req, err := web.NewHTTPRequestWithPath(t.Request, urlPathServerStatus)
92 + if err != nil {
93 + return nil, err
94 + }
95 +
96 + req.URL.RawQuery = urlQueryServerStatus
97 +
98 + var status serverStatusResponse
99 +
100 + if err := t.doOKDecode(req, &status); err != nil {
101 + return nil, err
102 + }
103 +
104 + return &status, nil
105 +}
106 +
107 +func (t *Tomcat) doOKDecode(req *http.Request, in interface{}) error {
108 + resp, err := t.httpClient.Do(req)
109 + if err != nil {
110 + return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
111 + }
112 + defer closeBody(resp)
113 +
114 + if resp.StatusCode != http.StatusOK {
115 + return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
116 + }
117 +
118 + if err := xml.NewDecoder(resp.Body).Decode(in); err != nil {
119 + return fmt.Errorf("error decoding XML response from '%s': %v", req.URL, err)
120 + }
121 +
122 + return nil
123 +}
124 +
125 +func closeBody(resp *http.Response) {
126 + if resp != nil && resp.Body != nil {
127 + _, _ = io.Copy(io.Discard, resp.Body)
128 + _ = resp.Body.Close()
129 + }
130 +}
src/go/plugin/go.d/modules/tomcat/config_schema.json new
+183
@@ -0,0 +1,183 @@
1 +{
2 + "jsonSchema": {
3 + "$schema": "http://json-schema.org/draft-07/schema#",
4 + "title": "Tomcat collector configuration.",
5 + "type": "object",
6 + "properties": {
7 + "update_every": {
8 + "title": "Update every",
9 + "description": "Data collection interval, measured in seconds.",
10 + "type": "integer",
11 + "minimum": 1,
12 + "default": 1
13 + },
14 + "url": {
15 + "title": "URL",
16 + "description": "The base URL of the Tomcat server.",
17 + "type": "string",
18 + "default": "http://127.0.0.1:8080",
19 + "format": "uri"
20 + },
21 + "timeout": {
22 + "title": "Timeout",
23 + "description": "The timeout in seconds for the HTTP request.",
24 + "type": "number",
25 + "minimum": 0.5,
26 + "default": 1
27 + },
28 + "not_follow_redirects": {
29 + "title": "Not follow redirects",
30 + "description": "If set, the client will not follow HTTP redirects automatically.",
31 + "type": "boolean"
32 + },
33 + "username": {
34 + "title": "Username",
35 + "description": "The username for basic authentication.",
36 + "type": "string",
37 + "sensitive": true
38 + },
39 + "password": {
40 + "title": "Password",
41 + "description": "The password for basic authentication.",
42 + "type": "string",
43 + "sensitive": true
44 + },
45 + "proxy_url": {
46 + "title": "Proxy URL",
47 + "description": "The URL of the proxy server.",
48 + "type": "string"
49 + },
50 + "proxy_username": {
51 + "title": "Proxy username",
52 + "description": "The username for proxy authentication.",
53 + "type": "string",
54 + "sensitive": true
55 + },
56 + "proxy_password": {
57 + "title": "Proxy password",
58 + "description": "The password for proxy authentication.",
59 + "type": "string",
60 + "sensitive": true
61 + },
62 + "headers": {
63 + "title": "Headers",
64 + "description": "Additional HTTP headers to include in the request.",
65 + "type": [
66 + "object",
67 + "null"
68 + ],
69 + "additionalProperties": {
70 + "type": "string"
71 + }
72 + },
73 + "tls_skip_verify": {
74 + "title": "Skip TLS verification",
75 + "description": "If set, TLS certificate verification will be skipped.",
76 + "type": "boolean"
77 + },
78 + "tls_ca": {
79 + "title": "TLS CA",
80 + "description": "The path to the CA certificate file for TLS verification.",
81 + "type": "string",
82 + "pattern": "^$|^/"
83 + },
84 + "tls_cert": {
85 + "title": "TLS certificate",
86 + "description": "The path to the client certificate file for TLS authentication.",
87 + "type": "string",
88 + "pattern": "^$|^/"
89 + },
90 + "tls_key": {
91 + "title": "TLS key",
92 + "description": "The path to the client key file for TLS authentication.",
93 + "type": "string",
94 + "pattern": "^$|^/"
95 + },
96 + "body": {
97 + "title": "Body",
98 + "type": "string"
99 + },
100 + "method": {
101 + "title": "Method",
102 + "type": "string"
103 + }
104 + },
105 + "required": [
106 + "url"
107 + ],
108 + "additionalProperties": false,
109 + "patternProperties": {
110 + "^name$": {}
111 + }
112 + },
113 + "uiSchema": {
114 + "uiOptions": {
115 + "fullPage": true
116 + },
117 + "body": {
118 + "ui:widget": "hidden"
119 + },
120 + "method": {
121 + "ui:widget": "hidden"
122 + },
123 + "timeout": {
124 + "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
125 + },
126 + "username": {
127 + "ui:widget": "password"
128 + },
129 + "proxy_username": {
130 + "ui:widget": "password"
131 + },
132 + "password": {
133 + "ui:widget": "password"
134 + },
135 + "proxy_password": {
136 + "ui:widget": "password"
137 + },
138 + "ui:flavour": "tabs",
139 + "ui:options": {
140 + "tabs": [
141 + {
142 + "title": "Base",
143 + "fields": [
144 + "update_every",
145 + "url",
146 + "timeout",
147 + "not_follow_redirects"
148 + ]
149 + },
150 + {
151 + "title": "Auth",
152 + "fields": [
153 + "username",
154 + "password"
155 + ]
156 + },
157 + {
158 + "title": "TLS",
159 + "fields": [
160 + "tls_skip_verify",
161 + "tls_ca",
162 + "tls_cert",
163 + "tls_key"
164 + ]
165 + },
166 + {
167 + "title": "Proxy",
168 + "fields": [
169 + "proxy_url",
170 + "proxy_username",
171 + "proxy_password"
172 + ]
173 + },
174 + {
175 + "title": "Headers",
176 + "fields": [
177 + "headers"
178 + ]
179 + }
180 + ]
181 + }
182 + }
183 +}
src/go/plugin/go.d/modules/tomcat/init.go new
+21
@@ -0,0 +1,21 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package tomcat
4 +
5 +import (
6 + "fmt"
7 + "net/http"
8 +
9 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
10 +)
11 +
12 +func (t *Tomcat) validateConfig() error {
13 + if t.URL == "" {
14 + return fmt.Errorf("url not set")
15 + }
16 + return nil
17 +}
18 +
19 +func (t *Tomcat) initHTTPClient() (*http.Client, error) {
20 + return web.NewHTTPClient(t.Client)
21 +}
src/go/plugin/go.d/modules/tomcat/metadata.yaml new
+241
@@ -0,0 +1,241 @@
1 +plugin_name: go.d.plugin
2 +modules:
3 + - meta:
4 + plugin_name: go.d.plugin
5 + module_name: tomcat
6 + monitored_instance:
7 + name: Tomcat
8 + link: "https://tomcat.apache.org/"
9 + categories:
10 + - data-collection.web-servers-and-web-proxies
11 + icon_filename: "tomcat.svg"
12 + related_resources:
13 + integrations:
14 + list: []
15 + info_provided_to_referring_integrations:
16 + description: ""
17 + keywords:
18 + - apache
19 + - tomcat
20 + - webserver
21 + - websocket
22 + - jakarta
23 + - javaEE
24 + most_popular: false
25 + overview:
26 + data_collection:
27 + metrics_description: |
28 + This collector monitors Tomcat metrics about bandwidth, processing time, threads and more.
29 + method_description: |
30 + It parses the information provided by the [Server Status](https://tomcat.apache.org/tomcat-10.0-doc/manager-howto.html#Server_Status) HTTP endpoint.
31 + supported_platforms:
32 + include: []
33 + exclude: []
34 + multi_instance: true
35 + additional_permissions:
36 + description: >
37 + By default, this Tomcat collector cannot access the server's status page.
38 + To enable data collection, you will need to configure access credentials with appropriate permissions.
39 + default_behavior:
40 + auto_detection:
41 + description: >
42 + If the Netdata agent and Tomcat are on the same host, the collector will attempt to connect to the Tomcat server's status page at `http://localhost:8080/manager/status?XML=true`.
43 + limits:
44 + description: ""
45 + performance_impact:
46 + description: ""
47 + setup:
48 + prerequisites:
49 + list:
50 + - title: Access to Tomcat Status Endpoint
51 + description: |
52 + The Netdata agent needs read-only access to its status endpoint to collect data from the Tomcat server.
53 +
54 + You can achieve this by creating a dedicated user named `netdata` with read-only permissions specifically for accessing the [Server Status](https://tomcat.apache.org/tomcat-10.0-doc/manager-howto.html#Server_Status) endpoint.
55 +
56 + Once you've created the `netdata` user, you'll need to configure the username and password in the collector configuration file.
57 + configuration:
58 + file:
59 + name: "go.d/tomcat.conf"
60 + options:
61 + description: |
62 + The following options can be defined globally: update_every, autodetection_retry.
63 + folding:
64 + title: "Config options"
65 + enabled: true
66 + list:
67 + - name: update_every
68 + description: Data collection frequency.
69 + default_value: 1
70 + required: false
71 + - name: autodetection_retry
72 + description: Recheck interval in seconds. Zero means no recheck will be scheduled.
73 + default_value: 0
74 + required: false
75 + - name: url
76 + description: Server URL.
77 + default_value: http://127.0.0.1:8080
78 + required: true
79 + - name: timeout
80 + description: HTTP request timeout.
81 + default_value: 1
82 + required: false
83 + - name: username
84 + description: Username for basic HTTP authentication.
85 + default_value: ""
86 + required: false
87 + - name: password
88 + description: Password for basic HTTP authentication.
89 + default_value: ""
90 + required: false
91 + - name: proxy_url
92 + description: Proxy URL.
93 + default_value: ""
94 + required: false
95 + - name: proxy_username
96 + description: Username for proxy basic HTTP authentication.
97 + default_value: ""
98 + required: false
99 + - name: proxy_password
100 + description: Password for proxy basic HTTP authentication.
101 + default_value: ""
102 + required: false
103 + - name: method
104 + description: HTTP request method.
105 + default_value: POST
106 + required: false
107 + - name: body
108 + description: HTTP request body.
109 + default_value: ""
110 + required: false
111 + - name: headers
112 + description: HTTP request headers.
113 + default_value: ""
114 + required: false
115 + - name: not_follow_redirects
116 + description: Redirect handling policy. Controls whether the client follows redirects.
117 + default_value: false
118 + required: false
119 + - name: tls_skip_verify
120 + description: Server certificate chain and hostname validation policy. Controls whether the client performs this check.
121 + default_value: false
122 + required: false
123 + - name: tls_ca
124 + description: Certification authority that the client uses when verifying the server's certificates.
125 + default_value: ""
126 + required: false
127 + - name: tls_cert
128 + description: Client TLS certificate.
129 + default_value: ""
130 + required: false
131 + - name: tls_key
132 + description: Client TLS key.
133 + default_value: ""
134 + required: false
135 + examples:
136 + folding:
137 + enabled: true
138 + title: "Config"
139 + list:
140 + - name: Basic
141 + description: A basic example configuration.
142 + folding:
143 + enabled: false
144 + config: |
145 + jobs:
146 + - name: local
147 + url: http://127.0.0.1:8080
148 + username: John
149 + password: Doe
150 + - name: Multi-instance
151 + description: |
152 + > **Note**: When you define multiple jobs, their names must be unique.
153 +
154 + Collecting metrics from local and remote instances.
155 + config: |
156 + jobs:
157 + - name: local
158 + url: http://127.0.0.1:8080
159 + username: admin1
160 + password: hackme1
161 +
162 + - name: remote
163 + url: http://192.0.2.1:8080
164 + username: admin2
165 + password: hackme2
166 + troubleshooting:
167 + problems:
168 + list: []
169 + alerts: []
170 + metrics:
171 + folding:
172 + title: Metrics
173 + enabled: false
174 + description: ""
175 + availability: []
176 + scopes:
177 + - name: global
178 + description: "These metrics refer to the entire monitored application."
179 + labels: []
180 + metrics:
181 + - name: tomcat.jvm_memory_usage
182 + description: Requests
183 + unit: bytes
184 + chart_type: stacked
185 + dimensions:
186 + - name: free
187 + - name: used
188 + - name: jvm memory pool
189 + description: "These metrics refer to the JVM memory pool."
190 + labels:
191 + - name: mempool_name
192 + description: Memory Pool name.
193 + - name: mempool_type
194 + description: Memory Pool type.
195 + metrics:
196 + - name: tomcat.jvm_mem_pool_memory_usage
197 + description: JVM Mem Pool Memory Usage
198 + unit: bytes
199 + chart_type: area
200 + dimensions:
201 + - name: commited
202 + - name: used
203 + - name: max
204 + - name: connector
205 + description: "These metrics refer to the connector."
206 + labels:
207 + - name: connector_name
208 + description: Connector name.
209 + metrics:
210 + - name: tomcat.connector_requests
211 + description: Connector Requests
212 + unit: requests/s
213 + chart_type: line
214 + dimensions:
215 + - name: requests
216 + - name: tomcat.connector_bandwidth
217 + description: Connector Bandwidth
218 + unit: bytes/s
219 + chart_type: area
220 + dimensions:
221 + - name: received
222 + - name: sent
223 + - name: tomcat.connector_requests_processing_time
224 + description: Connector Requests Processing Time
225 + unit: milliseconds
226 + chart_type: line
227 + dimensions:
228 + - name: processing_time
229 + - name: tomcat.connector_errors
230 + description: Connector Errors
231 + unit: errors/s
232 + chart_type: line
233 + dimensions:
234 + - name: errors
235 + - name: tomcat.connector_request_threads
236 + description: Connector Request Threads
237 + unit: threads
238 + chart_type: stacked
239 + dimensions:
240 + - name: idle
241 + - name: busy
src/go/plugin/go.d/modules/tomcat/status_response.go new
+51
@@ -0,0 +1,51 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package tomcat
4 +
5 +import "encoding/xml"
6 +
7 +type serverStatusResponse struct {
8 + XMLName xml.Name `xml:"status"`
9 +
10 + JVM struct {
11 + Memory struct {
12 + Used int64 `stm:"used"` // calculated manually
13 + Free int64 `xml:"free,attr" stm:"free"`
14 + Total int64 `xml:"total,attr" stm:"total"`
15 + Max int64 `xml:"max,attr"`
16 + } `xml:"memory" stm:"memory"`
17 +
18 + MemoryPools []struct {
19 + STMKey string
20 +
21 + Name string `xml:"name,attr"`
22 + Type string `xml:"type,attr"`
23 + UsageInit int64 `xml:"usageInit,attr"`
24 + UsageCommitted int64 `xml:"usageCommitted,attr" stm:"commited"`
25 + UsageMax int64 `xml:"usageMax,attr" stm:"max"`
26 + UsageUsed int64 `xml:"usageUsed,attr" stm:"used"`
27 + } `xml:"memorypool" stm:"memorypool"`
28 + } `xml:"jvm" stm:"jvm"`
29 +
30 + Connectors []struct {
31 + STMKey string
32 +
33 + Name string `xml:"name,attr"`
34 +
35 + ThreadInfo struct {
36 + MaxThreads int64 `xml:"maxThreads,attr"`
37 + CurrentThreadCount int64 `xml:"currentThreadCount,attr" stm:"count"`
38 + CurrentThreadsBusy int64 `xml:"currentThreadsBusy,attr" stm:"busy"`
39 + CurrentThreadsIdle int64 `stm:"idle"` // calculated manually
40 + } `xml:"threadInfo" stm:"thread_info"`
41 +
42 + RequestInfo struct {
43 + MaxTime int64 `xml:"maxTime,attr"`
44 + ProcessingTime int64 `xml:"processingTime,attr" stm:"processing_time"`
45 + RequestCount int64 `xml:"requestCount,attr" stm:"request_count"`
46 + ErrorCount int64 `xml:"errorCount,attr" stm:"error_count"`
47 + BytesReceived int64 `xml:"bytesReceived,attr" stm:"bytes_received"`
48 + BytesSent int64 `xml:"bytesSent,attr" stm:"bytes_sent"`
49 + } `xml:"requestInfo" stm:"request_info"`
50 + } `xml:"connector" stm:"connector"`
51 +}
src/go/plugin/go.d/modules/tomcat/testdata/config.json new
+20
@@ -0,0 +1,20 @@
1 +{
2 + "update_every": 123,
3 + "url": "ok",
4 + "body": "ok",
5 + "method": "ok",
6 + "headers": {
7 + "ok": "ok"
8 + },
9 + "username": "ok",
10 + "password": "ok",
11 + "proxy_url": "ok",
12 + "proxy_username": "ok",
13 + "proxy_password": "ok",
14 + "timeout": 123.123,
15 + "not_follow_redirects": true,
16 + "tls_ca": "ok",
17 + "tls_cert": "ok",
18 + "tls_key": "ok",
19 + "tls_skip_verify": true
20 +}
src/go/plugin/go.d/modules/tomcat/testdata/config.yaml new
+17
@@ -0,0 +1,17 @@
1 +update_every: 123
2 +url: "ok"
3 +body: "ok"
4 +method: "ok"
5 +headers:
6 + ok: "ok"
7 +username: "ok"
8 +password: "ok"
9 +proxy_url: "ok"
10 +proxy_username: "ok"
11 +proxy_password: "ok"
12 +timeout: 123.123
13 +not_follow_redirects: yes
14 +tls_ca: "ok"
15 +tls_cert: "ok"
16 +tls_key: "ok"
17 +tls_skip_verify: yes
src/go/plugin/go.d/modules/tomcat/testdata/server_status.xml new
+54
@@ -0,0 +1,54 @@
1 +<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="/manager/xform.xsl" ?>
2 +<status>
3 + <jvm>
4 + <memory free='144529816' total='179306496' max='1914699776'/>
5 + <memorypool name='G1 Eden Space' type='Heap memory' usageInit='24117248' usageCommitted='108003328'
6 + usageMax='-1' usageUsed='23068672'/>
7 + <memorypool name='G1 Old Gen' type='Heap memory' usageInit='97517568' usageCommitted='66060288'
8 + usageMax='1914699776' usageUsed='6175120'/>
9 + <memorypool name='G1 Survivor Space' type='Heap memory' usageInit='0' usageCommitted='5242880' usageMax='-1'
10 + usageUsed='5040192'/>
11 + <memorypool name='CodeHeap &apos;non-nmethods&apos;' type='Non-heap memory' usageInit='2555904'
12 + usageCommitted='2555904' usageMax='5840896' usageUsed='1477888'/>
13 + <memorypool name='CodeHeap &apos;non-profiled nmethods&apos;' type='Non-heap memory' usageInit='2555904'
14 + usageCommitted='4587520' usageMax='122908672' usageUsed='4536704'/>
15 + <memorypool name='CodeHeap &apos;profiled nmethods&apos;' type='Non-heap memory' usageInit='2555904'
16 + usageCommitted='13172736' usageMax='122908672' usageUsed='13132032'/>
17 + <memorypool name='Compressed Class Space' type='Non-heap memory' usageInit='0' usageCommitted='1900544'
18 + usageMax='1073741824' usageUsed='1712872'/>
19 + <memorypool name='Metaspace' type='Non-heap memory' usageInit='0' usageCommitted='18939904' usageMax='-1'
20 + usageUsed='18537336'/>
21 + </jvm>
22 + <connector name='"http-nio-8080"'>
23 + <threadInfo maxThreads="200" currentThreadCount="10" currentThreadsBusy="1"/>
24 + <requestInfo maxTime="247" processingTime="28326" requestCount="4838" errorCount="24" bytesReceived="0"
25 + bytesSent="12174519"/>
26 + <workers>
27 + <worker stage="R" requestProcessingTime="0" requestBytesSent="0" requestBytesReceived="0" remoteAddr="&#63;"
28 + virtualHost="&#63;" method="&#63;" currentUri="&#63;" currentQueryString="&#63;" protocol="&#63;"/>
29 + <worker stage="R" requestProcessingTime="0" requestBytesSent="0" requestBytesReceived="0" remoteAddr="&#63;"
30 + virtualHost="&#63;" method="&#63;" currentUri="&#63;" currentQueryString="&#63;" protocol="&#63;"/>
31 + <worker stage="R" requestProcessingTime="0" requestBytesSent="0" requestBytesReceived="0" remoteAddr="&#63;"
32 + virtualHost="&#63;" method="&#63;" currentUri="&#63;" currentQueryString="&#63;" protocol="&#63;"/>
33 + <worker stage="S" requestProcessingTime="30" requestBytesSent="0" requestBytesReceived="0"
34 + remoteAddr="127.0.0.1" virtualHost="127.0.0.1" method="GET" currentUri="&#47;manager&#47;status"
35 + currentQueryString="XML=true" protocol="HTTP&#47;1.1"/>
36 + </workers>
37 + </connector>
38 + <connector name='"http-nio-8081"'>
39 + <threadInfo maxThreads="200" currentThreadCount="10" currentThreadsBusy="1"/>
40 + <requestInfo maxTime="247" processingTime="28326" requestCount="4838" errorCount="24" bytesReceived="0"
41 + bytesSent="12174519"/>
42 + <workers>
43 + <worker stage="R" requestProcessingTime="0" requestBytesSent="0" requestBytesReceived="0" remoteAddr="&#63;"
44 + virtualHost="&#63;" method="&#63;" currentUri="&#63;" currentQueryString="&#63;" protocol="&#63;"/>
45 + <worker stage="R" requestProcessingTime="0" requestBytesSent="0" requestBytesReceived="0" remoteAddr="&#63;"
46 + virtualHost="&#63;" method="&#63;" currentUri="&#63;" currentQueryString="&#63;" protocol="&#63;"/>
47 + <worker stage="R" requestProcessingTime="0" requestBytesSent="0" requestBytesReceived="0" remoteAddr="&#63;"
48 + virtualHost="&#63;" method="&#63;" currentUri="&#63;" currentQueryString="&#63;" protocol="&#63;"/>
49 + <worker stage="S" requestProcessingTime="30" requestBytesSent="0" requestBytesReceived="0"
50 + remoteAddr="127.0.0.1" virtualHost="127.0.0.1" method="GET" currentUri="&#47;manager&#47;status"
51 + currentQueryString="XML=true" protocol="HTTP&#47;1.1"/>
52 + </workers>
53 + </connector>
54 +</status>
src/go/plugin/go.d/modules/tomcat/tomcat.go new
+120
@@ -0,0 +1,120 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package tomcat
4 +
5 +import (
6 + _ "embed"
7 + "errors"
8 + "net/http"
9 + "time"
10 +
11 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
12 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
13 +)
14 +
15 +//go:embed "config_schema.json"
16 +var configSchema string
17 +
18 +func init() {
19 + module.Register("tomcat", module.Creator{
20 + JobConfigSchema: configSchema,
21 + Create: func() module.Module { return New() },
22 + Config: func() any { return &Config{} },
23 + })
24 +}
25 +
26 +func New() *Tomcat {
27 + return &Tomcat{
28 + Config: Config{
29 + HTTP: web.HTTP{
30 + Request: web.Request{
31 + URL: "http://127.0.0.1:8080",
32 + },
33 + Client: web.Client{
34 + Timeout: web.Duration(time.Second * 1),
35 + },
36 + },
37 + },
38 + charts: defaultCharts.Copy(),
39 + seenConnectors: make(map[string]bool),
40 + seenMemPools: make(map[string]bool),
41 + }
42 +}
43 +
44 +type Config struct {
45 + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
46 + web.HTTP `yaml:",inline" json:""`
47 +}
48 +
49 +type Tomcat struct {
50 + module.Base
51 + Config `yaml:",inline" json:""`
52 +
53 + charts *module.Charts
54 +
55 + httpClient *http.Client
56 +
57 + seenConnectors map[string]bool
58 + seenMemPools map[string]bool
59 +}
60 +
61 +func (t *Tomcat) Configuration() any {
62 + return t.Config
63 +}
64 +
65 +func (t *Tomcat) Init() error {
66 + if err := t.validateConfig(); err != nil {
67 + t.Errorf("config validation: %v", err)
68 + return err
69 + }
70 +
71 + httpClient, err := t.initHTTPClient()
72 + if err != nil {
73 + t.Errorf("init HTTP client: %v", err)
74 + return err
75 + }
76 +
77 + t.httpClient = httpClient
78 +
79 + t.Debugf("using URL %s", t.URL)
80 + t.Debugf("using timeout: %s", t.Timeout)
81 +
82 + return nil
83 +}
84 +
85 +func (t *Tomcat) Check() error {
86 + mx, err := t.collect()
87 + if err != nil {
88 + t.Error(err)
89 + return err
90 + }
91 +
92 + if len(mx) == 0 {
93 + return errors.New("no metrics collected")
94 + }
95 +
96 + return nil
97 +}
98 +
99 +func (t *Tomcat) Charts() *module.Charts {
100 + return t.charts
101 +}
102 +
103 +func (t *Tomcat) Collect() map[string]int64 {
104 + mx, err := t.collect()
105 + if err != nil {
106 + t.Error(err)
107 + }
108 +
109 + if len(mx) == 0 {
110 + return nil
111 + }
112 +
113 + return mx
114 +}
115 +
116 +func (t *Tomcat) Cleanup() {
117 + if t.httpClient != nil {
118 + t.httpClient.CloseIdleConnections()
119 + }
120 +}
src/go/plugin/go.d/modules/tomcat/tomcat_test.go new
+272
@@ -0,0 +1,272 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package tomcat
4 +
5 +import (
6 + "net/http"
7 + "net/http/httptest"
8 + "os"
9 + "testing"
10 +
11 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
12 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
13 +
14 + "github.com/stretchr/testify/assert"
15 + "github.com/stretchr/testify/require"
16 +)
17 +
18 +var (
19 + dataConfigJSON, _ = os.ReadFile("testdata/config.json")
20 + dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
21 +
22 + dataServerStatus, _ = os.ReadFile("testdata/server_status.xml")
23 +)
24 +
25 +func Test_testDataIsValid(t *testing.T) {
26 + for name, data := range map[string][]byte{
27 + "dataConfigJSON": dataConfigJSON,
28 + "dataConfigYAML": dataConfigYAML,
29 + "dataServerStatus": dataServerStatus,
30 + } {
31 + require.NotNil(t, data, name)
32 + }
33 +}
34 +
35 +func TestTomcat_ConfigurationSerialize(t *testing.T) {
36 + module.TestConfigurationSerialize(t, &Tomcat{}, dataConfigJSON, dataConfigYAML)
37 +}
38 +
39 +func TestTomcat_Init(t *testing.T) {
40 + tests := map[string]struct {
41 + wantFail bool
42 + config Config
43 + }{
44 + "success with default": {
45 + wantFail: false,
46 + config: New().Config,
47 + },
48 + "fail when URL not set": {
49 + wantFail: true,
50 + config: Config{
51 + HTTP: web.HTTP{
52 + Request: web.Request{URL: ""},
53 + },
54 + },
55 + },
56 + }
57 +
58 + for name, test := range tests {
59 + t.Run(name, func(t *testing.T) {
60 + tomcat := New()
61 + tomcat.Config = test.config
62 +
63 + if test.wantFail {
64 + assert.Error(t, tomcat.Init())
65 + } else {
66 + assert.NoError(t, tomcat.Init())
67 + }
68 + })
69 + }
70 +}
71 +
72 +func TestTomcat_Charts(t *testing.T) {
73 + assert.NotNil(t, New().Charts())
74 +}
75 +
76 +func TestTomcat_Check(t *testing.T) {
77 + tests := map[string]struct {
78 + wantFail bool
79 + prepare func(t *testing.T) (*Tomcat, func())
80 + }{
81 + "success case": {
82 + wantFail: false,
83 + prepare: prepareCaseSuccess,
84 + },
85 + "fails on unexpected xml response": {
86 + wantFail: true,
87 + prepare: prepareCaseUnexpectedXMLResponse,
88 + },
89 + "fails on invalid format response": {
90 + wantFail: true,
91 + prepare: prepareCaseInvalidFormatResponse,
92 + },
93 + "fails on connection refused": {
94 + wantFail: true,
95 + prepare: prepareCaseConnectionRefused,
96 + },
97 + }
98 +
99 + for name, test := range tests {
100 + t.Run(name, func(t *testing.T) {
101 + tomcat, cleanup := test.prepare(t)
102 + defer cleanup()
103 +
104 + if test.wantFail {
105 + assert.Error(t, tomcat.Check())
106 + } else {
107 + assert.NoError(t, tomcat.Check())
108 + }
109 + })
110 + }
111 +}
112 +
113 +func TestTomcat_Collect(t *testing.T) {
114 + tests := map[string]struct {
115 + prepare func(t *testing.T) (tomcat *Tomcat, cleanup func())
116 + wantMetrics map[string]int64
117 + wantCharts int
118 + }{
119 + "success case": {
120 + prepare: prepareCaseSuccess,
121 + wantCharts: len(defaultCharts) + len(jvmMemoryPoolChartsTmpl)*8 + len(connectorChartsTmpl)*2,
122 + wantMetrics: map[string]int64{
123 + "connector_http-nio-8080_request_info_bytes_received": 0,
124 + "connector_http-nio-8080_request_info_bytes_sent": 12174519,
125 + "connector_http-nio-8080_request_info_error_count": 24,
126 + "connector_http-nio-8080_request_info_processing_time": 28326,
127 + "connector_http-nio-8080_request_info_request_count": 4838,
128 + "connector_http-nio-8080_thread_info_busy": 1,
129 + "connector_http-nio-8080_thread_info_count": 10,
130 + "connector_http-nio-8080_thread_info_idle": 9,
131 + "connector_http-nio-8081_request_info_bytes_received": 0,
132 + "connector_http-nio-8081_request_info_bytes_sent": 12174519,
133 + "connector_http-nio-8081_request_info_error_count": 24,
134 + "connector_http-nio-8081_request_info_processing_time": 28326,
135 + "connector_http-nio-8081_request_info_request_count": 4838,
136 + "connector_http-nio-8081_thread_info_busy": 1,
137 + "connector_http-nio-8081_thread_info_count": 10,
138 + "connector_http-nio-8081_thread_info_idle": 9,
139 + "jvm_memory_free": 144529816,
140 + "jvm_memory_total": 179306496,
141 + "jvm_memory_used": 34776680,
142 + "jvm_memorypool_codeheap_non-nmethods_commited": 2555904,
143 + "jvm_memorypool_codeheap_non-nmethods_max": 5840896,
144 + "jvm_memorypool_codeheap_non-nmethods_used": 1477888,
145 + "jvm_memorypool_codeheap_non-profiled_nmethods_commited": 4587520,
146 + "jvm_memorypool_codeheap_non-profiled_nmethods_max": 122908672,
147 + "jvm_memorypool_codeheap_non-profiled_nmethods_used": 4536704,
148 + "jvm_memorypool_codeheap_profiled_nmethods_commited": 13172736,
149 + "jvm_memorypool_codeheap_profiled_nmethods_max": 122908672,
150 + "jvm_memorypool_codeheap_profiled_nmethods_used": 13132032,
151 + "jvm_memorypool_compressed_class_space_commited": 1900544,
152 + "jvm_memorypool_compressed_class_space_max": 1073741824,
153 + "jvm_memorypool_compressed_class_space_used": 1712872,
154 + "jvm_memorypool_g1_eden_space_commited": 108003328,
155 + "jvm_memorypool_g1_eden_space_max": -1,
156 + "jvm_memorypool_g1_eden_space_used": 23068672,
157 + "jvm_memorypool_g1_old_gen_commited": 66060288,
158 + "jvm_memorypool_g1_old_gen_max": 1914699776,
159 + "jvm_memorypool_g1_old_gen_used": 6175120,
160 + "jvm_memorypool_g1_survivor_space_commited": 5242880,
161 + "jvm_memorypool_g1_survivor_space_max": -1,
162 + "jvm_memorypool_g1_survivor_space_used": 5040192,
163 + "jvm_memorypool_metaspace_commited": 18939904,
164 + "jvm_memorypool_metaspace_max": -1,
165 + "jvm_memorypool_metaspace_used": 18537336,
166 + },
167 + },
168 + "fails on unexpected xml response": {
169 + prepare: prepareCaseUnexpectedXMLResponse,
170 + },
171 + "fails on invalid format response": {
172 + prepare: prepareCaseInvalidFormatResponse,
173 + },
174 + "fails on connection refused": {
175 + prepare: prepareCaseConnectionRefused,
176 + },
177 + }
178 +
179 + for name, test := range tests {
180 + t.Run(name, func(t *testing.T) {
181 + tomcat, cleanup := test.prepare(t)
182 + defer cleanup()
183 +
184 + mx := tomcat.Collect()
185 +
186 + require.Equal(t, test.wantMetrics, mx)
187 +
188 + if len(test.wantMetrics) > 0 {
189 + assert.Equal(t, test.wantCharts, len(*tomcat.Charts()))
190 + module.TestMetricsHasAllChartsDims(t, tomcat.Charts(), mx)
191 + }
192 + })
193 + }
194 +}
195 +
196 +func prepareCaseSuccess(t *testing.T) (*Tomcat, func()) {
197 + t.Helper()
198 + srv := httptest.NewServer(http.HandlerFunc(
199 + func(w http.ResponseWriter, r *http.Request) {
200 + switch r.URL.Path {
201 + case urlPathServerStatus:
202 + if r.URL.RawQuery != urlQueryServerStatus {
203 + w.WriteHeader(http.StatusNotFound)
204 + } else {
205 + _, _ = w.Write(dataServerStatus)
206 + }
207 + default:
208 + w.WriteHeader(http.StatusNotFound)
209 + }
210 + }))
211 + tomcat := New()
212 + tomcat.URL = srv.URL
213 + require.NoError(t, tomcat.Init())
214 +
215 + return tomcat, srv.Close
216 +}
217 +
218 +func prepareCaseConnectionRefused(t *testing.T) (*Tomcat, func()) {
219 + t.Helper()
220 + tomcat := New()
221 + tomcat.URL = "http://127.0.0.1:65001"
222 + require.NoError(t, tomcat.Init())
223 +
224 + return tomcat, func() {}
225 +}
226 +
227 +func prepareCaseUnexpectedXMLResponse(t *testing.T) (*Tomcat, func()) {
228 + t.Helper()
229 + resp := `
230 +<?xml version="1.0" encoding="UTF-8" ?>
231 + <root>
232 + <elephant>
233 + <burn>false</burn>
234 + <mountain>true</mountain>
235 + <fog>false</fog>
236 + <skin>-1561907625</skin>
237 + <burst>anyway</burst>
238 + <shadow>1558616893</shadow>
239 + </elephant>
240 + <start>ever</start>
241 + <base>2093056027</base>
242 + <mission>-2007590351</mission>
243 + <victory>999053756</victory>
244 + <die>false</die>
245 + </root>
246 +
247 +`
248 + srv := httptest.NewServer(http.HandlerFunc(
249 + func(w http.ResponseWriter, r *http.Request) {
250 + _, _ = w.Write([]byte(resp))
251 + }))
252 +
253 + tomcat := New()
254 + tomcat.URL = srv.URL
255 + require.NoError(t, tomcat.Init())
256 +
257 + return tomcat, srv.Close
258 +}
259 +
260 +func prepareCaseInvalidFormatResponse(t *testing.T) (*Tomcat, func()) {
261 + t.Helper()
262 + srv := httptest.NewServer(http.HandlerFunc(
263 + func(w http.ResponseWriter, r *http.Request) {
264 + _, _ = w.Write([]byte("hello and\n goodbye"))
265 + }))
266 +
267 + tomcat := New()
268 + tomcat.URL = srv.URL
269 + require.NoError(t, tomcat.Init())
270 +
271 + return tomcat, srv.Close
272 +}