@cryptotaxi247 / netdata-1 / commits / de7689e0d

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