@cryptotaxi247 / netdata-1 / commits / e5e900d77

Port ipfs from python to Go (#18070)

* port ipfs to go * format testdata * fix imports * updates --------- Co-authored-by: ilyam8 <ilya@netdata.cloud>

Fotis Voutsas committed Jul 5, 2024 at 08:24 UTC e5e900d77207f442259a94235a739caca538b71d
26 files changed +1299 -645
CMakeLists.txt
-2
@@ -2858,7 +2858,6 @@ install(FILES
2858 src/collectors/python.d.plugin/go_expvar/go_expvar.conf
2859 src/collectors/python.d.plugin/haproxy/haproxy.conf
2860 src/collectors/python.d.plugin/icecast/icecast.conf
2861 - src/collectors/python.d.plugin/ipfs/ipfs.conf
2861 src/collectors/python.d.plugin/memcached/memcached.conf
2862 src/collectors/python.d.plugin/monit/monit.conf
2863 src/collectors/python.d.plugin/nsd/nsd.conf
@@ -2898,7 +2897,6 @@ install(FILES
2897 src/collectors/python.d.plugin/go_expvar/go_expvar.chart.py
2898 src/collectors/python.d.plugin/haproxy/haproxy.chart.py
2899 src/collectors/python.d.plugin/icecast/icecast.chart.py
2901 - src/collectors/python.d.plugin/ipfs/ipfs.chart.py
2900 src/collectors/python.d.plugin/memcached/memcached.chart.py
2901 src/collectors/python.d.plugin/monit/monit.chart.py
2902 src/collectors/python.d.plugin/nsd/nsd.chart.py
src/collectors/python.d.plugin/ipfs/README.md deleted
-1
@@ -1 +0,0 @@
1 -integrations/ipfs.md
\ No newline at end of file
src/collectors/python.d.plugin/ipfs/integrations/ipfs.md deleted
-236
@@ -1,236 +0,0 @@
1 -<!--startmeta
2 -custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/python.d.plugin/ipfs/README.md"
3 -meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/python.d.plugin/ipfs/metadata.yaml"
4 -sidebar_label: "IPFS"
5 -learn_status: "Published"
6 -learn_rel_path: "Collecting Metrics/Storage, Mount Points and Filesystems"
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 -# IPFS
12 -
13 -
14 -<img src="https://netdata.cloud/img/ipfs.svg" width="150"/>
15 -
16 -
17 -Plugin: python.d.plugin
18 -Module: ipfs
19 -
20 -<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
21 -
22 -## Overview
23 -
24 -This collector monitors IPFS server metrics about its quality and performance.
25 -
26 -It connects to an http endpoint of the IPFS server to collect the metrics
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 the endpoint is accessible by the Agent, netdata will autodetect it
38 -
39 -#### Limits
40 -
41 -Calls to the following endpoints are disabled due to IPFS bugs:
42 -
43 -/api/v0/stats/repo (https://github.com/ipfs/go-ipfs/issues/3874)
44 -/api/v0/pin/ls (https://github.com/ipfs/go-ipfs/issues/7528)
45 -
46 -
47 -#### Performance Impact
48 -
49 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
50 -
51 -
52 -## Metrics
53 -
54 -Metrics grouped by *scope*.
55 -
56 -The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels.
57 -
58 -
59 -
60 -### Per IPFS instance
61 -
62 -These metrics refer to the entire monitored application.
63 -
64 -This scope has no labels.
65 -
66 -Metrics:
67 -
68 -| Metric | Dimensions | Unit |
69 -|:------|:----------|:----|
70 -| ipfs.bandwidth | in, out | kilobits/s |
71 -| ipfs.peers | peers | peers |
72 -| ipfs.repo_size | avail, size | GiB |
73 -| ipfs.repo_objects | objects, pinned, recursive_pins | objects |
74 -
75 -
76 -
77 -## Alerts
78 -
79 -
80 -The following alerts are available:
81 -
82 -| Alert name | On metric | Description |
83 -|:------------|:----------|:------------|
84 -| [ ipfs_datastore_usage ](https://github.com/netdata/netdata/blob/master/src/health/health.d/ipfs.conf) | ipfs.repo_size | IPFS datastore utilization |
85 -
86 -
87 -## Setup
88 -
89 -### Prerequisites
90 -
91 -No action required.
92 -
93 -### Configuration
94 -
95 -#### File
96 -
97 -The configuration file name for this integration is `python.d/ipfs.conf`.
98 -
99 -
100 -You can edit the configuration file using the `edit-config` script from the
101 -Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory).
102 -
103 -```bash
104 -cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
105 -sudo ./edit-config python.d/ipfs.conf
106 -```
107 -#### Options
108 -
109 -There are 2 sections:
110 -
111 -* Global variables
112 -* One or more JOBS that can define multiple different instances to monitor.
113 -
114 -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.
115 -
116 -Additionally, the following collapsed table contains all the options that can be configured inside a JOB definition.
117 -
118 -Every configuration JOB starts with a `job_name` value which will appear in the dashboard, unless a `name` parameter is specified.
119 -
120 -
121 -<details open><summary></summary>
122 -
123 -| Name | Description | Default | Required |
124 -|:----|:-----------|:-------|:--------:|
125 -| update_every | Sets the default data collection frequency. | 5 | no |
126 -| priority | Controls the order of charts at the netdata dashboard. | 60000 | no |
127 -| autodetection_retry | Sets the job re-check interval in seconds. | 0 | no |
128 -| penalty | Indicates whether to apply penalty to update_every in case of failures. | yes | no |
129 -| name | The JOB's name as it will appear at the dashboard (by default is the job_name) | job_name | no |
130 -| url | URL to the IPFS API | no | yes |
131 -| repoapi | Collect repo metrics. | no | no |
132 -| pinapi | Set status of IPFS pinned object polling. | no | no |
133 -
134 -</details>
135 -
136 -#### Examples
137 -
138 -##### Basic (default out-of-the-box)
139 -
140 -A basic example configuration, one job will run at a time. Autodetect mechanism uses it by default.
141 -
142 -```yaml
143 -localhost:
144 - name: 'local'
145 - url: 'http://localhost:5001'
146 - repoapi: no
147 - pinapi: no
148 -
149 -```
150 -##### Multi-instance
151 -
152 -> **Note**: When you define multiple jobs, their names must be unique.
153 -
154 -Collecting metrics from local and remote instances.
155 -
156 -
157 -<details open><summary>Config</summary>
158 -
159 -```yaml
160 -localhost:
161 - name: 'local'
162 - url: 'http://localhost:5001'
163 - repoapi: no
164 - pinapi: no
165 -
166 -remote_host:
167 - name: 'remote'
168 - url: 'http://192.0.2.1:5001'
169 - repoapi: no
170 - pinapi: no
171 -
172 -```
173 -</details>
174 -
175 -
176 -
177 -## Troubleshooting
178 -
179 -### Debug Mode
180 -
181 -To troubleshoot issues with the `ipfs` collector, run the `python.d.plugin` with the debug option enabled. The output
182 -should give you clues as to why the collector isn't working.
183 -
184 -- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on
185 - your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`.
186 -
187 - ```bash
188 - cd /usr/libexec/netdata/plugins.d/
189 - ```
190 -
191 -- Switch to the `netdata` user.
192 -
193 - ```bash
194 - sudo -u netdata -s
195 - ```
196 -
197 -- Run the `python.d.plugin` to debug the collector:
198 -
199 - ```bash
200 - ./python.d.plugin ipfs debug trace
201 - ```
202 -
203 -### Getting Logs
204 -
205 -If you're encountering problems with the `ipfs` collector, follow these steps to retrieve logs and identify potential issues:
206 -
207 -- **Run the command** specific to your system (systemd, non-systemd, or Docker container).
208 -- **Examine the output** for any warnings or error messages that might indicate issues. These messages should provide clues about the root cause of the problem.
209 -
210 -#### System with systemd
211 -
212 -Use the following command to view logs generated since the last Netdata service restart:
213 -
214 -```bash
215 -journalctl _SYSTEMD_INVOCATION_ID="$(systemctl show --value --property=InvocationID netdata)" --namespace=netdata --grep ipfs
216 -```
217 -
218 -#### System without systemd
219 -
220 -Locate the collector log file, typically at `/var/log/netdata/collector.log`, and use `grep` to filter for collector's name:
221 -
222 -```bash
223 -grep ipfs /var/log/netdata/collector.log
224 -```
225 -
226 -**Note**: This method shows logs from all restarts. Focus on the **latest entries** for troubleshooting current issues.
227 -
228 -#### Docker Container
229 -
230 -If your Netdata runs in a Docker container named "netdata" (replace if different), use this command:
231 -
232 -```bash
233 -docker logs netdata 2>&1 | grep ipfs
234 -```
235 -
236 -
src/collectors/python.d.plugin/ipfs/ipfs.chart.py deleted
-149
@@ -1,149 +0,0 @@
1 -# -*- coding: utf-8 -*-
2 -# Description: IPFS netdata python.d module
3 -# Authors: davidak
4 -# SPDX-License-Identifier: GPL-3.0-or-later
5 -
6 -import json
7 -
8 -from bases.FrameworkServices.UrlService import UrlService
9 -
10 -ORDER = [
11 - 'bandwidth',
12 - 'peers',
13 - 'repo_size',
14 - 'repo_objects',
15 -]
16 -
17 -CHARTS = {
18 - 'bandwidth': {
19 - 'options': [None, 'IPFS Bandwidth', 'kilobits/s', 'Bandwidth', 'ipfs.bandwidth', 'line'],
20 - 'lines': [
21 - ['in', None, 'absolute', 8, 1000],
22 - ['out', None, 'absolute', -8, 1000]
23 - ]
24 - },
25 - 'peers': {
26 - 'options': [None, 'IPFS Peers', 'peers', 'Peers', 'ipfs.peers', 'line'],
27 - 'lines': [
28 - ['peers', None, 'absolute']
29 - ]
30 - },
31 - 'repo_size': {
32 - 'options': [None, 'IPFS Repo Size', 'GiB', 'Size', 'ipfs.repo_size', 'area'],
33 - 'lines': [
34 - ['avail', None, 'absolute', 1, 1 << 30],
35 - ['size', None, 'absolute', 1, 1 << 30],
36 - ]
37 - },
38 - 'repo_objects': {
39 - 'options': [None, 'IPFS Repo Objects', 'objects', 'Objects', 'ipfs.repo_objects', 'line'],
40 - 'lines': [
41 - ['objects', None, 'absolute', 1, 1],
42 - ['pinned', None, 'absolute', 1, 1],
43 - ['recursive_pins', None, 'absolute', 1, 1]
44 - ]
45 - }
46 -}
47 -
48 -SI_zeroes = {
49 - 'k': 3,
50 - 'm': 6,
51 - 'g': 9,
52 - 't': 12,
53 - 'p': 15,
54 - 'e': 18,
55 - 'z': 21,
56 - 'y': 24
57 -}
58 -
59 -
60 -class Service(UrlService):
61 - def __init__(self, configuration=None, name=None):
62 - UrlService.__init__(self, configuration=configuration, name=name)
63 - self.order = ORDER
64 - self.definitions = CHARTS
65 - self.baseurl = self.configuration.get('url', 'http://localhost:5001')
66 - self.method = "POST"
67 - self.do_pinapi = self.configuration.get('pinapi')
68 - self.do_repoapi = self.configuration.get('repoapi')
69 - self.__storage_max = None
70 -
71 - def _get_json(self, sub_url):
72 - """
73 - :return: json decoding of the specified url
74 - """
75 - self.url = self.baseurl + sub_url
76 - try:
77 - return json.loads(self._get_raw_data())
78 - except (TypeError, ValueError):
79 - return dict()
80 -
81 - @staticmethod
82 - def _recursive_pins(keys):
83 - return sum(1 for k in keys if keys[k]['Type'] == b'recursive')
84 -
85 - @staticmethod
86 - def _dehumanize(store_max):
87 - # convert from '10Gb' to 10000000000
88 - if not isinstance(store_max, int):
89 - store_max = store_max.lower()
90 - if store_max.endswith('b'):
91 - val, units = store_max[:-2], store_max[-2]
92 - if units in SI_zeroes:
93 - val += '0' * SI_zeroes[units]
94 - store_max = val
95 - try:
96 - store_max = int(store_max)
97 - except (TypeError, ValueError):
98 - store_max = None
99 - return store_max
100 -
101 - def _storagemax(self, store_cfg):
102 - if self.__storage_max is None:
103 - self.__storage_max = self._dehumanize(store_cfg)
104 - return self.__storage_max
105 -
106 - def _get_data(self):
107 - """
108 - Get data from API
109 - :return: dict
110 - """
111 - # suburl : List of (result-key, original-key, transform-func)
112 - cfg = {
113 - '/api/v0/stats/bw':
114 - [
115 - ('in', 'RateIn', int),
116 - ('out', 'RateOut', int),
117 - ],
118 - '/api/v0/swarm/peers':
119 - [
120 - ('peers', 'Peers', len),
121 - ],
122 - }
123 - if self.do_repoapi:
124 - cfg.update({
125 - '/api/v0/stats/repo':
126 - [
127 - ('size', 'RepoSize', int),
128 - ('objects', 'NumObjects', int),
129 - ('avail', 'StorageMax', self._storagemax),
130 - ],
131 - })
132 -
133 - if self.do_pinapi:
134 - cfg.update({
135 - '/api/v0/pin/ls':
136 - [
137 - ('pinned', 'Keys', len),
138 - ('recursive_pins', 'Keys', self._recursive_pins),
139 - ]
140 - })
141 - r = dict()
142 - for suburl in cfg:
143 - in_json = self._get_json(suburl)
144 - for new_key, orig_key, xmute in cfg[suburl]:
145 - try:
146 - r[new_key] = xmute(in_json[orig_key])
147 - except Exception as error:
148 - self.debug(error)
149 - return r or None
src/collectors/python.d.plugin/ipfs/ipfs.conf deleted
-82
@@ -1,82 +0,0 @@
1 -# netdata python.d.plugin configuration for ipfs
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, ipfs also supports the following:
63 -#
64 -# url: 'URL' # URL to the IPFS API
65 -# repoapi: no # Collect repo metrics
66 -# # Currently defaults to disabled due to IPFS Bug
67 -# # https://github.com/ipfs/go-ipfs/issues/7528
68 -# # resulting in very high CPU Usage
69 -# pinapi: no # Set status of IPFS pinned object polling
70 -# # Currently defaults to disabled due to IPFS Bug
71 -# # https://github.com/ipfs/go-ipfs/issues/3874
72 -# # resulting in very high CPU Usage
73 -#
74 -# ----------------------------------------------------------------------
75 -# AUTO-DETECTION JOBS
76 -# only one of them will run (they have the same name)
77 -
78 -localhost:
79 - name: 'local'
80 - url: 'http://localhost:5001'
81 - repoapi: no
82 - pinapi: no
src/collectors/python.d.plugin/ipfs/metadata.yaml deleted
-172
@@ -1,172 +0,0 @@
1 -plugin_name: python.d.plugin
2 -modules:
3 - - meta:
4 - plugin_name: python.d.plugin
5 - module_name: ipfs
6 - monitored_instance:
7 - name: IPFS
8 - link: "https://ipfs.tech/"
9 - categories:
10 - - data-collection.storage-mount-points-and-filesystems
11 - icon_filename: "ipfs.svg"
12 - related_resources:
13 - integrations:
14 - list: []
15 - info_provided_to_referring_integrations:
16 - description: ""
17 - keywords: []
18 - most_popular: false
19 - overview:
20 - data_collection:
21 - metrics_description: "This collector monitors IPFS server metrics about its quality and performance."
22 - method_description: "It connects to an http endpoint of the IPFS server to collect the metrics"
23 - supported_platforms:
24 - include: []
25 - exclude: []
26 - multi_instance: true
27 - additional_permissions:
28 - description: ""
29 - default_behavior:
30 - auto_detection:
31 - description: "If the endpoint is accessible by the Agent, netdata will autodetect it"
32 - limits:
33 - description: |
34 - Calls to the following endpoints are disabled due to IPFS bugs:
35 -
36 - /api/v0/stats/repo (https://github.com/ipfs/go-ipfs/issues/3874)
37 - /api/v0/pin/ls (https://github.com/ipfs/go-ipfs/issues/7528)
38 - performance_impact:
39 - description: ""
40 - setup:
41 - prerequisites:
42 - list: []
43 - configuration:
44 - file:
45 - name: "python.d/ipfs.conf"
46 - options:
47 - description: |
48 - There are 2 sections:
49 -
50 - * Global variables
51 - * One or more JOBS that can define multiple different instances to monitor.
52 -
53 - 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.
54 -
55 - Additionally, the following collapsed table contains all the options that can be configured inside a JOB definition.
56 -
57 - Every configuration JOB starts with a `job_name` value which will appear in the dashboard, unless a `name` parameter is specified.
58 - folding:
59 - title: ""
60 - enabled: true
61 - list:
62 - - name: update_every
63 - description: Sets the default data collection frequency.
64 - default_value: 5
65 - required: false
66 - - name: priority
67 - description: Controls the order of charts at the netdata dashboard.
68 - default_value: 60000
69 - required: false
70 - - name: autodetection_retry
71 - description: Sets the job re-check interval in seconds.
72 - default_value: 0
73 - required: false
74 - - name: penalty
75 - description: Indicates whether to apply penalty to update_every in case of failures.
76 - default_value: yes
77 - required: false
78 - - name: name
79 - description: The JOB's name as it will appear at the dashboard (by default is the job_name)
80 - default_value: job_name
81 - required: false
82 - - name: url
83 - description: URL to the IPFS API
84 - default_value: no
85 - required: true
86 - - name: repoapi
87 - description: Collect repo metrics.
88 - default_value: no
89 - required: false
90 - - name: pinapi
91 - description: Set status of IPFS pinned object polling.
92 - default_value: no
93 - required: false
94 - examples:
95 - folding:
96 - enabled: true
97 - title: "Config"
98 - list:
99 - - name: Basic (default out-of-the-box)
100 - description: A basic example configuration, one job will run at a time. Autodetect mechanism uses it by default.
101 - folding:
102 - enabled: false
103 - config: |
104 - localhost:
105 - name: 'local'
106 - url: 'http://localhost:5001'
107 - repoapi: no
108 - pinapi: no
109 - - name: Multi-instance
110 - description: |
111 - > **Note**: When you define multiple jobs, their names must be unique.
112 -
113 - Collecting metrics from local and remote instances.
114 - config: |
115 - localhost:
116 - name: 'local'
117 - url: 'http://localhost:5001'
118 - repoapi: no
119 - pinapi: no
120 -
121 - remote_host:
122 - name: 'remote'
123 - url: 'http://192.0.2.1:5001'
124 - repoapi: no
125 - pinapi: no
126 - troubleshooting:
127 - problems:
128 - list: []
129 - alerts:
130 - - name: ipfs_datastore_usage
131 - link: https://github.com/netdata/netdata/blob/master/src/health/health.d/ipfs.conf
132 - metric: ipfs.repo_size
133 - info: IPFS datastore utilization
134 - metrics:
135 - folding:
136 - title: Metrics
137 - enabled: false
138 - description: ""
139 - availability: []
140 - scopes:
141 - - name: global
142 - description: "These metrics refer to the entire monitored application."
143 - labels: []
144 - metrics:
145 - - name: ipfs.bandwidth
146 - description: IPFS Bandwidth
147 - unit: "kilobits/s"
148 - chart_type: line
149 - dimensions:
150 - - name: in
151 - - name: out
152 - - name: ipfs.peers
153 - description: IPFS Peers
154 - unit: "peers"
155 - chart_type: line
156 - dimensions:
157 - - name: peers
158 - - name: ipfs.repo_size
159 - description: IPFS Repo Size
160 - unit: "GiB"
161 - chart_type: area
162 - dimensions:
163 - - name: avail
164 - - name: size
165 - - name: ipfs.repo_objects
166 - description: IPFS Repo Objects
167 - unit: "objects"
168 - chart_type: line
169 - dimensions:
170 - - name: objects
171 - - name: pinned
172 - - name: recursive_pins
src/collectors/python.d.plugin/python.d.conf
+1 -1
@@ -40,7 +40,6 @@ example: no
40 go_expvar: no
41 # haproxy: yes
42 # icecast: yes
43 -# ipfs: yes
43 # memcached: yes
44 # monit: yes
45 # nvidia_smi: yes
@@ -73,6 +72,7 @@ fail2ban: no # Removed (replaced with go.d/fail2ban).
72 freeradius: no # Removed (replaced with go.d/freeradius).
73 hddtemp: no # Removed (replaced with go.d/hddtemp).
74 hpssa: no # Removed (replaced with go.d/hpssa).
75 +ipfs: no # Removed (replaced with go.d/ipfs).
76 litespeed: no # Removed (replaced with go.d/litespeed).
77 megacli: no # Removed (replaced with go.d/megacli).
78 mongodb: no # Removed (replaced with go.d/mongodb).
src/go/plugin/go.d/README.md
+1
@@ -82,6 +82,7 @@ see the appropriate collector readme.
82 | [hpssa](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/hpssa) | HPE Smart Array |
83 | [httpcheck](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/httpcheck) | Any HTTP Endpoint |
84 | [intelgpu](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/intelgpu) | Intel integrated GPU |
85 +| [ipfs](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/ipfs) | IPFS |
86 | [isc_dhcpd](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/isc_dhcpd) | ISC DHCP |
87 | [k8s_kubelet](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/k8s_kubelet) | Kubelet |
88 | [k8s_kubeproxy](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/k8s_kubeproxy) | Kube-proxy |
src/go/plugin/go.d/config/go.d.conf
+1
@@ -47,6 +47,7 @@ modules:
47 # hpssa: yes
48 # httpcheck: yes
49 # intelgpu: yes
50 +# ipfs: yes
51 # isc_dhcpd: yes
52 # k8s_kubelet: yes
53 # k8s_kubeproxy: yes
src/go/plugin/go.d/config/go.d/ipfs.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/ipfs#readme
3 +
4 +#jobs:
5 +# - name: local
6 +# url: http://localhost:5001
src/go/plugin/go.d/config/go.d/sd/docker.conf
+7
@@ -38,6 +38,8 @@ classify:
38 expr: '{{ or (eq .PrivatePort "5984") (match "sp" .Image "couchdb couchdb:*") }}'
39 - tags: "elasticsearch"
40 expr: '{{ or (eq .PrivatePort "9200") (match "sp" .Image "elasticsearch elasticsearch:* */elasticsearch */elasticsearch:* */opensearch */opensearch:*") }}'
41 + - tags: "ipfs"
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: "mongodb"
@@ -115,6 +117,11 @@ compose:
117 username: admin
118 password: admin
119 {{ end -}}
120 + - selector: "ipfs"
121 + template: |
122 + module: ipfs
123 + name: docker_{{.Name}}
124 + url: http://{{.Address}}
125 - selector: "lighttpd"
126 template: |
127 module: lighttpd
src/go/plugin/go.d/config/go.d/sd/net_listeners.conf
+7
@@ -58,6 +58,8 @@ classify:
58 expr: '{{ and (eq .Port "9870") (eq .Comm "hadoop") }}'
59 - tags: "hdfs_datanode"
60 expr: '{{ and (eq .Port "9864") (eq .Comm "hadoop") }}'
61 + - tags: "ipfs"
62 + expr: '{{ and (eq .Port "5001") (eq .Comm "ipfs") }}'
63 - tags: "kubelet"
64 expr: '{{ and (eq .Port "10250" "10255") (eq .Comm "kubelet") }}'
65 - tags: "kubeproxy"
@@ -252,6 +254,11 @@ compose:
254 module: hdfs
255 name: datanode_local
256 url: http://{{.Address}}/jmx
257 + - selector: "ipfs"
258 + template: |
259 + module: ipfs
260 + name: local
261 + url: http://{{.Address}}
262 - selector: "kubelet"
263 template: |
264 module: k8s_kubelet
src/go/plugin/go.d/modules/init.go
+1
@@ -37,6 +37,7 @@ import (
37 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/hpssa"
38 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/httpcheck"
39 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/intelgpu"
40 + _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/ipfs"
41 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/isc_dhcpd"
42 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/k8s_kubelet"
43 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/k8s_kubeproxy"
src/go/plugin/go.d/modules/ipfs/charts.go new
+105
@@ -0,0 +1,105 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package ipfs
4 +
5 +import (
6 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
7 +)
8 +
9 +const (
10 + prioBandwidth = module.Priority + iota
11 + prioSwarmPeers
12 + prioDatastoreSpaceUtilization
13 + prioRepoSize
14 + prioRepoObj
15 + prioRepoPinnedObj
16 +)
17 +
18 +var charts = module.Charts{
19 + bandwidthChart.Copy(),
20 + peersChart.Copy(),
21 + datastoreUtilizationChart.Copy(),
22 + repoSizeChart.Copy(),
23 + repoObjChart.Copy(),
24 + repoPinnedObjChart.Copy(),
25 +}
26 +
27 +var (
28 + bandwidthChart = module.Chart{
29 + ID: "bandwidth",
30 + Title: "IPFS Bandwidth",
31 + Units: "bytes/s",
32 + Fam: "bandwidth",
33 + Ctx: "ipfs.bandwidth",
34 + Type: module.Area,
35 + Priority: prioBandwidth,
36 + Dims: module.Dims{
37 + {ID: "in", Algo: module.Incremental},
38 + {ID: "out", Mul: -1, Algo: module.Incremental},
39 + },
40 + }
41 +
42 + peersChart = module.Chart{
43 + ID: "peers",
44 + Title: "IPFS Peers",
45 + Units: "peers",
46 + Fam: "peers",
47 + Ctx: "ipfs.peers",
48 + Type: module.Line,
49 + Priority: prioSwarmPeers,
50 + Dims: module.Dims{
51 + {ID: "peers"},
52 + },
53 + }
54 +
55 + datastoreUtilizationChart = module.Chart{
56 + ID: "datastore_space_utilization",
57 + Title: "IPFS Datastore Space Utilization",
58 + Units: "percent",
59 + Fam: "size",
60 + Ctx: "ipfs.datastore_space_utilization",
61 + Type: module.Line,
62 + Priority: prioDatastoreSpaceUtilization,
63 + Dims: module.Dims{
64 + {ID: "used_percent", Name: "used"},
65 + },
66 + }
67 + repoSizeChart = module.Chart{
68 + ID: "repo_size",
69 + Title: "IPFS Repo Size",
70 + Units: "bytes",
71 + Fam: "size",
72 + Ctx: "ipfs.repo_size",
73 + Type: module.Line,
74 + Priority: prioRepoSize,
75 + Dims: module.Dims{
76 + {ID: "size"},
77 + },
78 + }
79 +
80 + repoObjChart = module.Chart{
81 + ID: "repo_objects",
82 + Title: "IPFS Repo Objects",
83 + Units: "objects",
84 + Fam: "objects",
85 + Ctx: "ipfs.repo_objects",
86 + Type: module.Line,
87 + Priority: prioRepoObj,
88 + Dims: module.Dims{
89 + {ID: "objects"},
90 + },
91 + }
92 + repoPinnedObjChart = module.Chart{
93 + ID: "repo_pinned_objects",
94 + Title: "IPFS Repo Pinned Objects",
95 + Units: "objects",
96 + Fam: "objects",
97 + Ctx: "ipfs.repo_pinned_objects",
98 + Type: module.Line,
99 + Priority: prioRepoPinnedObj,
100 + Dims: module.Dims{
101 + {ID: "pinned"},
102 + {ID: "recursive_pins"},
103 + },
104 + }
105 +)
src/go/plugin/go.d/modules/ipfs/collect.go new
+217
@@ -0,0 +1,217 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package ipfs
4 +
5 +import (
6 + "encoding/json"
7 + "fmt"
8 + "io"
9 + "net/http"
10 +
11 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
12 +)
13 +
14 +type (
15 + ipfsStatsBw struct {
16 + TotalIn int64 `json:"TotalIn"`
17 + TotalOut int64 `json:"TotalOut"`
18 + RateIn *float64 `json:"RateIn"`
19 + RateOut *float64 `json:"RateOut"`
20 + }
21 + ipfsStatsRepo struct {
22 + RepoSize int64 `json:"RepoSize"`
23 + StorageMax int64 `json:"StorageMax"`
24 + NumObjects int64 `json:"NumObjects"`
25 + }
26 + ipfsSwarmPeers struct {
27 + Peers []any `json:"Peers"`
28 + }
29 + ipfsPinsLs struct {
30 + Keys map[string]struct {
31 + Type string `json:"type"`
32 + } `json:"Keys"`
33 + }
34 +)
35 +
36 +const (
37 + urlPathStatsBandwidth = "/api/v0/stats/bw" // https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-stats-bw
38 + urlPathStatsRepo = "/api/v0/stats/repo" // https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-stats-repo
39 + urlPathSwarmPeers = "/api/v0/swarm/peers" // https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-swarm-peers
40 + urlPathPinLs = "/api/v0/pin/ls" // https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-pin-ls
41 +)
42 +
43 +func (ip *IPFS) collect() (map[string]int64, error) {
44 + mx := make(map[string]int64)
45 +
46 + if err := ip.collectStatsBandwidth(mx); err != nil {
47 + return nil, err
48 + }
49 + if err := ip.collectSwarmPeers(mx); err != nil {
50 + return nil, err
51 + }
52 + if ip.QueryRepoApi {
53 + // https://github.com/netdata/netdata/pull/9687
54 + // TODO: collect by default with "size-only"
55 + // https://github.com/ipfs/kubo/issues/7528#issuecomment-657398332
56 + if err := ip.collectStatsRepo(mx); err != nil {
57 + return nil, err
58 + }
59 + }
60 + if ip.QueryPinApi {
61 + if err := ip.collectPinLs(mx); err != nil {
62 + return nil, err
63 + }
64 + }
65 +
66 + return mx, nil
67 +}
68 +
69 +func (ip *IPFS) collectStatsBandwidth(mx map[string]int64) error {
70 + stats, err := ip.queryStatsBandwidth()
71 + if err != nil {
72 + return err
73 + }
74 +
75 + mx["in"] = stats.TotalIn
76 + mx["out"] = stats.TotalOut
77 +
78 + return nil
79 +}
80 +
81 +func (ip *IPFS) collectSwarmPeers(mx map[string]int64) error {
82 + stats, err := ip.querySwarmPeers()
83 + if err != nil {
84 + return err
85 + }
86 +
87 + mx["peers"] = int64(len(stats.Peers))
88 +
89 + return nil
90 +}
91 +
92 +func (ip *IPFS) collectStatsRepo(mx map[string]int64) error {
93 + stats, err := ip.queryStatsRepo()
94 + if err != nil {
95 + return err
96 + }
97 +
98 + mx["used_percent"] = 0
99 + if stats.StorageMax > 0 {
100 + mx["used_percent"] = stats.RepoSize * 100 / stats.StorageMax
101 + }
102 + mx["size"] = stats.RepoSize
103 + mx["objects"] = stats.NumObjects
104 +
105 + return nil
106 +}
107 +
108 +func (ip *IPFS) collectPinLs(mx map[string]int64) error {
109 + stats, err := ip.queryPinLs()
110 + if err != nil {
111 + return err
112 + }
113 +
114 + var n int64
115 + for _, v := range stats.Keys {
116 + if v.Type == "recursive" {
117 + n++
118 + }
119 + }
120 +
121 + mx["pinned"] = int64(len(stats.Keys))
122 + mx["recursive_pins"] = n
123 +
124 + return nil
125 +}
126 +
127 +func (ip *IPFS) queryStatsBandwidth() (*ipfsStatsBw, error) {
128 + req, err := web.NewHTTPRequest(ip.Request)
129 + if err != nil {
130 + return nil, err
131 + }
132 +
133 + req.URL.Path = urlPathStatsBandwidth
134 +
135 + var stats ipfsStatsBw
136 + if err := ip.doOKDecode(req, &stats); err != nil {
137 + return nil, err
138 + }
139 +
140 + if stats.RateIn == nil || stats.RateOut == nil {
141 + return nil, fmt.Errorf("unexpected response: not ipfs data")
142 + }
143 +
144 + return &stats, nil
145 +}
146 +
147 +func (ip *IPFS) querySwarmPeers() (*ipfsSwarmPeers, error) {
148 + req, err := web.NewHTTPRequest(ip.Request)
149 + if err != nil {
150 + return nil, err
151 + }
152 +
153 + req.URL.Path = urlPathSwarmPeers
154 +
155 + var stats ipfsSwarmPeers
156 + if err := ip.doOKDecode(req, &stats); err != nil {
157 + return nil, err
158 + }
159 +
160 + return &stats, nil
161 +}
162 +
163 +func (ip *IPFS) queryStatsRepo() (*ipfsStatsRepo, error) {
164 + req, err := web.NewHTTPRequest(ip.Request)
165 + if err != nil {
166 + return nil, err
167 + }
168 +
169 + req.URL.Path = urlPathStatsRepo
170 +
171 + var stats ipfsStatsRepo
172 + if err := ip.doOKDecode(req, &stats); err != nil {
173 + return nil, err
174 + }
175 +
176 + return &stats, nil
177 +}
178 +
179 +func (ip *IPFS) queryPinLs() (*ipfsPinsLs, error) {
180 + req, err := web.NewHTTPRequest(ip.Request)
181 + if err != nil {
182 + return nil, err
183 + }
184 +
185 + req.URL.Path = urlPathPinLs
186 +
187 + var stats ipfsPinsLs
188 + if err := ip.doOKDecode(req, &stats); err != nil {
189 + return nil, err
190 + }
191 +
192 + return &stats, nil
193 +}
194 +
195 +func (ip *IPFS) doOKDecode(req *http.Request, in interface{}) error {
196 + resp, err := ip.httpClient.Do(req)
197 + if err != nil {
198 + return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
199 + }
200 + defer closeBody(resp)
201 +
202 + if resp.StatusCode != http.StatusOK {
203 + return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
204 + }
205 +
206 + if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
207 + return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
208 + }
209 + return nil
210 +}
211 +
212 +func closeBody(resp *http.Response) {
213 + if resp != nil && resp.Body != nil {
214 + _, _ = io.Copy(io.Discard, resp.Body)
215 + _ = resp.Body.Close()
216 + }
217 +}
src/go/plugin/go.d/modules/ipfs/config_schema.json new
+195
@@ -0,0 +1,195 @@
1 +{
2 + "jsonSchema": {
3 + "$schema": "http://json-schema.org/draft-07/schema#",
4 + "title": "IPFS collector configuration.",
5 + "type": "object",
6 + "properties": {
7 + "update_every": {
8 + "title": "Update every",
9 + "description": "Data collection interval, measured in seconds.",
10 + "type": "integer",
11 + "minimum": 1,
12 + "default": 1
13 + },
14 + "url": {
15 + "title": "URL",
16 + "description": "The base URL where the IPFS API can be accessed.",
17 + "type": "string",
18 + "default": "http://127.0.0.1:5001",
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 + "repoapi": {
34 + "title": "Query Repo API",
35 + "description": "Enables querying the [/api/v0/stats/repo](https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-repo-stat) endpoint for repository statistics. **Disabled by default** due to potential high CPU usage.",
36 + "type": "boolean"
37 + },
38 + "pinapi": {
39 + "title": "Query Pin API",
40 + "description": "Enables querying the [/api/v0/pin/ls](https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-pin-ls) endpoint to retrieve a list of all pinned objects. **Consider enabling only if necessary**.",
41 + "type": "boolean"
42 + },
43 + "username": {
44 + "title": "Username",
45 + "description": "The username for basic authentication.",
46 + "type": "string",
47 + "sensitive": true
48 + },
49 + "password": {
50 + "title": "Password",
51 + "description": "The password for basic authentication.",
52 + "type": "string",
53 + "sensitive": true
54 + },
55 + "proxy_url": {
56 + "title": "Proxy URL",
57 + "description": "The URL of the proxy server.",
58 + "type": "string"
59 + },
60 + "proxy_username": {
61 + "title": "Proxy username",
62 + "description": "The username for proxy authentication.",
63 + "type": "string",
64 + "sensitive": true
65 + },
66 + "proxy_password": {
67 + "title": "Proxy password",
68 + "description": "The password for proxy authentication.",
69 + "type": "string",
70 + "sensitive": true
71 + },
72 + "headers": {
73 + "title": "Headers",
74 + "description": "Additional HTTP headers to include in the request.",
75 + "type": [
76 + "object",
77 + "null"
78 + ],
79 + "additionalProperties": {
80 + "type": "string"
81 + }
82 + },
83 + "tls_skip_verify": {
84 + "title": "Skip TLS verification",
85 + "description": "If set, TLS certificate verification will be skipped.",
86 + "type": "boolean"
87 + },
88 + "tls_ca": {
89 + "title": "TLS CA",
90 + "description": "The path to the CA certificate file for TLS verification.",
91 + "type": "string",
92 + "pattern": "^$|^/"
93 + },
94 + "tls_cert": {
95 + "title": "TLS certificate",
96 + "description": "The path to the client certificate file for TLS authentication.",
97 + "type": "string",
98 + "pattern": "^$|^/"
99 + },
100 + "tls_key": {
101 + "title": "TLS key",
102 + "description": "The path to the client key file for TLS authentication.",
103 + "type": "string",
104 + "pattern": "^$|^/"
105 + },
106 + "body": {
107 + "title": "Body",
108 + "type": "string"
109 + },
110 + "method": {
111 + "title": "Method",
112 + "type": "string"
113 + }
114 + },
115 + "required": [
116 + "url"
117 + ],
118 + "additionalProperties": false,
119 + "patternProperties": {
120 + "^name$": {}
121 + }
122 + },
123 + "uiSchema": {
124 + "uiOptions": {
125 + "fullPage": true
126 + },
127 + "body": {
128 + "ui:widget": "hidden"
129 + },
130 + "method": {
131 + "ui:widget": "hidden"
132 + },
133 + "timeout": {
134 + "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
135 + },
136 + "repoapi": {
137 + "ui:help": "This endpoint retrieves the number of objects in the local repository, which is not cached and can be computationally expensive for IPFS to calculate, especially with frequent collection intervals. See [#7528](https://github.com/ipfs/go-ipfs/issues/7528)."
138 + },
139 + "pinapi": {
140 + "ui:help": "Performance may decrease as the number of pinned objects grows, as the entire list needs to be retrieved. See [#3874](https://github.com/ipfs/go-ipfs/issues/3874)."
141 + },
142 + "password": {
143 + "ui:widget": "password"
144 + },
145 + "proxy_password": {
146 + "ui:widget": "password"
147 + },
148 + "ui:flavour": "tabs",
149 + "ui:options": {
150 + "tabs": [
151 + {
152 + "title": "Base",
153 + "fields": [
154 + "update_every",
155 + "url",
156 + "timeout",
157 + "repoapi",
158 + "pinapi",
159 + "not_follow_redirects"
160 + ]
161 + },
162 + {
163 + "title": "Auth",
164 + "fields": [
165 + "username",
166 + "password"
167 + ]
168 + },
169 + {
170 + "title": "TLS",
171 + "fields": [
172 + "tls_skip_verify",
173 + "tls_ca",
174 + "tls_cert",
175 + "tls_key"
176 + ]
177 + },
178 + {
179 + "title": "Proxy",
180 + "fields": [
181 + "proxy_url",
182 + "proxy_username",
183 + "proxy_password"
184 + ]
185 + },
186 + {
187 + "title": "Headers",
188 + "fields": [
189 + "headers"
190 + ]
191 + }
192 + ]
193 + }
194 + }
195 +}
src/go/plugin/go.d/modules/ipfs/ipfs.go new
+128
@@ -0,0 +1,128 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package ipfs
4 +
5 +import (
6 + _ "embed"
7 + "errors"
8 + "net/http"
9 + "time"
10 +
11 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
12 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
13 +)
14 +
15 +//go:embed "config_schema.json"
16 +var configSchema string
17 +
18 +func init() {
19 + module.Register("ipfs", module.Creator{
20 + JobConfigSchema: configSchema,
21 + Create: func() module.Module { return New() },
22 + Config: func() any { return &Config{} },
23 + })
24 +}
25 +
26 +func New() *IPFS {
27 + return &IPFS{
28 + Config: Config{
29 + HTTP: web.HTTP{
30 + Request: web.Request{
31 + URL: "http://127.0.0.1:5001",
32 + Method: http.MethodPost,
33 + },
34 + Client: web.Client{
35 + Timeout: web.Duration(time.Second * 1),
36 + },
37 + },
38 + QueryRepoApi: false,
39 + QueryPinApi: false,
40 + },
41 + charts: charts.Copy(),
42 + }
43 +}
44 +
45 +type Config struct {
46 + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
47 + web.HTTP `yaml:",inline" json:""`
48 + QueryPinApi bool `yaml:"pinapi" json:"pinapi"`
49 + QueryRepoApi bool `yaml:"repoapi" json:"repoapi"`
50 +}
51 +
52 +type IPFS struct {
53 + module.Base
54 + Config `yaml:",inline" json:""`
55 +
56 + charts *module.Charts
57 +
58 + httpClient *http.Client
59 +}
60 +
61 +func (ip *IPFS) Configuration() any {
62 + return ip.Config
63 +}
64 +
65 +func (ip *IPFS) Init() error {
66 + if ip.URL == "" {
67 + ip.Error("URL not set")
68 + return errors.New("url not set")
69 + }
70 +
71 + client, err := web.NewHTTPClient(ip.Client)
72 + if err != nil {
73 + ip.Error(err)
74 + return err
75 + }
76 + ip.httpClient = client
77 +
78 + if !ip.QueryPinApi {
79 + _ = ip.Charts().Remove(repoPinnedObjChart.ID)
80 + }
81 + if !ip.QueryRepoApi {
82 + _ = ip.Charts().Remove(datastoreUtilizationChart.ID)
83 + _ = ip.Charts().Remove(repoSizeChart.ID)
84 + _ = ip.Charts().Remove(repoObjChart.ID)
85 + }
86 +
87 + ip.Debugf("using URL %s", ip.URL)
88 + ip.Debugf("using timeout: %s", ip.Timeout)
89 +
90 + return nil
91 +}
92 +
93 +func (ip *IPFS) Check() error {
94 + mx, err := ip.collect()
95 + if err != nil {
96 + ip.Error(err)
97 + return err
98 + }
99 +
100 + if len(mx) == 0 {
101 + return errors.New("no metrics collected")
102 + }
103 +
104 + return nil
105 +}
106 +
107 +func (ip *IPFS) Charts() *module.Charts {
108 + return ip.charts
109 +}
110 +
111 +func (ip *IPFS) Collect() map[string]int64 {
112 + mx, err := ip.collect()
113 + if err != nil {
114 + ip.Error(err)
115 + }
116 +
117 + if len(mx) == 0 {
118 + return nil
119 + }
120 +
121 + return mx
122 +}
123 +
124 +func (ip *IPFS) Cleanup() {
125 + if ip.httpClient != nil {
126 + ip.httpClient.CloseIdleConnections()
127 + }
128 +}
src/go/plugin/go.d/modules/ipfs/ipfs_test.go new
+279
@@ -0,0 +1,279 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package ipfs
4 +
5 +import (
6 + "net/http"
7 + "net/http/httptest"
8 + "os"
9 + "testing"
10 +
11 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
12 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
13 +
14 + "github.com/stretchr/testify/assert"
15 + "github.com/stretchr/testify/require"
16 +)
17 +
18 +var (
19 + dataConfigJSON, _ = os.ReadFile("testdata/config.json")
20 + dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
21 +
22 + apiv0PinLsData, _ = os.ReadFile("testdata/api_v0_pin_ls.json")
23 + apiv0StatsBwData, _ = os.ReadFile("testdata/api_v0_stats_bw.json")
24 + apiv0StatsRepoData, _ = os.ReadFile("testdata/api_v0_stats_repo.json")
25 + apiv0SwarmPeersData, _ = os.ReadFile("testdata/api_v0_swarm_peers.json")
26 +)
27 +
28 +func Test_testDataIsValid(t *testing.T) {
29 + for name, data := range map[string][]byte{
30 + "dataConfigJSON": dataConfigJSON,
31 + "dataConfigYAML": dataConfigYAML,
32 + "apiv0PinLsData": apiv0PinLsData,
33 + "apiv0StatsBwData": apiv0StatsBwData,
34 + "apiv0StatsRepoData": apiv0StatsRepoData,
35 + "apiv0SwarmPeersData": apiv0SwarmPeersData,
36 + } {
37 + require.NotNil(t, data, name)
38 + }
39 +}
40 +
41 +func TestIPFS_ConfigurationSerialize(t *testing.T) {
42 + module.TestConfigurationSerialize(t, &IPFS{}, dataConfigJSON, dataConfigYAML)
43 +}
44 +
45 +func TestIPFS_Init(t *testing.T) {
46 + tests := map[string]struct {
47 + wantFail bool
48 + config Config
49 + }{
50 + "success with default": {
51 + wantFail: false,
52 + config: New().Config,
53 + },
54 + "fail when URL not set": {
55 + wantFail: true,
56 + config: Config{
57 + HTTP: web.HTTP{
58 + Request: web.Request{URL: ""},
59 + },
60 + },
61 + },
62 + }
63 +
64 + for name, test := range tests {
65 + t.Run(name, func(t *testing.T) {
66 + ipfs := New()
67 + ipfs.Config = test.config
68 +
69 + if test.wantFail {
70 + assert.Error(t, ipfs.Init())
71 + } else {
72 + assert.NoError(t, ipfs.Init())
73 + }
74 + })
75 + }
76 +}
77 +
78 +func TestIPFS_Charts(t *testing.T) {
79 + assert.NotNil(t, New().Charts())
80 +}
81 +
82 +func TestIPFS_Check(t *testing.T) {
83 + tests := map[string]struct {
84 + wantFail bool
85 + prepare func(t *testing.T) (*IPFS, func())
86 + }{
87 + "success default config": {
88 + wantFail: false,
89 + prepare: prepareCaseOkDefault,
90 + },
91 + "success all queries enabled": {
92 + wantFail: false,
93 + prepare: prepareCaseOkDefault,
94 + },
95 + "fails on unexpected json response": {
96 + wantFail: true,
97 + prepare: prepareCaseUnexpectedJsonResponse,
98 + },
99 + "fails on invalid format response": {
100 + wantFail: true,
101 + prepare: prepareCaseInvalidFormatResponse,
102 + },
103 + "fails on connection refused": {
104 + wantFail: true,
105 + prepare: prepareCaseConnectionRefused,
106 + },
107 + }
108 +
109 + for name, test := range tests {
110 + t.Run(name, func(t *testing.T) {
111 + ipfs, cleanup := test.prepare(t)
112 + defer cleanup()
113 +
114 + if test.wantFail {
115 + assert.Error(t, ipfs.Check())
116 + } else {
117 + assert.NoError(t, ipfs.Check())
118 + }
119 + })
120 + }
121 +}
122 +
123 +func TestIPFS_Collect(t *testing.T) {
124 + tests := map[string]struct {
125 + prepare func(t *testing.T) (*IPFS, func())
126 + wantMetrics map[string]int64
127 + }{
128 + "success default config": {
129 + prepare: prepareCaseOkDefault,
130 + wantMetrics: map[string]int64{
131 + "in": 20113594,
132 + "out": 3113852,
133 + "peers": 6,
134 + },
135 + },
136 + "success all queries enabled": {
137 + prepare: prepareCaseOkAllQueriesEnabled,
138 + wantMetrics: map[string]int64{
139 + "avail": 10000000000,
140 + "in": 20113594,
141 + "objects": 1,
142 + "out": 3113852,
143 + "peers": 6,
144 + "pinned": 1,
145 + "recursive_pins": 1,
146 + "size": 25495,
147 + "used_percent": 0,
148 + },
149 + },
150 + "fails on unexpected json response": {
151 + prepare: prepareCaseUnexpectedJsonResponse,
152 + },
153 + "fails on invalid format response": {
154 + prepare: prepareCaseInvalidFormatResponse,
155 + },
156 + "fails on connection refused": {
157 + prepare: prepareCaseConnectionRefused,
158 + },
159 + }
160 +
161 + for name, test := range tests {
162 + t.Run(name, func(t *testing.T) {
163 + ipfs, cleanup := test.prepare(t)
164 + defer cleanup()
165 +
166 + mx := ipfs.Collect()
167 +
168 + require.Equal(t, test.wantMetrics, mx)
169 + if len(test.wantMetrics) > 0 {
170 + testMetricsHasAllChartsDims(t, ipfs, mx)
171 + }
172 + })
173 + }
174 +}
175 +
176 +func testMetricsHasAllChartsDims(t *testing.T, ipfs *IPFS, mx map[string]int64) {
177 + for _, chart := range *ipfs.Charts() {
178 + if chart.Obsolete {
179 + continue
180 + }
181 + for _, dim := range chart.Dims {
182 + _, ok := mx[dim.ID]
183 + assert.Truef(t, ok, "collected metrics has no data for dim '%s' chart '%s'", dim.ID, chart.ID)
184 + }
185 + for _, v := range chart.Vars {
186 + _, ok := mx[v.ID]
187 + assert.Truef(t, ok, "collected metrics has no data for var '%s' chart '%s'", v.ID, chart.ID)
188 + }
189 + }
190 +}
191 +
192 +func prepareCaseOkDefault(t *testing.T) (*IPFS, func()) {
193 + t.Helper()
194 + srv := httptest.NewServer(http.HandlerFunc(
195 + func(w http.ResponseWriter, r *http.Request) {
196 + switch r.URL.Path {
197 + case urlPathStatsBandwidth:
198 + _, _ = w.Write(apiv0StatsBwData)
199 + case urlPathStatsRepo:
200 + _, _ = w.Write(apiv0StatsRepoData)
201 + case urlPathSwarmPeers:
202 + _, _ = w.Write(apiv0SwarmPeersData)
203 + case urlPathPinLs:
204 + _, _ = w.Write(apiv0PinLsData)
205 + default:
206 + w.WriteHeader(http.StatusNotFound)
207 + }
208 + }))
209 +
210 + ipfs := New()
211 + ipfs.URL = srv.URL
212 + require.NoError(t, ipfs.Init())
213 +
214 + return ipfs, srv.Close
215 +}
216 +
217 +func prepareCaseOkAllQueriesEnabled(t *testing.T) (*IPFS, func()) {
218 + t.Helper()
219 + ipfs, cleanup := prepareCaseOkDefault(t)
220 +
221 + ipfs.QueryRepoApi = true
222 + ipfs.QueryPinApi = true
223 +
224 + return ipfs, cleanup
225 +}
226 +
227 +func prepareCaseUnexpectedJsonResponse(t *testing.T) (*IPFS, func()) {
228 + t.Helper()
229 + resp := `
230 +{
231 + "elephant": {
232 + "burn": false,
233 + "mountain": true,
234 + "fog": false,
235 + "skin": -1561907625,
236 + "burst": "anyway",
237 + "shadow": 1558616893
238 + },
239 + "start": "ever",
240 + "base": 2093056027,
241 + "mission": -2007590351,
242 + "victory": 999053756,
243 + "die": false
244 +}
245 +`
246 + srv := httptest.NewServer(http.HandlerFunc(
247 + func(w http.ResponseWriter, r *http.Request) {
248 + _, _ = w.Write([]byte(resp))
249 + }))
250 +
251 + ipfs := New()
252 + ipfs.URL = srv.URL
253 + require.NoError(t, ipfs.Init())
254 +
255 + return ipfs, srv.Close
256 +}
257 +
258 +func prepareCaseInvalidFormatResponse(t *testing.T) (*IPFS, func()) {
259 + t.Helper()
260 + srv := httptest.NewServer(http.HandlerFunc(
261 + func(w http.ResponseWriter, r *http.Request) {
262 + _, _ = w.Write([]byte("hello and\n goodbye"))
263 + }))
264 +
265 + ipfs := New()
266 + ipfs.URL = srv.URL
267 + require.NoError(t, ipfs.Init())
268 +
269 + return ipfs, srv.Close
270 +}
271 +
272 +func prepareCaseConnectionRefused(t *testing.T) (*IPFS, func()) {
273 + t.Helper()
274 + ipfs := New()
275 + ipfs.URL = "http://127.0.0.1:65001"
276 + require.NoError(t, ipfs.Init())
277 +
278 + return ipfs, func() {}
279 +}
src/go/plugin/go.d/modules/ipfs/metadata.yaml new
+217
@@ -0,0 +1,217 @@
1 +plugin_name: go.d.plugin
2 +modules:
3 + - meta:
4 + id: collector-go.d.plugin-ipfs
5 + plugin_name: go.d.plugin
6 + module_name: ipfs
7 + monitored_instance:
8 + name: IPFS
9 + link: "https://ipfs.tech/"
10 + categories:
11 + - data-collection.storage-mount-points-and-filesystems
12 + icon_filename: "ipfs.svg"
13 + related_resources:
14 + integrations:
15 + list: []
16 + info_provided_to_referring_integrations:
17 + description: ""
18 + keywords:
19 + - ipfs
20 + - filesystem
21 + most_popular: false
22 + overview:
23 + data_collection:
24 + metrics_description: "This collector monitors IPFS daemon health and network activity."
25 + method_description: |
26 + It uses [RPC API](https://docs.ipfs.tech/reference/kubo/rpc/) to collect metrics.
27 +
28 + Used endpoints:
29 +
30 + - [/api/v0/stats/bw](https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-stats-bw)
31 + - [/api/v0/swarm/peers](https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-swarm-peers)
32 + - [/api/v0/stats/repo](https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-stats-repo)
33 + - [/api/v0/pin/ls](https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-pin-ls)
34 + supported_platforms:
35 + include: []
36 + exclude: []
37 + multi_instance: true
38 + additional_permissions:
39 + description: ""
40 + default_behavior:
41 + auto_detection:
42 + description: |
43 + By default, it detects IPFS instances running on localhost that are listening on port 5001.
44 + limits:
45 + description: ""
46 + performance_impact:
47 + description: |
48 + Calls to the following endpoints are disabled by default due to IPFS bugs:
49 +
50 + - /api/v0/stats/repo ([#7528](https://github.com/ipfs/go-ipfs/issues/7528)).
51 + - /api/v0/pin/ls ([#3874](https://github.com/ipfs/go-ipfs/issues/3874)).
52 +
53 + **Disabled by default** due to potential high CPU usage. Consider enabling only if necessary.
54 + setup:
55 + prerequisites:
56 + list: []
57 + configuration:
58 + file:
59 + name: go.d/ipfs.conf
60 + options:
61 + description: |
62 + The following options can be defined globally: update_every, autodetection_retry.
63 + folding:
64 + title: ""
65 + enabled: true
66 + list:
67 + - name: update_every
68 + description: Data collection frequency.
69 + default_value: 1
70 + required: false
71 + - name: autodetection_retry
72 + description: Recheck interval in seconds. Zero means no recheck will be scheduled.
73 + default_value: 0
74 + required: false
75 + - name: repoapi
76 + description: Enables querying the [/api/v0/stats/repo](https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-repo-stat) endpoint for repository statistics.
77 + default_value: false
78 + required: false
79 + - name: pinapi
80 + description: Enables querying the [/api/v0/pin/ls](https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-pin-ls) endpoint to retrieve a list of all pinned objects.
81 + default_value: false
82 + required: false
83 + - name: url
84 + description: Server URL.
85 + default_value: http://127.0.0.1:5001
86 + required: true
87 + - name: timeout
88 + description: HTTP request timeout.
89 + default_value: 1
90 + required: false
91 + - name: username
92 + description: Username for basic HTTP authentication.
93 + default_value: ""
94 + required: false
95 + - name: password
96 + description: Password for basic HTTP authentication.
97 + default_value: ""
98 + required: false
99 + - name: proxy_url
100 + description: Proxy URL.
101 + default_value: ""
102 + required: false
103 + - name: proxy_username
104 + description: Username for proxy basic HTTP authentication.
105 + default_value: ""
106 + required: false
107 + - name: proxy_password
108 + description: Password for proxy basic HTTP authentication.
109 + default_value: ""
110 + required: false
111 + - name: method
112 + description: HTTP request method.
113 + default_value: POST
114 + required: false
115 + - name: body
116 + description: HTTP request body.
117 + default_value: ""
118 + required: false
119 + - name: headers
120 + description: HTTP request headers.
121 + default_value: ""
122 + required: false
123 + - name: not_follow_redirects
124 + description: Redirect handling policy. Controls whether the client follows redirects.
125 + default_value: false
126 + required: false
127 + - name: tls_skip_verify
128 + description: Server certificate chain and hostname validation policy. Controls whether the client performs this check.
129 + default_value: false
130 + required: false
131 + - name: tls_ca
132 + description: Certification authority that the client uses when verifying the server's certificates.
133 + default_value: ""
134 + required: false
135 + - name: tls_cert
136 + description: Client TLS certificate.
137 + default_value: ""
138 + required: false
139 + - name: tls_key
140 + description: Client TLS key.
141 + default_value: ""
142 + required: false
143 + examples:
144 + folding:
145 + enabled: true
146 + title: "Config"
147 + list:
148 + - name: Basic
149 + description: A basic example configuration.
150 + folding:
151 + enabled: false
152 + config: |
153 + jobs:
154 + - name: local
155 + url: http://127.0.0.1:5001
156 + - name: Multi-instance
157 + description: |
158 + > **Note**: When you define multiple jobs, their names must be unique.
159 +
160 + Collecting metrics from local and remote instances.
161 + config: |
162 + jobs:
163 + - name: local
164 + url: http://127.0.0.1:5001
165 +
166 + - name: remote
167 + url: http://192.0.2.1:5001
168 + troubleshooting:
169 + problems:
170 + list: []
171 + alerts:
172 + - name: ipfs_datastore_usage
173 + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/ipfs.conf
174 + metric: ipfs.datastore_space_utilization
175 + info: IPFS datastore utilization
176 + metrics:
177 + folding:
178 + title: Metrics
179 + enabled: false
180 + description: ""
181 + availability: []
182 + scopes:
183 + - name: global
184 + description: "These metrics refer to the entire monitored application."
185 + labels: []
186 + metrics:
187 + - name: ipfs.bandwidth
188 + description: IPFS Bandwidth
189 + unit: "bytes/s"
190 + chart_type: line
191 + dimensions:
192 + - name: in
193 + - name: out
194 + - name: ipfs.peers
195 + description: IPFS Peers
196 + unit: "peers"
197 + chart_type: line
198 + dimensions:
199 + - name: peers
200 + - name: ipfs.repo_size
201 + description: IPFS Repo Size
202 + unit: "GiB"
203 + chart_type: area
204 + dimensions:
205 + - name: size
206 + - name: ipfs.repo_objects
207 + description: IPFS Repo Objects
208 + unit: "objects"
209 + chart_type: line
210 + dimensions:
211 + - name: objects
212 + - name: ipfs.repo_pinned_objects
213 + description: IPFS Repo Pinned Objects
214 + unit: "objects"
215 + chart_type: line
216 + dimensions:
217 + - name: recursive_pins
src/go/plugin/go.d/modules/ipfs/testdata/api_v0_pin_ls.json new
+8
@@ -0,0 +1,8 @@
1 +{
2 + "Keys": {
3 + "k1i2m3c4h5i6key": {
4 + "Type": "recursive",
5 + "Name": ""
6 + }
7 + }
8 +}
src/go/plugin/go.d/modules/ipfs/testdata/api_v0_stats_bw.json new
+6
@@ -0,0 +1,6 @@
1 +{
2 + "TotalIn": 20113594,
3 + "TotalOut": 3113852,
4 + "RateIn": 1623.2181369394084,
5 + "RateOut": 0.13743234792898051
6 +}
src/go/plugin/go.d/modules/ipfs/testdata/api_v0_stats_repo.json new
+7
@@ -0,0 +1,7 @@
1 +{
2 + "RepoSize": 25495,
3 + "StorageMax": 10000000000,
4 + "NumObjects": 1,
5 + "RepoPath": "/home/fotis/.ipfs",
6 + "Version": "fs-repo@15"
7 +}
src/go/plugin/go.d/modules/ipfs/testdata/api_v0_swarm_peers.json new
+70
@@ -0,0 +1,70 @@
1 +{
2 + "Peers": [
3 + {
4 + "Addr": "/ip4/1/tcp/27963",
5 + "Peer": "a",
6 + "Identify": {
7 + "ID": "",
8 + "PublicKey": "",
9 + "Addresses": null,
10 + "AgentVersion": "",
11 + "Protocols": null
12 + }
13 + },
14 + {
15 + "Addr": "/ip4/1/udp/4001/quic-v1",
16 + "Peer": "b",
17 + "Identify": {
18 + "ID": "",
19 + "PublicKey": "",
20 + "Addresses": null,
21 + "AgentVersion": "",
22 + "Protocols": null
23 + }
24 + },
25 + {
26 + "Addr": "/ip4/1/udp/4001/quic-v1/p2p/12D3KooWCqocoHdBANn2hH5acYAU4NdjEeBqERYk1MMTX49s1syY/p2p-circuit",
27 + "Peer": "c",
28 + "Identify": {
29 + "ID": "",
30 + "PublicKey": "",
31 + "Addresses": null,
32 + "AgentVersion": "",
33 + "Protocols": null
34 + }
35 + },
36 + {
37 + "Addr": "/ip4/1/tcp/4001",
38 + "Peer": "c",
39 + "Identify": {
40 + "ID": "",
41 + "PublicKey": "",
42 + "Addresses": null,
43 + "AgentVersion": "",
44 + "Protocols": null
45 + }
46 + },
47 + {
48 + "Addr": "/ip4/1/udp/33556/quic-v1",
49 + "Peer": "e",
50 + "Identify": {
51 + "ID": "",
52 + "PublicKey": "",
53 + "Addresses": null,
54 + "AgentVersion": "",
55 + "Protocols": null
56 + }
57 + },
58 + {
59 + "Addr": "/ip6/1::1/udp/4001/quic-v1",
60 + "Peer": "f",
61 + "Identify": {
62 + "ID": "",
63 + "PublicKey": "",
64 + "Addresses": null,
65 + "AgentVersion": "",
66 + "Protocols": null
67 + }
68 + }
69 + ]
70 +}
src/go/plugin/go.d/modules/ipfs/testdata/config.json new
+22
@@ -0,0 +1,22 @@
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 + "pinapi": false,
21 + "repoapi": false
22 +}
src/go/plugin/go.d/modules/ipfs/testdata/config.yaml new
+19
@@ -0,0 +1,19 @@
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
18 +pinapi: no
19 +repoapi: no
src/health/health.d/ipfs.conf
+2 -2
@@ -1,10 +1,10 @@
1
2 template: ipfs_datastore_usage
3 - on: ipfs.repo_size
3 + on: ipfs.datastore_space_utilization
4 class: Utilization
5 type: Data Sharing
6 component: IPFS
7 - calc: $size * 100 / $avail
7 + calc: $used
8 units: %
9 every: 10s
10 warn: $this > (($status >= $WARNING) ? (80) : (90))