Squid collector port to Go (#18276)
Co-authored-by: ilyam8 <ilya@netdata.cloud>
Fotis Voutsas committed
Aug 8, 2024 at 23:05 UTC
de7689e0dfebff5a940f1fa2deb44731d73fad79
15 files changed
+1014
src/go/plugin/go.d/README.md
+1
@@ -128,6 +128,7 @@ see the appropriate collector readme.
128
| [scaleio](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/scaleio) | Dell EMC ScaleIO |
129
| [sensors](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/sensors) | Hardware Sensors |
130
| [SNMP](https://github.com/netdata/netdata/blob/master/src/go/plugin/go.d/modules/snmp) | SNMP |
131
+| [squid](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/squid) | Squid |
132
| [squidlog](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/squidlog) | Squid |
133
| [smartctl](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/smartctl) | S.M.A.R.T Storage Devices |
134
| [storcli](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/modules/storcli) | Broadcom Hardware RAID |
src/go/plugin/go.d/config/go.d.conf
+1
@@ -92,6 +92,7 @@ modules:
92
# scaleio: yes
93
# sensors: yes
94
# snmp: yes
95
+# squid: yes
96
# squidlog: yes
97
# smartctl: yes
98
# storcli: yes
src/go/plugin/go.d/config/go.d/sd/docker.conf
+7
@@ -68,6 +68,8 @@ classify:
68
expr: '{{ or (eq .PrivatePort "6379") (match "sp" .Image "redis redis:* */redis */redis:*") }}'
69
- tags: "rethinkdb"
70
expr: '{{ and (eq .PrivatePort "28015") (match "sp" .Image "rethinkdb rethinkdb:* */rethinkdb */rethinkdb:*") }}'
71
+ - tags: "squid"
72
+ expr: '{{ match "sp" .Image "*/squid */squid:*" }}'
73
- tags: "tengine"
74
expr: '{{ match "sp" .Image "*/tengine */tengine:*" }}'
75
- tags: "tomcat"
@@ -212,6 +214,11 @@ compose:
214
module: redis
215
name: docker_{{.Name}}
216
address: redis://@{{.Address}}
217
+ - selector: "squid"
218
+ template: |
219
+ module: squid
220
+ name: docker_{{.Name}}
221
+ url: http://{{.Address}}
222
- selector: "tengine"
223
template: |
224
module: tengine
src/go/plugin/go.d/config/go.d/sd/net_listeners.conf
+7
@@ -108,6 +108,8 @@ classify:
108
expr: '{{ and (eq .Port "28015") (eq .Comm "rethinkdb") }}'
109
- tags: "rspamd"
110
expr: '{{ and (eq .Port "11334") (eq .Comm "rspamd") }}'
111
+ - tags: "squid"
112
+ expr: '{{ and (eq .Port "3128") (eq .Comm "squid") }}'
113
- tags: "supervisord"
114
expr: '{{ and (eq .Port "9001") (eq .Comm "supervisord") }}'
115
- tags: "tomcat"
@@ -423,6 +425,11 @@ compose:
425
module: redis
426
name: local
427
address: redis://@{{.Address}}
428
+ - selector: "squid"
429
+ template: |
430
+ module: squid
431
+ name: local
432
+ url: http://{{.Address}}
433
- selector: "supervisord"
434
template: |
435
module: supervisord
src/go/plugin/go.d/config/go.d/squid.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/squid#readme
3
+
4
+#jobs:
5
+# - name: local
6
+# url: http://localhost:3128
src/go/plugin/go.d/modules/init.go
+1
@@ -85,6 +85,7 @@ import (
85
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/sensors"
86
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/smartctl"
87
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/snmp"
88
+ _ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/squid"
89
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/squidlog"
90
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/storcli"
91
_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules/supervisord"
src/go/plugin/go.d/modules/squid/charts.go
new
+81
@@ -0,0 +1,81 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package squid
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
7
+)
8
+
9
+const (
10
+ prioClientsNet = module.Priority + iota
11
+ prioClientsRequests
12
+ prioServersNet
13
+ prioServersRequests
14
+)
15
+
16
+var charts = module.Charts{
17
+ clientsNetChart.Copy(),
18
+ clientsRequestsChart.Copy(),
19
+ serversNetChart.Copy(),
20
+ serversRequestsChart.Copy(),
21
+}
22
+
23
+var (
24
+ clientsNetChart = module.Chart{
25
+ ID: "clients_net",
26
+ Title: "Squid Client Bandwidth",
27
+ Units: "kilobits/s",
28
+ Fam: "clients",
29
+ Ctx: "squid.clients_net",
30
+ Type: module.Area,
31
+ Priority: prioClientsNet,
32
+ Dims: module.Dims{
33
+ {ID: "client_http.kbytes_in", Name: "in", Algo: module.Incremental, Mul: 8},
34
+ {ID: "client_http.kbytes_out", Name: "out", Algo: module.Incremental, Mul: -8},
35
+ {ID: "client_http.hit_kbytes_out", Name: "hits", Algo: module.Incremental, Mul: -8},
36
+ },
37
+ }
38
+
39
+ clientsRequestsChart = module.Chart{
40
+ ID: "clients_requests",
41
+ Title: "Squid Client Requests",
42
+ Units: "requests/s",
43
+ Fam: "clients",
44
+ Ctx: "squid.clients_requests",
45
+ Type: module.Line,
46
+ Priority: prioClientsRequests,
47
+ Dims: module.Dims{
48
+ {ID: "client_http.requests", Name: "requests", Algo: module.Incremental},
49
+ {ID: "client_http.hits", Name: "hits", Algo: module.Incremental},
50
+ {ID: "client_http.errors", Name: "errors", Algo: module.Incremental, Mul: -1},
51
+ },
52
+ }
53
+
54
+ serversNetChart = module.Chart{
55
+ ID: "servers_net",
56
+ Title: "Squid Server Bandwidth",
57
+ Units: "kilobits/s",
58
+ Fam: "servers",
59
+ Ctx: "squid.servers_net",
60
+ Type: module.Area,
61
+ Priority: prioServersNet,
62
+ Dims: module.Dims{
63
+ {ID: "server.all.kbytes_in", Name: "in", Algo: module.Incremental, Mul: 8},
64
+ {ID: "server.all.kbytes_out", Name: "out", Algo: module.Incremental, Mul: -8},
65
+ },
66
+ }
67
+
68
+ serversRequestsChart = module.Chart{
69
+ ID: "servers_requests",
70
+ Title: "Squid Server Requests",
71
+ Units: "requests/s",
72
+ Fam: "servers",
73
+ Ctx: "squid.servers_requests",
74
+ Type: module.Line,
75
+ Priority: prioServersRequests,
76
+ Dims: module.Dims{
77
+ {ID: "server.all.requests", Name: "requests", Algo: module.Incremental},
78
+ {ID: "server.all.errors", Name: "errors", Algo: module.Incremental, Mul: -1},
79
+ },
80
+ }
81
+)
src/go/plugin/go.d/modules/squid/collect.go
new
+105
@@ -0,0 +1,105 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package squid
4
+
5
+import (
6
+ "bufio"
7
+ "fmt"
8
+ "io"
9
+ "net/http"
10
+ "strconv"
11
+ "strings"
12
+
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
14
+)
15
+
16
+const (
17
+ // https://wiki.squid-cache.org/Features/CacheManager/Index#controlling-access-to-the-cache-manager
18
+ urlPathServerStats = "/squid-internal-mgr/counters"
19
+)
20
+
21
+var statsCounters = map[string]bool{
22
+ "client_http.kbytes_in": true,
23
+ "client_http.kbytes_out": true,
24
+ "server.all.errors": true,
25
+ "server.all.requests": true,
26
+ "server.all.kbytes_out": true,
27
+ "server.all.kbytes_in": true,
28
+ "client_http.errors": true,
29
+ "client_http.hits": true,
30
+ "client_http.requests": true,
31
+ "client_http.hit_kbytes_out": true,
32
+}
33
+
34
+func (s *Squid) collect() (map[string]int64, error) {
35
+ mx := make(map[string]int64)
36
+
37
+ if err := s.collectCounters(mx); err != nil {
38
+ return nil, err
39
+ }
40
+
41
+ return mx, nil
42
+}
43
+
44
+func (s *Squid) collectCounters(mx map[string]int64) error {
45
+ req, err := web.NewHTTPRequestWithPath(s.Request, urlPathServerStats)
46
+ if err != nil {
47
+ return err
48
+ }
49
+
50
+ if err := s.doOK(req, func(body io.Reader) error {
51
+ sc := bufio.NewScanner(body)
52
+
53
+ for sc.Scan() {
54
+ key, value, ok := strings.Cut(sc.Text(), "=")
55
+ if !ok {
56
+ continue
57
+ }
58
+
59
+ key, value = strings.TrimSpace(key), strings.TrimSpace(value)
60
+
61
+ if !statsCounters[key] {
62
+ continue
63
+ }
64
+
65
+ v, err := strconv.ParseInt(value, 10, 64)
66
+ if err != nil {
67
+ s.Debugf("failed to parse key %s value %s: %v", key, value, err)
68
+ continue
69
+ }
70
+
71
+ mx[key] = v
72
+ }
73
+ return nil
74
+ }); err != nil {
75
+ return err
76
+ }
77
+
78
+ if len(mx) == 0 {
79
+ return fmt.Errorf("unexpected response from '%s': no metrics found", req.URL)
80
+ }
81
+
82
+ return nil
83
+}
84
+
85
+func (s *Squid) doOK(req *http.Request, parse func(body io.Reader) error) error {
86
+ resp, err := s.httpClient.Do(req)
87
+ if err != nil {
88
+ return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
89
+ }
90
+
91
+ defer closeBody(resp)
92
+
93
+ if resp.StatusCode != http.StatusOK {
94
+ return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
95
+ }
96
+
97
+ return parse(resp.Body)
98
+}
99
+
100
+func closeBody(resp *http.Response) {
101
+ if resp != nil && resp.Body != nil {
102
+ _, _ = io.Copy(io.Discard, resp.Body)
103
+ _ = resp.Body.Close()
104
+ }
105
+}
src/go/plugin/go.d/modules/squid/config_schema.json
new
+177
@@ -0,0 +1,177 @@
1
+{
2
+ "jsonSchema": {
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Squid 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 Squid endpoint can be accessed.",
17
+ "type": "string",
18
+ "default": "http://127.0.0.1:1328",
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/squid/metadata.yaml
new
+195
@@ -0,0 +1,195 @@
1
+plugin_name: go.d.plugin
2
+modules:
3
+ - meta:
4
+ id: collector-go.d.plugin-squid
5
+ plugin_name: go.d.plugin
6
+ module_name: squid
7
+ monitored_instance:
8
+ name: Squid
9
+ link: "https://www.squid-cache.org/"
10
+ categories:
11
+ - data-collection.web-servers-and-web-proxies
12
+ icon_filename: "squid.png"
13
+ related_resources:
14
+ integrations:
15
+ list: []
16
+ info_provided_to_referring_integrations:
17
+ description: ""
18
+ keywords:
19
+ - squid
20
+ - web delivery
21
+ - squid caching proxy
22
+ most_popular: false
23
+ overview:
24
+ data_collection:
25
+ metrics_description: |
26
+ This collector monitors statistics about the Squid Clients and Servers, like bandwidth and requests.
27
+ method_description: "It collects metrics from the `squid-internal-mgr/counters` endpoint."
28
+ supported_platforms:
29
+ include: []
30
+ exclude: []
31
+ multi_instance: true
32
+ additional_permissions:
33
+ description: ""
34
+ default_behavior:
35
+ auto_detection:
36
+ description: |
37
+ By default, it detects Squid instances running on localhost that are listening on port 3128.
38
+ On startup, it tries to collect metrics from:
39
+
40
+ - https://127.0.0.1:3128
41
+ limits:
42
+ description: ""
43
+ performance_impact:
44
+ description: ""
45
+ setup:
46
+ prerequisites:
47
+ list: []
48
+ configuration:
49
+ file:
50
+ name: "go.d/squid.conf"
51
+ options:
52
+ description: |
53
+ The following options can be defined globally: update_every, autodetection_retry.
54
+ folding:
55
+ title: "Config options"
56
+ enabled: true
57
+ list:
58
+ - name: update_every
59
+ description: Data collection frequency.
60
+ default_value: 1
61
+ required: false
62
+ - name: autodetection_retry
63
+ description: Recheck interval in seconds. Zero means no recheck will be scheduled.
64
+ default_value: 0
65
+ required: false
66
+ - name: url
67
+ description: Server URL.
68
+ default_value: http://127.0.0.1:3128
69
+ required: true
70
+ - name: timeout
71
+ description: HTTP request timeout.
72
+ default_value: 1
73
+ required: false
74
+ - name: username
75
+ description: Username for basic HTTP authentication.
76
+ default_value: ""
77
+ required: false
78
+ - name: password
79
+ description: Password for basic HTTP authentication.
80
+ default_value: ""
81
+ required: false
82
+ - name: proxy_url
83
+ description: Proxy URL.
84
+ default_value: ""
85
+ required: false
86
+ - name: proxy_username
87
+ description: Username for proxy basic HTTP authentication.
88
+ default_value: ""
89
+ required: false
90
+ - name: proxy_password
91
+ description: Password for proxy basic HTTP authentication.
92
+ default_value: ""
93
+ required: false
94
+ - name: method
95
+ description: HTTP request method.
96
+ default_value: POST
97
+ required: false
98
+ - name: body
99
+ description: HTTP request body.
100
+ default_value: ""
101
+ required: false
102
+ - name: headers
103
+ description: HTTP request headers.
104
+ default_value: ""
105
+ required: false
106
+ - name: not_follow_redirects
107
+ description: Redirect handling policy. Controls whether the client follows redirects.
108
+ default_value: false
109
+ required: false
110
+ - name: tls_skip_verify
111
+ description: Server certificate chain and hostname validation policy. Controls whether the client performs this check.
112
+ default_value: false
113
+ required: false
114
+ - name: tls_ca
115
+ description: Certification authority that the client uses when verifying the server's certificates.
116
+ default_value: ""
117
+ required: false
118
+ - name: tls_cert
119
+ description: Client TLS certificate.
120
+ default_value: ""
121
+ required: false
122
+ - name: tls_key
123
+ description: Client TLS key.
124
+ default_value: ""
125
+ required: false
126
+ examples:
127
+ folding:
128
+ enabled: true
129
+ title: Config
130
+ list:
131
+ - name: Basic
132
+ description: A basic example configuration.
133
+ folding:
134
+ enabled: false
135
+ config: |
136
+ jobs:
137
+ - name: local
138
+ url: http://127.0.0.1:3128
139
+ - name: Multi-instance
140
+ description: |
141
+ > **Note**: When you define multiple jobs, their names must be unique.
142
+
143
+ Collecting metrics from local and remote instances.
144
+ config: |
145
+ jobs:
146
+ - name: local
147
+ url: http://127.0.0.1:3128
148
+
149
+ - name: remote
150
+ url: http://192.0.2.1:3128
151
+ troubleshooting:
152
+ problems:
153
+ list: []
154
+ alerts: []
155
+ metrics:
156
+ folding:
157
+ title: Metrics
158
+ enabled: false
159
+ description: ""
160
+ availability: []
161
+ scopes:
162
+ - name: Squid instance
163
+ description: "These metrics refer to each monitored Squid instance."
164
+ labels: []
165
+ metrics:
166
+ - name: squid.clients_net
167
+ description: Squid Client Bandwidth
168
+ unit: "kilobits/s"
169
+ chart_type: area
170
+ dimensions:
171
+ - name: in
172
+ - name: out
173
+ - name: hits
174
+ - name: squid.clients_requests
175
+ description: Squid Client Requests
176
+ unit: "requests/s"
177
+ chart_type: line
178
+ dimensions:
179
+ - name: requests
180
+ - name: hits
181
+ - name: errors
182
+ - name: squid.servers_net
183
+ description: Squid Server Bandwidth
184
+ unit: "kilobits/s"
185
+ chart_type: area
186
+ dimensions:
187
+ - name: in
188
+ - name: out
189
+ - name: squid.servers_requests
190
+ description: Squid Server Requests
191
+ unit: "requests/s"
192
+ chart_type: line
193
+ dimensions:
194
+ - name: requests
195
+ - name: errors
src/go/plugin/go.d/modules/squid/squid.go
new
+114
@@ -0,0 +1,114 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package squid
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("squid", module.Creator{
20
+ JobConfigSchema: configSchema,
21
+ Create: func() module.Module { return New() },
22
+ Config: func() any { return &Config{} },
23
+ })
24
+}
25
+
26
+func New() *Squid {
27
+ return &Squid{
28
+ Config: Config{
29
+ HTTP: web.HTTP{
30
+ Request: web.Request{
31
+ URL: "http://127.0.0.1:3128",
32
+ },
33
+ Client: web.Client{
34
+ Timeout: web.Duration(time.Second * 1),
35
+ },
36
+ },
37
+ },
38
+ charts: charts.Copy(),
39
+ }
40
+}
41
+
42
+type Config struct {
43
+ UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
44
+ web.HTTP `yaml:",inline" json:""`
45
+}
46
+
47
+type Squid struct {
48
+ module.Base
49
+ Config `yaml:",inline" json:""`
50
+
51
+ charts *module.Charts
52
+
53
+ httpClient *http.Client
54
+}
55
+
56
+func (s *Squid) Configuration() any {
57
+ return s.Config
58
+}
59
+
60
+func (s *Squid) Init() error {
61
+ if s.URL == "" {
62
+ s.Error("URL not set")
63
+ return errors.New("url not set")
64
+ }
65
+
66
+ client, err := web.NewHTTPClient(s.Client)
67
+ if err != nil {
68
+ s.Error(err)
69
+ return err
70
+ }
71
+ s.httpClient = client
72
+
73
+ s.Debugf("using URL %s", s.URL)
74
+ s.Debugf("using timeout: %s", s.Timeout)
75
+
76
+ return nil
77
+}
78
+
79
+func (s *Squid) Check() error {
80
+ mx, err := s.collect()
81
+ if err != nil {
82
+ s.Error(err)
83
+ return err
84
+ }
85
+
86
+ if len(mx) == 0 {
87
+ return errors.New("no metrics collected")
88
+ }
89
+
90
+ return nil
91
+}
92
+
93
+func (s *Squid) Charts() *module.Charts {
94
+ return s.charts
95
+}
96
+
97
+func (s *Squid) Collect() map[string]int64 {
98
+ mx, err := s.collect()
99
+ if err != nil {
100
+ s.Error(err)
101
+ }
102
+
103
+ if len(mx) == 0 {
104
+ return nil
105
+ }
106
+
107
+ return mx
108
+}
109
+
110
+func (s *Squid) Cleanup() {
111
+ if s.httpClient != nil {
112
+ s.httpClient.CloseIdleConnections()
113
+ }
114
+}
src/go/plugin/go.d/modules/squid/squid_test.go
new
+223
@@ -0,0 +1,223 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package squid
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
+ dataCounters, _ = os.ReadFile("testdata/counters.txt")
22
+)
23
+
24
+func Test_testDataIsValid(t *testing.T) {
25
+ for name, data := range map[string][]byte{
26
+ "dataConfigJSON": dataConfigJSON,
27
+ "dataConfigYAML": dataConfigYAML,
28
+ "dataCounters": dataCounters,
29
+ } {
30
+ require.NotNil(t, data, name)
31
+ }
32
+}
33
+
34
+func TestSquid_ConfigurationSerialize(t *testing.T) {
35
+ module.TestConfigurationSerialize(t, &Squid{}, dataConfigJSON, dataConfigYAML)
36
+}
37
+
38
+func TestSquid_Init(t *testing.T) {
39
+ tests := map[string]struct {
40
+ wantFail bool
41
+ config Config
42
+ }{
43
+ "success with default": {
44
+ wantFail: false,
45
+ config: New().Config,
46
+ },
47
+ "fail when URL not set": {
48
+ wantFail: true,
49
+ config: Config{
50
+ HTTP: web.HTTP{
51
+ Request: web.Request{URL: ""},
52
+ },
53
+ },
54
+ },
55
+ }
56
+
57
+ for name, test := range tests {
58
+ t.Run(name, func(t *testing.T) {
59
+ squid := New()
60
+ squid.Config = test.config
61
+
62
+ if test.wantFail {
63
+ assert.Error(t, squid.Init())
64
+ } else {
65
+ assert.NoError(t, squid.Init())
66
+ }
67
+ })
68
+ }
69
+}
70
+
71
+func TestSquid_Charts(t *testing.T) {
72
+ assert.NotNil(t, New().Charts())
73
+}
74
+
75
+func TestSquid_Check(t *testing.T) {
76
+ tests := map[string]struct {
77
+ wantFail bool
78
+ prepare func(t *testing.T) (*Squid, func())
79
+ }{
80
+ "success case": {
81
+ wantFail: false,
82
+ prepare: prepareCaseSuccess,
83
+ },
84
+ "fails on unexpected response": {
85
+ wantFail: true,
86
+ prepare: prepareCaseUnexpectedResponse,
87
+ },
88
+ "fails on empty response": {
89
+ wantFail: true,
90
+ prepare: prepareCaseEmptyResponse,
91
+ },
92
+ "fails on connection refused": {
93
+ wantFail: true,
94
+ prepare: prepareCaseConnectionRefused,
95
+ },
96
+ }
97
+
98
+ for name, test := range tests {
99
+ t.Run(name, func(t *testing.T) {
100
+ squid, cleanup := test.prepare(t)
101
+ defer cleanup()
102
+
103
+ if test.wantFail {
104
+ assert.Error(t, squid.Check())
105
+ } else {
106
+ assert.NoError(t, squid.Check())
107
+ }
108
+ })
109
+ }
110
+}
111
+
112
+func TestSquid_Collect(t *testing.T) {
113
+ tests := map[string]struct {
114
+ prepare func(t *testing.T) (*Squid, func())
115
+ wantMetrics map[string]int64
116
+ wantCharts int
117
+ }{
118
+ "success case": {
119
+ prepare: prepareCaseSuccess,
120
+ wantCharts: len(charts),
121
+ wantMetrics: map[string]int64{
122
+ "client_http.errors": 5,
123
+ "client_http.hit_kbytes_out": 11,
124
+ "client_http.hits": 1,
125
+ "client_http.kbytes_in": 566,
126
+ "client_http.kbytes_out": 16081,
127
+ "client_http.requests": 9019,
128
+ "server.all.errors": 0,
129
+ "server.all.kbytes_in": 0,
130
+ "server.all.kbytes_out": 0,
131
+ "server.all.requests": 0,
132
+ },
133
+ },
134
+ "fails on unexpected response": {
135
+ prepare: prepareCaseUnexpectedResponse,
136
+ },
137
+ "fails on empty response": {
138
+ prepare: prepareCaseEmptyResponse,
139
+ },
140
+ "fails on connection refused": {
141
+ prepare: prepareCaseConnectionRefused,
142
+ },
143
+ }
144
+
145
+ for name, test := range tests {
146
+ t.Run(name, func(t *testing.T) {
147
+ squid, cleanup := test.prepare(t)
148
+ defer cleanup()
149
+
150
+ mx := squid.Collect()
151
+
152
+ require.Equal(t, test.wantMetrics, mx)
153
+
154
+ if len(test.wantMetrics) > 0 {
155
+ assert.Equal(t, test.wantCharts, len(*squid.Charts()))
156
+ module.TestMetricsHasAllChartsDims(t, squid.Charts(), mx)
157
+ }
158
+ })
159
+ }
160
+}
161
+
162
+func prepareCaseSuccess(t *testing.T) (*Squid, func()) {
163
+ t.Helper()
164
+ srv := httptest.NewServer(http.HandlerFunc(
165
+ func(w http.ResponseWriter, r *http.Request) {
166
+ switch r.URL.Path {
167
+ case urlPathServerStats:
168
+ _, _ = w.Write(dataCounters)
169
+ default:
170
+ w.WriteHeader(http.StatusNotFound)
171
+ }
172
+ }))
173
+
174
+ squid := New()
175
+ squid.URL = srv.URL
176
+ require.NoError(t, squid.Init())
177
+
178
+ return squid, srv.Close
179
+}
180
+
181
+func prepareCaseUnexpectedResponse(t *testing.T) (*Squid, func()) {
182
+ t.Helper()
183
+ resp := []byte(`
184
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
185
+Nulla malesuada erat id magna mattis, eu viverra tellus rhoncus.
186
+Fusce et felis pulvinar, posuere sem non, porttitor eros.`)
187
+
188
+ srv := httptest.NewServer(http.HandlerFunc(
189
+ func(w http.ResponseWriter, r *http.Request) {
190
+ _, _ = w.Write([]byte(resp))
191
+ }))
192
+
193
+ squid := New()
194
+ squid.URL = srv.URL
195
+ require.NoError(t, squid.Init())
196
+
197
+ return squid, srv.Close
198
+}
199
+
200
+func prepareCaseEmptyResponse(t *testing.T) (*Squid, func()) {
201
+ t.Helper()
202
+ resp := []byte(``)
203
+
204
+ srv := httptest.NewServer(http.HandlerFunc(
205
+ func(w http.ResponseWriter, r *http.Request) {
206
+ _, _ = w.Write([]byte(resp))
207
+ }))
208
+
209
+ squid := New()
210
+ squid.URL = srv.URL
211
+ require.NoError(t, squid.Init())
212
+
213
+ return squid, srv.Close
214
+}
215
+
216
+func prepareCaseConnectionRefused(t *testing.T) (*Squid, func()) {
217
+ t.Helper()
218
+ squid := New()
219
+ squid.URL = "http://127.0.0.1:65001"
220
+ require.NoError(t, squid.Init())
221
+
222
+ return squid, func() {}
223
+}
src/go/plugin/go.d/modules/squid/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/squid/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/squid/testdata/counters.txt
new
+59
@@ -0,0 +1,59 @@
1
+sample_time = 1723030944.784818 (Wed, 07 Aug 2024 11:42:24 GMT)
2
+client_http.requests = 9019
3
+client_http.hits = 1
4
+client_http.errors = 5
5
+client_http.kbytes_in = 566
6
+client_http.kbytes_out = 16081
7
+client_http.hit_kbytes_out = 11
8
+server.all.requests = 0
9
+server.all.errors = 0
10
+server.all.kbytes_in = 0
11
+server.all.kbytes_out = 0
12
+server.http.requests = 0
13
+server.http.errors = 0
14
+server.http.kbytes_in = 0
15
+server.http.kbytes_out = 0
16
+server.ftp.requests = 0
17
+server.ftp.errors = 0
18
+server.ftp.kbytes_in = 0
19
+server.ftp.kbytes_out = 0
20
+server.other.requests = 0
21
+server.other.errors = 0
22
+server.other.kbytes_in = 0
23
+server.other.kbytes_out = 0
24
+icp.pkts_sent = 0
25
+icp.pkts_recv = 0
26
+icp.queries_sent = 0
27
+icp.replies_sent = 0
28
+icp.queries_recv = 0
29
+icp.replies_recv = 0
30
+icp.query_timeouts = 0
31
+icp.replies_queued = 0
32
+icp.kbytes_sent = 0
33
+icp.kbytes_recv = 0
34
+icp.q_kbytes_sent = 0
35
+icp.r_kbytes_sent = 0
36
+icp.q_kbytes_recv = 0
37
+icp.r_kbytes_recv = 0
38
+icp.times_used = 0
39
+cd.times_used = 0
40
+cd.msgs_sent = 0
41
+cd.msgs_recv = 0
42
+cd.memory = 0
43
+cd.local_memory = 0
44
+cd.kbytes_sent = 0
45
+cd.kbytes_recv = 0
46
+unlink.requests = 0
47
+page_faults = 874
48
+select_loops = 91146
49
+cpu_time = 8.501572
50
+wall_time = 13.524214
51
+swap.outs = 0
52
+swap.ins = 0
53
+swap.files_cleaned = 0
54
+aborted_requests = 0
55
+hit_validation.attempts = 0
56
+hit_validation.refusals.due_to_locking = 0
57
+hit_validation.refusals.due_to_zeroSize = 0
58
+hit_validation.refusals.due_to_timeLimit = 0
59
+hit_validation.failures = 0