@cryptotaxi247 / netdata-1 / commits / 0b45d3be8

feat(go.d.plugin): add MaxScale collector (#18859)

Ilya Mashchenko committed Oct 24, 2024 at 16:47 UTC 0b45d3be87e43e87ea29100b34220a26691e09c2
19 files changed +1740 -2
src/go/plugin/go.d/README.md
+1
@@ -89,6 +89,7 @@ see the appropriate collector readme.
89 | [logind](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/logind) | systemd-logind |
90 | [logstash](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/logstash) | Logstash |
91 | [lvm](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/lvm) | LVM logical volumes |
92 +| [maxscale](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/maxscale) | MaxScale |
93 | [megacli](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/megacli) | MegaCli Hardware Raid |
94 | [memcached](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/memcached) | Memcached |
95 | [mongoDB](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/mongodb) | MongoDB |
src/go/plugin/go.d/config/go.d.conf
+1
@@ -64,6 +64,7 @@ modules:
64 # logind: yes
65 # logstash: yes
66 # lvm: yes
67 +# maxscale: yes
68 # megacli: yes
69 # memcached: yes
70 # mongodb: yes
src/go/plugin/go.d/config/go.d/maxscale.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/maxscale#readme
3 +
4 +#jobs:
5 +# - name: local
6 +# url: http://127.0.0.1:8989
src/go/plugin/go.d/config/go.d/sd/docker.conf
+7
@@ -50,6 +50,8 @@ classify:
50 expr: '{{ and (eq .PrivatePort "5001") (match "sp" .Image "ipfs/kubo ipfs/kubo:*") }}'
51 - tags: "lighttpd"
52 expr: '{{ match "sp" .Image "*/lighttpd */lighttpd:*" }}'
53 + - tags: "maxscale"
54 + expr: '{{ or (eq .PrivatePort "8989") (match "sp" .Image "mariadb/maxscale mariadb/maxscale:*") }}'
55 - tags: "memcached"
56 expr: '{{ or (eq .PrivatePort "11211") (match "sp" .Image "memcached memcached:* */memcached */memcached:*") }}'
57 - tags: "mongodb"
@@ -175,6 +177,11 @@ compose:
177 module: lighttpd
178 name: docker_{{.Name}}
179 url: http://{{.Address}}/server-status?auto
180 + - selector: "maxscale"
181 + template: |
182 + module: maxscale
183 + name: docker_{{.Name}}
184 + url: http://{{.Address}}
185 - selector: "memcached"
186 template: |
187 module: memcached
src/go/plugin/go.d/config/go.d/sd/net_listeners.conf
+7
@@ -82,6 +82,8 @@ classify:
82 expr: '{{ and (eq .Port "80" "8080") (eq .Comm "lighttpd") }}'
83 - tags: "logstash"
84 expr: '{{ and (eq .Port "9600") (glob .Cmdline "*logstash*") }}'
85 + - tags: "maxscale"
86 + expr: '{{ or (eq .Port "8989") (eq .Comm "maxscale") }}'
87 - tags: "memcached"
88 expr: '{{ or (eq .Port "11211") (eq .Comm "memcached") }}'
89 - tags: "mongodb"
@@ -359,6 +361,11 @@ compose:
361 module: logstash
362 name: local
363 url: http://{{.Address}}
364 + - selector: "maxscale"
365 + template: |
366 + module: maxscale
367 + name: local
368 + url: http://{{.Address}}
369 - selector: "memcached"
370 template: |
371 module: memcached
src/go/plugin/go.d/modules/ceph/metadata.yaml
+2 -2
@@ -24,7 +24,7 @@ modules:
24 This collector monitors the overall health status and performance of your Ceph clusters.
25 It gathers key metrics for the entire cluster, individual Pools, and OSDs.
26 method_description: |
27 - It collects metrics by periodically issuing HTTP GET requests to the Ceph Manager [RESP API](https://docs.ceph.com/en/reef/mgr/ceph_api/#):
27 + It collects metrics by periodically issuing HTTP GET requests to the Ceph Manager [REST API](https://docs.ceph.com/en/reef/mgr/ceph_api/#):
28
29 - [/api/monitor](https://docs.ceph.com/en/reef/mgr/ceph_api/#get--api-monitor) (only once to get the Ceph cluster id (fsid))
30 - [/api/health/minimal](https://docs.ceph.com/en/reef/mgr/ceph_api/#get--api-health-minimal)
@@ -44,7 +44,7 @@ modules:
44 - localhost that are listening on port 8443
45 - within Docker containers
46
47 - > **Note that the Ceph RESP API requires a username and password**.
47 + > **Note that the Ceph REST API requires a username and password**.
48 > While Netdata can automatically detect Ceph Manager instances and create data collection jobs, these jobs will fail unless you provide the necessary credentials.
49 limits:
50 description: ""
src/go/plugin/go.d/modules/init.go
+1
@@ -55,6 +55,7 @@ import (
55 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/logind"
56 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/logstash"
57 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/lvm"
58 + _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/maxscale"
59 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/megacli"
60 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/memcached"
61 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/mongodb"
src/go/plugin/go.d/modules/maxscale/charts.go new
+224
@@ -0,0 +1,224 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package maxscale
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 + prioPollEvents = module.Priority + iota
14 +
15 + prioSessions
16 + prioZombies
17 +
18 + prioServerState
19 + prioServerConnections
20 +
21 + prioThreadsByState
22 +
23 + prioCurrentFDs
24 +
25 + prioQCCacheEfficiency
26 + prioQCCacheOperations
27 +
28 + prioUptime
29 +)
30 +
31 +var charts = module.Charts{
32 + pollEventsChart.Copy(),
33 + currentSessionsChart.Copy(),
34 + currentZombieConnectionsChart.Copy(),
35 + threadsByStateChart.Copy(),
36 + currentFDsChart.Copy(),
37 + qcCacheEfficiencyChart.Copy(),
38 + qcCacheOperationsChart.Copy(),
39 + uptimeChart.Copy(),
40 +}
41 +
42 +var (
43 + pollEventsChart = module.Chart{
44 + ID: "poll_events",
45 + Title: "Poll Events",
46 + Units: "events/s",
47 + Fam: "poll events",
48 + Ctx: "maxscale.poll_events",
49 + Priority: prioPollEvents,
50 + Dims: module.Dims{
51 + {ID: "threads_reads", Name: "reads", Algo: module.Incremental},
52 + {ID: "threads_writes", Name: "writes", Algo: module.Incremental},
53 + {ID: "threads_accepts", Name: "accepts", Algo: module.Incremental},
54 + {ID: "threads_errors", Name: "errors", Algo: module.Incremental},
55 + {ID: "threads_hangups", Name: "hangups", Algo: module.Incremental},
56 + },
57 + }
58 +
59 + currentSessionsChart = module.Chart{
60 + ID: "current_sessions",
61 + Title: "Curren Sessions",
62 + Units: "sessions",
63 + Fam: "sessions",
64 + Ctx: "maxscale.current_sessions",
65 + Priority: prioSessions,
66 + Dims: module.Dims{
67 + {ID: "threads_sessions", Name: "sessions"},
68 + },
69 + }
70 + currentZombieConnectionsChart = module.Chart{
71 + ID: "current_zombie_connections",
72 + Title: "Current Zombie Connections",
73 + Units: "connections",
74 + Fam: "sessions",
75 + Ctx: "maxscale.current_zombie_connections",
76 + Priority: prioZombies,
77 + Dims: module.Dims{
78 + {ID: "threads_zombies", Name: "zombie"},
79 + },
80 + }
81 +
82 + threadsByStateChart = func() module.Chart {
83 + chart := module.Chart{
84 + ID: "threads_by_state",
85 + Title: "Threads Count by State",
86 + Units: "threads",
87 + Fam: "threads",
88 + Ctx: "maxscale.threads_by_state",
89 + Priority: prioThreadsByState,
90 + Type: module.Stacked,
91 + }
92 + for _, v := range threadStates {
93 + chart.Dims = append(chart.Dims, &module.Dim{
94 + ID: "threads_state_" + v,
95 + Name: strings.ToLower(v),
96 + })
97 + }
98 + return chart
99 + }()
100 +
101 + currentFDsChart = module.Chart{
102 + ID: "current_file_descriptors",
103 + Title: "Current Managed File Descriptors",
104 + Units: "fds",
105 + Fam: "fds",
106 + Ctx: "maxscale.current_fds",
107 + Priority: prioCurrentFDs,
108 + Dims: module.Dims{
109 + {ID: "threads_current_fds", Name: "managed"},
110 + },
111 + }
112 +
113 + qcCacheEfficiencyChart = module.Chart{
114 + ID: "qc_cache_efficiency",
115 + Title: "QC Cache Efficiency",
116 + Units: "requests/s",
117 + Fam: "qc cache",
118 + Ctx: "maxscale.qc_cache_efficiency",
119 + Priority: prioQCCacheEfficiency,
120 + Type: module.Stacked,
121 + Dims: module.Dims{
122 + {ID: "threads_qc_cache_hits", Name: "hits", Algo: module.Incremental},
123 + {ID: "threads_qc_cache_misses", Name: "misses", Algo: module.Incremental},
124 + },
125 + }
126 + qcCacheOperationsChart = module.Chart{
127 + ID: "qc_cache_operations",
128 + Title: "QC Cache Operations",
129 + Units: "operations/s",
130 + Fam: "qc cache",
131 + Ctx: "maxscale.qc_cache_operations",
132 + Priority: prioQCCacheOperations,
133 + Type: module.Stacked,
134 + Dims: module.Dims{
135 + {ID: "threads_qc_cache_inserts", Name: "inserts", Algo: module.Incremental},
136 + {ID: "threads_qc_cache_evictions", Name: "evictions", Algo: module.Incremental},
137 + },
138 + }
139 +
140 + uptimeChart = module.Chart{
141 + ID: "uptime",
142 + Title: "Uptime",
143 + Units: "seconds",
144 + Fam: "uptime",
145 + Ctx: "maxscale.uptime",
146 + Priority: prioUptime,
147 + Dims: module.Dims{
148 + {ID: "uptime"},
149 + },
150 + }
151 +)
152 +
153 +var serverChartsTmpl = module.Charts{
154 + serverStateChartTmpl.Copy(),
155 + serverCurrentConnectionsChartTmpl.Copy(),
156 +}
157 +
158 +var (
159 + serverStateChartTmpl = func() module.Chart {
160 + chart := module.Chart{
161 + ID: "server_%s_state",
162 + Title: "Server State",
163 + Units: "state",
164 + Fam: "servers",
165 + Ctx: "maxscale.server_state",
166 + Priority: prioServerState,
167 + }
168 + for _, v := range serverStates {
169 + chart.Dims = append(chart.Dims, &module.Dim{
170 + ID: "server_%s_state_" + v,
171 + Name: strings.ToLower(cleanChartID(v)),
172 + })
173 + }
174 + return chart
175 + }()
176 + serverCurrentConnectionsChartTmpl = module.Chart{
177 + ID: "server_%s_current_connections",
178 + Title: "Server Current Connections",
179 + Units: "connections",
180 + Fam: "servers",
181 + Ctx: "maxscale.server_current_connections",
182 + Priority: prioServerConnections,
183 + Dims: module.Dims{
184 + {ID: "server_%s_connections", Name: "connections"},
185 + },
186 + }
187 +)
188 +
189 +func (m *MaxScale) addServerCharts(id, addr string) {
190 + srvCharts := serverChartsTmpl.Copy()
191 +
192 + for _, chart := range *srvCharts {
193 + chart.ID = fmt.Sprintf(chart.ID, id)
194 + chart.ID = cleanChartID(chart.ID)
195 + chart.Labels = []module.Label{
196 + {Key: "server", Value: id},
197 + {Key: "address", Value: addr},
198 + }
199 + for _, dim := range chart.Dims {
200 + dim.ID = fmt.Sprintf(dim.ID, id)
201 + }
202 + }
203 +
204 + if err := m.Charts().Add(*srvCharts...); err != nil {
205 + m.Warning(err)
206 + }
207 +}
208 +
209 +func (m *MaxScale) removeServerCharts(id string) {
210 + px := fmt.Sprintf("server_%s_", id)
211 + px = cleanChartID(px)
212 +
213 + for _, chart := range *m.Charts() {
214 + if strings.HasPrefix(chart.ID, px) {
215 + chart.MarkRemove()
216 + chart.MarkNotCreated()
217 + }
218 + }
219 +}
220 +
221 +func cleanChartID(id string) string {
222 + r := strings.NewReplacer(".", "_", " ", "_")
223 + return r.Replace(id)
224 +}
src/go/plugin/go.d/modules/maxscale/collect.go new
+141
@@ -0,0 +1,141 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package maxscale
4 +
5 +import (
6 + "fmt"
7 + "net"
8 + "strconv"
9 + "strings"
10 + "unicode"
11 +
12 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
13 +)
14 +
15 +const (
16 + urlPathMaxscale = "/maxscale"
17 + urlPathMaxscaleThreads = "/maxscale/threads"
18 + urlPathServers = "/servers"
19 +)
20 +
21 +func (m *MaxScale) collect() (map[string]int64, error) {
22 + mx := make(map[string]int64)
23 +
24 + if err := m.collectMaxScaleGlobal(mx); err != nil {
25 + return nil, err
26 + }
27 + if err := m.collectMaxScaleThreads(mx); err != nil {
28 + return nil, err
29 + }
30 + if err := m.collectServers(mx); err != nil {
31 + return nil, err
32 + }
33 +
34 + return mx, nil
35 +}
36 +
37 +func (m *MaxScale) collectMaxScaleGlobal(mx map[string]int64) error {
38 + req, err := web.NewHTTPRequestWithPath(m.RequestConfig, urlPathMaxscale)
39 + if err != nil {
40 + return fmt.Errorf("failed to create HTTP request: %v", err)
41 + }
42 +
43 + var resp maxscaleGlobalResponse
44 +
45 + if err := web.DoHTTP(m.httpClient).RequestJSON(req, &resp); err != nil {
46 + return err
47 + }
48 +
49 + if resp.Data == nil {
50 + return fmt.Errorf("invalid response from '%s': missing expected MaxScale data", req.URL)
51 + }
52 +
53 + mx["uptime"] = resp.Data.Attrs.Uptime
54 +
55 + return nil
56 +}
57 +
58 +func (m *MaxScale) collectMaxScaleThreads(mx map[string]int64) error {
59 + req, err := web.NewHTTPRequestWithPath(m.RequestConfig, urlPathMaxscaleThreads)
60 + if err != nil {
61 + return fmt.Errorf("failed to create HTTP request: %v", err)
62 + }
63 +
64 + var resp maxscaleThreadsResponse
65 +
66 + if err := web.DoHTTP(m.httpClient).RequestJSON(req, &resp); err != nil {
67 + return err
68 + }
69 +
70 + for _, r := range resp.Data {
71 + st := r.Attrs.Stats
72 + mx["threads_reads"] += st.Reads
73 + mx["threads_writes"] += st.Writes
74 + mx["threads_errors"] += st.Errors
75 + mx["threads_hangups"] += st.Hangups
76 + mx["threads_accepts"] += st.Accepts
77 + mx["threads_sessions"] += st.Sessions
78 + mx["threads_zombies"] += st.Zombies
79 + mx["threads_current_fds"] += st.CurrentDescriptors
80 + mx["threads_total_fds"] += st.TotalDescriptors
81 + mx["threads_qc_cache_inserts"] += st.QCCache.Inserts
82 + mx["threads_qc_cache_evictions"] += st.QCCache.Evictions
83 + mx["threads_qc_cache_hits"] += st.QCCache.Hits
84 + mx["threads_qc_cache_misses"] += st.QCCache.Misses
85 + for _, v := range threadStates {
86 + mx["threads_state_"+v] = 0
87 + }
88 + mx["threads_state_"+st.State]++
89 + }
90 +
91 + return nil
92 +}
93 +
94 +func (m *MaxScale) collectServers(mx map[string]int64) error {
95 + req, err := web.NewHTTPRequestWithPath(m.RequestConfig, urlPathServers)
96 + if err != nil {
97 + return fmt.Errorf("failed to create HTTP request: %v", err)
98 + }
99 +
100 + var resp serversResponse
101 +
102 + if err := web.DoHTTP(m.httpClient).RequestJSON(req, &resp); err != nil {
103 + return err
104 + }
105 +
106 + seen := make(map[string]bool)
107 +
108 + for _, r := range resp.Data {
109 + if r.ID == "" {
110 + continue
111 + }
112 +
113 + seen[r.ID] = true
114 +
115 + if !m.seenServers[r.ID] {
116 + m.seenServers[r.ID] = true
117 + addr := net.JoinHostPort(r.Attrs.Params.Address, strconv.Itoa(int(r.Attrs.Params.Port)))
118 + m.addServerCharts(r.ID, addr)
119 + }
120 +
121 + px := fmt.Sprintf("server_%s_", r.ID)
122 +
123 + mx[px+"connections"] = r.Attrs.Statistics.Connections
124 +
125 + for _, v := range serverStates {
126 + mx[px+"state_"+v] = 0
127 + }
128 + for _, v := range strings.FieldsFunc(r.Attrs.State, unicode.IsSpace) {
129 + mx[px+"state_"+v] = 1
130 + }
131 + }
132 +
133 + for id := range m.seenServers {
134 + if !seen[id] {
135 + delete(m.seenServers, id)
136 + m.removeServerCharts(id)
137 + }
138 + }
139 +
140 + return nil
141 +}
src/go/plugin/go.d/modules/maxscale/config_schema.json new
+185
@@ -0,0 +1,185 @@
1 +{
2 + "jsonSchema": {
3 + "$schema": "http://json-schema.org/draft-07/schema#",
4 + "title": "MaxScale 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 URL of the MaxScale HTTP API endpoint.",
17 + "type": "string",
18 + "default": "http://127.0.0.1:8989",
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 + "default": "admin",
38 + "sensitive": true
39 + },
40 + "password": {
41 + "title": "Password",
42 + "description": "The password for basic authentication.",
43 + "type": "string",
44 + "default": "mariadb",
45 + "sensitive": true
46 + },
47 + "proxy_url": {
48 + "title": "Proxy URL",
49 + "description": "The URL of the proxy server.",
50 + "type": "string"
51 + },
52 + "proxy_username": {
53 + "title": "Proxy username",
54 + "description": "The username for proxy authentication.",
55 + "type": "string",
56 + "sensitive": true
57 + },
58 + "proxy_password": {
59 + "title": "Proxy password",
60 + "description": "The password for proxy authentication.",
61 + "type": "string",
62 + "sensitive": true
63 + },
64 + "headers": {
65 + "title": "Headers",
66 + "description": "Additional HTTP headers to include in the request.",
67 + "type": [
68 + "object",
69 + "null"
70 + ],
71 + "additionalProperties": {
72 + "type": "string"
73 + }
74 + },
75 + "tls_skip_verify": {
76 + "title": "Skip TLS verification",
77 + "description": "If set, TLS certificate verification will be skipped.",
78 + "type": "boolean"
79 + },
80 + "tls_ca": {
81 + "title": "TLS CA",
82 + "description": "The path to the CA certificate file for TLS verification.",
83 + "type": "string",
84 + "pattern": "^$|^/"
85 + },
86 + "tls_cert": {
87 + "title": "TLS certificate",
88 + "description": "The path to the client certificate file for TLS authentication.",
89 + "type": "string",
90 + "pattern": "^$|^/"
91 + },
92 + "tls_key": {
93 + "title": "TLS key",
94 + "description": "The path to the client key file for TLS authentication.",
95 + "type": "string",
96 + "pattern": "^$|^/"
97 + },
98 + "body": {
99 + "title": "Body",
100 + "type": "string"
101 + },
102 + "method": {
103 + "title": "Method",
104 + "type": "string"
105 + }
106 + },
107 + "required": [
108 + "url"
109 + ],
110 + "additionalProperties": false,
111 + "patternProperties": {
112 + "^name$": {}
113 + }
114 + },
115 + "uiSchema": {
116 + "uiOptions": {
117 + "fullPage": true
118 + },
119 + "body": {
120 + "ui:widget": "hidden"
121 + },
122 + "method": {
123 + "ui:widget": "hidden"
124 + },
125 + "timeout": {
126 + "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
127 + },
128 + "username": {
129 + "ui:widget": "password"
130 + },
131 + "proxy_username": {
132 + "ui:widget": "password"
133 + },
134 + "password": {
135 + "ui:widget": "password"
136 + },
137 + "proxy_password": {
138 + "ui:widget": "password"
139 + },
140 + "ui:flavour": "tabs",
141 + "ui:options": {
142 + "tabs": [
143 + {
144 + "title": "Base",
145 + "fields": [
146 + "update_every",
147 + "url",
148 + "timeout",
149 + "not_follow_redirects"
150 + ]
151 + },
152 + {
153 + "title": "Auth",
154 + "fields": [
155 + "username",
156 + "password"
157 + ]
158 + },
159 + {
160 + "title": "TLS",
161 + "fields": [
162 + "tls_skip_verify",
163 + "tls_ca",
164 + "tls_cert",
165 + "tls_key"
166 + ]
167 + },
168 + {
169 + "title": "Proxy",
170 + "fields": [
171 + "proxy_url",
172 + "proxy_username",
173 + "proxy_password"
174 + ]
175 + },
176 + {
177 + "title": "Headers",
178 + "fields": [
179 + "headers"
180 + ]
181 + }
182 + ]
183 + }
184 + }
185 +}
src/go/plugin/go.d/modules/maxscale/maxscale.go new
+115
@@ -0,0 +1,115 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package maxscale
4 +
5 +import (
6 + _ "embed"
7 + "errors"
8 + "fmt"
9 + "net/http"
10 + "time"
11 +
12 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
13 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/confopt"
14 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
15 +)
16 +
17 +//go:embed "config_schema.json"
18 +var configSchema string
19 +
20 +func init() {
21 + module.Register("maxscale", module.Creator{
22 + JobConfigSchema: configSchema,
23 + Create: func() module.Module { return New() },
24 + Config: func() any { return &Config{} },
25 + })
26 +}
27 +
28 +func New() *MaxScale {
29 + return &MaxScale{
30 + Config: Config{
31 + HTTPConfig: web.HTTPConfig{
32 + RequestConfig: web.RequestConfig{
33 + URL: "http://127.0.0.1:8989",
34 + Username: "admin",
35 + Password: "mariadb",
36 + },
37 + ClientConfig: web.ClientConfig{
38 + Timeout: confopt.Duration(time.Second * 1),
39 + },
40 + },
41 + },
42 + charts: charts.Copy(),
43 + seenServers: make(map[string]bool),
44 + }
45 +}
46 +
47 +type Config struct {
48 + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
49 + web.HTTPConfig `yaml:",inline" json:""`
50 +}
51 +
52 +type MaxScale struct {
53 + module.Base
54 + Config `yaml:",inline" json:""`
55 +
56 + charts *module.Charts
57 +
58 + httpClient *http.Client
59 +
60 + seenServers map[string]bool
61 +}
62 +
63 +func (m *MaxScale) Configuration() any {
64 + return m.Config
65 +}
66 +
67 +func (m *MaxScale) Init() error {
68 + if m.URL == "" {
69 + return errors.New("URL required but not set")
70 + }
71 +
72 + httpClient, err := web.NewHTTPClient(m.ClientConfig)
73 + if err != nil {
74 + return fmt.Errorf("failed initializing http client: %w", err)
75 + }
76 + m.httpClient = httpClient
77 +
78 + m.Debugf("using URL %s", m.URL)
79 + m.Debugf("using timeout: %s", m.Timeout)
80 +
81 + return nil
82 +}
83 +
84 +func (m *MaxScale) Check() error {
85 + mx, err := m.collect()
86 + if err != nil {
87 + return err
88 + }
89 +
90 + if len(mx) == 0 {
91 + return errors.New("no metrics collected")
92 + }
93 +
94 + return nil
95 +}
96 +
97 +func (m *MaxScale) Charts() *module.Charts {
98 + return m.charts
99 +}
100 +
101 +func (m *MaxScale) Collect() map[string]int64 {
102 + mx, err := m.collect()
103 + if err != nil {
104 + m.Error(err)
105 + return nil
106 + }
107 +
108 + return mx
109 +}
110 +
111 +func (m *MaxScale) Cleanup() {
112 + if m.httpClient != nil {
113 + m.httpClient.CloseIdleConnections()
114 + }
115 +}
src/go/plugin/go.d/modules/maxscale/maxscale_test.go new
+284
@@ -0,0 +1,284 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package maxscale
4 +
5 +import (
6 + "net/http"
7 + "net/http/httptest"
8 + "os"
9 + "testing"
10 +
11 + "github.com/stretchr/testify/assert"
12 + "github.com/stretchr/testify/require"
13 +
14 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
15 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
16 +)
17 +
18 +var (
19 + dataConfigJSON, _ = os.ReadFile("testdata/config.json")
20 + dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
21 +
22 + dataVer24MaxScale, _ = os.ReadFile("testdata/v24.02.3/maxscale.json")
23 + dataVer24MaxScaleThreads, _ = os.ReadFile("testdata/v24.02.3/maxscale_threads.json")
24 + dataVer24Servers, _ = os.ReadFile("testdata/v24.02.3/servers.json")
25 +)
26 +
27 +func Test_testDataIsValid(t *testing.T) {
28 + for name, data := range map[string][]byte{
29 + "dataConfigJSON": dataConfigJSON,
30 + "dataConfigYAML": dataConfigYAML,
31 + "dataVer24MaxScale": dataVer24MaxScale,
32 + "dataVer24MaxScaleThreads": dataVer24MaxScaleThreads,
33 + "dataVer24Servers": dataVer24Servers,
34 + } {
35 + require.NotNil(t, data, name)
36 + }
37 +}
38 +
39 +func TestMaxScale_ConfigurationSerialize(t *testing.T) {
40 + module.TestConfigurationSerialize(t, &MaxScale{}, dataConfigJSON, dataConfigYAML)
41 +}
42 +
43 +func TestMaxScale_Init(t *testing.T) {
44 + tests := map[string]struct {
45 + wantFail bool
46 + config Config
47 + }{
48 + "success with default": {
49 + wantFail: false,
50 + config: New().Config,
51 + },
52 + "fail when URL not set": {
53 + wantFail: true,
54 + config: Config{
55 + HTTPConfig: web.HTTPConfig{
56 + RequestConfig: web.RequestConfig{URL: ""},
57 + },
58 + },
59 + },
60 + }
61 +
62 + for name, test := range tests {
63 + t.Run(name, func(t *testing.T) {
64 + ms := New()
65 + ms.Config = test.config
66 +
67 + if test.wantFail {
68 + assert.Error(t, ms.Init())
69 + } else {
70 + assert.NoError(t, ms.Init())
71 + }
72 + })
73 + }
74 +}
75 +
76 +func TestMaxScale_Check(t *testing.T) {
77 + tests := map[string]struct {
78 + wantFail bool
79 + prepare func(t *testing.T) (nu *MaxScale, cleanup func())
80 + }{
81 + "success on valid response": {
82 + wantFail: false,
83 + prepare: caseOk,
84 + },
85 + "fail on unexpected JSON response": {
86 + wantFail: true,
87 + prepare: caseUnexpectedJsonResponse,
88 + },
89 + "fail on invalid data response": {
90 + wantFail: true,
91 + prepare: caseInvalidDataResponse,
92 + },
93 + "fail on connection refused": {
94 + wantFail: true,
95 + prepare: caseConnectionRefused,
96 + },
97 + "fail on 404 response": {
98 + wantFail: true,
99 + prepare: case404,
100 + },
101 + }
102 +
103 + for name, test := range tests {
104 + t.Run(name, func(t *testing.T) {
105 + ms, cleanup := test.prepare(t)
106 + defer cleanup()
107 +
108 + if test.wantFail {
109 + assert.Error(t, ms.Check())
110 + } else {
111 + assert.NoError(t, ms.Check())
112 + }
113 + })
114 + }
115 +}
116 +
117 +func TestMaxScale_Charts(t *testing.T) {
118 + assert.NotNil(t, New().Charts())
119 +}
120 +
121 +func TestMaxScale_Collect(t *testing.T) {
122 + tests := map[string]struct {
123 + prepare func(t *testing.T) (nu *MaxScale, cleanup func())
124 + wantNumOfCharts int
125 + wantMetrics map[string]int64
126 + }{
127 + "success on valid response": {
128 + prepare: caseOk,
129 + wantNumOfCharts: len(charts) + len(serverChartsTmpl)*1,
130 + wantMetrics: map[string]int64{
131 + "server_kek_connections": 0,
132 + "server_kek_state_Binlog Relay": 0,
133 + "server_kek_state_Down": 1,
134 + "server_kek_state_Drained": 0,
135 + "server_kek_state_Draining": 0,
136 + "server_kek_state_Maintenance": 0,
137 + "server_kek_state_Master": 0,
138 + "server_kek_state_Relay Master": 0,
139 + "server_kek_state_Running": 0,
140 + "server_kek_state_Slave": 0,
141 + "server_kek_state_Synced": 0,
142 + "threads_accepts": 0,
143 + "threads_current_fds": 3,
144 + "threads_errors": 0,
145 + "threads_hangups": 0,
146 + "threads_qc_cache_evictions": 0,
147 + "threads_qc_cache_hits": 0,
148 + "threads_qc_cache_inserts": 0,
149 + "threads_qc_cache_misses": 0,
150 + "threads_reads": 68359,
151 + "threads_sessions": 0,
152 + "threads_state_Active": 1,
153 + "threads_state_Dormant": 0,
154 + "threads_state_Draining": 0,
155 + "threads_total_fds": 3,
156 + "threads_writes": 0,
157 + "threads_zombies": 0,
158 + "uptime": 61298,
159 + },
160 + },
161 + "fail on unexpected JSON response": {
162 + prepare: caseUnexpectedJsonResponse,
163 + wantMetrics: nil,
164 + },
165 + "fail on invalid data response": {
166 + prepare: caseInvalidDataResponse,
167 + wantMetrics: nil,
168 + },
169 + "fail on connection refused": {
170 + prepare: caseConnectionRefused,
171 + wantMetrics: nil,
172 + },
173 + "fail on 404 response": {
174 + prepare: case404,
175 + wantMetrics: nil,
176 + },
177 + }
178 +
179 + for name, test := range tests {
180 + t.Run(name, func(t *testing.T) {
181 + ms, cleanup := test.prepare(t)
182 + defer cleanup()
183 +
184 + _ = ms.Check()
185 +
186 + mx := ms.Collect()
187 +
188 + require.Equal(t, test.wantMetrics, mx)
189 +
190 + if len(test.wantMetrics) > 0 {
191 + assert.Equal(t, test.wantNumOfCharts, len(*ms.Charts()), "want charts")
192 +
193 + module.TestMetricsHasAllChartsDims(t, ms.Charts(), mx)
194 + }
195 + })
196 + }
197 +}
198 +
199 +func caseOk(t *testing.T) (*MaxScale, func()) {
200 + t.Helper()
201 + srv := httptest.NewServer(http.HandlerFunc(
202 + func(w http.ResponseWriter, r *http.Request) {
203 + switch r.URL.Path {
204 + case urlPathMaxscale:
205 + _, _ = w.Write(dataVer24MaxScale)
206 + case urlPathMaxscaleThreads:
207 + _, _ = w.Write(dataVer24MaxScaleThreads)
208 + case urlPathServers:
209 + _, _ = w.Write(dataVer24Servers)
210 + default:
211 + w.WriteHeader(http.StatusNotFound)
212 + }
213 + }))
214 + ms := New()
215 + ms.URL = srv.URL
216 + require.NoError(t, ms.Init())
217 +
218 + return ms, srv.Close
219 +}
220 +
221 +func caseUnexpectedJsonResponse(t *testing.T) (*MaxScale, func()) {
222 + t.Helper()
223 + resp := `
224 +{
225 + "elephant": {
226 + "burn": false,
227 + "mountain": true,
228 + "fog": false,
229 + "skin": -1561907625,
230 + "burst": "anyway",
231 + "shadow": 1558616893
232 + },
233 + "start": "ever",
234 + "base": 2093056027,
235 + "mission": -2007590351,
236 + "victory": 999053756,
237 + "die": false
238 +}
239 +`
240 + srv := httptest.NewServer(http.HandlerFunc(
241 + func(w http.ResponseWriter, r *http.Request) {
242 + _, _ = w.Write([]byte(resp))
243 + }))
244 + ms := New()
245 + ms.URL = srv.URL
246 + require.NoError(t, ms.Init())
247 +
248 + return ms, srv.Close
249 +}
250 +
251 +func caseInvalidDataResponse(t *testing.T) (*MaxScale, func()) {
252 + t.Helper()
253 + srv := httptest.NewServer(http.HandlerFunc(
254 + func(w http.ResponseWriter, r *http.Request) {
255 + _, _ = w.Write([]byte("hello and\n goodbye"))
256 + }))
257 + ms := New()
258 + ms.URL = srv.URL
259 + require.NoError(t, ms.Init())
260 +
261 + return ms, srv.Close
262 +}
263 +
264 +func caseConnectionRefused(t *testing.T) (*MaxScale, func()) {
265 + t.Helper()
266 + ms := New()
267 + ms.URL = "http://127.0.0.1:65001"
268 + require.NoError(t, ms.Init())
269 +
270 + return ms, func() {}
271 +}
272 +
273 +func case404(t *testing.T) (*MaxScale, func()) {
274 + t.Helper()
275 + srv := httptest.NewServer(http.HandlerFunc(
276 + func(w http.ResponseWriter, r *http.Request) {
277 + w.WriteHeader(http.StatusNotFound)
278 + }))
279 + ms := New()
280 + ms.URL = srv.URL
281 + require.NoError(t, ms.Init())
282 +
283 + return ms, srv.Close
284 +}
src/go/plugin/go.d/modules/maxscale/metadata.yaml new
+272
@@ -0,0 +1,272 @@
1 +plugin_name: go.d.plugin
2 +modules:
3 + - meta:
4 + id: collector-go.d.plugin-maxscale
5 + plugin_name: go.d.plugin
6 + module_name: maxscale
7 + monitored_instance:
8 + name: MaxScale
9 + link: https://mariadb.com/kb/en/maxscale/
10 + categories:
11 + - data-collection.database-servers
12 + icon_filename: maxscale.svg
13 + related_resources:
14 + integrations:
15 + list: []
16 + alternative_monitored_instances: []
17 + info_provided_to_referring_integrations:
18 + description: ""
19 + keywords:
20 + - maria
21 + - mariadb
22 + - maxscale
23 + - database
24 + - db
25 + most_popular: false
26 + overview:
27 + data_collection:
28 + metrics_description: |
29 + This collector monitors the activity and performance of MaxScale servers.
30 + method_description: |
31 + It sends HTTP requests to the MaxScale [REST API](https://mariadb.com/kb/en/maxscale-24-02rest-api/).
32 + default_behavior:
33 + auto_detection:
34 + description: |
35 + The collector can automatically detect MaxScale instances running on:
36 +
37 + - localhost that are listening on port 8989
38 + - within Docker containers
39 +
40 + > **Note that the MaxScale REST API requires a username and password**.
41 + > While Netdata can automatically detect MaxScale instances and create data collection jobs, these jobs will fail unless you provide the necessary credentials.
42 + limits:
43 + description: ""
44 + performance_impact:
45 + description: ""
46 + additional_permissions:
47 + description: ""
48 + multi_instance: true
49 + supported_platforms:
50 + include: []
51 + exclude: []
52 + setup:
53 + prerequisites:
54 + list: []
55 + configuration:
56 + file:
57 + name: go.d/maxscale.conf
58 + options:
59 + description: |
60 + The following options can be defined globally: update_every, autodetection_retry.
61 + folding:
62 + title: Config options
63 + enabled: true
64 + list:
65 + - name: update_every
66 + description: Data collection frequency.
67 + default_value: 1
68 + required: false
69 + - name: autodetection_retry
70 + description: Recheck interval in seconds. Zero means no recheck will be scheduled.
71 + default_value: 0
72 + required: false
73 + - name: url
74 + description: The URL of the MaxScale HTTP API endpoint.
75 + default_value: http://127.0.0.1:8989
76 + required: true
77 + - name: timeout
78 + description: HTTP request timeout.
79 + default_value: 1
80 + required: false
81 + - name: username
82 + description: Username for basic HTTP authentication.
83 + default_value: "admin"
84 + required: false
85 + - name: password
86 + description: Password for basic HTTP authentication.
87 + default_value: "mariadb"
88 + required: false
89 + - name: proxy_url
90 + description: Proxy URL.
91 + default_value: ""
92 + required: false
93 + - name: proxy_username
94 + description: Username for proxy basic HTTP authentication.
95 + default_value: ""
96 + required: false
97 + - name: proxy_password
98 + description: Password for proxy basic HTTP authentication.
99 + default_value: ""
100 + required: false
101 + - name: method
102 + description: HTTP request method.
103 + default_value: GET
104 + required: false
105 + - name: body
106 + description: HTTP request body.
107 + default_value: ""
108 + required: false
109 + - name: headers
110 + description: HTTP request headers.
111 + default_value: ""
112 + required: false
113 + - name: not_follow_redirects
114 + description: Redirect handling policy. Controls whether the client follows redirects.
115 + default_value: false
116 + required: false
117 + - name: tls_skip_verify
118 + description: Server certificate chain and hostname validation policy. Controls whether the client performs this check.
119 + default_value: false
120 + required: false
121 + - name: tls_ca
122 + description: Certification authority that the client uses when verifying the server's certificates.
123 + default_value: ""
124 + required: false
125 + - name: tls_cert
126 + description: Client TLS certificate.
127 + default_value: ""
128 + required: false
129 + - name: tls_key
130 + description: Client TLS key.
131 + default_value: ""
132 + required: false
133 + examples:
134 + folding:
135 + title: Config
136 + enabled: true
137 + list:
138 + - name: Basic
139 + description: A basic example configuration.
140 + folding:
141 + enabled: false
142 + config: |
143 + jobs:
144 + - name: local
145 + url: http://127.0.0.1:8989
146 + username: admin
147 + password: mariadb
148 + - name: HTTPS with self-signed certificate
149 + description: MaxScale with enabled HTTPS and self-signed certificate.
150 + config: |
151 + jobs:
152 + - name: local
153 + url: http://127.0.0.1:8989
154 + username: admin
155 + password: mariadb
156 + tls_skip_verify: yes
157 + - name: Multi-instance
158 + description: |
159 + > **Note**: When you define multiple jobs, their names must be unique.
160 +
161 + Collecting metrics from local and remote instances.
162 + config: |
163 + jobs:
164 + - name: local
165 + url: http://127.0.0.1:8989
166 + username: admin
167 + password: mariadb
168 +
169 + - name: remote
170 + url: http://192.0.2.1:8989
171 + username: admin
172 + password: mariadb
173 + troubleshooting:
174 + problems:
175 + list: []
176 + alerts: []
177 + metrics:
178 + folding:
179 + title: Metrics
180 + enabled: false
181 + description: ""
182 + availability: []
183 + scopes:
184 + - name: global
185 + description: These metrics refer to the monitored MaxScale instance.
186 + labels: []
187 + metrics:
188 + - name: maxscale.poll_events
189 + description: Poll Events
190 + unit: events/s
191 + chart_type: line
192 + dimensions:
193 + - name: reads
194 + - name: writes
195 + - name: accepts
196 + - name: errors
197 + - name: hangups
198 + - name: maxscale.current_sessions
199 + description: Current Sessions
200 + unit: sessions
201 + chart_type: line
202 + dimensions:
203 + - name: sessions
204 + - name: maxscale.current_zombie_connections
205 + description: Current Zombie Connections
206 + unit: connections
207 + chart_type: line
208 + dimensions:
209 + - name: zombie
210 + - name: maxscale.threads_by_state
211 + description: Threads Count by State
212 + unit: threads
213 + chart_type: stacked
214 + dimensions:
215 + - name: active
216 + - name: draining
217 + - name: dormant
218 + - name: maxscale.current_fds
219 + description: Current Managed File Descriptors
220 + unit: fds
221 + chart_type: line
222 + dimensions:
223 + - name: managed
224 + - name: maxscale.qc_cache_efficiency
225 + description: QC Cache Efficiency
226 + unit: requests/s
227 + chart_type: stacked
228 + dimensions:
229 + - name: hits
230 + - name: misses
231 + - name: maxscale.qc_cache_operations
232 + description: QC Cache Operations
233 + unit: operations/s
234 + chart_type: stacked
235 + dimensions:
236 + - name: inserts
237 + - name: evictions
238 + - name: maxscale.uptime
239 + description: Uptime
240 + unit: seconds
241 + chart_type: line
242 + dimensions:
243 + - name: uptime
244 + - name: server
245 + description: These metrics refer to the MariaDB server.
246 + labels:
247 + - name: server
248 + description: Server ID.
249 + - name: address
250 + description: Server address.
251 + metrics:
252 + - name: maxscale.server_state
253 + description: Server State
254 + unit: state
255 + chart_type: line
256 + dimensions:
257 + - name: master
258 + - name: slave
259 + - name: running
260 + - name: down
261 + - name: maintenance
262 + - name: draining
263 + - name: drained
264 + - name: relay_master
265 + - name: binlog_relay
266 + - name: synced
267 + - name: maxscale.server_current_connections
268 + description: Server Current connections
269 + unit: connections
270 + chart_type: line
271 + dimensions:
272 + - name: connections
src/go/plugin/go.d/modules/maxscale/restapi.go new
+84
@@ -0,0 +1,84 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package maxscale
4 +
5 +// https://mariadb.com/kb/en/maxscale-24-02rest-api/
6 +
7 +type maxscaleGlobalResponse struct {
8 + Data *struct {
9 + Attrs struct {
10 + Params struct {
11 + Threads int64 `json:"threads"`
12 + Passive bool `json:"passive"`
13 + } `json:"parameters"`
14 + Uptime int64 `json:"uptime"`
15 + } `json:"attributes"`
16 + } `json:"data"`
17 +}
18 +
19 +// https://github.com/mariadb-corporation/MaxScale/blob/f72af9927243f59f2b20cc835273dbe6dc158623/server/core/routingworker.cc#L3021
20 +// https://github.com/mariadb-corporation/MaxScale/blob/f72af9927243f59f2b20cc835273dbe6dc158623/maxctrl/lib/show.js#L471
21 +type maxscaleThreadsResponse struct {
22 + Data []struct {
23 + ID string `json:"id"`
24 + Attrs struct {
25 + Stats struct {
26 + State string `json:"state"`
27 + Reads int64 `json:"reads"`
28 + Writes int64 `json:"writes"`
29 + Errors int64 `json:"errors"`
30 + Hangups int64 `json:"hangups"`
31 + Accepts int64 `json:"accepts"`
32 + Sessions int64 `json:"sessions"`
33 + Zombies int64 `json:"zombies"`
34 + CurrentDescriptors int64 `json:"current_descriptors"`
35 + TotalDescriptors int64 `json:"total_descriptors"`
36 + QCCache struct {
37 + Size int64 `json:"size"`
38 + Inserts int64 `json:"inserts"`
39 + Hits int64 `json:"hits"`
40 + Misses int64 `json:"misses"`
41 + Evictions int64 `json:"evictions"`
42 + } `json:"query_classifier_cache"`
43 + } `json:"stats"`
44 + } `json:"attributes"`
45 + } `json:"data"`
46 +}
47 +
48 +// // https://github.com/mariadb-corporation/MaxScale/blob/f72af9927243f59f2b20cc835273dbe6dc158623/server/core/routingworker.cc#L3064
49 +var threadStates = []string{
50 + "Active",
51 + "Draining",
52 + "Dormant",
53 +}
54 +
55 +type serversResponse struct {
56 + Data []struct {
57 + ID string `json:"id"`
58 + Type string `json:"type"`
59 + Attrs struct {
60 + Params struct {
61 + Address string `json:"address"`
62 + Port int `json:"port"`
63 + } `json:"parameters"`
64 + State string `json:"state"`
65 + Statistics struct {
66 + Connections int64 `json:"connections"`
67 + } `json:"statistics"`
68 + } `json:"attributes"`
69 + } `json:"data"`
70 +}
71 +
72 +// https://github.com/mariadb-corporation/MaxScale/blob/f72af9927243f59f2b20cc835273dbe6dc158623/system-test/maxtest/src/maxscales.cc#L43
73 +var serverStates = []string{
74 + "Master",
75 + "Slave",
76 + "Running",
77 + "Down",
78 + "Maintenance",
79 + "Draining",
80 + "Drained",
81 + "Relay Master",
82 + "Binlog Relay",
83 + "Synced",
84 +}
src/go/plugin/go.d/modules/maxscale/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/maxscale/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/maxscale/testdata/v24.02.3/maxscale.json new
+124
@@ -0,0 +1,124 @@
1 +{
2 + "data": {
3 + "attributes": {
4 + "activated_at": "Wed, 23 Oct 2024 18:39:44 GMT",
5 + "commit": "d77a737db314a3dab3811a1e4b81e20ac2aedbe9",
6 + "config_sync": null,
7 + "parameters": {
8 + "admin_audit": false,
9 + "admin_audit_exclude_methods": [],
10 + "admin_audit_file": "/var/log/maxscale/admin_audit.csv",
11 + "admin_auth": true,
12 + "admin_enabled": true,
13 + "admin_gui": true,
14 + "admin_host": "0.0.0.0",
15 + "admin_jwt_algorithm": "auto",
16 + "admin_jwt_issuer": "maxscale",
17 + "admin_jwt_key": null,
18 + "admin_jwt_max_age": "86400000ms",
19 + "admin_log_auth_failures": true,
20 + "admin_oidc_url": null,
21 + "admin_pam_readonly_service": null,
22 + "admin_pam_readwrite_service": null,
23 + "admin_port": 8989,
24 + "admin_readonly_hosts": "%",
25 + "admin_readwrite_hosts": "%",
26 + "admin_secure_gui": false,
27 + "admin_ssl_ca": null,
28 + "admin_ssl_cert": null,
29 + "admin_ssl_key": null,
30 + "admin_ssl_version": "MAX",
31 + "admin_verify_url": null,
32 + "auth_connect_timeout": "10000ms",
33 + "auth_read_timeout": "10000ms",
34 + "auth_write_timeout": "10000ms",
35 + "auto_tune": [],
36 + "cachedir": "/var/cache/maxscale",
37 + "config_sync_cluster": null,
38 + "config_sync_db": "mysql",
39 + "config_sync_interval": "5000ms",
40 + "config_sync_password": null,
41 + "config_sync_timeout": "10000ms",
42 + "config_sync_user": null,
43 + "connector_plugindir": "/usr/lib64/maxscale/plugin",
44 + "datadir": "/var/lib/maxscale",
45 + "debug": null,
46 + "dump_last_statements": "never",
47 + "execdir": "/usr/bin",
48 + "key_manager": "none",
49 + "language": "/var/lib/maxscale",
50 + "libdir": "/usr/lib64/maxscale",
51 + "load_persisted_configs": true,
52 + "local_address": null,
53 + "log_debug": false,
54 + "log_info": false,
55 + "log_notice": true,
56 + "log_throttling": {
57 + "count": 10,
58 + "suppress": 10000,
59 + "window": 1000
60 + },
61 + "log_warn_super_user": false,
62 + "log_warning": true,
63 + "logdir": "/var/log/maxscale",
64 + "max_auth_errors_until_block": 10,
65 + "max_read_amount": 0,
66 + "maxlog": true,
67 + "module_configdir": "/etc/maxscale.modules.d",
68 + "ms_timestamp": false,
69 + "passive": false,
70 + "persist_runtime_changes": true,
71 + "persistdir": "/var/lib/maxscale/maxscale.cnf.d",
72 + "piddir": "/var/run/maxscale",
73 + "query_classifier_cache_size": 3779569459,
74 + "query_retries": 1,
75 + "query_retry_timeout": "5000ms",
76 + "rebalance_period": "0ms",
77 + "rebalance_threshold": 20,
78 + "rebalance_window": 10,
79 + "retain_last_statements": 0,
80 + "secretsdir": null,
81 + "session_trace": 0,
82 + "session_trace_match": null,
83 + "skip_name_resolve": false,
84 + "sql_mode": "default",
85 + "syslog": false,
86 + "threads": 1,
87 + "threads_max": 256,
88 + "users_refresh_interval": "0ms",
89 + "users_refresh_time": "30000ms",
90 + "writeq_high_water": 65536,
91 + "writeq_low_water": 1024
92 + },
93 + "process_datadir": "/var/lib/maxscale/data20",
94 + "started_at": "Wed, 23 Oct 2024 18:39:44 GMT",
95 + "system": {
96 + "machine": {
97 + "cores_available": 16,
98 + "cores_physical": 16,
99 + "cores_virtual": 16.0,
100 + "memory_available": 25197129728,
101 + "memory_physical": 25197129728
102 + },
103 + "maxscale": {
104 + "query_classifier_cache_size": 3779569459,
105 + "threads": 1
106 + },
107 + "os": {
108 + "machine": "x86_64",
109 + "nodename": "pve-deb-work",
110 + "release": "6.1.0-25-amd64",
111 + "sysname": "Linux",
112 + "version": "#1 SMP PREEMPT_DYNAMIC Debian 6.1.106-3 (2024-08-26)"
113 + }
114 + },
115 + "uptime": 61298,
116 + "version": "24.02.3"
117 + },
118 + "id": "maxscale",
119 + "type": "maxscale"
120 + },
121 + "links": {
122 + "self": "http://localhost:8989/v1/maxscale/"
123 + }
124 +}
src/go/plugin/go.d/modules/maxscale/testdata/v24.02.3/maxscale_threads.json new
+51
@@ -0,0 +1,51 @@
1 +{
2 + "data": [
3 + {
4 + "attributes": {
5 + "stats": {
6 + "accepts": 0,
7 + "avg_event_queue_length": 1,
8 + "current_descriptors": 3,
9 + "errors": 0,
10 + "hangups": 0,
11 + "listening": true,
12 + "load": {
13 + "last_hour": 0,
14 + "last_minute": 0,
15 + "last_second": 0
16 + },
17 + "max_event_queue_length": 1,
18 + "max_exec_time": 0,
19 + "max_queue_time": 0,
20 + "memory": {
21 + "query_classifier": 0,
22 + "sessions": 0,
23 + "total": 0,
24 + "zombies": 0
25 + },
26 + "query_classifier_cache": {
27 + "evictions": 0,
28 + "hits": 0,
29 + "inserts": 0,
30 + "misses": 0,
31 + "size": 0
32 + },
33 + "reads": 68359,
34 + "sessions": 0,
35 + "state": "Active",
36 + "total_descriptors": 3,
37 + "writes": 0,
38 + "zombies": 0
39 + }
40 + },
41 + "id": "0",
42 + "links": {
43 + "self": "http://localhost:8989/v1/threads/0/"
44 + },
45 + "type": "threads"
46 + }
47 + ],
48 + "links": {
49 + "self": "http://localhost:8989/v1/maxscale/threads/"
50 + }
51 +}
src/go/plugin/go.d/modules/maxscale/testdata/v24.02.3/servers.json new
+198
@@ -0,0 +1,198 @@
1 +{
2 + "data": [
3 + {
4 + "attributes": {
5 + "parameters": {
6 + "address": "127.0.0.1",
7 + "disk_space_threshold": null,
8 + "extra_port": 0,
9 + "max_routing_connections": 0,
10 + "monitorpw": null,
11 + "monitoruser": null,
12 + "persistmaxtime": "0ms",
13 + "persistpoolmax": 0,
14 + "port": 3306,
15 + "priority": 0,
16 + "private_address": null,
17 + "proxy_protocol": false,
18 + "rank": "primary",
19 + "replication_custom_options": null,
20 + "socket": null,
21 + "ssl": false,
22 + "ssl_ca": null,
23 + "ssl_cert": null,
24 + "ssl_cert_verify_depth": 9,
25 + "ssl_cipher": null,
26 + "ssl_key": null,
27 + "ssl_verify_peer_certificate": false,
28 + "ssl_verify_peer_host": false,
29 + "ssl_version": "MAX"
30 + },
31 + "replication_lag": -1,
32 + "source": {
33 + "file": "/var/lib/maxscale/maxscale.cnf.d/kek.cnf",
34 + "type": "runtime"
35 + },
36 + "state": "Down",
37 + "statistics": {
38 + "active_operations": 0,
39 + "adaptive_avg_select_time": "0ns",
40 + "connection_pool_empty": 0,
41 + "connections": 0,
42 + "failed_auths": 0,
43 + "max_connections": 0,
44 + "max_pool_size": 0,
45 + "persistent_connections": 0,
46 + "response_time_distribution": {
47 + "read": {
48 + "distribution": [
49 + {
50 + "count": 0,
51 + "time": "0.000001",
52 + "total": 0
53 + },
54 + {
55 + "count": 0,
56 + "time": "0.000010",
57 + "total": 0
58 + },
59 + {
60 + "count": 0,
61 + "time": "0.000100",
62 + "total": 0
63 + },
64 + {
65 + "count": 0,
66 + "time": "0.001000",
67 + "total": 0
68 + },
69 + {
70 + "count": 0,
71 + "time": "0.010000",
72 + "total": 0
73 + },
74 + {
75 + "count": 0,
76 + "time": "0.100000",
77 + "total": 0
78 + },
79 + {
80 + "count": 0,
81 + "time": "1.000000",
82 + "total": 0
83 + },
84 + {
85 + "count": 0,
86 + "time": "10.000000",
87 + "total": 0
88 + },
89 + {
90 + "count": 0,
91 + "time": "100.000000",
92 + "total": 0
93 + },
94 + {
95 + "count": 0,
96 + "time": "1000.000000",
97 + "total": 0
98 + },
99 + {
100 + "count": 0,
101 + "time": "10000.000000",
102 + "total": 0
103 + },
104 + {
105 + "count": 0,
106 + "time": "100000.000000",
107 + "total": 0
108 + }
109 + ],
110 + "operation": "read",
111 + "range_base": 10
112 + },
113 + "write": {
114 + "distribution": [
115 + {
116 + "count": 0,
117 + "time": "0.000001",
118 + "total": 0
119 + },
120 + {
121 + "count": 0,
122 + "time": "0.000010",
123 + "total": 0
124 + },
125 + {
126 + "count": 0,
127 + "time": "0.000100",
128 + "total": 0
129 + },
130 + {
131 + "count": 0,
132 + "time": "0.001000",
133 + "total": 0
134 + },
135 + {
136 + "count": 0,
137 + "time": "0.010000",
138 + "total": 0
139 + },
140 + {
141 + "count": 0,
142 + "time": "0.100000",
143 + "total": 0
144 + },
145 + {
146 + "count": 0,
147 + "time": "1.000000",
148 + "total": 0
149 + },
150 + {
151 + "count": 0,
152 + "time": "10.000000",
153 + "total": 0
154 + },
155 + {
156 + "count": 0,
157 + "time": "100.000000",
158 + "total": 0
159 + },
160 + {
161 + "count": 0,
162 + "time": "1000.000000",
163 + "total": 0
164 + },
165 + {
166 + "count": 0,
167 + "time": "10000.000000",
168 + "total": 0
169 + },
170 + {
171 + "count": 0,
172 + "time": "100000.000000",
173 + "total": 0
174 + }
175 + ],
176 + "operation": "write",
177 + "range_base": 10
178 + }
179 + },
180 + "reused_connections": 0,
181 + "routed_packets": 0,
182 + "total_connections": 0
183 + },
184 + "uptime": 0,
185 + "version_string": ""
186 + },
187 + "id": "kek",
188 + "links": {
189 + "self": "http://localhost:8989/v1/servers/kek/"
190 + },
191 + "relationships": {},
192 + "type": "servers"
193 + }
194 + ],
195 + "links": {
196 + "self": "http://localhost:8989/v1/servers/"
197 + }
198 +}