@cryptotaxi247 / netdata-1 / commits / 35d062673

Port memcached collector to Go (#18209)

Co-authored-by: ilyam8 <ilya@netdata.cloud>

Fotis Voutsas committed Jul 22, 2024 at 22:38 UTC 35d0626732eb15799f9b3b796102e5e322dc0c5a
22 files changed +1002 -603
CMakeLists.txt
-2
@@ -2776,7 +2776,6 @@ install(FILES
2776 src/collectors/python.d.plugin/gearman/gearman.conf
2777 src/collectors/python.d.plugin/go_expvar/go_expvar.conf
2778 src/collectors/python.d.plugin/haproxy/haproxy.conf
2779 - src/collectors/python.d.plugin/memcached/memcached.conf
2779 src/collectors/python.d.plugin/monit/monit.conf
2780 src/collectors/python.d.plugin/nsd/nsd.conf
2781 src/collectors/python.d.plugin/nvidia_smi/nvidia_smi.conf
@@ -2813,7 +2812,6 @@ install(FILES
2812 src/collectors/python.d.plugin/gearman/gearman.chart.py
2813 src/collectors/python.d.plugin/go_expvar/go_expvar.chart.py
2814 src/collectors/python.d.plugin/haproxy/haproxy.chart.py
2816 - src/collectors/python.d.plugin/memcached/memcached.chart.py
2815 src/collectors/python.d.plugin/monit/monit.chart.py
2816 src/collectors/python.d.plugin/nsd/nsd.chart.py
2817 src/collectors/python.d.plugin/nvidia_smi/nvidia_smi.chart.py
src/collectors/python.d.plugin/memcached/README.md deleted
-1
@@ -1 +0,0 @@
1 -integrations/memcached.md
\ No newline at end of file
src/collectors/python.d.plugin/memcached/integrations/memcached.md deleted
-248
@@ -1,248 +0,0 @@
1 -<!--startmeta
2 -custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/python.d.plugin/memcached/README.md"
3 -meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/python.d.plugin/memcached/metadata.yaml"
4 -sidebar_label: "Memcached"
5 -learn_status: "Published"
6 -learn_rel_path: "Collecting Metrics/Databases"
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 -# Memcached
12 -
13 -
14 -<img src="https://netdata.cloud/img/memcached.svg" width="150"/>
15 -
16 -
17 -Plugin: python.d.plugin
18 -Module: memcached
19 -
20 -<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
21 -
22 -## Overview
23 -
24 -Monitor Memcached metrics for proficient in-memory key-value store operations. Track cache hits, misses, and memory usage for efficient data caching.
25 -
26 -It reads server response to stats command ([stats interface](https://github.com/memcached/memcached/wiki/Commands#stats)).
27 -
28 -This collector is supported on all platforms.
29 -
30 -This collector supports collecting metrics from multiple instances of this integration, including remote instances.
31 -
32 -
33 -### Default Behavior
34 -
35 -#### Auto-Detection
36 -
37 -If no configuration is given, collector will attempt to connect to memcached instance on `127.0.0.1:11211` address.
38 -
39 -
40 -#### Limits
41 -
42 -The default configuration for this integration does not impose any limits on data collection.
43 -
44 -#### Performance Impact
45 -
46 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
47 -
48 -
49 -## Metrics
50 -
51 -Metrics grouped by *scope*.
52 -
53 -The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels.
54 -
55 -
56 -
57 -### Per Memcached instance
58 -
59 -These metrics refer to the entire monitored application.
60 -
61 -This scope has no labels.
62 -
63 -Metrics:
64 -
65 -| Metric | Dimensions | Unit |
66 -|:------|:----------|:----|
67 -| memcached.cache | available, used | MiB |
68 -| memcached.net | in, out | kilobits/s |
69 -| memcached.connections | current, rejected, total | connections/s |
70 -| memcached.items | current, total | items |
71 -| memcached.evicted_reclaimed | reclaimed, evicted | items |
72 -| memcached.get | hints, misses | requests |
73 -| memcached.get_rate | rate | requests/s |
74 -| memcached.set_rate | rate | requests/s |
75 -| memcached.delete | hits, misses | requests |
76 -| memcached.cas | hits, misses, bad value | requests |
77 -| memcached.increment | hits, misses | requests |
78 -| memcached.decrement | hits, misses | requests |
79 -| memcached.touch | hits, misses | requests |
80 -| memcached.touch_rate | rate | requests/s |
81 -
82 -
83 -
84 -## Alerts
85 -
86 -
87 -The following alerts are available:
88 -
89 -| Alert name | On metric | Description |
90 -|:------------|:----------|:------------|
91 -| [ memcached_cache_memory_usage ](https://github.com/netdata/netdata/blob/master/src/health/health.d/memcached.conf) | memcached.cache | cache memory utilization |
92 -| [ memcached_cache_fill_rate ](https://github.com/netdata/netdata/blob/master/src/health/health.d/memcached.conf) | memcached.cache | average rate the cache fills up (positive), or frees up (negative) space over the last hour |
93 -| [ memcached_out_of_cache_space_time ](https://github.com/netdata/netdata/blob/master/src/health/health.d/memcached.conf) | memcached.cache | estimated time the cache will run out of space if the system continues to add data at the same rate as the past hour |
94 -
95 -
96 -## Setup
97 -
98 -### Prerequisites
99 -
100 -No action required.
101 -
102 -### Configuration
103 -
104 -#### File
105 -
106 -The configuration file name for this integration is `python.d/memcached.conf`.
107 -
108 -
109 -You can edit the configuration file using the `edit-config` script from the
110 -Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory).
111 -
112 -```bash
113 -cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
114 -sudo ./edit-config python.d/memcached.conf
115 -```
116 -#### Options
117 -
118 -There are 2 sections:
119 -
120 -* Global variables
121 -* One or more JOBS that can define multiple different instances to monitor.
122 -
123 -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.
124 -
125 -Additionally, the following collapsed table contains all the options that can be configured inside a JOB definition.
126 -
127 -Every configuration JOB starts with a `job_name` value which will appear in the dashboard, unless a `name` parameter is specified.
128 -
129 -
130 -<details open><summary>Config options</summary>
131 -
132 -| Name | Description | Default | Required |
133 -|:----|:-----------|:-------|:--------:|
134 -| host | the host to connect to. | 127.0.0.1 | no |
135 -| port | the port to connect to. | 11211 | no |
136 -| update_every | Sets the default data collection frequency. | 10 | no |
137 -| priority | Controls the order of charts at the netdata dashboard. | 60000 | no |
138 -| autodetection_retry | Sets the job re-check interval in seconds. | 0 | no |
139 -| penalty | Indicates whether to apply penalty to update_every in case of failures. | yes | no |
140 -| name | Job name. This value will overwrite the `job_name` value. JOBS with the same name are mutually exclusive. Only one of them will be allowed running at any time. This allows autodetection to try several alternatives and pick the one that works. | | no |
141 -
142 -</details>
143 -
144 -#### Examples
145 -
146 -##### localhost
147 -
148 -An example configuration for localhost.
149 -
150 -```yaml
151 -localhost:
152 - name: 'local'
153 - host: 'localhost'
154 - port: 11211
155 -
156 -```
157 -##### localipv4
158 -
159 -An example configuration for localipv4.
160 -
161 -<details open><summary>Config</summary>
162 -
163 -```yaml
164 -localhost:
165 - name: 'local'
166 - host: '127.0.0.1'
167 - port: 11211
168 -
169 -```
170 -</details>
171 -
172 -##### localipv6
173 -
174 -An example configuration for localipv6.
175 -
176 -<details open><summary>Config</summary>
177 -
178 -```yaml
179 -localhost:
180 - name: 'local'
181 - host: '::1'
182 - port: 11211
183 -
184 -```
185 -</details>
186 -
187 -
188 -
189 -## Troubleshooting
190 -
191 -### Debug Mode
192 -
193 -To troubleshoot issues with the `memcached` collector, run the `python.d.plugin` with the debug option enabled. The output
194 -should give you clues as to why the collector isn't working.
195 -
196 -- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on
197 - your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`.
198 -
199 - ```bash
200 - cd /usr/libexec/netdata/plugins.d/
201 - ```
202 -
203 -- Switch to the `netdata` user.
204 -
205 - ```bash
206 - sudo -u netdata -s
207 - ```
208 -
209 -- Run the `python.d.plugin` to debug the collector:
210 -
211 - ```bash
212 - ./python.d.plugin memcached debug trace
213 - ```
214 -
215 -### Getting Logs
216 -
217 -If you're encountering problems with the `memcached` collector, follow these steps to retrieve logs and identify potential issues:
218 -
219 -- **Run the command** specific to your system (systemd, non-systemd, or Docker container).
220 -- **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.
221 -
222 -#### System with systemd
223 -
224 -Use the following command to view logs generated since the last Netdata service restart:
225 -
226 -```bash
227 -journalctl _SYSTEMD_INVOCATION_ID="$(systemctl show --value --property=InvocationID netdata)" --namespace=netdata --grep memcached
228 -```
229 -
230 -#### System without systemd
231 -
232 -Locate the collector log file, typically at `/var/log/netdata/collector.log`, and use `grep` to filter for collector's name:
233 -
234 -```bash
235 -grep memcached /var/log/netdata/collector.log
236 -```
237 -
238 -**Note**: This method shows logs from all restarts. Focus on the **latest entries** for troubleshooting current issues.
239 -
240 -#### Docker Container
241 -
242 -If your Netdata runs in a Docker container named "netdata" (replace if different), use this command:
243 -
244 -```bash
245 -docker logs netdata 2>&1 | grep memcached
246 -```
247 -
248 -
src/collectors/python.d.plugin/memcached/memcached.chart.py deleted
-197
@@ -1,197 +0,0 @@
1 -# -*- coding: utf-8 -*-
2 -# Description: memcached netdata python.d module
3 -# Author: Pawel Krupa (paulfantom)
4 -# SPDX-License-Identifier: GPL-3.0-or-later
5 -
6 -from bases.FrameworkServices.SocketService import SocketService
7 -
8 -ORDER = [
9 - 'cache',
10 - 'net',
11 - 'connections',
12 - 'items',
13 - 'evicted_reclaimed',
14 - 'get',
15 - 'get_rate',
16 - 'set_rate',
17 - 'cas',
18 - 'delete',
19 - 'increment',
20 - 'decrement',
21 - 'touch',
22 - 'touch_rate',
23 -]
24 -
25 -CHARTS = {
26 - 'cache': {
27 - 'options': [None, 'Cache Size', 'MiB', 'cache', 'memcached.cache', 'stacked'],
28 - 'lines': [
29 - ['avail', 'available', 'absolute', 1, 1 << 20],
30 - ['used', 'used', 'absolute', 1, 1 << 20]
31 - ]
32 - },
33 - 'net': {
34 - 'options': [None, 'Network', 'kilobits/s', 'network', 'memcached.net', 'area'],
35 - 'lines': [
36 - ['bytes_read', 'in', 'incremental', 8, 1000],
37 - ['bytes_written', 'out', 'incremental', -8, 1000],
38 - ]
39 - },
40 - 'connections': {
41 - 'options': [None, 'Connections', 'connections/s', 'connections', 'memcached.connections', 'line'],
42 - 'lines': [
43 - ['curr_connections', 'current', 'incremental'],
44 - ['rejected_connections', 'rejected', 'incremental'],
45 - ['total_connections', 'total', 'incremental']
46 - ]
47 - },
48 - 'items': {
49 - 'options': [None, 'Items', 'items', 'items', 'memcached.items', 'line'],
50 - 'lines': [
51 - ['curr_items', 'current', 'absolute'],
52 - ['total_items', 'total', 'absolute']
53 - ]
54 - },
55 - 'evicted_reclaimed': {
56 - 'options': [None, 'Evicted and Reclaimed Items', 'items', 'items', 'memcached.evicted_reclaimed', 'line'],
57 - 'lines': [
58 - ['reclaimed', 'reclaimed', 'absolute'],
59 - ['evictions', 'evicted', 'absolute']
60 - ]
61 - },
62 - 'get': {
63 - 'options': [None, 'Get Requests', 'requests', 'get ops', 'memcached.get', 'stacked'],
64 - 'lines': [
65 - ['get_hits', 'hits', 'percent-of-absolute-row'],
66 - ['get_misses', 'misses', 'percent-of-absolute-row']
67 - ]
68 - },
69 - 'get_rate': {
70 - 'options': [None, 'Get Request Rate', 'requests/s', 'get ops', 'memcached.get_rate', 'line'],
71 - 'lines': [
72 - ['cmd_get', 'rate', 'incremental']
73 - ]
74 - },
75 - 'set_rate': {
76 - 'options': [None, 'Set Request Rate', 'requests/s', 'set ops', 'memcached.set_rate', 'line'],
77 - 'lines': [
78 - ['cmd_set', 'rate', 'incremental']
79 - ]
80 - },
81 - 'delete': {
82 - 'options': [None, 'Delete Requests', 'requests', 'delete ops', 'memcached.delete', 'stacked'],
83 - 'lines': [
84 - ['delete_hits', 'hits', 'percent-of-absolute-row'],
85 - ['delete_misses', 'misses', 'percent-of-absolute-row'],
86 - ]
87 - },
88 - 'cas': {
89 - 'options': [None, 'Check and Set Requests', 'requests', 'check and set ops', 'memcached.cas', 'stacked'],
90 - 'lines': [
91 - ['cas_hits', 'hits', 'percent-of-absolute-row'],
92 - ['cas_misses', 'misses', 'percent-of-absolute-row'],
93 - ['cas_badval', 'bad value', 'percent-of-absolute-row']
94 - ]
95 - },
96 - 'increment': {
97 - 'options': [None, 'Increment Requests', 'requests', 'increment ops', 'memcached.increment', 'stacked'],
98 - 'lines': [
99 - ['incr_hits', 'hits', 'percent-of-absolute-row'],
100 - ['incr_misses', 'misses', 'percent-of-absolute-row']
101 - ]
102 - },
103 - 'decrement': {
104 - 'options': [None, 'Decrement Requests', 'requests', 'decrement ops', 'memcached.decrement', 'stacked'],
105 - 'lines': [
106 - ['decr_hits', 'hits', 'percent-of-absolute-row'],
107 - ['decr_misses', 'misses', 'percent-of-absolute-row']
108 - ]
109 - },
110 - 'touch': {
111 - 'options': [None, 'Touch Requests', 'requests', 'touch ops', 'memcached.touch', 'stacked'],
112 - 'lines': [
113 - ['touch_hits', 'hits', 'percent-of-absolute-row'],
114 - ['touch_misses', 'misses', 'percent-of-absolute-row']
115 - ]
116 - },
117 - 'touch_rate': {
118 - 'options': [None, 'Touch Request Rate', 'requests/s', 'touch ops', 'memcached.touch_rate', 'line'],
119 - 'lines': [
120 - ['cmd_touch', 'rate', 'incremental']
121 - ]
122 - }
123 -}
124 -
125 -
126 -class Service(SocketService):
127 - def __init__(self, configuration=None, name=None):
128 - SocketService.__init__(self, configuration=configuration, name=name)
129 - self.order = ORDER
130 - self.definitions = CHARTS
131 - self.request = 'stats\r\n'
132 - self.host = 'localhost'
133 - self.port = 11211
134 - self._keep_alive = True
135 - self.unix_socket = None
136 -
137 - def _get_data(self):
138 - """
139 - Get data from socket
140 - :return: dict
141 - """
142 - response = self._get_raw_data()
143 - if response is None:
144 - # error has already been logged
145 - return None
146 -
147 - if response.startswith('ERROR'):
148 - self.error('received ERROR')
149 - return None
150 -
151 - try:
152 - parsed = response.split('\n')
153 - except AttributeError:
154 - self.error('response is invalid/empty')
155 - return None
156 -
157 - # split the response
158 - data = {}
159 - for line in parsed:
160 - if line.startswith('STAT'):
161 - try:
162 - t = line[5:].split(' ')
163 - data[t[0]] = t[1]
164 - except (IndexError, ValueError):
165 - self.debug('invalid line received: ' + str(line))
166 -
167 - if not data:
168 - self.error("received data doesn't have any records")
169 - return None
170 -
171 - # custom calculations
172 - try:
173 - data['avail'] = int(data['limit_maxbytes']) - int(data['bytes'])
174 - data['used'] = int(data['bytes'])
175 - except (KeyError, ValueError, TypeError):
176 - pass
177 -
178 - return data
179 -
180 - def _check_raw_data(self, data):
181 - if data.endswith('END\r\n'):
182 - self.debug('received full response from memcached')
183 - return True
184 -
185 - self.debug('waiting more data from memcached')
186 - return False
187 -
188 - def check(self):
189 - """
190 - Parse configuration, check if memcached is available
191 - :return: boolean
192 - """
193 - self._parse_config()
194 - data = self._get_data()
195 - if data is None:
196 - return False
197 - return True
src/collectors/python.d.plugin/memcached/memcached.conf deleted
-90
@@ -1,90 +0,0 @@
1 -# netdata python.d.plugin configuration for memcached
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, memcached also supports the following:
63 -#
64 -# socket: 'path/to/memcached.sock'
65 -#
66 -# or
67 -# host: 'IP or HOSTNAME' # the host to connect to
68 -# port: PORT # the port to connect to
69 -#
70 -#
71 -
72 -# ----------------------------------------------------------------------
73 -# AUTO-DETECTION JOBS
74 -# only one of them will run (they have the same name)
75 -
76 -localhost:
77 - name : 'local'
78 - host : 'localhost'
79 - port : 11211
80 -
81 -localipv4:
82 - name : 'local'
83 - host : '127.0.0.1'
84 - port : 11211
85 -
86 -localipv6:
87 - name : 'local'
88 - host : '::1'
89 - port : 11211
90 -
src/collectors/python.d.plugin/python.d.conf
+1 -1
@@ -39,7 +39,6 @@ example: no
39 # gearman: yes
40 go_expvar: no
41 # haproxy: yes
42 -# memcached: yes
42 # monit: yes
43 # nvidia_smi: yes
44 # nsd: yes
@@ -74,6 +73,7 @@ icecast: no # Removed (replaced with go.d/icecast)
73 ipfs: no # Removed (replaced with go.d/ipfs).
74 litespeed: no # Removed (replaced with go.d/litespeed).
75 megacli: no # Removed (replaced with go.d/megacli).
76 +memcached: no # Removed (replaced with go.d/memcached).
77 mongodb: no # Removed (replaced with go.d/mongodb).
78 mysql: no # Removed (replaced with go.d/mysql).
79 nginx: no # Removed (replaced with go.d/nginx).
src/go/plugin/go.d/README.md
+1
@@ -95,6 +95,7 @@ see the appropriate collector readme.
95 | [logstash](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/logstash) | Logstash |
96 | [lvm](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/lvm) | LVM logical volumes |
97 | [megacli](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/megacli) | MegaCli Hardware Raid |
98 +| [memcached](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/memcached) | Memcached |
99 | [mongoDB](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/mongodb) | MongoDB |
100 | [mysql](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/mysql) | MySQL |
101 | [nginx](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/nginx) | NGINX |
src/go/plugin/go.d/config/go.d.conf
+1
@@ -59,6 +59,7 @@ modules:
59 # logstash: yes
60 # lvm: yes
61 # megacli: yes
62 +# memcached: yes
63 # mongodb: yes
64 # mysql: yes
65 # nginx: yes
src/go/plugin/go.d/config/go.d/memcached.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/memcached#readme
3 +
4 +#jobs:
5 +# - name: local
6 +# address: 127.0.0.1:11211
src/go/plugin/go.d/config/go.d/sd/docker.conf
+8 -1
@@ -42,6 +42,8 @@ classify:
42 expr: '{{ and (eq .PrivatePort "5001") (match "sp" .Image "ipfs/kubo ipfs/kubo:*") }}'
43 - tags: "lighttpd"
44 expr: '{{ match "sp" .Image "*/lighttpd */lighttpd:*" }}'
45 + - tags: "memcached"
46 + expr: '{{ or (eq .PrivatePort "11211") (match "sp" .Image "memcached memcached:* */memcached */memcached:*") }}'
47 - tags: "mongodb"
48 expr: '{{ or (eq .PrivatePort "27017") (match "sp" .Image "mongo mongo:* */mongodb */mongodb:* */mongodb-community-server */mongodb-community-server:*") }}'
49 - tags: "mysql"
@@ -129,6 +131,11 @@ compose:
131 module: lighttpd
132 name: docker_{{.Name}}
133 url: http://{{.Address}}/server-status?auto
134 + - selector: "memcached"
135 + template: |
136 + module: memcached
137 + name: docker_{{.Name}}
138 + address: {{.Address}}
139 - selector: "mongodb"
140 template: |
141 module: mongodb
@@ -201,6 +208,6 @@ compose:
208 url: http://{{.Address}}/metrics
209 - selector: "zookeeper"
210 template: |
204 - module: vernemq
211 + module: zookeeper
212 name: docker_{{.Name}}
213 address: {{.Address}}
src/go/plugin/go.d/config/go.d/sd/net_listeners.conf
+7
@@ -70,6 +70,8 @@ classify:
70 expr: '{{ and (eq .Port "80" "8080") (eq .Comm "lighttpd") }}'
71 - tags: "logstash"
72 expr: '{{ and (eq .Port "9600") (glob .Cmdline "*logstash*") }}'
73 + - tags: "memcached"
74 + expr: '{{ or (eq .Port "11211") (eq .Comm "memcached") }}'
75 - tags: "mongodb"
76 expr: '{{ or (eq .Port "27017") (eq .Comm "mongod") }}'
77 - tags: "mysql"
@@ -293,6 +295,11 @@ compose:
295 module: logstash
296 name: local
297 url: http://{{.Address}}
298 + - selector: "memcached"
299 + template: |
300 + module: memcached
301 + name: local
302 + address: {{.Address}}
303 - selector: "mongodb"
304 template: |
305 module: mongodb
src/go/plugin/go.d/modules/init.go
+1
@@ -50,6 +50,7 @@ import (
50 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/logstash"
51 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/lvm"
52 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/megacli"
53 + _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/memcached"
54 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/mongodb"
55 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/mysql"
56 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/nginx"
src/go/plugin/go.d/modules/memcached/charts.go new
+229
@@ -0,0 +1,229 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package memcached
4 +
5 +import (
6 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
7 +)
8 +
9 +const (
10 + prioCache = module.Priority + iota
11 + prioNet
12 + prioConnections
13 + prioItems
14 + prioEvictedReclaimed
15 + prioGet
16 + prioGetRate
17 + prioSetRate
18 + prioDelete
19 + prioCas
20 + prioIncrement
21 + prioDecrement
22 + prioTouch
23 + prioTouchRate
24 +)
25 +
26 +var charts = module.Charts{
27 + cacheChart.Copy(),
28 + netChart.Copy(),
29 + connectionsChart.Copy(),
30 + itemsChart.Copy(),
31 + EvictedReclaimedChart.Copy(),
32 + getChart.Copy(),
33 + getRateChart.Copy(),
34 + setRateChart.Copy(),
35 + deleteChart.Copy(),
36 + casChart.Copy(),
37 + incrementChart.Copy(),
38 + decrementChart.Copy(),
39 + touchChart.Copy(),
40 + touchRateChart.Copy(),
41 +}
42 +
43 +const (
44 + byteToMiB = 1 << 20
45 +)
46 +
47 +var (
48 + cacheChart = module.Chart{
49 + ID: "cache",
50 + Title: "Cache Size",
51 + Units: "MiB",
52 + Fam: "cache",
53 + Ctx: "memcached.cache",
54 + Type: module.Stacked,
55 + Priority: prioCache,
56 + Dims: module.Dims{
57 + {ID: "avail", Div: byteToMiB},
58 + {ID: "bytes", Name: "used", Div: byteToMiB},
59 + },
60 + }
61 + netChart = module.Chart{
62 + ID: "net",
63 + Title: "Network",
64 + Units: "kilobits/s",
65 + Fam: "network",
66 + Ctx: "memcached.net",
67 + Type: module.Area,
68 + Priority: prioNet,
69 + Dims: module.Dims{
70 + {ID: "bytes_read", Name: "in", Mul: 8, Div: 1000, Algo: module.Incremental},
71 + {ID: "bytes_written", Name: "out", Mul: -8, Div: 1000, Algo: module.Incremental},
72 + },
73 + }
74 + connectionsChart = module.Chart{
75 + ID: "connections",
76 + Title: "Connections",
77 + Units: "connections/s",
78 + Fam: "connections",
79 + Ctx: "memcached.connections",
80 + Type: module.Line,
81 + Priority: prioConnections,
82 + Dims: module.Dims{
83 + {ID: "curr_connections", Name: "current", Algo: module.Incremental},
84 + {ID: "rejected_connections", Name: "rejected", Algo: module.Incremental},
85 + {ID: "total_connections", Name: "total", Algo: module.Incremental},
86 + },
87 + }
88 + itemsChart = module.Chart{
89 + ID: "items",
90 + Title: "Items",
91 + Units: "items",
92 + Fam: "items",
93 + Ctx: "memcached.items",
94 + Type: module.Line,
95 + Priority: prioItems,
96 + Dims: module.Dims{
97 + {ID: "curr_items", Name: "current"},
98 + {ID: "total_items", Name: "total"},
99 + },
100 + }
101 + EvictedReclaimedChart = module.Chart{
102 + ID: "evicted_reclaimed",
103 + Title: "Evicted and Reclaimed Items",
104 + Units: "items",
105 + Fam: "items",
106 + Ctx: "memcached.evicted_reclaimed",
107 + Type: module.Line,
108 + Priority: prioEvictedReclaimed,
109 + Dims: module.Dims{
110 + {ID: "reclaimed"},
111 + {ID: "evictions", Name: "evicted"},
112 + },
113 + }
114 + getChart = module.Chart{
115 + ID: "get",
116 + Title: "Get Requests",
117 + Units: "requests",
118 + Fam: "get ops",
119 + Ctx: "memcached.get",
120 + Type: module.Stacked,
121 + Priority: prioGet,
122 + Dims: module.Dims{
123 + {ID: "get_hits", Name: "hits", Algo: module.PercentOfAbsolute},
124 + {ID: "get_misses", Name: "misses", Algo: module.PercentOfAbsolute},
125 + },
126 + }
127 + getRateChart = module.Chart{
128 + ID: "get_rate",
129 + Title: "Get Request Rate",
130 + Units: "requests/s",
131 + Fam: "get ops",
132 + Ctx: "memcached.get_rate",
133 + Type: module.Line,
134 + Priority: prioGetRate,
135 + Dims: module.Dims{
136 + {ID: "cmd_get", Name: "rate", Algo: module.Incremental},
137 + },
138 + }
139 + setRateChart = module.Chart{
140 + ID: "set_rate",
141 + Title: "Set Request Rate",
142 + Units: "requests/s",
143 + Fam: "set ops",
144 + Ctx: "memcached.set_rate",
145 + Type: module.Line,
146 + Priority: prioSetRate,
147 + Dims: module.Dims{
148 + {ID: "cmd_set", Name: "rate", Algo: module.Incremental},
149 + },
150 + }
151 + deleteChart = module.Chart{
152 + ID: "delete",
153 + Title: "Delete Requests",
154 + Units: "requests",
155 + Fam: "delete ops",
156 + Ctx: "memcached.delete",
157 + Type: module.Stacked,
158 + Priority: prioDelete,
159 + Dims: module.Dims{
160 + {ID: "delete_hits", Name: "hits", Algo: module.PercentOfAbsolute},
161 + {ID: "delete_misses", Name: "misses", Algo: module.PercentOfAbsolute},
162 + },
163 + }
164 + casChart = module.Chart{
165 + ID: "cas",
166 + Title: "Check and Set Requests",
167 + Units: "requests",
168 + Fam: "check and set ops",
169 + Ctx: "memcached.cas",
170 + Type: module.Stacked,
171 + Priority: prioCas,
172 + Dims: module.Dims{
173 + {ID: "cas_hits", Name: "hits", Algo: module.PercentOfAbsolute},
174 + {ID: "cas_misses", Name: "misses", Algo: module.PercentOfAbsolute},
175 + {ID: "cas_badval", Name: "bad value", Algo: module.PercentOfAbsolute},
176 + },
177 + }
178 + incrementChart = module.Chart{
179 + ID: "increment",
180 + Title: "Increment Requests",
181 + Units: "requests",
182 + Fam: "increment ops",
183 + Ctx: "memcached.increment",
184 + Type: module.Stacked,
185 + Priority: prioIncrement,
186 + Dims: module.Dims{
187 + {ID: "incr_hits", Name: "hits", Algo: module.PercentOfAbsolute},
188 + {ID: "incr_misses", Name: "misses", Algo: module.PercentOfAbsolute},
189 + },
190 + }
191 + decrementChart = module.Chart{
192 + ID: "decrement",
193 + Title: "Decrement Requests",
194 + Units: "requests",
195 + Fam: "decrement ops",
196 + Ctx: "memcached.decrement",
197 + Type: module.Stacked,
198 + Priority: prioDecrement,
199 + Dims: module.Dims{
200 + {ID: "decr_hits", Name: "hits", Algo: module.PercentOfAbsolute},
201 + {ID: "decr_misses", Name: "misses", Algo: module.PercentOfAbsolute},
202 + },
203 + }
204 + touchChart = module.Chart{
205 + ID: "touch",
206 + Title: "Touch Requests",
207 + Units: "requests",
208 + Fam: "touch ops",
209 + Ctx: "memcached.touch",
210 + Type: module.Stacked,
211 + Priority: prioTouch,
212 + Dims: module.Dims{
213 + {ID: "touch_hits", Name: "hits", Algo: module.PercentOfAbsolute},
214 + {ID: "touch_misses", Name: "misses", Algo: module.PercentOfAbsolute},
215 + },
216 + }
217 + touchRateChart = module.Chart{
218 + ID: "touch_rate",
219 + Title: "Touch Requests Rate",
220 + Units: "requests/s",
221 + Fam: "touch ops",
222 + Ctx: "memcached.touch_rate",
223 + Type: module.Line,
224 + Priority: prioTouchRate,
225 + Dims: module.Dims{
226 + {ID: "cmd_touch", Name: "rate", Algo: module.Incremental},
227 + },
228 + }
229 +)
src/go/plugin/go.d/modules/memcached/client.go new
+45
@@ -0,0 +1,45 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package memcached
4 +
5 +import (
6 + "bytes"
7 + "strings"
8 +
9 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/socket"
10 +)
11 +
12 +func newMemcachedConn(conf Config) memcachedConn {
13 + return &memcachedClient{conn: socket.New(socket.Config{
14 + Address: conf.Address,
15 + ConnectTimeout: conf.Timeout.Duration(),
16 + ReadTimeout: conf.Timeout.Duration(),
17 + WriteTimeout: conf.Timeout.Duration(),
18 + })}
19 +}
20 +
21 +type memcachedClient struct {
22 + conn socket.Client
23 +}
24 +
25 +func (c *memcachedClient) connect() error {
26 + return c.conn.Connect()
27 +}
28 +
29 +func (c *memcachedClient) disconnect() {
30 + _ = c.conn.Disconnect()
31 +}
32 +
33 +func (c *memcachedClient) queryStats() ([]byte, error) {
34 + var b bytes.Buffer
35 + err := c.conn.Command("stats\r\n", func(bytes []byte) bool {
36 + s := strings.TrimSpace(string(bytes))
37 + b.WriteString(s)
38 + b.WriteByte('\n')
39 + return !(strings.HasPrefix(s, "END") || strings.HasPrefix(s, "ERROR"))
40 + })
41 + if err != nil {
42 + return nil, err
43 + }
44 + return b.Bytes(), nil
45 +}
src/go/plugin/go.d/modules/memcached/collect.go new
+121
@@ -0,0 +1,121 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package memcached
4 +
5 +import (
6 + "bufio"
7 + "bytes"
8 + "errors"
9 + "strconv"
10 + "strings"
11 +)
12 +
13 +// https://github.com/memcached/memcached/blob/b1aefcdf8a265f8a5126e8aa107a50988fa1ec35/doc/protocol.txt#L1267
14 +var statsMetrics = map[string]bool{
15 + "limit_maxbytes": true,
16 + "bytes": true,
17 + "bytes_read": true,
18 + "bytes_written": true,
19 + "cas_badval": true,
20 + "cas_hits": true,
21 + "cas_misses": true,
22 + "cmd_get": true,
23 + "cmd_set": true,
24 + "cmd_touch": true,
25 + "curr_connections": true,
26 + "curr_items": true,
27 + "decr_hits": true,
28 + "decr_misses": true,
29 + "delete_hits": true,
30 + "delete_misses": true,
31 + "evictions": true,
32 + "get_hits": true,
33 + "get_misses": true,
34 + "incr_hits": true,
35 + "incr_misses": true,
36 + "reclaimed": true,
37 + "rejected_connections": true,
38 + "total_connections": true,
39 + "total_items": true,
40 + "touch_hits": true,
41 + "touch_misses": true,
42 +}
43 +
44 +func (m *Memcached) collect() (map[string]int64, error) {
45 + if m.conn == nil {
46 + conn, err := m.establishConn()
47 + if err != nil {
48 + return nil, err
49 + }
50 + m.conn = conn
51 + }
52 +
53 + stats, err := m.conn.queryStats()
54 + if err != nil {
55 + m.conn.disconnect()
56 + m.conn = nil
57 + return nil, err
58 + }
59 +
60 + mx := make(map[string]int64)
61 +
62 + if err := m.collectStats(mx, stats); err != nil {
63 + return nil, err
64 + }
65 +
66 + return mx, nil
67 +}
68 +
69 +func (m *Memcached) collectStats(mx map[string]int64, stats []byte) error {
70 + if len(stats) == 0 {
71 + return errors.New("empty stats response")
72 + }
73 +
74 + var n int
75 + sc := bufio.NewScanner(bytes.NewReader(stats))
76 +
77 + for sc.Scan() {
78 + line := strings.TrimSpace(sc.Text())
79 +
80 + switch {
81 + case strings.HasPrefix(line, "STAT"):
82 + key, value := getStatKeyValue(line)
83 + if !statsMetrics[key] {
84 + continue
85 + }
86 + if v, err := strconv.ParseInt(value, 10, 64); err == nil {
87 + mx[key] = v
88 + n++
89 + }
90 + case strings.HasPrefix(line, "ERROR"):
91 + return errors.New("received ERROR response")
92 + }
93 + }
94 +
95 + if n == 0 {
96 + return errors.New("unexpected memcached response")
97 + }
98 +
99 + mx["avail"] = mx["limit_maxbytes"] - mx["bytes"]
100 +
101 + return nil
102 +}
103 +
104 +func (m *Memcached) establishConn() (memcachedConn, error) {
105 + conn := m.newMemcachedConn(m.Config)
106 +
107 + if err := conn.connect(); err != nil {
108 + return nil, err
109 + }
110 +
111 + return conn, nil
112 +}
113 +
114 +func getStatKeyValue(line string) (string, string) {
115 + line = strings.TrimPrefix(line, "STAT ")
116 + i := strings.IndexByte(line, ' ')
117 + if i < 0 {
118 + return "", ""
119 + }
120 + return line[:i], line[i+1:]
121 +}
src/go/plugin/go.d/modules/memcached/config_schema.json new
+44
@@ -0,0 +1,44 @@
1 +{
2 + "jsonSchema": {
3 + "$schema": "http://json-schema.org/draft-07/schema#",
4 + "title": "Memcached 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 + "address": {
15 + "title": "Address",
16 + "description": "The IP address and port where the memcached service listens for connections.",
17 + "type": "string",
18 + "default": "127.0.0.1:11211"
19 + },
20 + "timeout": {
21 + "title": "Timeout",
22 + "description": "Timeout for establishing a connection and communication (reading and writing) in seconds.",
23 + "type": "number",
24 + "minimum": 0.5,
25 + "default": 1
26 + }
27 + },
28 + "required": [
29 + "address"
30 + ],
31 + "additionalProperties": false,
32 + "patternProperties": {
33 + "^name$": {}
34 + }
35 + },
36 + "uiSchema": {
37 + "uiOptions": {
38 + "fullPage": true
39 + },
40 + "timeout": {
41 + "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
42 + }
43 + }
44 +}
\ No newline at end of file
src/go/plugin/go.d/modules/memcached/memcached.go new
+108
@@ -0,0 +1,108 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package memcached
4 +
5 +import (
6 + _ "embed"
7 + "errors"
8 + "time"
9 +
10 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
11 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
12 +)
13 +
14 +//go:embed "config_schema.json"
15 +var configSchema string
16 +
17 +func init() {
18 + module.Register("memcached", module.Creator{
19 + JobConfigSchema: configSchema,
20 + Create: func() module.Module { return New() },
21 + Config: func() any { return &Config{} },
22 + })
23 +}
24 +
25 +func New() *Memcached {
26 + return &Memcached{
27 + Config: Config{
28 + Address: "127.0.0.1:11211",
29 + Timeout: web.Duration(time.Second * 1),
30 + },
31 + newMemcachedConn: newMemcachedConn,
32 + charts: charts.Copy(),
33 + }
34 +}
35 +
36 +type Config struct {
37 + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
38 + Address string `yaml:"address" json:"address"`
39 + Timeout web.Duration `yaml:"timeout" json:"timeout"`
40 +}
41 +
42 +type (
43 + Memcached struct {
44 + module.Base
45 + Config `yaml:",inline" json:""`
46 +
47 + charts *module.Charts
48 +
49 + newMemcachedConn func(Config) memcachedConn
50 + conn memcachedConn
51 + }
52 + memcachedConn interface {
53 + connect() error
54 + disconnect()
55 + queryStats() ([]byte, error)
56 + }
57 +)
58 +
59 +func (m *Memcached) Configuration() any {
60 + return m.Config
61 +}
62 +
63 +func (m *Memcached) Init() error {
64 + if m.Address == "" {
65 + m.Error("config: 'address' not set")
66 + return errors.New("address not set")
67 + }
68 +
69 + return nil
70 +}
71 +
72 +func (m *Memcached) Check() error {
73 + mx, err := m.collect()
74 + if err != nil {
75 + m.Error(err)
76 + return err
77 + }
78 +
79 + if len(mx) == 0 {
80 + return errors.New("no metrics collected")
81 + }
82 +
83 + return nil
84 +}
85 +
86 +func (m *Memcached) Charts() *module.Charts {
87 + return m.charts
88 +}
89 +
90 +func (m *Memcached) Collect() map[string]int64 {
91 + mx, err := m.collect()
92 + if err != nil {
93 + m.Error(err)
94 + }
95 +
96 + if len(mx) == 0 {
97 + return nil
98 + }
99 +
100 + return mx
101 +}
102 +
103 +func (m *Memcached) Cleanup() {
104 + if m.conn != nil {
105 + m.conn.disconnect()
106 + m.conn = nil
107 + }
108 +}
src/go/plugin/go.d/modules/memcached/memcached_test.go new
+296
@@ -0,0 +1,296 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package memcached
4 +
5 +import (
6 + "errors"
7 + "os"
8 + "testing"
9 +
10 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
11 +
12 + "github.com/stretchr/testify/assert"
13 + "github.com/stretchr/testify/require"
14 +)
15 +
16 +var (
17 + dataConfigJSON, _ = os.ReadFile("testdata/config.json")
18 + dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
19 +
20 + dataMemcachedStats, _ = os.ReadFile("testdata/stats.txt")
21 +)
22 +
23 +func Test_testDataIsValid(t *testing.T) {
24 + for name, data := range map[string][]byte{
25 + "dataConfigJSON": dataConfigJSON,
26 + "dataConfigYAML": dataConfigYAML,
27 +
28 + "dataMemcachedStats": dataMemcachedStats,
29 + } {
30 + require.NotNil(t, data, name)
31 + }
32 +}
33 +
34 +func TestMemcached_ConfigurationSerialize(t *testing.T) {
35 + module.TestConfigurationSerialize(t, &Memcached{}, dataConfigJSON, dataConfigYAML)
36 +}
37 +
38 +func TestMemcached_Init(t *testing.T) {
39 + tests := map[string]struct {
40 + config Config
41 + wantFail bool
42 + }{
43 + "success with default config": {
44 + wantFail: false,
45 + config: New().Config,
46 + },
47 + "fails if address not set": {
48 + wantFail: true,
49 + config: func() Config {
50 + conf := New().Config
51 + conf.Address = ""
52 + return conf
53 + }(),
54 + },
55 + }
56 +
57 + for name, test := range tests {
58 + t.Run(name, func(t *testing.T) {
59 + mem := New()
60 + mem.Config = test.config
61 +
62 + if test.wantFail {
63 + assert.Error(t, mem.Init())
64 + } else {
65 + assert.NoError(t, mem.Init())
66 + }
67 + })
68 + }
69 +}
70 +
71 +func TestMemcached_Cleanup(t *testing.T) {
72 + tests := map[string]struct {
73 + prepare func() *Memcached
74 + }{
75 + "not initialized": {
76 + prepare: func() *Memcached {
77 + return New()
78 + },
79 + },
80 + "after check": {
81 + prepare: func() *Memcached {
82 + mem := New()
83 + mem.newMemcachedConn = func(config Config) memcachedConn { return prepareMockOk() }
84 + _ = mem.Check()
85 + return mem
86 + },
87 + },
88 + "after collect": {
89 + prepare: func() *Memcached {
90 + mem := New()
91 + mem.newMemcachedConn = func(config Config) memcachedConn { return prepareMockOk() }
92 + _ = mem.Collect()
93 + return mem
94 + },
95 + },
96 + }
97 +
98 + for name, test := range tests {
99 + t.Run(name, func(t *testing.T) {
100 + mem := test.prepare()
101 +
102 + assert.NotPanics(t, mem.Cleanup)
103 + })
104 + }
105 +}
106 +
107 +func TestMemcached_Charts(t *testing.T) {
108 + assert.NotNil(t, New().Charts())
109 +}
110 +
111 +func TestMemcached_Check(t *testing.T) {
112 + tests := map[string]struct {
113 + prepareMock func() *mockMemcachedConn
114 + wantFail bool
115 + }{
116 + "success case": {
117 + wantFail: false,
118 + prepareMock: prepareMockOk,
119 + },
120 + "err on connect": {
121 + wantFail: true,
122 + prepareMock: prepareMockErrOnConnect,
123 + },
124 + "unexpected response": {
125 + wantFail: true,
126 + prepareMock: prepareMockUnexpectedResponse,
127 + },
128 + "empty response": {
129 + wantFail: true,
130 + prepareMock: prepareMockEmptyResponse,
131 + },
132 + }
133 +
134 + for name, test := range tests {
135 + t.Run(name, func(t *testing.T) {
136 + mem := New()
137 + mock := test.prepareMock()
138 + mem.newMemcachedConn = func(config Config) memcachedConn { return mock }
139 +
140 + if test.wantFail {
141 + assert.Error(t, mem.Check())
142 + } else {
143 + assert.NoError(t, mem.Check())
144 + }
145 + })
146 + }
147 +}
148 +
149 +func TestMemcached_Collect(t *testing.T) {
150 + tests := map[string]struct {
151 + prepareMock func() *mockMemcachedConn
152 + wantMetrics map[string]int64
153 + disconnectBeforeCleanup bool
154 + disconnectAfterCleanup bool
155 + }{
156 + "success case": {
157 + prepareMock: prepareMockOk,
158 + disconnectBeforeCleanup: false,
159 + disconnectAfterCleanup: true,
160 + wantMetrics: map[string]int64{
161 + "avail": 67108831,
162 + "bytes": 33,
163 + "bytes_read": 108662,
164 + "bytes_written": 9761348,
165 + "cas_badval": 0,
166 + "cas_hits": 0,
167 + "cas_misses": 0,
168 + "cmd_get": 1,
169 + "cmd_set": 1,
170 + "cmd_touch": 0,
171 + "curr_connections": 3,
172 + "curr_items": 0,
173 + "decr_hits": 0,
174 + "decr_misses": 0,
175 + "delete_hits": 0,
176 + "delete_misses": 0,
177 + "evictions": 0,
178 + "get_hits": 0,
179 + "get_misses": 1,
180 + "incr_hits": 0,
181 + "incr_misses": 0,
182 + "limit_maxbytes": 67108864,
183 + "reclaimed": 1,
184 + "rejected_connections": 0,
185 + "total_connections": 39,
186 + "total_items": 1,
187 + "touch_hits": 0,
188 + "touch_misses": 0,
189 + },
190 + },
191 + "error response": {
192 + prepareMock: prepareMockErrorResponse,
193 + disconnectBeforeCleanup: false,
194 + disconnectAfterCleanup: true,
195 + },
196 + "unexpected response": {
197 + prepareMock: prepareMockUnexpectedResponse,
198 + disconnectBeforeCleanup: false,
199 + disconnectAfterCleanup: true,
200 + },
201 + "empty response": {
202 + prepareMock: prepareMockEmptyResponse,
203 + disconnectBeforeCleanup: false,
204 + disconnectAfterCleanup: true,
205 + },
206 + "err on connect": {
207 + prepareMock: prepareMockErrOnConnect,
208 + disconnectBeforeCleanup: false,
209 + disconnectAfterCleanup: false,
210 + },
211 + "err on query stats": {
212 + prepareMock: prepareMockErrOnQueryStats,
213 + disconnectBeforeCleanup: true,
214 + disconnectAfterCleanup: true,
215 + },
216 + }
217 +
218 + for name, test := range tests {
219 + t.Run(name, func(t *testing.T) {
220 + mem := New()
221 + mock := test.prepareMock()
222 + mem.newMemcachedConn = func(config Config) memcachedConn { return mock }
223 +
224 + mx := mem.Collect()
225 +
226 + require.Equal(t, test.wantMetrics, mx)
227 +
228 + if len(test.wantMetrics) > 0 {
229 + module.TestMetricsHasAllChartsDims(t, mem.Charts(), mx)
230 + }
231 +
232 + assert.Equal(t, test.disconnectBeforeCleanup, mock.disconnectCalled, "disconnect before cleanup")
233 + mem.Cleanup()
234 + assert.Equal(t, test.disconnectAfterCleanup, mock.disconnectCalled, "disconnect after cleanup")
235 + })
236 + }
237 +}
238 +
239 +func prepareMockOk() *mockMemcachedConn {
240 + return &mockMemcachedConn{
241 + statsResponse: dataMemcachedStats,
242 + }
243 +}
244 +
245 +func prepareMockErrorResponse() *mockMemcachedConn {
246 + return &mockMemcachedConn{
247 + statsResponse: []byte("ERROR"),
248 + }
249 +}
250 +
251 +func prepareMockErrOnConnect() *mockMemcachedConn {
252 + return &mockMemcachedConn{
253 + errOnConnect: true,
254 + }
255 +}
256 +
257 +func prepareMockErrOnQueryStats() *mockMemcachedConn {
258 + return &mockMemcachedConn{
259 + errOnQueryStats: true,
260 + }
261 +}
262 +
263 +func prepareMockUnexpectedResponse() *mockMemcachedConn {
264 + return &mockMemcachedConn{
265 + statsResponse: []byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
266 + }
267 +}
268 +
269 +func prepareMockEmptyResponse() *mockMemcachedConn {
270 + return &mockMemcachedConn{}
271 +}
272 +
273 +type mockMemcachedConn struct {
274 + errOnConnect bool
275 + errOnQueryStats bool
276 + statsResponse []byte
277 + disconnectCalled bool
278 +}
279 +
280 +func (m *mockMemcachedConn) connect() error {
281 + if m.errOnConnect {
282 + return errors.New("mock.connect() error")
283 + }
284 + return nil
285 +}
286 +
287 +func (m *mockMemcachedConn) disconnect() {
288 + m.disconnectCalled = true
289 +}
290 +
291 +func (m *mockMemcachedConn) queryStats() ([]byte, error) {
292 + if m.errOnQueryStats {
293 + return nil, errors.New("mock.queryStats() error")
294 + }
295 + return m.statsResponse, nil
296 +}
src/go/plugin/go.d/modules/memcached/metadata.yaml renamed
+33 -63
@@ -1,7 +1,8 @@
1 -plugin_name: python.d.plugin
1 +plugin_name: go.d.plugin
2 modules:
3 - meta:
4 - plugin_name: python.d.plugin
4 + id: collector-go.d.plugin-memcached
5 + plugin_name: go.d.plugin
6 module_name: memcached
7 monitored_instance:
8 name: Memcached
@@ -23,7 +24,7 @@ modules:
24 overview:
25 data_collection:
26 metrics_description: "Monitor Memcached metrics for proficient in-memory key-value store operations. Track cache hits, misses, and memory usage for efficient data caching."
26 - method_description: "It reads server response to stats command ([stats interface](https://github.com/memcached/memcached/wiki/Commands#stats))."
27 + method_description: "It reads the server's response to the `stats` command."
28 supported_platforms:
29 include: []
30 exclude: []
@@ -43,84 +44,53 @@ modules:
44 list: []
45 configuration:
46 file:
46 - name: python.d/memcached.conf
47 - description: ""
47 + name: go.d/memcached.conf
48 options:
49 description: |
50 - There are 2 sections:
51 -
52 - * Global variables
53 - * One or more JOBS that can define multiple different instances to monitor.
54 -
55 - 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.
56 -
57 - Additionally, the following collapsed table contains all the options that can be configured inside a JOB definition.
58 -
59 - Every configuration JOB starts with a `job_name` value which will appear in the dashboard, unless a `name` parameter is specified.
50 + The following options can be defined globally: update_every, autodetection_retry.
51 folding:
52 title: Config options
53 enabled: true
54 list:
64 - - name: host
65 - description: the host to connect to.
66 - default_value: "127.0.0.1"
67 - required: false
68 - - name: port
69 - description: the port to connect to.
70 - default_value: "11211"
71 - required: false
55 - name: update_every
73 - description: Sets the default data collection frequency.
74 - default_value: 10
75 - required: false
76 - - name: priority
77 - description: Controls the order of charts at the netdata dashboard.
78 - default_value: 60000
56 + description: Data collection frequency.
57 + default_value: 1
58 required: false
59 - name: autodetection_retry
81 - description: Sets the job re-check interval in seconds.
60 + description: Recheck interval in seconds. Zero means no recheck will be scheduled.
61 default_value: 0
62 required: false
84 - - name: penalty
85 - description: Indicates whether to apply penalty to update_every in case of failures.
86 - default_value: yes
87 - required: false
88 - - name: name
89 - description: Job name. This value will overwrite the `job_name` value. JOBS with the same name are mutually exclusive. Only one of them will be allowed running at any time. This allows autodetection to try several alternatives and pick the one that works.
90 - default_value: ""
63 + - name: address
64 + description: The IP address and port where the memcached service listens for connections.
65 + default_value: 127.0.0.1:11211
66 + required: true
67 + - name: timeout
68 + description: Connection, read, and write timeout duration in seconds. The timeout includes name resolution.
69 + default_value: 1
70 required: false
71 examples:
72 folding:
73 + title: Config
74 enabled: true
95 - title: "Config"
75 list:
97 - - name: localhost
98 - description: An example configuration for localhost.
99 - folding:
100 - enabled: false
101 - config: |
102 - localhost:
103 - name: 'local'
104 - host: 'localhost'
105 - port: 11211
106 - - name: localipv4
107 - description: An example configuration for localipv4.
108 - folding:
109 - enabled: true
76 + - name: Basic
77 + description: A basic example configuration.
78 config: |
111 - localhost:
112 - name: 'local'
113 - host: '127.0.0.1'
114 - port: 11211
115 - - name: localipv6
116 - description: An example configuration for localipv6.
117 - folding:
118 - enabled: true
79 + jobs:
80 + - name: local
81 + address: 127.0.0.1:11211
82 + - name: Multi-instance
83 + description: |
84 + > **Note**: When you define multiple jobs, their names must be unique.
85 +
86 + Collecting metrics from local and remote instances.
87 config: |
120 - localhost:
121 - name: 'local'
122 - host: '::1'
123 - port: 11211
88 + jobs:
89 + - name: local
90 + address: 127.0.0.1:11211
91 +
92 + - name: remote
93 + address: 203.0.113.0:11211
94 troubleshooting:
95 problems:
96 list: []
src/go/plugin/go.d/modules/memcached/testdata/config.json new
+5
@@ -0,0 +1,5 @@
1 +{
2 + "update_every": 123,
3 + "address": "ok",
4 + "timeout": 123.123
5 +}
src/go/plugin/go.d/modules/memcached/testdata/config.yaml new
+3
@@ -0,0 +1,3 @@
1 +update_every: 123
2 +address: "ok"
3 +timeout: 123.123
src/go/plugin/go.d/modules/memcached/testdata/stats.txt new
+93
@@ -0,0 +1,93 @@
1 +STAT pid 30783
2 +STAT uptime 5028
3 +STAT time 1721297802
4 +STAT version 1.6.24
5 +STAT libevent 2.1.12-stable
6 +STAT pointer_size 64
7 +STAT rusage_user 1.026626
8 +STAT rusage_system 0.685365
9 +STAT max_connections 1024
10 +STAT curr_connections 3
11 +STAT total_connections 39
12 +STAT rejected_connections 0
13 +STAT connection_structures 6
14 +STAT response_obj_oom 0
15 +STAT response_obj_count 1
16 +STAT response_obj_bytes 65536
17 +STAT read_buf_count 9
18 +STAT read_buf_bytes 147456
19 +STAT read_buf_bytes_free 65536
20 +STAT read_buf_oom 0
21 +STAT reserved_fds 20
22 +STAT cmd_get 1
23 +STAT cmd_set 1
24 +STAT cmd_flush 0
25 +STAT cmd_touch 0
26 +STAT cmd_meta 0
27 +STAT get_hits 0
28 +STAT get_misses 1
29 +STAT get_expired 0
30 +STAT get_flushed 0
31 +STAT delete_misses 0
32 +STAT delete_hits 0
33 +STAT incr_misses 0
34 +STAT incr_hits 0
35 +STAT decr_misses 0
36 +STAT decr_hits 0
37 +STAT cas_misses 0
38 +STAT cas_hits 0
39 +STAT cas_badval 0
40 +STAT touch_hits 0
41 +STAT touch_misses 0
42 +STAT store_too_large 0
43 +STAT store_no_memory 0
44 +STAT auth_cmds 0
45 +STAT auth_errors 0
46 +STAT bytes_read 108662
47 +STAT bytes_written 9761348
48 +STAT limit_maxbytes 67108864
49 +STAT accepting_conns 1
50 +STAT listen_disabled_num 0
51 +STAT time_in_listen_disabled_us 0
52 +STAT threads 4
53 +STAT conn_yields 0
54 +STAT hash_power_level 16
55 +STAT hash_bytes 524288
56 +STAT hash_is_expanding 0
57 +STAT slab_reassign_rescues 0
58 +STAT slab_reassign_chunk_rescues 0
59 +STAT slab_reassign_evictions_nomem 0
60 +STAT slab_reassign_inline_reclaim 0
61 +STAT slab_reassign_busy_items 0
62 +STAT slab_reassign_busy_deletes 0
63 +STAT slab_reassign_running 0
64 +STAT slabs_moved 0
65 +STAT lru_crawler_running 0
66 +STAT lru_crawler_starts 13
67 +STAT lru_maintainer_juggles 9280
68 +STAT malloc_fails 0
69 +STAT log_worker_dropped 0
70 +STAT log_worker_written 0
71 +STAT log_watcher_skipped 0
72 +STAT log_watcher_sent 0
73 +STAT log_watchers 0
74 +STAT unexpected_napi_ids 0
75 +STAT round_robin_fallback 0
76 +STAT bytes 33
77 +STAT curr_items 0
78 +STAT total_items 1
79 +STAT slab_global_page_pool 0
80 +STAT expired_unfetched 1
81 +STAT evicted_unfetched 0
82 +STAT evicted_active 0
83 +STAT evictions 0
84 +STAT reclaimed 1
85 +STAT crawler_reclaimed 0
86 +STAT crawler_items_checked 0
87 +STAT lrutail_reflocked 0
88 +STAT moves_to_cold 1
89 +STAT moves_to_warm 0
90 +STAT moves_within_lru 0
91 +STAT direct_reclaims 0
92 +STAT lru_bumps_dropped 0
93 +END