@cryptotaxi247 / netdata-1 / commits / 9b6287d9b

Port Icecast collector to Go (#18190)

* initial commit for icecast port (dir only) + ipfs meta minor fix * icecast dir edits * nuke old icecast collector, cmakelists, readme additions, listeners and tie up loose ends * simplify * update go.d.conf and readme --------- Co-authored-by: ilyam8 <ilya@netdata.cloud>

Fotis Voutsas committed Jul 17, 2024 at 22:41 UTC 9b6287d9b168dfd7415b3f16ff06abfe5c3d6c8a
24 files changed +1036 -508
CMakeLists.txt
-2
@@ -2765,7 +2765,6 @@ install(FILES
2765 src/collectors/python.d.plugin/gearman/gearman.conf
2766 src/collectors/python.d.plugin/go_expvar/go_expvar.conf
2767 src/collectors/python.d.plugin/haproxy/haproxy.conf
2768 - src/collectors/python.d.plugin/icecast/icecast.conf
2768 src/collectors/python.d.plugin/memcached/memcached.conf
2769 src/collectors/python.d.plugin/monit/monit.conf
2770 src/collectors/python.d.plugin/nsd/nsd.conf
@@ -2803,7 +2802,6 @@ install(FILES
2802 src/collectors/python.d.plugin/gearman/gearman.chart.py
2803 src/collectors/python.d.plugin/go_expvar/go_expvar.chart.py
2804 src/collectors/python.d.plugin/haproxy/haproxy.chart.py
2806 - src/collectors/python.d.plugin/icecast/icecast.chart.py
2805 src/collectors/python.d.plugin/memcached/memcached.chart.py
2806 src/collectors/python.d.plugin/monit/monit.chart.py
2807 src/collectors/python.d.plugin/nsd/nsd.chart.py
src/collectors/python.d.plugin/icecast/README.md deleted
-1
@@ -1 +0,0 @@
1 -integrations/icecast.md
\ No newline at end of file
src/collectors/python.d.plugin/icecast/icecast.chart.py deleted
-94
@@ -1,94 +0,0 @@
1 -# -*- coding: utf-8 -*-
2 -# Description: icecast netdata python.d module
3 -# Author: Ilya Mashchenko (ilyam8)
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 - 'listeners',
12 -]
13 -
14 -CHARTS = {
15 - 'listeners': {
16 - 'options': [None, 'Number Of Listeners', 'listeners', 'listeners', 'icecast.listeners', 'line'],
17 - 'lines': [
18 - ]
19 - }
20 -}
21 -
22 -
23 -class Source:
24 - def __init__(self, idx, data):
25 - self.name = 'source_{0}'.format(idx)
26 - self.is_active = data.get('stream_start') and data.get('server_name')
27 - self.listeners = data['listeners']
28 -
29 -
30 -class Service(UrlService):
31 - def __init__(self, configuration=None, name=None):
32 - UrlService.__init__(self, configuration=configuration, name=name)
33 - self.order = ORDER
34 - self.definitions = CHARTS
35 - self.url = self.configuration.get('url')
36 - self._manager = self._build_manager()
37 -
38 - def check(self):
39 - """
40 - Add active sources to the "listeners" chart
41 - :return: bool
42 - """
43 - sources = self.get_sources()
44 - if not sources:
45 - return None
46 -
47 - active_sources = 0
48 - for idx, raw_source in enumerate(sources):
49 - if Source(idx, raw_source).is_active:
50 - active_sources += 1
51 - dim_id = 'source_{0}'.format(idx)
52 - dim = 'source {0}'.format(idx)
53 - self.definitions['listeners']['lines'].append([dim_id, dim])
54 -
55 - return bool(active_sources)
56 -
57 - def _get_data(self):
58 - """
59 - Get number of listeners for every source
60 - :return: dict
61 - """
62 - sources = self.get_sources()
63 - if not sources:
64 - return None
65 -
66 - data = dict()
67 -
68 - for idx, raw_source in enumerate(sources):
69 - source = Source(idx, raw_source)
70 - data[source.name] = source.listeners
71 -
72 - return data
73 -
74 - def get_sources(self):
75 - """
76 - Format data received from http request and return list of sources
77 - :return: list
78 - """
79 -
80 - raw_data = self._get_raw_data()
81 - if not raw_data:
82 - return None
83 -
84 - try:
85 - data = json.loads(raw_data)
86 - except ValueError as error:
87 - self.error('JSON decode error:', error)
88 - return None
89 -
90 - sources = data['icestats'].get('source')
91 - if not sources:
92 - return None
93 -
94 - return sources if isinstance(sources, list) else [sources]
src/collectors/python.d.plugin/icecast/icecast.conf deleted
-81
@@ -1,81 +0,0 @@
1 -# netdata python.d.plugin configuration for icecast
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, icecast also supports the following:
63 -#
64 -# url: 'URL' # the URL to fetch icecast's stats
65 -#
66 -# if the URL is password protected, the following are supported:
67 -#
68 -# user: 'username'
69 -# pass: 'password'
70 -
71 -# ----------------------------------------------------------------------
72 -# AUTO-DETECTION JOBS
73 -# only one of them will run (they have the same name)
74 -
75 -localhost:
76 - name : 'local'
77 - url : 'http://localhost:8443/status-json.xsl'
78 -
79 -localipv4:
80 - name : 'local'
81 - url : 'http://127.0.0.1:8443/status-json.xsl'
\ No newline at end of file
src/collectors/python.d.plugin/icecast/integrations/icecast.md deleted
-199
@@ -1,199 +0,0 @@
1 -<!--startmeta
2 -custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/python.d.plugin/icecast/README.md"
3 -meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/python.d.plugin/icecast/metadata.yaml"
4 -sidebar_label: "Icecast"
5 -learn_status: "Published"
6 -learn_rel_path: "Collecting Metrics/Media Services"
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 -# Icecast
12 -
13 -
14 -<img src="https://netdata.cloud/img/icecast.svg" width="150"/>
15 -
16 -
17 -Plugin: python.d.plugin
18 -Module: icecast
19 -
20 -<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
21 -
22 -## Overview
23 -
24 -This collector monitors Icecast listener counts.
25 -
26 -It connects to an icecast URL and uses the `status-json.xsl` endpoint to retrieve statistics.
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 -Without configuration, the collector attempts to connect to http://localhost:8443/status-json.xsl
38 -
39 -#### Limits
40 -
41 -The default configuration for this integration does not impose any limits on data collection.
42 -
43 -#### Performance Impact
44 -
45 -The default configuration for this integration is not expected to impose a significant performance impact on the system.
46 -
47 -
48 -## Metrics
49 -
50 -Metrics grouped by *scope*.
51 -
52 -The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels.
53 -
54 -
55 -
56 -### Per Icecast instance
57 -
58 -These metrics refer to the entire monitored application.
59 -
60 -This scope has no labels.
61 -
62 -Metrics:
63 -
64 -| Metric | Dimensions | Unit |
65 -|:------|:----------|:----|
66 -| icecast.listeners | a dimension for each active source | listeners |
67 -
68 -
69 -
70 -## Alerts
71 -
72 -There are no alerts configured by default for this integration.
73 -
74 -
75 -## Setup
76 -
77 -### Prerequisites
78 -
79 -#### Icecast minimum version
80 -
81 -Needs at least icecast version >= 2.4.0
82 -
83 -
84 -### Configuration
85 -
86 -#### File
87 -
88 -The configuration file name for this integration is `python.d/icecast.conf`.
89 -
90 -
91 -You can edit the configuration file using the `edit-config` script from the
92 -Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory).
93 -
94 -```bash
95 -cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
96 -sudo ./edit-config python.d/icecast.conf
97 -```
98 -#### Options
99 -
100 -There are 2 sections:
101 -
102 -* Global variables
103 -* One or more JOBS that can define multiple different instances to monitor.
104 -
105 -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.
106 -
107 -Additionally, the following collapsed table contains all the options that can be configured inside a JOB definition.
108 -
109 -Every configuration JOB starts with a `job_name` value which will appear in the dashboard, unless a `name` parameter is specified.
110 -
111 -
112 -<details open><summary>Config options</summary>
113 -
114 -| Name | Description | Default | Required |
115 -|:----|:-----------|:-------|:--------:|
116 -| update_every | Sets the default data collection frequency. | 5 | no |
117 -| priority | Controls the order of charts at the netdata dashboard. | 60000 | no |
118 -| autodetection_retry | Sets the job re-check interval in seconds. | 0 | no |
119 -| penalty | Indicates whether to apply penalty to update_every in case of failures. | yes | no |
120 -| 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 |
121 -| url | The URL (and port) to the icecast server. Needs to also include `/status-json.xsl` | http://localhost:8443/status-json.xsl | no |
122 -| user | Username to use to connect to `url` if it's password protected. | | no |
123 -| pass | Password to use to connect to `url` if it's password protected. | | no |
124 -
125 -</details>
126 -
127 -#### Examples
128 -
129 -##### Remote Icecast server
130 -
131 -Configure a remote icecast server
132 -
133 -```yaml
134 -remote:
135 - url: 'http://1.2.3.4:8443/status-json.xsl'
136 -
137 -```
138 -
139 -
140 -## Troubleshooting
141 -
142 -### Debug Mode
143 -
144 -To troubleshoot issues with the `icecast` collector, run the `python.d.plugin` with the debug option enabled. The output
145 -should give you clues as to why the collector isn't working.
146 -
147 -- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on
148 - your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`.
149 -
150 - ```bash
151 - cd /usr/libexec/netdata/plugins.d/
152 - ```
153 -
154 -- Switch to the `netdata` user.
155 -
156 - ```bash
157 - sudo -u netdata -s
158 - ```
159 -
160 -- Run the `python.d.plugin` to debug the collector:
161 -
162 - ```bash
163 - ./python.d.plugin icecast debug trace
164 - ```
165 -
166 -### Getting Logs
167 -
168 -If you're encountering problems with the `icecast` collector, follow these steps to retrieve logs and identify potential issues:
169 -
170 -- **Run the command** specific to your system (systemd, non-systemd, or Docker container).
171 -- **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.
172 -
173 -#### System with systemd
174 -
175 -Use the following command to view logs generated since the last Netdata service restart:
176 -
177 -```bash
178 -journalctl _SYSTEMD_INVOCATION_ID="$(systemctl show --value --property=InvocationID netdata)" --namespace=netdata --grep icecast
179 -```
180 -
181 -#### System without systemd
182 -
183 -Locate the collector log file, typically at `/var/log/netdata/collector.log`, and use `grep` to filter for collector's name:
184 -
185 -```bash
186 -grep icecast /var/log/netdata/collector.log
187 -```
188 -
189 -**Note**: This method shows logs from all restarts. Focus on the **latest entries** for troubleshooting current issues.
190 -
191 -#### Docker Container
192 -
193 -If your Netdata runs in a Docker container named "netdata" (replace if different), use this command:
194 -
195 -```bash
196 -docker logs netdata 2>&1 | grep icecast
197 -```
198 -
199 -
src/collectors/python.d.plugin/icecast/metadata.yaml deleted
-127
@@ -1,127 +0,0 @@
1 -plugin_name: python.d.plugin
2 -modules:
3 - - meta:
4 - plugin_name: python.d.plugin
5 - module_name: icecast
6 - monitored_instance:
7 - name: Icecast
8 - link: 'https://icecast.org/'
9 - categories:
10 - - data-collection.media-streaming-servers
11 - icon_filename: 'icecast.svg'
12 - related_resources:
13 - integrations:
14 - list: []
15 - info_provided_to_referring_integrations:
16 - description: ''
17 - keywords:
18 - - icecast
19 - - streaming
20 - - media
21 - most_popular: false
22 - overview:
23 - data_collection:
24 - metrics_description: 'This collector monitors Icecast listener counts.'
25 - method_description: 'It connects to an icecast URL and uses the `status-json.xsl` endpoint to retrieve statistics.'
26 - supported_platforms:
27 - include: []
28 - exclude: []
29 - multi_instance: true
30 - additional_permissions:
31 - description: ''
32 - default_behavior:
33 - auto_detection:
34 - description: 'Without configuration, the collector attempts to connect to http://localhost:8443/status-json.xsl'
35 - limits:
36 - description: ''
37 - performance_impact:
38 - description: ''
39 - setup:
40 - prerequisites:
41 - list:
42 - - title: 'Icecast minimum version'
43 - description: 'Needs at least icecast version >= 2.4.0'
44 - configuration:
45 - file:
46 - name: python.d/icecast.conf
47 - options:
48 - description: |
49 - There are 2 sections:
50 -
51 - * Global variables
52 - * One or more JOBS that can define multiple different instances to monitor.
53 -
54 - 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.
55 -
56 - Additionally, the following collapsed table contains all the options that can be configured inside a JOB definition.
57 -
58 - Every configuration JOB starts with a `job_name` value which will appear in the dashboard, unless a `name` parameter is specified.
59 - folding:
60 - title: "Config options"
61 - enabled: true
62 - list:
63 - - name: update_every
64 - description: Sets the default data collection frequency.
65 - default_value: 5
66 - required: false
67 - - name: priority
68 - description: Controls the order of charts at the netdata dashboard.
69 - default_value: 60000
70 - required: false
71 - - name: autodetection_retry
72 - description: Sets the job re-check interval in seconds.
73 - default_value: 0
74 - required: false
75 - - name: penalty
76 - description: Indicates whether to apply penalty to update_every in case of failures.
77 - default_value: yes
78 - required: false
79 - - name: name
80 - 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.
81 - default_value: ''
82 - required: false
83 - - name: url
84 - description: The URL (and port) to the icecast server. Needs to also include `/status-json.xsl`
85 - default_value: 'http://localhost:8443/status-json.xsl'
86 - required: false
87 - - name: user
88 - description: Username to use to connect to `url` if it's password protected.
89 - default_value: ''
90 - required: false
91 - - name: pass
92 - description: Password to use to connect to `url` if it's password protected.
93 - default_value: ''
94 - required: false
95 - examples:
96 - folding:
97 - enabled: false
98 - title: "Config"
99 - list:
100 - - name: Remote Icecast server
101 - description: Configure a remote icecast server
102 - folding:
103 - enabled: false
104 - config: |
105 - remote:
106 - url: 'http://1.2.3.4:8443/status-json.xsl'
107 - troubleshooting:
108 - problems:
109 - list: []
110 - alerts: []
111 - metrics:
112 - folding:
113 - title: Metrics
114 - enabled: false
115 - description: ""
116 - availability: []
117 - scopes:
118 - - name: global
119 - description: "These metrics refer to the entire monitored application."
120 - labels: []
121 - metrics:
122 - - name: icecast.listeners
123 - description: Number Of Listeners
124 - unit: "listeners"
125 - chart_type: line
126 - dimensions:
127 - - name: a dimension for each active source
src/collectors/python.d.plugin/python.d.conf
+2 -2
@@ -39,7 +39,6 @@ example: no
39 # gearman: yes
40 go_expvar: no
41 # haproxy: yes
42 -# icecast: yes
42 # memcached: yes
43 # monit: yes
44 # nvidia_smi: yes
@@ -71,7 +70,8 @@ fail2ban: no # Removed (replaced with go.d/fail2ban).
70 freeradius: no # Removed (replaced with go.d/freeradius).
71 hddtemp: no # Removed (replaced with go.d/hddtemp).
72 hpssa: no # Removed (replaced with go.d/hpssa).
74 -ipfs: no # Removed (replaced with go.d/ipfs).
73 +icecast: no # Removed (replaced with go.d/icecast)
74 +ipfs: no # Removed (replaced with go.d/ipfs).
75 litespeed: no # Removed (replaced with go.d/litespeed).
76 megacli: no # Removed (replaced with go.d/megacli).
77 mongodb: no # Removed (replaced with go.d/mongodb).
src/go/plugin/go.d/README.md
+3 -1
@@ -51,7 +51,7 @@ see the appropriate collector readme.
51 |:-------------------------------------------------------------------------------------------------------------------|:-----------------------------:|
52 | [adaptec_raid](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/adaptecraid) | Adaptec Hardware RAID |
53 | [activemq](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/activemq) | ActiveMQ |
54 -| [activemq](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/ap) | Access Points |
54 +| [ap](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/ap) | Wireless AP |
55 | [apache](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/apache) | Apache |
56 | [bind](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/bind) | ISC Bind |
57 | [cassandra](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/cassandra) | Cassandra |
@@ -82,6 +82,7 @@ see the appropriate collector readme.
82 | [hdfs](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/hdfs) | HDFS |
83 | [hpssa](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/hpssa) | HPE Smart Array |
84 | [httpcheck](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/httpcheck) | Any HTTP Endpoint |
85 +| [icecast](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/icecast) | Icecast |
86 | [intelgpu](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/intelgpu) | Intel integrated GPU |
87 | [ipfs](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/ipfs) | IPFS |
88 | [isc_dhcpd](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/isc_dhcpd) | ISC DHCP |
@@ -112,6 +113,7 @@ see the appropriate collector readme.
113 | [prometheus](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/prometheus) | Any Prometheus Endpoint |
114 | [portcheck](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/portcheck) | Any TCP Endpoint |
115 | [postgres](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/postgres) | PostgreSQL |
116 +| [postfix](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/postfix) | Postfix |
117 | [powerdns](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/powerdns) | PowerDNS Authoritative Server |
118 | [powerdns_recursor](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/powerdns_recursor) | PowerDNS Recursor |
119 | [proxysql](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/proxysql) | ProxySQL |
src/go/plugin/go.d/agent/module/charts.go
+19
@@ -6,7 +6,10 @@ import (
6 "errors"
7 "fmt"
8 "strings"
9 + "testing"
10 "unicode"
11 +
12 + "github.com/stretchr/testify/assert"
13 )
14
15 type (
@@ -460,3 +463,19 @@ func checkID(id string) int {
463 }
464 return -1
465 }
466 +
467 +func TestMetricsHasAllChartsDims(t *testing.T, charts *Charts, mx map[string]int64) {
468 + for _, chart := range *charts {
469 + if chart.Obsolete {
470 + continue
471 + }
472 + for _, dim := range chart.Dims {
473 + _, ok := mx[dim.ID]
474 + assert.Truef(t, ok, "missing data for dimension '%s' in chart '%s'", dim.ID, chart.ID)
475 + }
476 + for _, v := range chart.Vars {
477 + _, ok := mx[v.ID]
478 + assert.Truef(t, ok, "missing data for variable '%s' in chart '%s'", v.ID, chart.ID)
479 + }
480 + }
481 +}
src/go/plugin/go.d/config/go.d.conf
+1
@@ -47,6 +47,7 @@ modules:
47 # hdfs: yes
48 # hpssa: yes
49 # httpcheck: yes
50 +# icecast: yes
51 # intelgpu: yes
52 # ipfs: yes
53 # isc_dhcpd: yes
src/go/plugin/go.d/config/go.d/icecast.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/icecast#readme
3 +
4 +#jobs:
5 +# - name: local
6 +# url: http://localhost:8000
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: "icecast"
62 + expr: '{{ and (eq .Port "8000") (eq .Comm "icecast") }}'
63 - tags: "ipfs"
64 expr: '{{ and (eq .Port "5001") (eq .Comm "ipfs") }}'
65 - tags: "kubelet"
@@ -256,6 +258,11 @@ compose:
258 module: hdfs
259 name: datanode_local
260 url: http://{{.Address}}/jmx
261 + - selector: "icecast"
262 + template: |
263 + module: icecast
264 + name: local
265 + url: http://{{.Address}}
266 - selector: "ipfs"
267 template: |
268 module: ipfs
src/go/plugin/go.d/modules/icecast/charts.go new
+65
@@ -0,0 +1,65 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package icecast
4 +
5 +import (
6 + "fmt"
7 + "strings"
8 +
9 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
10 +)
11 +
12 +const (
13 + prioSourceListeners = module.Priority + iota
14 +)
15 +
16 +var sourceChartsTmpl = module.Charts{
17 + sourceListenersChartTmpl.Copy(),
18 +}
19 +
20 +var (
21 + sourceListenersChartTmpl = module.Chart{
22 + ID: "icecast_%s_listeners",
23 + Title: "Icecast Listeners",
24 + Units: "listeners",
25 + Fam: "listeners",
26 + Ctx: "icecast.listeners",
27 + Type: module.Line,
28 + Priority: prioSourceListeners,
29 + Dims: module.Dims{
30 + {ID: "source_%s_listeners", Name: "listeners"},
31 + },
32 + }
33 +)
34 +
35 +func (ic *Icecast) addSourceCharts(name string) {
36 + chart := sourceListenersChartTmpl.Copy()
37 +
38 + chart.ID = fmt.Sprintf(chart.ID, cleanSource(name))
39 + chart.Labels = []module.Label{
40 + {Key: "source", Value: name},
41 + }
42 + for _, dim := range chart.Dims {
43 + dim.ID = fmt.Sprintf(dim.ID, name)
44 + }
45 +
46 + if err := ic.Charts().Add(chart); err != nil {
47 + ic.Warning(err)
48 + }
49 +
50 +}
51 +
52 +func (ic *Icecast) removeSourceCharts(name string) {
53 + px := fmt.Sprintf("icecast_%s_", cleanSource(name))
54 + for _, chart := range *ic.Charts() {
55 + if strings.HasPrefix(chart.ID, px) {
56 + chart.MarkRemove()
57 + chart.MarkNotCreated()
58 + }
59 + }
60 +}
61 +
62 +func cleanSource(name string) string {
63 + r := strings.NewReplacer(" ", "_", ".", "_", ",", "_")
64 + return r.Replace(name)
65 +}
src/go/plugin/go.d/modules/icecast/collect.go new
+120
@@ -0,0 +1,120 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package icecast
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 + serverStats struct {
16 + IceStats *struct {
17 + Source []sourceStats `json:"source"`
18 + } `json:"icestats"`
19 + }
20 + sourceStats struct {
21 + ServerName string `json:"server_name"`
22 + StreamStart string `json:"stream_start"`
23 + Listeners int64 `json:"listeners"`
24 + }
25 +)
26 +
27 +const (
28 + urlPathServerStats = "/status-json.xsl" // https://icecast.org/docs/icecast-trunk/server_stats/
29 +)
30 +
31 +func (ic *Icecast) collect() (map[string]int64, error) {
32 + mx := make(map[string]int64)
33 +
34 + if err := ic.collectServerStats(mx); err != nil {
35 + return nil, err
36 + }
37 +
38 + return mx, nil
39 +}
40 +
41 +func (ic *Icecast) collectServerStats(mx map[string]int64) error {
42 + stats, err := ic.queryServerStats()
43 + if err != nil {
44 + return err
45 + }
46 + if stats.IceStats == nil {
47 + return fmt.Errorf("unexpected response: no icestats found")
48 + }
49 + if len(stats.IceStats.Source) == 0 {
50 + return fmt.Errorf("no icecast sources found")
51 + }
52 +
53 + seen := make(map[string]bool)
54 +
55 + for _, src := range stats.IceStats.Source {
56 + name := src.ServerName
57 + if name == "" {
58 + continue
59 + }
60 +
61 + seen[name] = true
62 +
63 + if !ic.seenSources[name] {
64 + ic.seenSources[name] = true
65 + ic.addSourceCharts(name)
66 + }
67 +
68 + px := fmt.Sprintf("source_%s_", name)
69 +
70 + mx[px+"listeners"] = src.Listeners
71 + }
72 +
73 + for name := range ic.seenSources {
74 + if !seen[name] {
75 + delete(ic.seenSources, name)
76 + ic.removeSourceCharts(name)
77 + }
78 + }
79 +
80 + return nil
81 +}
82 +
83 +func (ic *Icecast) queryServerStats() (*serverStats, error) {
84 + req, err := web.NewHTTPRequestWithPath(ic.Request, urlPathServerStats)
85 + if err != nil {
86 + return nil, err
87 + }
88 +
89 + var stats serverStats
90 +
91 + if err := ic.doOKDecode(req, &stats); err != nil {
92 + return nil, err
93 + }
94 +
95 + return &stats, nil
96 +}
97 +
98 +func (ic *Icecast) doOKDecode(req *http.Request, in interface{}) error {
99 + resp, err := ic.httpClient.Do(req)
100 + if err != nil {
101 + return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
102 + }
103 + defer closeBody(resp)
104 +
105 + if resp.StatusCode != http.StatusOK {
106 + return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
107 + }
108 +
109 + if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
110 + return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
111 + }
112 + return nil
113 +}
114 +
115 +func closeBody(resp *http.Response) {
116 + if resp != nil && resp.Body != nil {
117 + _, _ = io.Copy(io.Discard, resp.Body)
118 + _ = resp.Body.Close()
119 + }
120 +}
src/go/plugin/go.d/modules/icecast/config_schema.json new
+177
@@ -0,0 +1,177 @@
1 +{
2 + "jsonSchema": {
3 + "$schema": "http://json-schema.org/draft-07/schema#",
4 + "title": "Icecast 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 Icecast API can be accessed.",
17 + "type": "string",
18 + "default": "http://127.0.0.1:8000",
19 + "format": "uri"
20 + },
21 + "timeout": {
22 + "title": "Timeout",
23 + "description": "The timeout in seconds for the HTTP request.",
24 + "type": "number",
25 + "minimum": 0.5,
26 + "default": 1
27 + },
28 + "not_follow_redirects": {
29 + "title": "Not follow redirects",
30 + "description": "If set, the client will not follow HTTP redirects automatically.",
31 + "type": "boolean"
32 + },
33 + "username": {
34 + "title": "Username",
35 + "description": "The username for basic authentication.",
36 + "type": "string",
37 + "sensitive": true
38 + },
39 + "password": {
40 + "title": "Password",
41 + "description": "The password for basic authentication.",
42 + "type": "string",
43 + "sensitive": true
44 + },
45 + "proxy_url": {
46 + "title": "Proxy URL",
47 + "description": "The URL of the proxy server.",
48 + "type": "string"
49 + },
50 + "proxy_username": {
51 + "title": "Proxy username",
52 + "description": "The username for proxy authentication.",
53 + "type": "string",
54 + "sensitive": true
55 + },
56 + "proxy_password": {
57 + "title": "Proxy password",
58 + "description": "The password for proxy authentication.",
59 + "type": "string",
60 + "sensitive": true
61 + },
62 + "headers": {
63 + "title": "Headers",
64 + "description": "Additional HTTP headers to include in the request.",
65 + "type": [
66 + "object",
67 + "null"
68 + ],
69 + "additionalProperties": {
70 + "type": "string"
71 + }
72 + },
73 + "tls_skip_verify": {
74 + "title": "Skip TLS verification",
75 + "description": "If set, TLS certificate verification will be skipped.",
76 + "type": "boolean"
77 + },
78 + "tls_ca": {
79 + "title": "TLS CA",
80 + "description": "The path to the CA certificate file for TLS verification.",
81 + "type": "string",
82 + "pattern": "^$|^/"
83 + },
84 + "tls_cert": {
85 + "title": "TLS certificate",
86 + "description": "The path to the client certificate file for TLS authentication.",
87 + "type": "string",
88 + "pattern": "^$|^/"
89 + },
90 + "tls_key": {
91 + "title": "TLS key",
92 + "description": "The path to the client key file for TLS authentication.",
93 + "type": "string",
94 + "pattern": "^$|^/"
95 + },
96 + "body": {
97 + "title": "Body",
98 + "type": "string"
99 + },
100 + "method": {
101 + "title": "Method",
102 + "type": "string"
103 + }
104 + },
105 + "required": [
106 + "url"
107 + ],
108 + "additionalProperties": false,
109 + "patternProperties": {
110 + "^name$": {}
111 + }
112 + },
113 + "uiSchema": {
114 + "uiOptions": {
115 + "fullPage": true
116 + },
117 + "body": {
118 + "ui:widget": "hidden"
119 + },
120 + "method": {
121 + "ui:widget": "hidden"
122 + },
123 + "timeout": {
124 + "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
125 + },
126 + "password": {
127 + "ui:widget": "password"
128 + },
129 + "proxy_password": {
130 + "ui:widget": "password"
131 + },
132 + "ui:flavour": "tabs",
133 + "ui:options": {
134 + "tabs": [
135 + {
136 + "title": "Base",
137 + "fields": [
138 + "update_every",
139 + "url",
140 + "timeout",
141 + "not_follow_redirects"
142 + ]
143 + },
144 + {
145 + "title": "Auth",
146 + "fields": [
147 + "username",
148 + "password"
149 + ]
150 + },
151 + {
152 + "title": "TLS",
153 + "fields": [
154 + "tls_skip_verify",
155 + "tls_ca",
156 + "tls_cert",
157 + "tls_key"
158 + ]
159 + },
160 + {
161 + "title": "Proxy",
162 + "fields": [
163 + "proxy_url",
164 + "proxy_username",
165 + "proxy_password"
166 + ]
167 + },
168 + {
169 + "title": "Headers",
170 + "fields": [
171 + "headers"
172 + ]
173 + }
174 + ]
175 + }
176 + }
177 +}
src/go/plugin/go.d/modules/icecast/icecast.go new
+118
@@ -0,0 +1,118 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package icecast
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("icecast", module.Creator{
20 + JobConfigSchema: configSchema,
21 + Create: func() module.Module { return New() },
22 + Config: func() any { return &Config{} },
23 + })
24 +}
25 +
26 +func New() *Icecast {
27 + return &Icecast{
28 + Config: Config{
29 + HTTP: web.HTTP{
30 + Request: web.Request{
31 + URL: "http://127.0.0.1:8000",
32 + },
33 + Client: web.Client{
34 + Timeout: web.Duration(time.Second * 1),
35 + },
36 + },
37 + },
38 + charts: &module.Charts{},
39 +
40 + seenSources: make(map[string]bool),
41 + }
42 +}
43 +
44 +type Config struct {
45 + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
46 + web.HTTP `yaml:",inline" json:""`
47 +}
48 +
49 +type Icecast struct {
50 + module.Base
51 + Config `yaml:",inline" json:""`
52 +
53 + charts *module.Charts
54 +
55 + seenSources map[string]bool
56 +
57 + httpClient *http.Client
58 +}
59 +
60 +func (ic *Icecast) Configuration() any {
61 + return ic.Config
62 +}
63 +
64 +func (ic *Icecast) Init() error {
65 + if ic.URL == "" {
66 + ic.Error("URL not set")
67 + return errors.New("url not set")
68 + }
69 +
70 + client, err := web.NewHTTPClient(ic.Client)
71 + if err != nil {
72 + ic.Error(err)
73 + return err
74 + }
75 + ic.httpClient = client
76 +
77 + ic.Debugf("using URL %s", ic.URL)
78 + ic.Debugf("using timeout: %s", ic.Timeout)
79 +
80 + return nil
81 +}
82 +
83 +func (ic *Icecast) Check() error {
84 + mx, err := ic.collect()
85 + if err != nil {
86 + ic.Error(err)
87 + return err
88 + }
89 +
90 + if len(mx) == 0 {
91 + return errors.New("no metrics collected")
92 + }
93 +
94 + return nil
95 +}
96 +
97 +func (ic *Icecast) Charts() *module.Charts {
98 + return ic.charts
99 +}
100 +
101 +func (ic *Icecast) Collect() map[string]int64 {
102 + mx, err := ic.collect()
103 + if err != nil {
104 + ic.Error(err)
105 + }
106 +
107 + if len(mx) == 0 {
108 + return nil
109 + }
110 +
111 + return mx
112 +}
113 +
114 +func (ic *Icecast) Cleanup() {
115 + if ic.httpClient != nil {
116 + ic.httpClient.CloseIdleConnections()
117 + }
118 +}
src/go/plugin/go.d/modules/icecast/icecast_test.go new
+253
@@ -0,0 +1,253 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package icecast
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 + dataServerStats, _ = os.ReadFile("testdata/server_stats.json")
23 + dataServerStatsNoSources, _ = os.ReadFile("testdata/server_stats_no_sources.json")
24 +)
25 +
26 +func Test_testDataIsValid(t *testing.T) {
27 + for name, data := range map[string][]byte{
28 + "dataConfigJSON": dataConfigJSON,
29 + "dataConfigYAML": dataConfigYAML,
30 + "dataServerStats": dataServerStats,
31 + "dataServerStatsNoSources": dataServerStatsNoSources,
32 + } {
33 + require.NotNil(t, data, name)
34 + }
35 +}
36 +
37 +func TestIcecast_ConfigurationSerialize(t *testing.T) {
38 + module.TestConfigurationSerialize(t, &Icecast{}, dataConfigJSON, dataConfigYAML)
39 +}
40 +
41 +func TestIcecast_Init(t *testing.T) {
42 + tests := map[string]struct {
43 + wantFail bool
44 + config Config
45 + }{
46 + "success with default": {
47 + wantFail: false,
48 + config: New().Config,
49 + },
50 + "fail when URL not set": {
51 + wantFail: true,
52 + config: Config{
53 + HTTP: web.HTTP{
54 + Request: web.Request{URL: ""},
55 + },
56 + },
57 + },
58 + }
59 +
60 + for name, test := range tests {
61 + t.Run(name, func(t *testing.T) {
62 + icecast := New()
63 + icecast.Config = test.config
64 +
65 + if test.wantFail {
66 + assert.Error(t, icecast.Init())
67 + } else {
68 + assert.NoError(t, icecast.Init())
69 + }
70 + })
71 + }
72 +}
73 +
74 +func TestIcecast_Charts(t *testing.T) {
75 + assert.NotNil(t, New().Charts())
76 +}
77 +
78 +func TestIcecast_Check(t *testing.T) {
79 + tests := map[string]struct {
80 + wantFail bool
81 + prepare func(t *testing.T) (*Icecast, func())
82 + }{
83 + "success default config": {
84 + wantFail: false,
85 + prepare: prepareCaseOk,
86 + },
87 + "fails on no sources": {
88 + wantFail: true,
89 + prepare: prepareCaseNoSources,
90 + },
91 + "fails on unexpected json response": {
92 + wantFail: true,
93 + prepare: prepareCaseUnexpectedJsonResponse,
94 + },
95 + "fails on invalid format response": {
96 + wantFail: true,
97 + prepare: prepareCaseInvalidFormatResponse,
98 + },
99 + "fails on connection refused": {
100 + wantFail: true,
101 + prepare: prepareCaseConnectionRefused,
102 + },
103 + }
104 +
105 + for name, test := range tests {
106 + t.Run(name, func(t *testing.T) {
107 + icecast, cleanup := test.prepare(t)
108 + defer cleanup()
109 +
110 + if test.wantFail {
111 + assert.Error(t, icecast.Check())
112 + } else {
113 + assert.NoError(t, icecast.Check())
114 + }
115 + })
116 + }
117 +}
118 +
119 +func TestIcecast_Collect(t *testing.T) {
120 + tests := map[string]struct {
121 + prepare func(t *testing.T) (*Icecast, func())
122 + wantMetrics map[string]int64
123 + wantCharts int
124 + }{
125 + "success default config": {
126 + prepare: prepareCaseOk,
127 + wantCharts: len(sourceChartsTmpl) * 2,
128 + wantMetrics: map[string]int64{
129 + "source_abc_listeners": 1,
130 + "source_efg_listeners": 10,
131 + },
132 + },
133 + "fails on no sources": {
134 + prepare: prepareCaseNoSources,
135 + },
136 + "fails on unexpected json response": {
137 + prepare: prepareCaseUnexpectedJsonResponse,
138 + },
139 + "fails on invalid format response": {
140 + prepare: prepareCaseInvalidFormatResponse,
141 + },
142 + "fails on connection refused": {
143 + prepare: prepareCaseConnectionRefused,
144 + },
145 + }
146 +
147 + for name, test := range tests {
148 + t.Run(name, func(t *testing.T) {
149 + icecast, cleanup := test.prepare(t)
150 + defer cleanup()
151 +
152 + mx := icecast.Collect()
153 +
154 + require.Equal(t, test.wantMetrics, mx)
155 + if len(test.wantMetrics) > 0 {
156 + assert.Equal(t, test.wantCharts, len(*icecast.Charts()))
157 + module.TestMetricsHasAllChartsDims(t, icecast.Charts(), mx)
158 + }
159 + })
160 + }
161 +}
162 +
163 +func prepareCaseOk(t *testing.T) (*Icecast, func()) {
164 + t.Helper()
165 + srv := httptest.NewServer(http.HandlerFunc(
166 + func(w http.ResponseWriter, r *http.Request) {
167 + switch r.URL.Path {
168 + case urlPathServerStats:
169 + _, _ = w.Write(dataServerStats)
170 + default:
171 + w.WriteHeader(http.StatusNotFound)
172 + }
173 + }))
174 +
175 + icecast := New()
176 + icecast.URL = srv.URL
177 + require.NoError(t, icecast.Init())
178 +
179 + return icecast, srv.Close
180 +}
181 +
182 +func prepareCaseNoSources(t *testing.T) (*Icecast, func()) {
183 + t.Helper()
184 + srv := httptest.NewServer(http.HandlerFunc(
185 + func(w http.ResponseWriter, r *http.Request) {
186 + switch r.URL.Path {
187 + case urlPathServerStats:
188 + _, _ = w.Write(dataServerStatsNoSources)
189 + default:
190 + w.WriteHeader(http.StatusNotFound)
191 + }
192 + }))
193 +
194 + icecast := New()
195 + icecast.URL = srv.URL
196 + require.NoError(t, icecast.Init())
197 +
198 + return icecast, srv.Close
199 +}
200 +
201 +func prepareCaseUnexpectedJsonResponse(t *testing.T) (*Icecast, func()) {
202 + t.Helper()
203 + resp := `
204 +{
205 + "elephant": {
206 + "burn": false,
207 + "mountain": true,
208 + "fog": false,
209 + "skin": -1561907625,
210 + "burst": "anyway",
211 + "shadow": 1558616893
212 + },
213 + "start": "ever",
214 + "base": 2093056027,
215 + "mission": -2007590351,
216 + "victory": 999053756,
217 + "die": false
218 +}
219 +`
220 + srv := httptest.NewServer(http.HandlerFunc(
221 + func(w http.ResponseWriter, r *http.Request) {
222 + _, _ = w.Write([]byte(resp))
223 + }))
224 +
225 + icecast := New()
226 + icecast.URL = srv.URL
227 + require.NoError(t, icecast.Init())
228 +
229 + return icecast, srv.Close
230 +}
231 +
232 +func prepareCaseInvalidFormatResponse(t *testing.T) (*Icecast, func()) {
233 + t.Helper()
234 + srv := httptest.NewServer(http.HandlerFunc(
235 + func(w http.ResponseWriter, r *http.Request) {
236 + _, _ = w.Write([]byte("hello and\n goodbye"))
237 + }))
238 +
239 + icecast := New()
240 + icecast.URL = srv.URL
241 + require.NoError(t, icecast.Init())
242 +
243 + return icecast, srv.Close
244 +}
245 +
246 +func prepareCaseConnectionRefused(t *testing.T) (*Icecast, func()) {
247 + t.Helper()
248 + icecast := New()
249 + icecast.URL = "http://127.0.0.1:65001"
250 + require.NoError(t, icecast.Init())
251 +
252 + return icecast, func() {}
253 +}
src/go/plugin/go.d/modules/icecast/metadata.yaml new
+169
@@ -0,0 +1,169 @@
1 +plugin_name: go.d.plugin
2 +modules:
3 + - meta:
4 + plugin_name: go.d.plugin
5 + module_name: icecast
6 + monitored_instance:
7 + name: Icecast
8 + link: "https://icecast.org/"
9 + categories:
10 + - data-collection.media-streaming-servers
11 + icon_filename: "icecast.svg"
12 + related_resources:
13 + integrations:
14 + list: []
15 + info_provided_to_referring_integrations:
16 + description: ""
17 + keywords:
18 + - icecast
19 + - streaming
20 + - media
21 + most_popular: false
22 + overview:
23 + data_collection:
24 + metrics_description: "This collector monitors Icecast listener counts."
25 + method_description: "It uses the Icecast server statistics `status-json.xsl` endpoint to retrieve the metrics."
26 + supported_platforms:
27 + include: []
28 + exclude: []
29 + multi_instance: true
30 + additional_permissions:
31 + description: ""
32 + default_behavior:
33 + auto_detection:
34 + description: By default, it detects Icecast instances running on localhost that are listening on port 8000.
35 + limits:
36 + description: ""
37 + performance_impact:
38 + description: ""
39 + setup:
40 + prerequisites:
41 + list:
42 + - title: "Icecast minimum version"
43 + description: "Needs at least Icecast version >= 2.4.0"
44 + configuration:
45 + file:
46 + name: go.d/icecast.conf
47 + options:
48 + description: |
49 + The following options can be defined globally: update_every, autodetection_retry.
50 + folding:
51 + title: "Config options"
52 + enabled: true
53 + list:
54 + - name: update_every
55 + description: Data collection frequency.
56 + default_value: 1
57 + required: false
58 + - name: autodetection_retry
59 + description: Recheck interval in seconds. Zero means no recheck will be scheduled.
60 + default_value: 0
61 + required: false
62 + - name: url
63 + description: Server URL.
64 + default_value: http://127.0.0.1:8000
65 + required: true
66 + - name: timeout
67 + description: HTTP request timeout.
68 + default_value: 1
69 + required: false
70 + - name: username
71 + description: Username for basic HTTP authentication.
72 + default_value: ""
73 + required: false
74 + - name: password
75 + description: Password for basic HTTP authentication.
76 + default_value: ""
77 + required: false
78 + - name: proxy_url
79 + description: Proxy URL.
80 + default_value: ""
81 + required: false
82 + - name: proxy_username
83 + description: Username for proxy basic HTTP authentication.
84 + default_value: ""
85 + required: false
86 + - name: proxy_password
87 + description: Password for proxy basic HTTP authentication.
88 + default_value: ""
89 + required: false
90 + - name: method
91 + description: HTTP request method.
92 + default_value: POST
93 + required: false
94 + - name: body
95 + description: HTTP request body.
96 + default_value: ""
97 + required: false
98 + - name: headers
99 + description: HTTP request headers.
100 + default_value: ""
101 + required: false
102 + - name: not_follow_redirects
103 + description: Redirect handling policy. Controls whether the client follows redirects.
104 + default_value: false
105 + required: false
106 + - name: tls_skip_verify
107 + description: Server certificate chain and hostname validation policy. Controls whether the client performs this check.
108 + default_value: false
109 + required: false
110 + - name: tls_ca
111 + description: Certification authority that the client uses when verifying the server's certificates.
112 + default_value: ""
113 + required: false
114 + - name: tls_cert
115 + description: Client TLS certificate.
116 + default_value: ""
117 + required: false
118 + - name: tls_key
119 + description: Client TLS key.
120 + default_value: ""
121 + required: false
122 + examples:
123 + folding:
124 + enabled: true
125 + title: Config
126 + list:
127 + - name: Basic
128 + description: A basic example configuration.
129 + folding:
130 + enabled: false
131 + config: |
132 + jobs:
133 + - name: local
134 + url: http://127.0.0.1:8000
135 + - name: Multi-instance
136 + description: |
137 + > **Note**: When you define multiple jobs, their names must be unique.
138 +
139 + Collecting metrics from local and remote instances.
140 + config: |
141 + jobs:
142 + - name: local
143 + url: http://127.0.0.1:8000
144 +
145 + - name: remote
146 + url: http://192.0.2.1:8000
147 + troubleshooting:
148 + problems:
149 + list: []
150 + alerts: []
151 + metrics:
152 + folding:
153 + title: Metrics
154 + enabled: false
155 + description: ""
156 + availability: []
157 + scopes:
158 + - name: Icecast source
159 + description: "These metrics refer to an icecast source."
160 + labels:
161 + - name: source
162 + description: Source name.
163 + metrics:
164 + - name: icecast.listeners
165 + description: Icecast Listeners
166 + unit: "listeners"
167 + chart_type: line
168 + dimensions:
169 + - name: listeners
src/go/plugin/go.d/modules/icecast/testdata/config.json new
+20
@@ -0,0 +1,20 @@
1 +{
2 + "update_every": 123,
3 + "url": "ok",
4 + "body": "ok",
5 + "method": "ok",
6 + "headers": {
7 + "ok": "ok"
8 + },
9 + "username": "ok",
10 + "password": "ok",
11 + "proxy_url": "ok",
12 + "proxy_username": "ok",
13 + "proxy_password": "ok",
14 + "timeout": 123.123,
15 + "not_follow_redirects": true,
16 + "tls_ca": "ok",
17 + "tls_cert": "ok",
18 + "tls_key": "ok",
19 + "tls_skip_verify": true
20 +}
src/go/plugin/go.d/modules/icecast/testdata/config.yaml new
+17
@@ -0,0 +1,17 @@
1 +update_every: 123
2 +url: "ok"
3 +body: "ok"
4 +method: "ok"
5 +headers:
6 + ok: "ok"
7 +username: "ok"
8 +password: "ok"
9 +proxy_url: "ok"
10 +proxy_username: "ok"
11 +proxy_password: "ok"
12 +timeout: 123.123
13 +not_follow_redirects: yes
14 +tls_ca: "ok"
15 +tls_cert: "ok"
16 +tls_key: "ok"
17 +tls_skip_verify: yes
src/go/plugin/go.d/modules/icecast/testdata/server_stats.json new
+46
@@ -0,0 +1,46 @@
1 +{
2 + "icestats": {
3 + "admin": "icemaster@localhost",
4 + "host": "localhost",
5 + "location": "Earth",
6 + "server_id": "Icecast 2.4.4",
7 + "server_start": "Wed, 17 Jul 2024 11:27:40 +0300",
8 + "server_start_iso8601": "2024-07-17T11:27:40+0300",
9 + "source": [
10 + {
11 + "audio_info": "ice-bitrate=128;ice-channels=2;ice-samplerate=44100",
12 + "genre": "(null)",
13 + "ice-bitrate": 128,
14 + "ice-channels": 2,
15 + "ice-samplerate": 44100,
16 + "listener_peak": 2,
17 + "listeners": 1,
18 + "listenurl": "http://localhost:8000/line.nsv",
19 + "server_description": "(null)",
20 + "server_name": "abc",
21 + "server_type": "audio/mpeg",
22 + "server_url": "(null)",
23 + "stream_start": "Wed, 17 Jul 2024 12:10:20 +0300",
24 + "stream_start_iso8601": "2024-07-17T12:10:20+0300",
25 + "dummy": null
26 + },
27 + {
28 + "audio_info": "ice-bitrate=128;ice-channels=2;ice-samplerate=44100",
29 + "genre": "(null)",
30 + "ice-bitrate": 128,
31 + "ice-channels": 2,
32 + "ice-samplerate": 44100,
33 + "listener_peak": 10,
34 + "listeners": 10,
35 + "listenurl": "http://localhost:8000/lineb.nsv",
36 + "server_description": "(null)",
37 + "server_name": "efg",
38 + "server_type": "audio/mpeg",
39 + "server_url": "(null)",
40 + "stream_start": "Wed, 17 Jul 2024 12:10:20 +0300",
41 + "stream_start_iso8601": "2024-07-17T12:10:20+0300",
42 + "dummy": null
43 + }
44 + ]
45 + }
46 +}
src/go/plugin/go.d/modules/icecast/testdata/server_stats_no_sources.json new
+11
@@ -0,0 +1,11 @@
1 +{
2 + "icestats": {
3 + "admin": "icemaster@localhost",
4 + "host": "localhost",
5 + "location": "Earth",
6 + "server_id": "Icecast 2.4.4",
7 + "server_start": "Wed, 17 Jul 2024 11:27:40 +0300",
8 + "server_start_iso8601": "2024-07-17T11:27:40+0300",
9 + "dummy": null
10 + }
11 +}
\ No newline at end of file
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/hdfs"
38 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/hpssa"
39 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/httpcheck"
40 + _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/icecast"
41 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/intelgpu"
42 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/ipfs"
43 _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/isc_dhcpd"
src/go/plugin/go.d/modules/ipfs/metadata.yaml
+1 -1
@@ -61,7 +61,7 @@ modules:
61 description: |
62 The following options can be defined globally: update_every, autodetection_retry.
63 folding:
64 - title: ""
64 + title: "Config options"
65 enabled: true
66 list:
67 - name: update_every