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 'non-nmethods'' type='Non-heap memory' usageInit='2555904'
12
+ usageCommitted='2555904' usageMax='5840896' usageUsed='1477888'/>
13
+ <memorypool name='CodeHeap 'non-profiled nmethods'' type='Non-heap memory' usageInit='2555904'
14
+ usageCommitted='4587520' usageMax='122908672' usageUsed='4536704'/>
15
+ <memorypool name='CodeHeap 'profiled nmethods'' 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="?"
28
+ virtualHost="?" method="?" currentUri="?" currentQueryString="?" protocol="?"/>
29
+ <worker stage="R" requestProcessingTime="0" requestBytesSent="0" requestBytesReceived="0" remoteAddr="?"
30
+ virtualHost="?" method="?" currentUri="?" currentQueryString="?" protocol="?"/>
31
+ <worker stage="R" requestProcessingTime="0" requestBytesSent="0" requestBytesReceived="0" remoteAddr="?"
32
+ virtualHost="?" method="?" currentUri="?" currentQueryString="?" protocol="?"/>
33
+ <worker stage="S" requestProcessingTime="30" requestBytesSent="0" requestBytesReceived="0"
34
+ remoteAddr="127.0.0.1" virtualHost="127.0.0.1" method="GET" currentUri="/manager/status"
35
+ currentQueryString="XML=true" protocol="HTTP/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="?"
44
+ virtualHost="?" method="?" currentUri="?" currentQueryString="?" protocol="?"/>
45
+ <worker stage="R" requestProcessingTime="0" requestBytesSent="0" requestBytesReceived="0" remoteAddr="?"
46
+ virtualHost="?" method="?" currentUri="?" currentQueryString="?" protocol="?"/>
47
+ <worker stage="R" requestProcessingTime="0" requestBytesSent="0" requestBytesReceived="0" remoteAddr="?"
48
+ virtualHost="?" method="?" currentUri="?" currentQueryString="?" protocol="?"/>
49
+ <worker stage="S" requestProcessingTime="30" requestBytesSent="0" requestBytesReceived="0"
50
+ remoteAddr="127.0.0.1" virtualHost="127.0.0.1" method="GET" currentUri="/manager/status"
51
+ currentQueryString="XML=true" protocol="HTTP/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
+}