@cryptotaxi247 / netdata-1 / commits / e37e1b801

go.d move doing http req logic to web (#18546)

Ilya Mashchenko committed Sep 14, 2024 at 21:43 UTC e37e1b801e8ccbcddbecd8d4cb5314e37357e7d7
71 files changed +651 -1389
src/go/plugin/go.d/agent/module/job.go
+3 -3
@@ -227,14 +227,14 @@ func (j *Job) AutoDetection() (err error) {
227 }
228
229 if err = j.init(); err != nil {
230 - j.Error("init failed")
230 + j.Errorf("init failed: %v", err)
231 j.Unmute()
232 j.disableAutoDetection()
233 return err
234 }
235
236 if err = j.check(); err != nil {
237 - j.Error("check failed")
237 + j.Errorf("check failed: %v", err)
238 j.Unmute()
239 return err
240 }
@@ -243,7 +243,7 @@ func (j *Job) AutoDetection() (err error) {
243 j.Info("check success")
244
245 if err = j.postCheck(); err != nil {
246 - j.Error("postCheck failed")
246 + j.Errorf("postCheck failed: %v", err)
247 j.disableAutoDetection()
248 return err
249 }
src/go/plugin/go.d/modules/activemq/apiclient.go
+8 -50
@@ -6,8 +6,6 @@ import (
6 "encoding/xml"
7 "fmt"
8 "net/http"
9 - "net/url"
10 - "path"
9
10 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
11 )
@@ -59,71 +57,31 @@ type apiClient struct {
57 }
58
59 func (a *apiClient) getQueues() (*queues, error) {
62 - req, err := a.createRequest(fmt.Sprintf(pathStats, a.webadmin, keyQueues))
60 + req, err := web.NewHTTPRequestWithPath(a.request, fmt.Sprintf(pathStats, a.webadmin, keyQueues))
61 if err != nil {
64 - return nil, fmt.Errorf("error on creating request '%s' : %v", a.request.URL, err)
65 - }
66 -
67 - resp, err := a.doRequestOK(req)
68 -
69 - defer web.CloseBody(resp)
70 -
71 - if err != nil {
72 - return nil, err
62 + return nil, fmt.Errorf("failed to create HTTP request '%s': %v", a.request.URL, err)
63 }
64
65 var queues queues
66
77 - if err := xml.NewDecoder(resp.Body).Decode(&queues); err != nil {
78 - return nil, fmt.Errorf("error on decoding resp from %s : %s", req.URL, err)
67 + if err := web.DoHTTP(a.httpClient).RequestXML(req, &queues); err != nil {
68 + return nil, err
69 }
70
71 return &queues, nil
72 }
73
74 func (a *apiClient) getTopics() (*topics, error) {
85 - req, err := a.createRequest(fmt.Sprintf(pathStats, a.webadmin, keyTopics))
75 + req, err := web.NewHTTPRequestWithPath(a.request, fmt.Sprintf(pathStats, a.webadmin, keyTopics))
76 if err != nil {
87 - return nil, fmt.Errorf("error on creating request '%s' : %v", a.request.URL, err)
88 - }
89 -
90 - resp, err := a.doRequestOK(req)
91 -
92 - defer web.CloseBody(resp)
93 -
94 - if err != nil {
95 - return nil, err
77 + return nil, fmt.Errorf("failed to create HTTP request '%s': %v", a.request.URL, err)
78 }
79
80 var topics topics
81
100 - if err := xml.NewDecoder(resp.Body).Decode(&topics); err != nil {
101 - return nil, fmt.Errorf("error on decoding resp from %s : %s", req.URL, err)
82 + if err := web.DoHTTP(a.httpClient).RequestXML(req, &topics); err != nil {
83 + return nil, err
84 }
85
86 return &topics, nil
87 }
106 -
107 -func (a *apiClient) doRequestOK(req *http.Request) (*http.Response, error) {
108 - resp, err := a.httpClient.Do(req)
109 - if err != nil {
110 - return resp, fmt.Errorf("error on request to %s : %v", req.URL, err)
111 - }
112 -
113 - if resp.StatusCode != http.StatusOK {
114 - return resp, fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode)
115 - }
116 -
117 - return resp, err
118 -}
119 -
120 -func (a *apiClient) createRequest(urlPath string) (*http.Request, error) {
121 - req := a.request.Copy()
122 - u, err := url.Parse(req.URL)
123 - if err != nil {
124 - return nil, err
125 - }
126 - u.Path = path.Join(u.Path, urlPath)
127 - req.URL = u.String()
128 - return web.NewHTTPRequest(req)
129 -}
src/go/plugin/go.d/modules/apache/collect.go
+10 -10
@@ -6,7 +6,6 @@ import (
6 "bufio"
7 "fmt"
8 "io"
9 - "net/http"
9 "strconv"
10 "strings"
11
@@ -36,18 +35,19 @@ func (a *Apache) scrapeStatus() (*serverStatus, error) {
35 return nil, err
36 }
37
39 - resp, err := a.httpClient.Do(req)
40 - if err != nil {
41 - return nil, fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
42 - }
43 -
44 - defer web.CloseBody(resp)
38 + var stats *serverStatus
39 + var perr error
40
46 - if resp.StatusCode != http.StatusOK {
47 - return nil, fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
41 + if err := web.DoHTTP(a.httpClient).Request(req, func(body io.Reader) error {
42 + if stats, perr = parseResponse(body); perr != nil {
43 + return perr
44 + }
45 + return nil
46 + }); err != nil {
47 + return nil, err
48 }
49
50 - return parseResponse(resp.Body)
50 + return stats, nil
51 }
52
53 func parseResponse(r io.Reader) (*serverStatus, error) {
src/go/plugin/go.d/modules/bind/json_client.go
+6 -27
@@ -3,11 +3,8 @@
3 package bind
4
5 import (
6 - "encoding/json"
6 "fmt"
7 "net/http"
9 - "net/url"
10 - "path"
8
9 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
10 )
@@ -42,34 +39,16 @@ type jsonClient struct {
39 }
40
41 func (c jsonClient) serverStats() (*serverStats, error) {
45 - req := c.request.Copy()
46 - u, err := url.Parse(req.URL)
42 + req, err := web.NewHTTPRequestWithPath(c.request, "/server")
43 if err != nil {
48 - return nil, fmt.Errorf("error on parsing URL: %v", err)
44 + return nil, fmt.Errorf("failed to create HTTP request: %v", err)
45 }
46
51 - u.Path = path.Join(u.Path, "/server")
52 - req.URL = u.String()
47 + var stats jsonServerStats
48
54 - httpReq, err := web.NewHTTPRequest(req)
55 - if err != nil {
56 - return nil, fmt.Errorf("error on creating HTTP request: %v", err)
57 - }
58 -
59 - resp, err := c.httpClient.Do(httpReq)
60 - if err != nil {
61 - return nil, fmt.Errorf("error on request : %v", err)
49 + if err := web.DoHTTP(c.httpClient).RequestJSON(req, &stats); err != nil {
50 + return nil, err
51 }
52
64 - defer web.CloseBody(resp)
65 -
66 - if resp.StatusCode != http.StatusOK {
67 - return nil, fmt.Errorf("%s returned HTTP status %d", httpReq.URL, resp.StatusCode)
68 - }
69 -
70 - stats := &jsonServerStats{}
71 - if err = json.NewDecoder(resp.Body).Decode(stats); err != nil {
72 - return nil, fmt.Errorf("error on decoding response from %s : %v", httpReq.URL, err)
73 - }
74 - return stats, nil
53 + return &stats, nil
54 }
src/go/plugin/go.d/modules/bind/xml3_client.go
+5 -26
@@ -3,11 +3,8 @@
3 package bind
4
5 import (
6 - "encoding/xml"
6 "fmt"
7 "net/http"
9 - "net/url"
10 - "path"
8
9 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
10 )
@@ -44,35 +41,17 @@ type xml3Client struct {
41 }
42
43 func (c xml3Client) serverStats() (*serverStats, error) {
47 - req := c.request.Copy()
48 - u, err := url.Parse(req.URL)
44 + req, err := web.NewHTTPRequestWithPath(c.request, "/server")
45 if err != nil {
50 - return nil, fmt.Errorf("error on parsing URL: %v", err)
46 + return nil, fmt.Errorf("failed to create HTTP request: %v", err)
47 }
48
53 - u.Path = path.Join(u.Path, "/server")
54 - req.URL = u.String()
49 + var stats xml3Stats
50
56 - httpReq, err := web.NewHTTPRequest(req)
57 - if err != nil {
58 - return nil, fmt.Errorf("error on creating HTTP request: %v", err)
59 - }
60 -
61 - resp, err := c.httpClient.Do(httpReq)
62 - if err != nil {
63 - return nil, fmt.Errorf("error on request : %v", err)
51 + if err := web.DoHTTP(c.httpClient).RequestXML(req, &stats); err != nil {
52 + return nil, err
53 }
54
66 - defer web.CloseBody(resp)
67 -
68 - if resp.StatusCode != http.StatusOK {
69 - return nil, fmt.Errorf("%s returned HTTP status %d", httpReq.URL, resp.StatusCode)
70 - }
71 -
72 - stats := xml3Stats{}
73 - if err = xml.NewDecoder(resp.Body).Decode(&stats); err != nil {
74 - return nil, fmt.Errorf("error on decoding response from %s : %v", httpReq.URL, err)
75 - }
55 return convertXML(stats), nil
56 }
57
src/go/plugin/go.d/modules/clickhouse/collect.go
+4 -13
@@ -40,19 +40,10 @@ func (c *ClickHouse) collect() (map[string]int64, error) {
40 return mx, nil
41 }
42
43 -func (c *ClickHouse) doOKDecodeCSV(req *http.Request, assign func(column, value string, lineEnd bool)) error {
44 - resp, err := c.httpClient.Do(req)
45 - if err != nil {
46 - return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
47 - }
48 -
49 - defer web.CloseBody(resp)
50 -
51 - if resp.StatusCode != http.StatusOK {
52 - return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
53 - }
54 -
55 - return readCSVResponseData(resp.Body, assign)
43 +func (c *ClickHouse) doHTTP(req *http.Request, assign func(column, value string, lineEnd bool)) error {
44 + return web.DoHTTP(c.httpClient).Request(req, func(body io.Reader) error {
45 + return readCSVResponseData(body, assign)
46 + })
47 }
48
49 func readCSVResponseData(reader io.Reader, assign func(column, value string, lineEnd bool)) error {
src/go/plugin/go.d/modules/clickhouse/collect_system_async_metrics.go
+1 -1
@@ -35,7 +35,7 @@ func (c *ClickHouse) collectSystemAsyncMetrics(mx map[string]int64) error {
35 var metric string
36 var n int
37
38 - err := c.doOKDecodeCSV(req, func(column, value string, lineEnd bool) {
38 + err := c.doHTTP(req, func(column, value string, lineEnd bool) {
39 switch column {
40 case "metric":
41 metric = value
src/go/plugin/go.d/modules/clickhouse/collect_system_disks.go
+1 -1
@@ -42,7 +42,7 @@ func (c *ClickHouse) collectSystemDisks(mx map[string]int64) error {
42
43 var name string
44
45 - err := c.doOKDecodeCSV(req, func(column, value string, lineEnd bool) {
45 + err := c.doHTTP(req, func(column, value string, lineEnd bool) {
46 switch column {
47 case "name":
48 name = value
src/go/plugin/go.d/modules/clickhouse/collect_system_events.go
+1 -1
@@ -25,7 +25,7 @@ func (c *ClickHouse) collectSystemEvents(mx map[string]int64) error {
25 var event string
26 var n int
27
28 - err := c.doOKDecodeCSV(req, func(column, value string, lineEnd bool) {
28 + err := c.doHTTP(req, func(column, value string, lineEnd bool) {
29 switch column {
30 case "event":
31 event = value
src/go/plugin/go.d/modules/clickhouse/collect_system_metrics.go
+1 -1
@@ -25,7 +25,7 @@ func (c *ClickHouse) collectSystemMetrics(mx map[string]int64) error {
25 var metric string
26 var n int
27
28 - err := c.doOKDecodeCSV(req, func(column, value string, lineEnd bool) {
28 + err := c.doHTTP(req, func(column, value string, lineEnd bool) {
29 switch column {
30 case "metric":
31 metric = value
src/go/plugin/go.d/modules/clickhouse/collect_system_parts.go
+1 -1
@@ -51,7 +51,7 @@ func (c *ClickHouse) collectSystemParts(mx map[string]int64) error {
51
52 var database, table string
53
54 - err := c.doOKDecodeCSV(req, func(column, value string, lineEnd bool) {
54 + err := c.doHTTP(req, func(column, value string, lineEnd bool) {
55 switch column {
56 case "database":
57 database = value
src/go/plugin/go.d/modules/clickhouse/collect_system_processes.go
+1 -1
@@ -19,7 +19,7 @@ func (c *ClickHouse) collectLongestRunningQueryTime(mx map[string]int64) error {
19 req, _ := web.NewHTTPRequest(c.RequestConfig)
20 req.URL.RawQuery = makeURLQuery(queryLongestQueryTime)
21
22 - return c.doOKDecodeCSV(req, func(column, value string, lineEnd bool) {
22 + return c.doHTTP(req, func(column, value string, lineEnd bool) {
23 if column == "value" {
24 if v, err := strconv.ParseFloat(value, 64); err == nil {
25 mx["LongestRunningQueryTime"] = int64(v * precision)
src/go/plugin/go.d/modules/consul/collect.go
+10 -24
@@ -3,9 +3,9 @@
3 package consul
4
5 import (
6 - "encoding/json"
6 "fmt"
7 "net/http"
8 + "slices"
9
10 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
11 )
@@ -67,37 +67,23 @@ func (c *Consul) isServer() bool {
67 return c.cfg.Config.Server
68 }
69
70 -func (c *Consul) doOKDecode(urlPath string, in interface{}, statusCodes ...int) error {
70 +func (c *Consul) client(statusCodes ...int) *web.Client {
71 + return web.DoHTTP(c.httpClient).OnNokCode(func(resp *http.Response) (bool, error) {
72 + return slices.Contains(statusCodes, resp.StatusCode), nil
73 + })
74 +}
75 +
76 +func (c *Consul) createRequest(urlPath string) (*http.Request, error) {
77 req, err := web.NewHTTPRequestWithPath(c.RequestConfig, urlPath)
78 if err != nil {
73 - return fmt.Errorf("error on creating request: %v", err)
79 + return nil, fmt.Errorf("failed to create '%s' request: %w", urlPath, err)
80 }
81
82 if c.ACLToken != "" {
83 req.Header.Set("X-Consul-Token", c.ACLToken)
84 }
85
80 - resp, err := c.httpClient.Do(req)
81 - if err != nil {
82 - return fmt.Errorf("error on request to %s : %v", req.URL, err)
83 - }
84 -
85 - defer web.CloseBody(resp)
86 -
87 - codes := map[int]bool{http.StatusOK: true}
88 - for _, v := range statusCodes {
89 - codes[v] = true
90 - }
91 -
92 - if !codes[resp.StatusCode] {
93 - return fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode)
94 - }
95 -
96 - if err = json.NewDecoder(resp.Body).Decode(&in); err != nil {
97 - return fmt.Errorf("error on decoding response from %s : %v", req.URL, err)
98 - }
99 -
100 - return nil
86 + return req, nil
87 }
88
89 func boolToInt(v bool) int64 {
src/go/plugin/go.d/modules/consul/collect_autopilot.go
+6 -1
@@ -25,11 +25,16 @@ type autopilotHealth struct {
25 }
26
27 func (c *Consul) collectAutopilotHealth(mx map[string]int64) error {
28 + req, err := c.createRequest(urlPathOperationAutopilotHealth)
29 + if err != nil {
30 + return err
31 + }
32 +
33 var health autopilotHealth
34
35 // The HTTP status code will indicate the health of the cluster: 200 is healthy, 429 is unhealthy.
36 // https://github.com/hashicorp/consul/blob/c7ef04c5979dbc311ff3c67b7bf3028a93e8b0f1/agent/operator_endpoint.go#L325
32 - if err := c.doOKDecode(urlPathOperationAutopilotHealth, &health, http.StatusTooManyRequests); err != nil {
37 + if err := c.client(http.StatusTooManyRequests).RequestJSON(req, &health); err != nil {
38 return err
39 }
40
src/go/plugin/go.d/modules/consul/collect_checks.go
+6 -1
@@ -18,9 +18,14 @@ type agentCheck struct {
18 }
19
20 func (c *Consul) collectChecks(mx map[string]int64) error {
21 + req, err := c.createRequest(urlPathAgentChecks)
22 + if err != nil {
23 + return err
24 + }
25 +
26 var checks map[string]*agentCheck
27
23 - if err := c.doOKDecode(urlPathAgentChecks, &checks); err != nil {
28 + if err := c.client().RequestJSON(req, &checks); err != nil {
29 return err
30 }
31
src/go/plugin/go.d/modules/consul/collect_config.go
+6 -1
@@ -46,9 +46,14 @@ type consulConfig struct {
46 }
47
48 func (c *Consul) collectConfiguration() error {
49 + req, err := c.createRequest(urlPathAgentSelf)
50 + if err != nil {
51 + return err
52 + }
53 +
54 var cfg consulConfig
55
51 - if err := c.doOKDecode(urlPathAgentSelf, &cfg); err != nil {
56 + if err := c.client().RequestJSON(req, &cfg); err != nil {
57 return err
58 }
59
src/go/plugin/go.d/modules/consul/collect_net_rtt.go
+6 -1
@@ -23,9 +23,14 @@ type nodeCoordinates struct {
23 }
24
25 func (c *Consul) collectNetworkRTT(mx map[string]int64) error {
26 + req, err := c.createRequest(urlPathCoordinateNodes)
27 + if err != nil {
28 + return err
29 + }
30 +
31 var coords []nodeCoordinates
32
28 - if err := c.doOKDecode(urlPathCoordinateNodes, &coords); err != nil {
33 + if err := c.client().RequestJSON(req, &coords); err != nil {
34 return err
35 }
36
src/go/plugin/go.d/modules/couchbase/collect.go
+2 -21
@@ -3,9 +3,7 @@
3 package couchbase
4
5 import (
6 - "encoding/json"
6 "fmt"
8 - "net/http"
7 "net/url"
8
9 "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
@@ -118,28 +116,11 @@ func (cb *Couchbase) scrapeCouchbase() (*cbMetrics, error) {
116 req.URL.RawQuery = url.Values{"skipMap": []string{"true"}}.Encode()
117
118 ms := &cbMetrics{}
121 - if err := cb.doOKDecode(req, &ms.BucketsBasicStats); err != nil {
119 + if err := web.DoHTTP(cb.httpClient).RequestJSON(req, &ms.BucketsBasicStats); err != nil {
120 return nil, err
121 }
124 - return ms, nil
125 -}
126 -
127 -func (cb *Couchbase) doOKDecode(req *http.Request, in interface{}) error {
128 - resp, err := cb.httpClient.Do(req)
129 - if err != nil {
130 - return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
131 - }
122
133 - defer web.CloseBody(resp)
134 -
135 - if resp.StatusCode != http.StatusOK {
136 - return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
137 - }
138 -
139 - if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
140 - return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
141 - }
142 - return nil
123 + return ms, nil
124 }
125
126 func indexDimID(name, metric string) string {
src/go/plugin/go.d/modules/couchdb/collect.go
+19 -23
@@ -123,10 +123,12 @@ func (cdb *CouchDB) scrapeNodeStats(ms *cdbMetrics) {
123 req, _ := web.NewHTTPRequestWithPath(cdb.RequestConfig, fmt.Sprintf(urlPathOverviewStats, cdb.Config.Node))
124
125 var stats cdbNodeStats
126 - if err := cdb.doOKDecode(req, &stats); err != nil {
126 +
127 + if err := cdb.client().RequestJSON(req, &stats); err != nil {
128 cdb.Warning(err)
129 return
130 }
131 +
132 ms.NodeStats = &stats
133 }
134
@@ -134,10 +136,12 @@ func (cdb *CouchDB) scrapeSystemStats(ms *cdbMetrics) {
136 req, _ := web.NewHTTPRequestWithPath(cdb.RequestConfig, fmt.Sprintf(urlPathSystemStats, cdb.Config.Node))
137
138 var stats cdbNodeSystem
137 - if err := cdb.doOKDecode(req, &stats); err != nil {
139 +
140 + if err := cdb.client().RequestJSON(req, &stats); err != nil {
141 cdb.Warning(err)
142 return
143 }
144 +
145 ms.NodeSystem = &stats
146 }
147
@@ -145,10 +149,12 @@ func (cdb *CouchDB) scrapeActiveTasks(ms *cdbMetrics) {
149 req, _ := web.NewHTTPRequestWithPath(cdb.RequestConfig, urlPathActiveTasks)
150
151 var stats []cdbActiveTask
148 - if err := cdb.doOKDecode(req, &stats); err != nil {
152 +
153 + if err := cdb.client().RequestJSON(req, &stats); err != nil {
154 cdb.Warning(err)
155 return
156 }
157 +
158 ms.ActiveTasks = stats
159 }
160
@@ -170,10 +176,12 @@ func (cdb *CouchDB) scrapeDBStats(ms *cdbMetrics) {
176 req.Body = io.NopCloser(bytes.NewReader(body))
177
178 var stats []cdbDBStats
173 - if err := cdb.doOKDecode(req, &stats); err != nil {
179 +
180 + if err := cdb.client().RequestJSON(req, &stats); err != nil {
181 cdb.Warning(err)
182 return
183 }
184 +
185 ms.DBStats = stats
186 }
187
@@ -196,7 +204,8 @@ func (cdb *CouchDB) pingCouchDB() error {
204 req, _ := web.NewHTTPRequest(cdb.RequestConfig)
205
206 var info struct{ Couchdb string }
199 - if err := cdb.doOKDecode(req, &info); err != nil {
207 +
208 + if err := cdb.client().RequestJSON(req, &info); err != nil {
209 return err
210 }
211
@@ -207,30 +216,17 @@ func (cdb *CouchDB) pingCouchDB() error {
216 return nil
217 }
218
210 -func (cdb *CouchDB) doOKDecode(req *http.Request, in interface{}) error {
211 - resp, err := cdb.httpClient.Do(req)
212 - if err != nil {
213 - return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
214 - }
215 -
216 - defer web.CloseBody(resp)
217 -
218 - if resp.StatusCode != http.StatusOK {
219 +func (cdb *CouchDB) client() *web.Client {
220 + return web.DoHTTP(cdb.httpClient).OnNokCode(func(resp *http.Response) (bool, error) {
221 var msg struct {
222 Error string `json:"error"`
223 Reason string `json:"reason"`
224 }
225 if err := json.NewDecoder(resp.Body).Decode(&msg); err == nil && msg.Error != "" {
224 - return fmt.Errorf("'%s' returned HTTP status code: %d (err '%s', reason '%s')",
225 - req.URL, resp.StatusCode, msg.Error, msg.Reason)
226 + return false, fmt.Errorf("error '%s', reason '%s'", msg.Error, msg.Reason)
227 }
227 - return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
228 - }
229 -
230 - if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
231 - return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
232 - }
233 - return nil
228 + return false, nil
229 + })
230 }
231
232 func merge(dst, src map[string]int64, prefix string) {
src/go/plugin/go.d/modules/dnsdist/collect.go
+3 -25
@@ -3,9 +3,6 @@
3 package dnsdist
4
5 import (
6 - "encoding/json"
7 - "fmt"
8 - "net/http"
6 "net/url"
7
8 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm"
@@ -41,29 +38,10 @@ func (d *DNSdist) scrapeStatistics() (*statisticMetrics, error) {
38 }
39 req.URL.RawQuery = url.Values{"command": []string{"stats"}}.Encode()
40
44 - var statistics statisticMetrics
45 - if err := d.doOKDecode(req, &statistics); err != nil {
41 + var stats statisticMetrics
42 + if err := web.DoHTTP(d.httpClient).RequestJSON(req, &stats); err != nil {
43 return nil, err
44 }
45
49 - return &statistics, nil
50 -}
51 -
52 -func (d *DNSdist) doOKDecode(req *http.Request, in interface{}) error {
53 - resp, err := d.httpClient.Do(req)
54 - if err != nil {
55 - return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
56 - }
57 -
58 - defer web.CloseBody(resp)
59 -
60 - if resp.StatusCode != http.StatusOK {
61 - return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
62 - }
63 -
64 - if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
65 - return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
66 - }
67 -
68 - return nil
46 + return &stats, nil
47 }
src/go/plugin/go.d/modules/dockerhub/apiclient.go
+2 -21
@@ -3,7 +3,6 @@
3 package dockerhub
4
5 import (
6 - "encoding/json"
6 "fmt"
7 "net/http"
8 "net/url"
@@ -37,29 +36,11 @@ func (a apiClient) getRepository(repoName string) (*repository, error) {
36 }
37
38 var repo repository
40 - if err := a.doOKDecode(req, &repo); err != nil {
39 + if err := web.DoHTTP(a.httpClient).RequestJSON(req, &repo); err != nil {
40 return nil, err
41 }
43 - return &repo, nil
44 -}
45 -
46 -func (a apiClient) doOKDecode(req *http.Request, in any) error {
47 - resp, err := a.httpClient.Do(req)
48 - if err != nil {
49 - return fmt.Errorf("error on request: %v", err)
50 - }
51 -
52 - defer web.CloseBody(resp)
42
54 - if resp.StatusCode != http.StatusOK {
55 - return fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode)
56 - }
57 -
58 - if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
59 - return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
60 - }
61 -
62 - return nil
43 + return &repo, nil
44 }
45
46 func (a apiClient) createRequest(urlPath string) (*http.Request, error) {
src/go/plugin/go.d/modules/elasticsearch/collect.go
+9 -35
@@ -3,11 +3,10 @@
3 package elasticsearch
4
5 import (
6 - "encoding/json"
6 "errors"
7 "fmt"
8 "math"
10 - "net/http"
9 + "slices"
10 "strconv"
11 "strings"
12 "sync"
@@ -167,7 +166,7 @@ func (es *Elasticsearch) scrapeNodesStats(ms *esMetrics) {
166 req, _ := web.NewHTTPRequestWithPath(es.RequestConfig, p)
167
168 var stats esNodesStats
170 - if err := es.doOKDecode(req, &stats); err != nil {
169 + if err := web.DoHTTP(es.httpClient).RequestJSON(req, &stats); err != nil {
170 es.Warning(err)
171 return
172 }
@@ -179,7 +178,7 @@ func (es *Elasticsearch) scrapeClusterHealth(ms *esMetrics) {
178 req, _ := web.NewHTTPRequestWithPath(es.RequestConfig, urlPathClusterHealth)
179
180 var health esClusterHealth
182 - if err := es.doOKDecode(req, &health); err != nil {
181 + if err := web.DoHTTP(es.httpClient).RequestJSON(req, &health); err != nil {
182 es.Warning(err)
183 return
184 }
@@ -191,7 +190,7 @@ func (es *Elasticsearch) scrapeClusterStats(ms *esMetrics) {
190 req, _ := web.NewHTTPRequestWithPath(es.RequestConfig, urlPathClusterStats)
191
192 var stats esClusterStats
194 - if err := es.doOKDecode(req, &stats); err != nil {
193 + if err := web.DoHTTP(es.httpClient).RequestJSON(req, &stats); err != nil {
194 es.Warning(err)
195 return
196 }
@@ -204,7 +203,7 @@ func (es *Elasticsearch) scrapeLocalIndicesStats(ms *esMetrics) {
203 req.URL.RawQuery = "local=true&format=json"
204
205 var stats []esIndexStats
207 - if err := es.doOKDecode(req, &stats); err != nil {
206 + if err := web.DoHTTP(es.httpClient).RequestJSON(req, &stats); err != nil {
207 es.Warning(err)
208 return
209 }
@@ -218,8 +217,7 @@ func (es *Elasticsearch) getClusterName() (string, error) {
217 var info struct {
218 ClusterName string `json:"cluster_name"`
219 }
221 -
222 - if err := es.doOKDecode(req, &info); err != nil {
220 + if err := web.DoHTTP(es.httpClient).RequestJSON(req, &info); err != nil {
221 return "", err
222 }
223
@@ -230,24 +228,6 @@ func (es *Elasticsearch) getClusterName() (string, error) {
228 return info.ClusterName, nil
229 }
230
233 -func (es *Elasticsearch) doOKDecode(req *http.Request, in interface{}) error {
234 - resp, err := es.httpClient.Do(req)
235 - if err != nil {
236 - return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
237 - }
238 -
239 - defer web.CloseBody(resp)
240 -
241 - if resp.StatusCode != http.StatusOK {
242 - return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
243 - }
244 -
245 - if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
246 - return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
247 - }
248 - return nil
249 -}
250 -
231 func convertIndexStoreSizeToBytes(size string) int64 {
232 var num float64
233 switch {
@@ -282,15 +262,9 @@ func boolToInt(v bool) int64 {
262 }
263
264 func removeSystemIndices(indices []esIndexStats) []esIndexStats {
285 - var i int
286 - for _, index := range indices {
287 - if strings.HasPrefix(index.Index, ".") {
288 - continue
289 - }
290 - indices[i] = index
291 - i++
292 - }
293 - return indices[:i]
265 + return slices.DeleteFunc(indices, func(stats esIndexStats) bool {
266 + return strings.HasPrefix(stats.Index, ".")
267 + })
268 }
269
270 func merge(dst, src map[string]int64, prefix string) {
src/go/plugin/go.d/modules/fluentd/apiclient.go
+2 -22
@@ -3,7 +3,6 @@
3 package fluentd
4
5 import (
6 - "encoding/json"
6 "fmt"
7 "net/http"
8 "net/url"
@@ -55,32 +54,13 @@ func (a apiClient) getPluginsInfo() (*pluginsInfo, error) {
54 }
55
56 var info pluginsInfo
58 - if err := a.doOKDecode(req, &info); err != nil {
59 - return nil, err
57 + if err := web.DoHTTP(a.httpClient).RequestJSON(req, &info); err != nil {
58 + return nil, fmt.Errorf("error on decoding request : %v", err)
59 }
60
61 return &info, nil
62 }
63
65 -func (a apiClient) doOKDecode(req *http.Request, in any) error {
66 - resp, err := a.httpClient.Do(req)
67 - if err != nil {
68 - return fmt.Errorf("error on request: %v", err)
69 - }
70 -
71 - defer web.CloseBody(resp)
72 -
73 - if resp.StatusCode != http.StatusOK {
74 - return fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode)
75 - }
76 -
77 - if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
78 - return fmt.Errorf("error on decoding response from %s : %v", req.URL, err)
79 - }
80 -
81 - return nil
82 -}
83 -
64 func (a apiClient) createRequest(urlPath string) (*http.Request, error) {
65 req := a.request.Copy()
66 u, err := url.Parse(req.URL)
src/go/plugin/go.d/modules/hdfs/client.go deleted
-61
@@ -1,61 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package hdfs
4 -
5 -import (
6 - "encoding/json"
7 - "fmt"
8 - "net/http"
9 -
10 - "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
11 -)
12 -
13 -func newClient(httpClient *http.Client, request web.RequestConfig) *client {
14 - return &client{
15 - httpClient: httpClient,
16 - request: request,
17 - }
18 -}
19 -
20 -type client struct {
21 - httpClient *http.Client
22 - request web.RequestConfig
23 -}
24 -
25 -func (c *client) do() (*http.Response, error) {
26 - req, err := web.NewHTTPRequest(c.request)
27 - if err != nil {
28 - return nil, fmt.Errorf("error on creating http request to %s : %v", c.request.URL, err)
29 - }
30 -
31 - // req.Header.Add("Accept-Encoding", "gzip")
32 - // req.Header.Set("User-Agent", "netdata/go.d.plugin")
33 -
34 - return c.httpClient.Do(req)
35 -}
36 -
37 -func (c *client) doOK() (*http.Response, error) {
38 - resp, err := c.do()
39 - if err != nil {
40 - return nil, err
41 - }
42 -
43 - if resp.StatusCode != http.StatusOK {
44 - return resp, fmt.Errorf("%s returned %d", c.request.URL, resp.StatusCode)
45 - }
46 - return resp, nil
47 -}
48 -
49 -func (c *client) doOKWithDecodeJSON(dst interface{}) error {
50 - resp, err := c.doOK()
51 - defer web.CloseBody(resp)
52 - if err != nil {
53 - return err
54 - }
55 -
56 - err = json.NewDecoder(resp.Body).Decode(dst)
57 - if err != nil {
58 - return fmt.Errorf("error on decoding response from %s : %v", c.request.URL, err)
59 - }
60 - return nil
61 -}
src/go/plugin/go.d/modules/hdfs/collect.go
+20 -18
@@ -9,12 +9,17 @@ import (
9 "strings"
10
11 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm"
12 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
13 )
14
15 func (h *HDFS) collect() (map[string]int64, error) {
15 - var raw rawJMX
16 - err := h.client.doOKWithDecodeJSON(&raw)
16 + req, err := web.NewHTTPRequest(h.RequestConfig)
17 if err != nil {
18 + return nil, fmt.Errorf("failed to create HTTP request: %v", err)
19 + }
20 +
21 + var raw rawJMX
22 + if err := web.DoHTTP(h.httpClient).RequestJSON(req, &raw); err != nil {
23 return nil, err
24 }
25
@@ -28,9 +33,13 @@ func (h *HDFS) collect() (map[string]int64, error) {
33 }
34
35 func (h *HDFS) determineNodeType() (nodeType, error) {
31 - var raw rawJMX
32 - err := h.client.doOKWithDecodeJSON(&raw)
36 + req, err := web.NewHTTPRequest(h.RequestConfig)
37 if err != nil {
38 + return "", fmt.Errorf("failed to create HTTP request: %v", err)
39 + }
40 +
41 + var raw rawJMX
42 + if err := web.DoHTTP(h.httpClient).RequestJSON(req, &raw); err != nil {
43 return "", err
44 }
45
@@ -69,40 +78,33 @@ func (h *HDFS) collectRawJMX(raw rawJMX) *metrics {
78 }
79
80 func (h *HDFS) collectNameNode(mx *metrics, raw rawJMX) {
72 - err := h.collectJVM(mx, raw)
73 - if err != nil {
81 + if err := h.collectJVM(mx, raw); err != nil {
82 h.Debugf("error on collecting jvm : %v", err)
83 }
84
77 - err = h.collectRPCActivity(mx, raw)
78 - if err != nil {
85 + if err := h.collectRPCActivity(mx, raw); err != nil {
86 h.Debugf("error on collecting rpc activity : %v", err)
87 }
88
82 - err = h.collectFSNameSystem(mx, raw)
83 - if err != nil {
89 + if err := h.collectFSNameSystem(mx, raw); err != nil {
90 h.Debugf("error on collecting fs name system : %v", err)
91 }
92 }
93
94 func (h *HDFS) collectDataNode(mx *metrics, raw rawJMX) {
89 - err := h.collectJVM(mx, raw)
90 - if err != nil {
95 + if err := h.collectJVM(mx, raw); err != nil {
96 h.Debugf("error on collecting jvm : %v", err)
97 }
98
94 - err = h.collectRPCActivity(mx, raw)
95 - if err != nil {
99 + if err := h.collectRPCActivity(mx, raw); err != nil {
100 h.Debugf("error on collecting rpc activity : %v", err)
101 }
102
99 - err = h.collectFSDatasetState(mx, raw)
100 - if err != nil {
103 + if err := h.collectFSDatasetState(mx, raw); err != nil {
104 h.Debugf("error on collecting fs dataset state : %v", err)
105 }
106
104 - err = h.collectDataNodeActivity(mx, raw)
105 - if err != nil {
107 + if err := h.collectDataNodeActivity(mx, raw); err != nil {
108 h.Debugf("error on collecting datanode activity state : %v", err)
109 }
110 }
src/go/plugin/go.d/modules/hdfs/hdfs.go
+13 -17
@@ -5,6 +5,8 @@ package hdfs
5 import (
6 _ "embed"
7 "errors"
8 + "fmt"
9 + "net/http"
10 "time"
11
12 "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
@@ -50,7 +52,7 @@ type (
52 module.Base
53 Config `yaml:",inline" json:""`
54
53 - client *client
55 + httpClient *http.Client
56
57 nodeType
58 }
@@ -67,17 +69,15 @@ func (h *HDFS) Configuration() any {
69 }
70
71 func (h *HDFS) Init() error {
70 - if err := h.validateConfig(); err != nil {
71 - h.Errorf("config validation: %v", err)
72 - return err
72 + if h.URL == "" {
73 + return errors.New("URL is required but not set")
74 }
75
75 - cl, err := h.createClient()
76 + httpClient, err := web.NewHTTPClient(h.ClientConfig)
77 if err != nil {
77 - h.Errorf("error on creating client : %v", err)
78 - return err
78 + return fmt.Errorf("failed to create HTTP client: %v", err)
79 }
80 - h.client = cl
80 + h.httpClient = httpClient
81
82 return nil
83 }
@@ -85,19 +85,19 @@ func (h *HDFS) Init() error {
85 func (h *HDFS) Check() error {
86 typ, err := h.determineNodeType()
87 if err != nil {
88 - h.Errorf("error on node type determination : %v", err)
89 - return err
88 + return fmt.Errorf("error on node type determination : %v", err)
89 }
90 h.nodeType = typ
91
92 mx, err := h.collect()
93 if err != nil {
95 - h.Error(err)
94 return err
95 }
96 +
97 if len(mx) == 0 {
98 return errors.New("no metrics collected")
99 }
100 +
101 return nil
102 }
103
@@ -114,12 +114,8 @@ func (h *HDFS) Charts() *Charts {
114
115 func (h *HDFS) Collect() map[string]int64 {
116 mx, err := h.collect()
117 -
117 if err != nil {
118 h.Error(err)
120 - }
121 -
122 - if len(mx) == 0 {
119 return nil
120 }
121
@@ -127,7 +123,7 @@ func (h *HDFS) Collect() map[string]int64 {
123 }
124
125 func (h *HDFS) Cleanup() {
130 - if h.client != nil && h.client.httpClient != nil {
131 - h.client.httpClient.CloseIdleConnections()
126 + if h.httpClient != nil {
127 + h.httpClient.CloseIdleConnections()
128 }
129 }
src/go/plugin/go.d/modules/hdfs/init.go deleted
-25
@@ -1,25 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package hdfs
4 -
5 -import (
6 - "errors"
7 -
8 - "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
9 -)
10 -
11 -func (h *HDFS) validateConfig() error {
12 - if h.URL == "" {
13 - return errors.New("url not set")
14 - }
15 - return nil
16 -}
17 -
18 -func (h *HDFS) createClient() (*client, error) {
19 - httpClient, err := web.NewHTTPClient(h.ClientConfig)
20 - if err != nil {
21 - return nil, err
22 - }
23 -
24 - return newClient(httpClient, h.RequestConfig), nil
25 -}
src/go/plugin/go.d/modules/icecast/collect.go
+1 -22
@@ -3,9 +3,7 @@
3 package icecast
4
5 import (
6 - "encoding/json"
6 "fmt"
8 - "net/http"
7
8 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
9 )
@@ -73,28 +71,9 @@ func (ic *Icecast) queryServerStats() (*serverStats, error) {
71 }
72
73 var stats serverStats
76 -
77 - if err := ic.doOKDecode(req, &stats); err != nil {
74 + if err := web.DoHTTP(ic.httpClient).RequestJSON(req, &stats); err != nil {
75 return nil, err
76 }
77
78 return &stats, nil
79 }
83 -
84 -func (ic *Icecast) doOKDecode(req *http.Request, in interface{}) error {
85 - resp, err := ic.httpClient.Do(req)
86 - if err != nil {
87 - return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
88 - }
89 -
90 - defer web.CloseBody(resp)
91 -
92 - if resp.StatusCode != http.StatusOK {
93 - return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
94 - }
95 -
96 - if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
97 - return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
98 - }
99 - return nil
100 -}
src/go/plugin/go.d/modules/ipfs/collect.go
+4 -24
@@ -3,9 +3,7 @@
3 package ipfs
4
5 import (
6 - "encoding/json"
6 "fmt"
8 - "net/http"
7
8 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
9 )
@@ -130,7 +128,7 @@ func (ip *IPFS) queryStatsBandwidth() (*ipfsStatsBw, error) {
128 }
129
130 var stats ipfsStatsBw
133 - if err := ip.doOKDecode(req, &stats); err != nil {
131 + if err := web.DoHTTP(ip.httpClient).RequestJSON(req, &stats); err != nil {
132 return nil, err
133 }
134
@@ -148,7 +146,7 @@ func (ip *IPFS) querySwarmPeers() (*ipfsSwarmPeers, error) {
146 }
147
148 var stats ipfsSwarmPeers
151 - if err := ip.doOKDecode(req, &stats); err != nil {
149 + if err := web.DoHTTP(ip.httpClient).RequestJSON(req, &stats); err != nil {
150 return nil, err
151 }
152
@@ -162,7 +160,7 @@ func (ip *IPFS) queryStatsRepo() (*ipfsStatsRepo, error) {
160 }
161
162 var stats ipfsStatsRepo
165 - if err := ip.doOKDecode(req, &stats); err != nil {
163 + if err := web.DoHTTP(ip.httpClient).RequestJSON(req, &stats); err != nil {
164 return nil, err
165 }
166
@@ -176,27 +174,9 @@ func (ip *IPFS) queryPinLs() (*ipfsPinsLs, error) {
174 }
175
176 var stats ipfsPinsLs
179 - if err := ip.doOKDecode(req, &stats); err != nil {
177 + if err := web.DoHTTP(ip.httpClient).RequestJSON(req, &stats); err != nil {
178 return nil, err
179 }
180
181 return &stats, nil
182 }
185 -
186 -func (ip *IPFS) doOKDecode(req *http.Request, in interface{}) error {
187 - resp, err := ip.httpClient.Do(req)
188 - if err != nil {
189 - return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
190 - }
191 -
192 - defer web.CloseBody(resp)
193 -
194 - if resp.StatusCode != http.StatusOK {
195 - return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
196 - }
197 -
198 - if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
199 - return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
200 - }
201 - return nil
202 -}
src/go/plugin/go.d/modules/k8s_state/cluster_meta.go
+20 -26
@@ -3,11 +3,14 @@
3 package k8s_state
4
5 import (
6 + "errors"
7 "fmt"
8 "io"
9 "net/http"
10 "time"
11
12 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
13 +
14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15 )
16
@@ -21,7 +24,7 @@ func (ks *KubeState) getKubeClusterID() string {
24 }
25
26 func (ks *KubeState) getKubeClusterName() string {
24 - client := http.Client{Timeout: time.Second}
27 + client := &http.Client{Timeout: time.Second}
28 n, err := getGKEKubeClusterName(client)
29 if err != nil {
30 ks.Debugf("error on getting GKE cluster name: %v", err)
@@ -29,7 +32,7 @@ func (ks *KubeState) getKubeClusterName() string {
32 return n
33 }
34
32 -func getGKEKubeClusterName(client http.Client) (string, error) {
35 +func getGKEKubeClusterName(client *http.Client) (string, error) {
36 id, err := doMetaGKEHTTPReq(client, "http://metadata/computeMetadata/v1/project/project-id")
37 if err != nil {
38 return "", err
@@ -46,39 +49,30 @@ func getGKEKubeClusterName(client http.Client) (string, error) {
49 return fmt.Sprintf("gke_%s_%s_%s", id, loc, name), nil
50 }
51
49 -func doMetaGKEHTTPReq(client http.Client, url string) (string, error) {
52 +func doMetaGKEHTTPReq(client *http.Client, url string) (string, error) {
53 req, err := http.NewRequest(http.MethodGet, url, nil)
54 if err != nil {
55 return "", err
56 }
57
58 req.Header.Add("Metadata-Flavor", "Google")
56 - resp, err := client.Do(req)
57 - if err != nil {
58 - return "", err
59 - }
60 - defer closeHTTPRespBody(resp)
59
62 - if resp.StatusCode != http.StatusOK {
63 - return "", fmt.Errorf("'%s' returned HTTP status code %d", url, resp.StatusCode)
64 - }
60 + var resp string
61
66 - bs, err := io.ReadAll(resp.Body)
67 - if err != nil {
68 - return "", err
69 - }
70 -
71 - s := string(bs)
72 - if s == "" {
73 - return "", fmt.Errorf("an empty response from '%s'", url)
74 - }
62 + if err := web.DoHTTP(client).Request(req, func(body io.Reader) error {
63 + bs, rerr := io.ReadAll(body)
64 + if rerr != nil {
65 + return rerr
66 + }
67
76 - return s, nil
77 -}
68 + if resp = string(bs); len(resp) == 0 {
69 + return errors.New("empty response")
70 + }
71
79 -func closeHTTPRespBody(resp *http.Response) {
80 - if resp != nil && resp.Body != nil {
81 - _, _ = io.Copy(io.Discard, resp.Body)
82 - _ = resp.Body.Close()
72 + return nil
73 + }); err != nil {
74 + return "", err
75 }
76 +
77 + return resp, nil
78 }
src/go/plugin/go.d/modules/lighttpd/collect.go
+14 -7
@@ -4,22 +4,29 @@ package lighttpd
4
5 import (
6 "fmt"
7 + "io"
8
9 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm"
10 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
11 )
12
13 func (l *Lighttpd) collect() (map[string]int64, error) {
12 - status, err := l.apiClient.getServerStatus()
13 -
14 + req, err := web.NewHTTPRequest(l.RequestConfig)
15 if err != nil {
15 - return nil, err
16 + return nil, fmt.Errorf("failed to create HTTP request: %v", err)
17 }
18
18 - mx := stm.ToMap(status)
19 + var status *serverStatus
20 + var perr error
21
20 - if len(mx) == 0 {
21 - return nil, fmt.Errorf("nothing was collected from %s", l.URL)
22 + if err := web.DoHTTP(l.httpClient).Request(req, func(body io.Reader) error {
23 + if status, perr = parseResponse(body); perr != nil {
24 + return perr
25 + }
26 + return nil
27 + }); err != nil {
28 + return nil, err
29 }
30
24 - return mx, nil
31 + return stm.ToMap(status), nil
32 }
src/go/plugin/go.d/modules/lighttpd/init.go deleted
-29
@@ -1,29 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package lighttpd
4 -
5 -import (
6 - "errors"
7 - "fmt"
8 - "strings"
9 -
10 - "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
11 -)
12 -
13 -func (l *Lighttpd) validateConfig() error {
14 - if l.URL == "" {
15 - return errors.New("url not set")
16 - }
17 - if !strings.HasSuffix(l.URL, "?auto") {
18 - return fmt.Errorf("bad URL '%s', should ends in '?auto'", l.URL)
19 - }
20 - return nil
21 -}
22 -
23 -func (l *Lighttpd) initApiClient() (*apiClient, error) {
24 - client, err := web.NewHTTPClient(l.ClientConfig)
25 - if err != nil {
26 - return nil, err
27 - }
28 - return newAPIClient(client, l.RequestConfig), nil
29 -}
src/go/plugin/go.d/modules/lighttpd/lighttpd.go
+22 -14
@@ -5,6 +5,9 @@ package lighttpd
5 import (
6 _ "embed"
7 "errors"
8 + "fmt"
9 + "net/http"
10 + "strings"
11 "time"
12
13 "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
@@ -33,7 +36,9 @@ func New() *Lighttpd {
36 Timeout: confopt.Duration(time.Second * 2),
37 },
38 },
36 - }}
39 + },
40 + charts: charts.Copy(),
41 + }
42 }
43
44 type Config struct {
@@ -45,7 +50,9 @@ type Lighttpd struct {
50 module.Base
51 Config `yaml:",inline" json:""`
52
48 - apiClient *apiClient
53 + charts *module.Charts
54 +
55 + httpClient *http.Client
56 }
57
58 func (l *Lighttpd) Configuration() any {
@@ -53,17 +60,18 @@ func (l *Lighttpd) Configuration() any {
60 }
61
62 func (l *Lighttpd) Init() error {
56 - if err := l.validateConfig(); err != nil {
57 - l.Errorf("config validation: %v", err)
58 - return err
63 + if l.URL == "" {
64 + return errors.New("URL is required but not set")
65 + }
66 + if !strings.HasSuffix(l.URL, "?auto") {
67 + return fmt.Errorf("bad URL '%s', should ends in '?auto'", l.URL)
68 }
69
61 - client, err := l.initApiClient()
70 + httpClient, err := web.NewHTTPClient(l.ClientConfig)
71 if err != nil {
63 - l.Error(err)
64 - return err
72 + return fmt.Errorf("failed to create http client: %v", err)
73 }
66 - l.apiClient = client
74 + l.httpClient = httpClient
75
76 l.Debugf("using URL %s", l.URL)
77 l.Debugf("using timeout: %s", l.Timeout.Duration())
@@ -74,22 +82,22 @@ func (l *Lighttpd) Init() error {
82 func (l *Lighttpd) Check() error {
83 mx, err := l.collect()
84 if err != nil {
77 - l.Error(err)
85 return err
86 }
87 +
88 if len(mx) == 0 {
89 return errors.New("no metrics collected")
90 }
91 +
92 return nil
93 }
94
95 func (l *Lighttpd) Charts() *Charts {
87 - return charts.Copy()
96 + return l.charts
97 }
98
99 func (l *Lighttpd) Collect() map[string]int64 {
100 mx, err := l.collect()
92 -
101 if err != nil {
102 l.Error(err)
103 return nil
@@ -99,7 +107,7 @@ func (l *Lighttpd) Collect() map[string]int64 {
107 }
108
109 func (l *Lighttpd) Cleanup() {
102 - if l.apiClient != nil && l.apiClient.httpClient != nil {
103 - l.apiClient.httpClient.CloseIdleConnections()
110 + if l.httpClient != nil {
111 + l.httpClient.CloseIdleConnections()
112 }
113 }
src/go/plugin/go.d/modules/lighttpd/lighttpd_test.go
-1
@@ -41,7 +41,6 @@ func TestLighttpd_Init(t *testing.T) {
41 job := New()
42
43 require.NoError(t, job.Init())
44 - assert.NotNil(t, job.apiClient)
44 }
45
46 func TestLighttpd_InitNG(t *testing.T) {
src/go/plugin/go.d/modules/lighttpd/metrics.go deleted
-33
@@ -1,33 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package lighttpd
4 -
5 -type (
6 - serverStatus struct {
7 - Total struct {
8 - Accesses *int64 `stm:"accesses"`
9 - KBytes *int64 `stm:"kBytes"`
10 - } `stm:"total"`
11 - Servers struct {
12 - Busy *int64 `stm:"busy_servers"`
13 - Idle *int64 `stm:"idle_servers"`
14 - } `stm:""`
15 - Uptime *int64 `stm:"uptime"`
16 - Scoreboard *scoreboard `stm:"scoreboard"`
17 - }
18 - scoreboard struct {
19 - Waiting int64 `stm:"waiting"`
20 - Open int64 `stm:"open"`
21 - Close int64 `stm:"close"`
22 - HardError int64 `stm:"hard_error"`
23 - KeepAlive int64 `stm:"keepalive"`
24 - Read int64 `stm:"read"`
25 - ReadPost int64 `stm:"read_post"`
26 - Write int64 `stm:"write"`
27 - HandleRequest int64 `stm:"handle_request"`
28 - RequestStart int64 `stm:"request_start"`
29 - RequestEnd int64 `stm:"request_end"`
30 - ResponseStart int64 `stm:"response_start"`
31 - ResponseEnd int64 `stm:"response_end"`
32 - }
33 -)
src/go/plugin/go.d/modules/lighttpd/status.go renamed
+27 -44
@@ -6,11 +6,8 @@ import (
6 "bufio"
7 "fmt"
8 "io"
9 - "net/http"
9 "strconv"
10 "strings"
12 -
13 - "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
11 )
12
13 const (
@@ -25,49 +22,35 @@ const (
22 scoreBoard = "Scoreboard"
23 )
24
28 -func newAPIClient(client *http.Client, request web.RequestConfig) *apiClient {
29 - return &apiClient{httpClient: client, request: request}
30 -}
31 -
32 -type apiClient struct {
33 - httpClient *http.Client
34 - request web.RequestConfig
35 -}
36 -
37 -func (a apiClient) getServerStatus() (*serverStatus, error) {
38 - req, err := web.NewHTTPRequest(a.request)
39 -
40 - if err != nil {
41 - return nil, fmt.Errorf("error on creating request : %v", err)
25 +type (
26 + serverStatus struct {
27 + Total struct {
28 + Accesses *int64 `stm:"accesses"`
29 + KBytes *int64 `stm:"kBytes"`
30 + } `stm:"total"`
31 + Servers struct {
32 + Busy *int64 `stm:"busy_servers"`
33 + Idle *int64 `stm:"idle_servers"`
34 + } `stm:""`
35 + Uptime *int64 `stm:"uptime"`
36 + Scoreboard *scoreboard `stm:"scoreboard"`
37 }
43 -
44 - resp, err := a.doRequestOK(req)
45 -
46 - defer web.CloseBody(resp)
47 -
48 - if err != nil {
49 - return nil, err
38 + scoreboard struct {
39 + Waiting int64 `stm:"waiting"`
40 + Open int64 `stm:"open"`
41 + Close int64 `stm:"close"`
42 + HardError int64 `stm:"hard_error"`
43 + KeepAlive int64 `stm:"keepalive"`
44 + Read int64 `stm:"read"`
45 + ReadPost int64 `stm:"read_post"`
46 + Write int64 `stm:"write"`
47 + HandleRequest int64 `stm:"handle_request"`
48 + RequestStart int64 `stm:"request_start"`
49 + RequestEnd int64 `stm:"request_end"`
50 + ResponseStart int64 `stm:"response_start"`
51 + ResponseEnd int64 `stm:"response_end"`
52 }
51 -
52 - status, err := parseResponse(resp.Body)
53 -
54 - if err != nil {
55 - return nil, fmt.Errorf("error on parsing response from %s : %v", req.URL, err)
56 - }
57 -
58 - return status, nil
59 -}
60 -
61 -func (a apiClient) doRequestOK(req *http.Request) (*http.Response, error) {
62 - resp, err := a.httpClient.Do(req)
63 - if err != nil {
64 - return nil, fmt.Errorf("error on request : %v", err)
65 - }
66 - if resp.StatusCode != http.StatusOK {
67 - return resp, fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode)
68 - }
69 - return resp, nil
70 -}
53 +)
54
55 func parseResponse(r io.Reader) (*serverStatus, error) {
56 s := bufio.NewScanner(r)
src/go/plugin/go.d/modules/logstash/collect.go
+2 -31
@@ -3,10 +3,7 @@
3 package logstash
4
5 import (
6 - "encoding/json"
6 "fmt"
8 - "io"
9 - "net/http"
7
8 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm"
9 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
@@ -47,40 +44,14 @@ func (l *Logstash) updateCharts(pipelines map[string]pipelineStats) {
44 func (l *Logstash) queryNodeStats() (*nodeStats, error) {
45 req, err := web.NewHTTPRequestWithPath(l.RequestConfig, urlPathNodeStatsAPI)
46 if err != nil {
50 - return nil, err
47 + return nil, fmt.Errorf("failed to create HTTP request: %w", err)
48 }
49
50 var stats nodeStats
51
55 - if err := l.doWithDecode(&stats, req); err != nil {
52 + if err := web.DoHTTP(l.httpClient).RequestJSON(req, &stats); err != nil {
53 return nil, err
54 }
55
56 return &stats, nil
57 }
61 -
62 -func (l *Logstash) doWithDecode(dst interface{}, req *http.Request) error {
63 - l.Debugf("executing %s '%s'", req.Method, req.URL)
64 -
65 - resp, err := l.httpClient.Do(req)
66 - if err != nil {
67 - return err
68 - }
69 -
70 - defer web.CloseBody(resp)
71 -
72 - if resp.StatusCode != http.StatusOK {
73 - return fmt.Errorf("%s returned %d status code (%s)", req.URL, resp.StatusCode, resp.Status)
74 - }
75 -
76 - content, err := io.ReadAll(resp.Body)
77 - if err != nil {
78 - return fmt.Errorf("error on reading response from %s : %v", req.URL, err)
79 - }
80 -
81 - if err := json.Unmarshal(content, dst); err != nil {
82 - return fmt.Errorf("error on parsing response from %s : %v", req.URL, err)
83 - }
84 -
85 - return nil
86 -}
src/go/plugin/go.d/modules/monit/collect.go
+3 -24
@@ -6,7 +6,6 @@ import (
6 "encoding/xml"
7 "errors"
8 "fmt"
9 - "net/http"
9 "net/url"
10
11 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
@@ -80,31 +79,11 @@ func (m *Monit) fetchStatus() (*monitStatus, error) {
79 req.URL.RawQuery = urlQueryStatus
80
81 var status monitStatus
83 - if err := m.doOKDecode(req, &status); err != nil {
82 + if err := web.DoHTTP(m.httpClient).RequestXML(req, &status, func(d *xml.Decoder) {
83 + d.CharsetReader = charset.NewReaderLabel
84 + }); err != nil {
85 return nil, err
86 }
87
88 return &status, nil
89 }
89 -
90 -func (m *Monit) doOKDecode(req *http.Request, in interface{}) error {
91 - resp, err := m.httpClient.Do(req)
92 - if err != nil {
93 - return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
94 - }
95 -
96 - defer web.CloseBody(resp)
97 -
98 - if resp.StatusCode != http.StatusOK {
99 - return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
100 - }
101 -
102 - dec := xml.NewDecoder(resp.Body)
103 - dec.CharsetReader = charset.NewReaderLabel
104 -
105 - if err := dec.Decode(in); err != nil {
106 - return fmt.Errorf("error on decoding XML response from '%s': %v", req.URL, err)
107 - }
108 -
109 - return nil
110 -}
src/go/plugin/go.d/modules/nginx/collect.go
+17 -2
@@ -3,13 +3,28 @@
3 package nginx
4
5 import (
6 + "fmt"
7 + "io"
8 +
9 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm"
10 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
11 )
12
13 func (n *Nginx) collect() (map[string]int64, error) {
10 - status, err := n.apiClient.getStubStatus()
11 -
14 + req, err := web.NewHTTPRequest(n.RequestConfig)
15 if err != nil {
16 + return nil, fmt.Errorf("failed to create HTTP request to '%s': %w'", n.URL, err)
17 + }
18 +
19 + var status *stubStatus
20 + var perr error
21 +
22 + if err := web.DoHTTP(n.httpClient).Request(req, func(body io.Reader) error {
23 + if status, perr = parseStubStatus(body); perr != nil {
24 + return perr
25 + }
26 + return nil
27 + }); err != nil {
28 return nil, err
29 }
30
src/go/plugin/go.d/modules/nginx/metrics.go deleted
-34
@@ -1,34 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package nginx
4 -
5 -type stubStatus struct {
6 - Connections struct {
7 - // The current number of active client connections including Waiting connections.
8 - Active int64 `stm:"active"`
9 -
10 - // The total number of accepted client connections.
11 - Accepts int64 `stm:"accepts"`
12 -
13 - // The total number of handled connections.
14 - // Generally, the parameter value is the same as accepts unless some resource limits have been reached.
15 - Handled int64 `stm:"handled"`
16 -
17 - // The current number of connections where nginx is reading the request header.
18 - Reading int64 `stm:"reading"`
19 -
20 - // The current number of connections where nginx is writing the response back to the client.
21 - Writing int64 `stm:"writing"`
22 -
23 - // The current number of idle client connections waiting for a request.
24 - Waiting int64 `stm:"waiting"`
25 - } `stm:""`
26 - Requests struct {
27 - // The total number of client requests.
28 - Total int64 `stm:"requests"`
29 -
30 - // Note: tengine specific
31 - // The total requests' response time, which is in millisecond
32 - Time *int64 `stm:"request_time"`
33 - } `stm:""`
34 -}
src/go/plugin/go.d/modules/nginx/nginx.go
+17 -13
@@ -5,6 +5,8 @@ package nginx
5 import (
6 _ "embed"
7 "errors"
8 + "fmt"
9 + "net/http"
10 "time"
11
12 "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
@@ -34,7 +36,9 @@ func New() *Nginx {
36 Timeout: confopt.Duration(time.Second * 1),
37 },
38 },
37 - }}
39 + },
40 + charts: charts.Copy(),
41 + }
42 }
43
44 type Config struct {
@@ -46,7 +50,9 @@ type Nginx struct {
50 module.Base
51 Config `yaml:",inline" json:""`
52
49 - apiClient *apiClient
53 + charts *module.Charts
54 +
55 + httpClient *http.Client
56 }
57
58 func (n *Nginx) Configuration() any {
@@ -55,17 +61,14 @@ func (n *Nginx) Configuration() any {
61
62 func (n *Nginx) Init() error {
63 if n.URL == "" {
58 - n.Error("URL not set")
59 - return errors.New("url not set")
64 + return errors.New("nginx URL required but not set")
65 }
66
62 - client, err := web.NewHTTPClient(n.ClientConfig)
67 + httpClient, err := web.NewHTTPClient(n.ClientConfig)
68 if err != nil {
64 - n.Error(err)
65 - return err
69 + return fmt.Errorf("failed initializing http client: %w", err)
70 }
67 -
68 - n.apiClient = newAPIClient(client, n.RequestConfig)
71 + n.httpClient = httpClient
72
73 n.Debugf("using URL %s", n.URL)
74 n.Debugf("using timeout: %s", n.Timeout)
@@ -79,15 +82,16 @@ func (n *Nginx) Check() error {
82 n.Error(err)
83 return err
84 }
85 +
86 if len(mx) == 0 {
87 return errors.New("no metrics collected")
84 -
88 }
89 +
90 return nil
91 }
92
93 func (n *Nginx) Charts() *Charts {
90 - return charts.Copy()
94 + return n.charts
95 }
96
97 func (n *Nginx) Collect() map[string]int64 {
@@ -101,7 +105,7 @@ func (n *Nginx) Collect() map[string]int64 {
105 }
106
107 func (n *Nginx) Cleanup() {
104 - if n.apiClient != nil && n.apiClient.httpClient != nil {
105 - n.apiClient.httpClient.CloseIdleConnections()
108 + if n.httpClient != nil {
109 + n.httpClient.CloseIdleConnections()
110 }
111 }
src/go/plugin/go.d/modules/nginx/nginx_test.go
-1
@@ -45,7 +45,6 @@ func TestNginx_Init(t *testing.T) {
45 job := New()
46
47 require.NoError(t, job.Init())
48 - assert.NotNil(t, job.apiClient)
48 }
49
50 func TestNginx_Check(t *testing.T) {
src/go/plugin/go.d/modules/nginx/status.go renamed
+23 -37
@@ -6,12 +6,9 @@ import (
6 "bufio"
7 "fmt"
8 "io"
9 - "net/http"
9 "regexp"
10 "strconv"
11 "strings"
13 -
14 - "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
12 )
13
14 const (
@@ -49,46 +46,35 @@ var (
46 reStatus = regexp.MustCompile(`^Active connections: ([0-9]+)\n[^\d]+([0-9]+) ([0-9]+) ([0-9]+) ?([0-9]+)?\nReading: ([0-9]+) Writing: ([0-9]+) Waiting: ([0-9]+)`)
47 )
48
52 -func newAPIClient(client *http.Client, request web.RequestConfig) *apiClient {
53 - return &apiClient{httpClient: client, request: request}
54 -}
49 +type stubStatus struct {
50 + Connections struct {
51 + // The current number of active client connections including Waiting connections.
52 + Active int64 `stm:"active"`
53
56 -type apiClient struct {
57 - httpClient *http.Client
58 - request web.RequestConfig
59 -}
54 + // The total number of accepted client connections.
55 + Accepts int64 `stm:"accepts"`
56
61 -func (a apiClient) getStubStatus() (*stubStatus, error) {
62 - req, err := web.NewHTTPRequest(a.request)
63 - if err != nil {
64 - return nil, fmt.Errorf("error on creating request : %v", err)
65 - }
57 + // The total number of handled connections.
58 + // Generally, the parameter value is the same as accepts unless some resource limits have been reached.
59 + Handled int64 `stm:"handled"`
60
67 - resp, err := a.doRequestOK(req)
68 - defer web.CloseBody(resp)
69 - if err != nil {
70 - return nil, err
71 - }
61 + // The current number of connections where nginx is reading the request header.
62 + Reading int64 `stm:"reading"`
63
73 - status, err := parseStubStatus(resp.Body)
74 - if err != nil {
75 - return nil, fmt.Errorf("error on parsing response : %v", err)
76 - }
77 -
78 - return status, nil
79 -}
64 + // The current number of connections where nginx is writing the response back to the client.
65 + Writing int64 `stm:"writing"`
66
81 -func (a apiClient) doRequestOK(req *http.Request) (*http.Response, error) {
82 - resp, err := a.httpClient.Do(req)
83 - if err != nil {
84 - return resp, fmt.Errorf("error on request : %v", err)
85 - }
86 -
87 - if resp.StatusCode != http.StatusOK {
88 - return resp, fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode)
89 - }
67 + // The current number of idle client connections waiting for a request.
68 + Waiting int64 `stm:"waiting"`
69 + } `stm:""`
70 + Requests struct {
71 + // The total number of client requests.
72 + Total int64 `stm:"requests"`
73
91 - return resp, err
74 + // Note: tengine specific
75 + // The total requests' response time, which is in millisecond
76 + Time *int64 `stm:"request_time"`
77 + } `stm:""`
78 }
79
80 func parseStubStatus(r io.Reader) (*stubStatus, error) {
src/go/plugin/go.d/modules/nginxplus/nginx_http_api_query.go
+23 -40
@@ -3,10 +3,8 @@
3 package nginxplus
4
5 import (
6 - "encoding/json"
6 "errors"
7 "fmt"
9 - "io"
8 "net/http"
9 "sync"
10
@@ -49,7 +47,7 @@ func (n *NginxPlus) queryAPIVersion() (int64, error) {
47 req, _ := web.NewHTTPRequestWithPath(n.RequestConfig, urlPathAPIVersions)
48
49 var versions nginxAPIVersions
52 - if err := n.doWithDecode(&versions, req); err != nil {
50 + if err := n.doHTTP(req, &versions); err != nil {
51 return 0, err
52 }
53
@@ -64,7 +62,7 @@ func (n *NginxPlus) queryAvailableEndpoints() error {
62 req, _ := web.NewHTTPRequestWithPath(n.RequestConfig, fmt.Sprintf(urlPathAPIEndpointsRoot, n.apiVersion))
63
64 var endpoints []string
67 - if err := n.doWithDecode(&endpoints, req); err != nil {
65 + if err := n.doHTTP(req, &endpoints); err != nil {
66 return err
67 }
68
@@ -91,7 +89,7 @@ func (n *NginxPlus) queryAvailableEndpoints() error {
89 endpoints = endpoints[:0]
90 req, _ = web.NewHTTPRequestWithPath(n.RequestConfig, fmt.Sprintf(urlPathAPIEndpointsHTTP, n.apiVersion))
91
94 - if err := n.doWithDecode(&endpoints, req); err != nil {
92 + if err := n.doHTTP(req, &endpoints); err != nil {
93 return err
94 }
95
@@ -116,7 +114,7 @@ func (n *NginxPlus) queryAvailableEndpoints() error {
114 endpoints = endpoints[:0]
115 req, _ = web.NewHTTPRequestWithPath(n.RequestConfig, fmt.Sprintf(urlPathAPIEndpointsStream, n.apiVersion))
116
119 - if err := n.doWithDecode(&endpoints, req); err != nil {
117 + if err := n.doHTTP(req, &endpoints); err != nil {
118 return err
119 }
120
@@ -171,7 +169,7 @@ func (n *NginxPlus) queryNginxInfo(ms *nginxMetrics) {
169
170 var v nginxInfo
171
174 - if err := n.doWithDecode(&v, req); err != nil {
172 + if err := n.doHTTP(req, &v); err != nil {
173 n.endpoints.nginx = !errors.Is(err, errPathNotFound)
174 n.Warning(err)
175 return
@@ -185,7 +183,7 @@ func (n *NginxPlus) queryConnections(ms *nginxMetrics) {
183
184 var v nginxConnections
185
188 - if err := n.doWithDecode(&v, req); err != nil {
186 + if err := n.doHTTP(req, &v); err != nil {
187 n.endpoints.connections = !errors.Is(err, errPathNotFound)
188 n.Warning(err)
189 return
@@ -199,7 +197,7 @@ func (n *NginxPlus) querySSL(ms *nginxMetrics) {
197
198 var v nginxSSL
199
202 - if err := n.doWithDecode(&v, req); err != nil {
200 + if err := n.doHTTP(req, &v); err != nil {
201 n.endpoints.ssl = !errors.Is(err, errPathNotFound)
202 n.Warning(err)
203 return
@@ -213,7 +211,7 @@ func (n *NginxPlus) queryHTTPRequests(ms *nginxMetrics) {
211
212 var v nginxHTTPRequests
213
216 - if err := n.doWithDecode(&v, req); err != nil {
214 + if err := n.doHTTP(req, &v); err != nil {
215 n.endpoints.httpRequest = !errors.Is(err, errPathNotFound)
216 n.Warning(err)
217 return
@@ -227,7 +225,7 @@ func (n *NginxPlus) queryHTTPServerZones(ms *nginxMetrics) {
225
226 var v nginxHTTPServerZones
227
230 - if err := n.doWithDecode(&v, req); err != nil {
228 + if err := n.doHTTP(req, &v); err != nil {
229 n.endpoints.httpServerZones = !errors.Is(err, errPathNotFound)
230 n.Warning(err)
231 return
@@ -241,7 +239,7 @@ func (n *NginxPlus) queryHTTPLocationZones(ms *nginxMetrics) {
239
240 var v nginxHTTPLocationZones
241
244 - if err := n.doWithDecode(&v, req); err != nil {
242 + if err := n.doHTTP(req, &v); err != nil {
243 n.endpoints.httpLocationZones = !errors.Is(err, errPathNotFound)
244 n.Warning(err)
245 return
@@ -255,7 +253,7 @@ func (n *NginxPlus) queryHTTPUpstreams(ms *nginxMetrics) {
253
254 var v nginxHTTPUpstreams
255
258 - if err := n.doWithDecode(&v, req); err != nil {
256 + if err := n.doHTTP(req, &v); err != nil {
257 n.endpoints.httpUpstreams = !errors.Is(err, errPathNotFound)
258 n.Warning(err)
259 return
@@ -269,7 +267,7 @@ func (n *NginxPlus) queryHTTPCaches(ms *nginxMetrics) {
267
268 var v nginxHTTPCaches
269
272 - if err := n.doWithDecode(&v, req); err != nil {
270 + if err := n.doHTTP(req, &v); err != nil {
271 n.endpoints.httpCaches = !errors.Is(err, errPathNotFound)
272 n.Warning(err)
273 return
@@ -283,7 +281,7 @@ func (n *NginxPlus) queryStreamServerZones(ms *nginxMetrics) {
281
282 var v nginxStreamServerZones
283
286 - if err := n.doWithDecode(&v, req); err != nil {
284 + if err := n.doHTTP(req, &v); err != nil {
285 n.endpoints.streamServerZones = !errors.Is(err, errPathNotFound)
286 n.Warning(err)
287 return
@@ -297,7 +295,7 @@ func (n *NginxPlus) queryStreamUpstreams(ms *nginxMetrics) {
295
296 var v nginxStreamUpstreams
297
300 - if err := n.doWithDecode(&v, req); err != nil {
298 + if err := n.doHTTP(req, &v); err != nil {
299 n.endpoints.streamUpstreams = !errors.Is(err, errPathNotFound)
300 n.Warning(err)
301 return
@@ -311,7 +309,7 @@ func (n *NginxPlus) queryResolvers(ms *nginxMetrics) {
309
310 var v nginxResolvers
311
314 - if err := n.doWithDecode(&v, req); err != nil {
312 + if err := n.doHTTP(req, &v); err != nil {
313 n.endpoints.resolvers = !errors.Is(err, errPathNotFound)
314 n.Warning(err)
315 return
@@ -324,32 +322,17 @@ var (
322 errPathNotFound = errors.New("path not found")
323 )
324
327 -func (n *NginxPlus) doWithDecode(dst interface{}, req *http.Request) error {
325 +func (n *NginxPlus) doHTTP(req *http.Request, dst any) error {
326 n.Debugf("executing %s '%s'", req.Method, req.URL)
329 - resp, err := n.httpClient.Do(req)
330 - if err != nil {
331 - return err
332 - }
333 -
334 - defer web.CloseBody(resp)
335 -
336 - if resp.StatusCode == http.StatusNotFound {
337 - return fmt.Errorf("%s returned %d status code (%w)", req.URL, resp.StatusCode, errPathNotFound)
338 - }
339 - if resp.StatusCode != http.StatusOK {
340 - return fmt.Errorf("%s returned %d status code (%s)", req.URL, resp.StatusCode, resp.Status)
341 - }
327
343 - content, err := io.ReadAll(resp.Body)
344 - if err != nil {
345 - return fmt.Errorf("error on reading response from %s : %v", req.URL, err)
346 - }
347 -
348 - if err := json.Unmarshal(content, dst); err != nil {
349 - return fmt.Errorf("error on parsing response from %s : %v", req.URL, err)
350 - }
328 + cl := web.DoHTTP(n.httpClient).OnNokCode(func(resp *http.Response) (bool, error) {
329 + if resp.StatusCode == http.StatusNotFound {
330 + return false, errPathNotFound
331 + }
332 + return false, nil
333 + })
334
352 - return nil
335 + return cl.RequestJSON(req, dst)
336 }
337
338 func (n *nginxMetrics) empty() bool {
src/go/plugin/go.d/modules/nginxvts/collect.go
+2 -24
@@ -3,10 +3,6 @@
3 package nginxvts
4
5 import (
6 - "encoding/json"
7 - "fmt"
8 - "net/http"
9 -
6 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm"
7 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
8 )
@@ -47,28 +43,10 @@ func (vts *NginxVTS) scapeVTS() (*vtsMetrics, error) {
43 req, _ := web.NewHTTPRequest(vts.RequestConfig)
44
45 var total vtsMetrics
50 -
51 - if err := vts.doOKDecode(req, &total); err != nil {
46 + if err := web.DoHTTP(vts.httpClient).RequestJSON(req, &total); err != nil {
47 vts.Warning(err)
48 return nil, err
49 }
55 - return &total, nil
56 -}
57 -
58 -func (vts *NginxVTS) doOKDecode(req *http.Request, in interface{}) error {
59 - resp, err := vts.httpClient.Do(req)
60 - if err != nil {
61 - return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
62 - }
63 -
64 - defer web.CloseBody(resp)
65 -
66 - if resp.StatusCode != http.StatusOK {
67 - return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
68 - }
50
70 - if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
71 - return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
72 - }
73 - return nil
51 + return &total, nil
52 }
src/go/plugin/go.d/modules/phpdaemon/client.go deleted
-70
@@ -1,70 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package phpdaemon
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 decodeFunc func(dst interface{}, reader io.Reader) error
15 -
16 -func decodeJson(dst interface{}, reader io.Reader) error { return json.NewDecoder(reader).Decode(dst) }
17 -
18 -func newAPIClient(httpClient *http.Client, request web.RequestConfig) *client {
19 - return &client{
20 - httpClient: httpClient,
21 - request: request,
22 - }
23 -}
24 -
25 -type client struct {
26 - httpClient *http.Client
27 - request web.RequestConfig
28 -}
29 -
30 -func (c *client) queryFullStatus() (*FullStatus, error) {
31 - var status FullStatus
32 - err := c.doWithDecode(&status, decodeJson, c.request)
33 - if err != nil {
34 - return nil, err
35 - }
36 -
37 - return &status, nil
38 -}
39 -
40 -func (c *client) doWithDecode(dst interface{}, decode decodeFunc, request web.RequestConfig) error {
41 - req, err := web.NewHTTPRequest(request)
42 - if err != nil {
43 - return fmt.Errorf("error on creating http request to %s : %v", request.URL, err)
44 - }
45 -
46 - resp, err := c.doOK(req)
47 - defer web.CloseBody(resp)
48 - if err != nil {
49 - return err
50 - }
51 -
52 - if err = decode(dst, resp.Body); err != nil {
53 - return fmt.Errorf("error on parsing response from %s : %v", req.URL, err)
54 - }
55 -
56 - return nil
57 -}
58 -
59 -func (c *client) doOK(req *http.Request) (*http.Response, error) {
60 - resp, err := c.httpClient.Do(req)
61 - if err != nil {
62 - return resp, fmt.Errorf("error on request : %v", err)
63 - }
64 -
65 - if resp.StatusCode != http.StatusOK {
66 - return resp, fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode)
67 - }
68 -
69 - return resp, err
70 -}
src/go/plugin/go.d/modules/phpdaemon/collect.go
+48 -5
@@ -2,18 +2,61 @@
2
3 package phpdaemon
4
5 -import "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm"
5 +import (
6 + "fmt"
7
7 -func (p *PHPDaemon) collect() (map[string]int64, error) {
8 - s, err := p.client.queryFullStatus()
8 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm"
9 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
10 +)
11 +
12 +// https://github.com/kakserpom/phpdaemon/blob/master/PHPDaemon/Core/Daemon.php
13 +// see getStateOfWorkers()
14 +
15 +type fullStatus struct {
16 + // Alive is sum of Idle, Busy and Reloading
17 + Alive int64 `json:"alive" stm:"alive"`
18 + Shutdown int64 `json:"shutdown" stm:"shutdown"`
19 +
20 + // Idle that the worker is not in the middle of execution valuable callback (e.g. request) at this moment of time.
21 + // It does not mean that worker not have any pending operations.
22 + // Idle is sum of Preinit, Init and Initialized.
23 + Idle int64 `json:"idle" stm:"idle"`
24 + // Busy means that the worker is in the middle of execution valuable callback.
25 + Busy int64 `json:"busy" stm:"busy"`
26 + Reloading int64 `json:"reloading" stm:"reloading"`
27 +
28 + Preinit int64 `json:"preinit" stm:"preinit"`
29 + // Init means that worker is starting right now.
30 + Init int64 `json:"init" stm:"init"`
31 + // Initialized means that the worker is in Idle state.
32 + Initialized int64 `json:"initialized" stm:"initialized"`
33 +
34 + Uptime *int64 `json:"uptime" stm:"uptime"`
35 +}
36
37 +func (p *PHPDaemon) collect() (map[string]int64, error) {
38 + req, err := web.NewHTTPRequest(p.RequestConfig)
39 if err != nil {
40 + return nil, fmt.Errorf("failed to create HTTP request to '%s': %w", p.URL, err)
41 + }
42 +
43 + var st fullStatus
44 +
45 + if err := web.DoHTTP(p.httpClient).RequestJSON(req, &st); err != nil {
46 return nil, err
47 }
48
49 // https://github.com/kakserpom/phpdaemon/blob/master/PHPDaemon/Core/Daemon.php
50 // see getStateOfWorkers()
16 - s.Initialized = s.Idle - (s.Init + s.Preinit)
51 + st.Initialized = st.Idle - (st.Init + st.Preinit)
52 +
53 + mx := stm.ToMap(st)
54 +
55 + p.once.Do(func() {
56 + if _, ok := mx["uptime"]; ok {
57 + _ = p.charts.Add(uptimeChart.Copy())
58 + }
59 + })
60
18 - return stm.ToMap(s), nil
61 + return mx, nil
62 }
src/go/plugin/go.d/modules/phpdaemon/init.go deleted
-27
@@ -1,27 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package phpdaemon
4 -
5 -import (
6 - "errors"
7 -
8 - "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
9 -)
10 -
11 -func (p *PHPDaemon) validateConfig() error {
12 - if p.URL == "" {
13 - return errors.New("url not set")
14 - }
15 - if _, err := web.NewHTTPRequest(p.RequestConfig); err != nil {
16 - return err
17 - }
18 - return nil
19 -}
20 -
21 -func (p *PHPDaemon) initClient() (*client, error) {
22 - httpClient, err := web.NewHTTPClient(p.ClientConfig)
23 - if err != nil {
24 - return nil, err
25 - }
26 - return newAPIClient(httpClient, p.RequestConfig), nil
27 -}
src/go/plugin/go.d/modules/phpdaemon/metrics.go deleted
-33
@@ -1,33 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package phpdaemon
4 -
5 -// https://github.com/kakserpom/phpdaemon/blob/master/PHPDaemon/Core/Daemon.php
6 -// see getStateOfWorkers()
7 -
8 -// WorkerState represents phpdaemon worker state.
9 -type WorkerState struct {
10 - // Alive is sum of Idle, Busy and Reloading
11 - Alive int64 `stm:"alive"`
12 - Shutdown int64 `stm:"shutdown"`
13 -
14 - // Idle that the worker is not in the middle of execution valuable callback (e.g. request) at this moment of time.
15 - // It does not mean that worker not have any pending operations.
16 - // Idle is sum of Preinit, Init and Initialized.
17 - Idle int64 `stm:"idle"`
18 - // Busy means that the worker is in the middle of execution valuable callback.
19 - Busy int64 `stm:"busy"`
20 - Reloading int64 `stm:"reloading"`
21 -
22 - Preinit int64 `stm:"preinit"`
23 - // Init means that worker is starting right now.
24 - Init int64 `stm:"init"`
25 - // Initialized means that the worker is in Idle state.
26 - Initialized int64 `stm:"initialized"`
27 -}
28 -
29 -// FullStatus FullStatus.
30 -type FullStatus struct {
31 - WorkerState `stm:""`
32 - Uptime *int64 `stm:"uptime"`
33 -}
src/go/plugin/go.d/modules/phpdaemon/phpdaemon.go
+13 -16
@@ -5,6 +5,9 @@ package phpdaemon
5 import (
6 _ "embed"
7 "errors"
8 + "fmt"
9 + "net/http"
10 + "sync"
11 "time"
12
13 "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
@@ -49,8 +52,9 @@ type PHPDaemon struct {
52 Config `yaml:",inline" json:""`
53
54 charts *Charts
55 + once sync.Once
56
53 - client *client
57 + httpClient *http.Client
58 }
59
60 func (p *PHPDaemon) Configuration() any {
@@ -58,17 +62,15 @@ func (p *PHPDaemon) Configuration() any {
62 }
63
64 func (p *PHPDaemon) Init() error {
61 - if err := p.validateConfig(); err != nil {
62 - p.Error(err)
63 - return err
65 + if p.URL == "" {
66 + return errors.New("phpDaemon URL is required but not set")
67 }
68
66 - c, err := p.initClient()
69 + httpClient, err := web.NewHTTPClient(p.ClientConfig)
70 if err != nil {
68 - p.Error(err)
69 - return err
71 + return fmt.Errorf("failed to initialize http client: %w", err)
72 }
71 - p.client = c
73 + p.httpClient = httpClient
74
75 p.Debugf("using URL %s", p.URL)
76 p.Debugf("using timeout: %s", p.Timeout)
@@ -79,17 +81,13 @@ func (p *PHPDaemon) Init() error {
81 func (p *PHPDaemon) Check() error {
82 mx, err := p.collect()
83 if err != nil {
82 - p.Error(err)
84 return err
85 }
86 +
87 if len(mx) == 0 {
88 return errors.New("no metrics collected")
89 }
90
89 - if _, ok := mx["uptime"]; ok {
90 - _ = p.charts.Add(uptimeChart.Copy())
91 - }
92 -
91 return nil
92 }
93
@@ -99,7 +97,6 @@ func (p *PHPDaemon) Charts() *Charts {
97
98 func (p *PHPDaemon) Collect() map[string]int64 {
99 mx, err := p.collect()
102 -
100 if err != nil {
101 p.Error(err)
102 return nil
@@ -109,7 +106,7 @@ func (p *PHPDaemon) Collect() map[string]int64 {
106 }
107
108 func (p *PHPDaemon) Cleanup() {
112 - if p.client != nil && p.client.httpClient != nil {
113 - p.client.httpClient.CloseIdleConnections()
109 + if p.httpClient != nil {
110 + p.httpClient.CloseIdleConnections()
111 }
112 }
src/go/plugin/go.d/modules/phpdaemon/phpdaemon_test.go
-1
@@ -39,7 +39,6 @@ func TestPHPDaemon_Init(t *testing.T) {
39 job := New()
40
41 require.NoError(t, job.Init())
42 - assert.NotNil(t, job.client)
42 }
43
44 func TestPHPDaemon_Check(t *testing.T) {
src/go/plugin/go.d/modules/phpfpm/client.go
+6 -16
@@ -77,25 +77,15 @@ func newHTTPClient(c *http.Client, r web.RequestConfig) (*httpClient, error) {
77 func (c *httpClient) getStatus() (*status, error) {
78 req, err := web.NewHTTPRequest(c.req)
79 if err != nil {
80 - return nil, fmt.Errorf("error on creating HTTP request: %v", err)
81 - }
82 -
83 - resp, err := c.client.Do(req)
84 - if err != nil {
85 - return nil, fmt.Errorf("error on HTTP request to '%s': %v", req.URL, err)
86 - }
87 - defer func() {
88 - _, _ = io.Copy(io.Discard, resp.Body)
89 - _ = resp.Body.Close()
90 - }()
91 -
92 - if resp.StatusCode != http.StatusOK {
93 - return nil, fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode)
80 + return nil, fmt.Errorf("failed to create HTTP request: %v", err)
81 }
82
83 st := &status{}
97 - if err := c.dec(resp.Body, st); err != nil {
98 - return nil, fmt.Errorf("error parsing HTTP response from '%s': %v", req.URL, err)
84 +
85 + if err := web.DoHTTP(c.client).Request(req, func(body io.Reader) error {
86 + return c.dec(body, st)
87 + }); err != nil {
88 + return nil, err
89 }
90
91 return st, nil
src/go/plugin/go.d/modules/pihole/collect.go
+20 -28
@@ -4,6 +4,7 @@ package pihole
4
5 import (
6 "encoding/json"
7 + "errors"
8 "fmt"
9 "io"
10 "net/http"
@@ -143,7 +144,7 @@ func (p *Pihole) querySummary(pmx *piholeMetrics) {
144 }.Encode()
145
146 var v summaryRawMetrics
146 - if err = p.doWithDecode(&v, req); err != nil {
147 + if err = p.doHTTP(req, &v); err != nil {
148 p.Error(err)
149 return
150 }
@@ -164,7 +165,7 @@ func (p *Pihole) queryQueryTypes(pmx *piholeMetrics) {
165 }.Encode()
166
167 var v queryTypesMetrics
167 - err = p.doWithDecode(&v, req)
168 + err = p.doHTTP(req, &v)
169 if err != nil {
170 p.Error(err)
171 return
@@ -186,7 +187,7 @@ func (p *Pihole) queryForwardedDestinations(pmx *piholeMetrics) {
187 }.Encode()
188
189 var v forwardDestinations
189 - err = p.doWithDecode(&v, req)
190 + err = p.doHTTP(req, &v)
191 if err != nil {
192 p.Error(err)
193 return
@@ -207,7 +208,7 @@ func (p *Pihole) queryAPIVersion() (int, error) {
208 }.Encode()
209
210 var v piholeAPIVersion
210 - err = p.doWithDecode(&v, req)
211 + err = p.doHTTP(req, &v)
212 if err != nil {
213 return 0, err
214 }
@@ -215,33 +216,24 @@ func (p *Pihole) queryAPIVersion() (int, error) {
216 return v.Version, nil
217 }
218
218 -func (p *Pihole) doWithDecode(dst interface{}, req *http.Request) error {
219 - resp, err := p.httpClient.Do(req)
220 - if err != nil {
221 - return err
222 - }
223 -
224 - defer web.CloseBody(resp)
225 -
226 - if resp.StatusCode != http.StatusOK {
227 - return fmt.Errorf("%s returned %d status code", req.URL, resp.StatusCode)
228 - }
229 -
230 - content, err := io.ReadAll(resp.Body)
231 - if err != nil {
232 - return fmt.Errorf("error on reading response from %s : %v", req.URL, err)
233 - }
219 +func (p *Pihole) doHTTP(req *http.Request, dst any) error {
220 + return web.DoHTTP(p.httpClient).Request(req, func(body io.Reader) error {
221 + content, err := io.ReadAll(body)
222 + if err != nil {
223 + return fmt.Errorf("failed to read response: %v", err)
224 + }
225
235 - // empty array if unauthorized query or wrong query
236 - if isEmptyArray(content) {
237 - return fmt.Errorf("unauthorized access to %s", req.URL)
238 - }
226 + // empty array if unauthorized query or wrong query
227 + if isEmptyArray(content) {
228 + return errors.New("unauthorized access")
229 + }
230
240 - if err := json.Unmarshal(content, dst); err != nil {
241 - return fmt.Errorf("error on parsing response from %s : %v", req.URL, err)
242 - }
231 + if err := json.Unmarshal(content, dst); err != nil {
232 + return fmt.Errorf("failed to decode JSON response: %v", err)
233 + }
234
244 - return nil
235 + return nil
236 + })
237 }
238
239 func isEmptyArray(data []byte) bool {
src/go/plugin/go.d/modules/powerdns/collect.go
+3 -24
@@ -3,10 +3,7 @@
3 package powerdns
4
5 import (
6 - "encoding/json"
6 "errors"
8 - "fmt"
9 - "net/http"
7 "strconv"
8
9 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
@@ -66,28 +63,10 @@ func (ns *AuthoritativeNS) collectStatistics(collected map[string]int64, statist
63 func (ns *AuthoritativeNS) scrapeStatistics() ([]statisticMetric, error) {
64 req, _ := web.NewHTTPRequestWithPath(ns.RequestConfig, urlPathLocalStatistics)
65
69 - var statistics statisticMetrics
70 - if err := ns.doOKDecode(req, &statistics); err != nil {
66 + var stats statisticMetrics
67 + if err := web.DoHTTP(ns.httpClient).RequestJSON(req, &stats); err != nil {
68 return nil, err
69 }
70
74 - return statistics, nil
75 -}
76 -
77 -func (ns *AuthoritativeNS) doOKDecode(req *http.Request, in interface{}) error {
78 - resp, err := ns.httpClient.Do(req)
79 - if err != nil {
80 - return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
81 - }
82 -
83 - defer web.CloseBody(resp)
84 -
85 - if resp.StatusCode != http.StatusOK {
86 - return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
87 - }
88 -
89 - if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
90 - return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
91 - }
92 - return nil
71 + return stats, nil
72 }
src/go/plugin/go.d/modules/powerdns_recursor/collect.go
+3 -24
@@ -3,10 +3,7 @@
3 package powerdns_recursor
4
5 import (
6 - "encoding/json"
6 "errors"
8 - "fmt"
9 - "net/http"
7 "strconv"
8
9 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
@@ -66,28 +63,10 @@ func (r *Recursor) collectStatistics(collected map[string]int64, statistics stat
63 func (r *Recursor) scrapeStatistics() ([]statisticMetric, error) {
64 req, _ := web.NewHTTPRequestWithPath(r.RequestConfig, urlPathLocalStatistics)
65
69 - var statistics statisticMetrics
70 - if err := r.doOKDecode(req, &statistics); err != nil {
66 + var stats statisticMetrics
67 + if err := web.DoHTTP(r.httpClient).RequestJSON(req, &stats); err != nil {
68 return nil, err
69 }
70
74 - return statistics, nil
75 -}
76 -
77 -func (r *Recursor) doOKDecode(req *http.Request, in interface{}) error {
78 - resp, err := r.httpClient.Do(req)
79 - if err != nil {
80 - return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
81 - }
82 -
83 - defer web.CloseBody(resp)
84 -
85 - if resp.StatusCode != http.StatusOK {
86 - return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
87 - }
88 -
89 - if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
90 - return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
91 - }
92 - return nil
71 + return stats, nil
72 }
src/go/plugin/go.d/modules/puppet/collect.go
+1 -21
@@ -3,9 +3,7 @@
3 package puppet
4
5 import (
6 - "encoding/json"
6 "fmt"
8 - "net/http"
7 "net/url"
8
9 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm"
@@ -38,7 +36,7 @@ func (p *Puppet) queryStatsService() (*statusServiceResponse, error) {
36 req.URL.RawQuery = urlQueryStatusService
37
38 var stats statusServiceResponse
41 - if err := p.doOKDecode(req, &stats); err != nil {
39 + if err := web.DoHTTP(p.httpClient).RequestJSON(req, &stats); err != nil {
40 return nil, err
41 }
42
@@ -48,21 +46,3 @@ func (p *Puppet) queryStatsService() (*statusServiceResponse, error) {
46
47 return &stats, nil
48 }
51 -
52 -func (p *Puppet) doOKDecode(req *http.Request, in interface{}) error {
53 - resp, err := p.httpClient.Do(req)
54 - if err != nil {
55 - return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
56 - }
57 -
58 - defer web.CloseBody(resp)
59 -
60 - if resp.StatusCode != http.StatusOK {
61 - return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
62 - }
63 -
64 - if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
65 - return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
66 - }
67 - return nil
68 -}
src/go/plugin/go.d/modules/rabbitmq/collect.go
+24 -31
@@ -3,9 +3,7 @@
3 package rabbitmq
4
5 import (
6 - "encoding/json"
6 "fmt"
8 - "net/http"
7 "path/filepath"
8
9 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm"
@@ -43,8 +41,13 @@ func (r *RabbitMQ) collect() (map[string]int64, error) {
41 }
42
43 func (r *RabbitMQ) collectOverviewStats(mx map[string]int64) error {
44 + req, err := web.NewHTTPRequestWithPath(r.RequestConfig, urlPathAPIOverview)
45 + if err != nil {
46 + return fmt.Errorf("failed to create overview stats request: %w", err)
47 + }
48 +
49 var stats overviewStats
47 - if err := r.doOKDecode(urlPathAPIOverview, &stats); err != nil {
50 + if err := web.DoHTTP(r.httpClient).RequestJSON(req, &stats); err != nil {
51 return err
52 }
53
@@ -64,8 +67,13 @@ func (r *RabbitMQ) collectNodeStats(mx map[string]int64) error {
67 return nil
68 }
69
70 + req, err := web.NewHTTPRequestWithPath(r.RequestConfig, filepath.Join(urlPathAPINodes, r.nodeName))
71 + if err != nil {
72 + return fmt.Errorf("failed to create node stats request: %w", err)
73 + }
74 +
75 var stats nodeStats
68 - if err := r.doOKDecode(filepath.Join(urlPathAPINodes, r.nodeName), &stats); err != nil {
76 + if err := web.DoHTTP(r.httpClient).RequestJSON(req, &stats); err != nil {
77 return err
78 }
79
@@ -78,8 +86,13 @@ func (r *RabbitMQ) collectNodeStats(mx map[string]int64) error {
86 }
87
88 func (r *RabbitMQ) collectVhostsStats(mx map[string]int64) error {
89 + req, err := web.NewHTTPRequestWithPath(r.RequestConfig, urlPathAPIVhosts)
90 + if err != nil {
91 + return fmt.Errorf("failed to create vhosts stats request: %w", err)
92 + }
93 +
94 var stats []vhostStats
82 - if err := r.doOKDecode(urlPathAPIVhosts, &stats); err != nil {
95 + if err := web.DoHTTP(r.httpClient).RequestJSON(req, &stats); err != nil {
96 return err
97 }
98
@@ -111,8 +124,13 @@ func (r *RabbitMQ) collectVhostsStats(mx map[string]int64) error {
124 }
125
126 func (r *RabbitMQ) collectQueuesStats(mx map[string]int64) error {
127 + req, err := web.NewHTTPRequestWithPath(r.RequestConfig, urlPathAPIQueues)
128 + if err != nil {
129 + return fmt.Errorf("failed to create queues stats request: %w", err)
130 + }
131 +
132 var stats []queueStats
115 - if err := r.doOKDecode(urlPathAPIQueues, &stats); err != nil {
133 + if err := web.DoHTTP(r.httpClient).RequestJSON(req, &stats); err != nil {
134 return err
135 }
136
@@ -142,28 +160,3 @@ func (r *RabbitMQ) collectQueuesStats(mx map[string]int64) error {
160
161 return nil
162 }
145 -
146 -func (r *RabbitMQ) doOKDecode(urlPath string, in interface{}) error {
147 - req, err := web.NewHTTPRequestWithPath(r.RequestConfig, urlPath)
148 - if err != nil {
149 - return fmt.Errorf("error on creating request: %v", err)
150 - }
151 -
152 - r.Debugf("doing HTTPConfig %s to '%s'", req.Method, req.URL)
153 - resp, err := r.httpClient.Do(req)
154 - if err != nil {
155 - return fmt.Errorf("error on request to %s: %v", req.URL, err)
156 - }
157 -
158 - defer web.CloseBody(resp)
159 -
160 - if resp.StatusCode != http.StatusOK {
161 - return fmt.Errorf("%s returned HTTP status %d (%s)", req.URL, resp.StatusCode, resp.Status)
162 - }
163 -
164 - if err = json.NewDecoder(resp.Body).Decode(&in); err != nil {
165 - return fmt.Errorf("error on decoding response from %s: %v", req.URL, err)
166 - }
167 -
168 - return nil
169 -}
src/go/plugin/go.d/modules/riakkv/collect.go
+6 -22
@@ -3,9 +3,7 @@
3 package riakkv
4
5 import (
6 - "encoding/json"
6 "errors"
8 - "fmt"
7 "net/http"
8
9 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm"
@@ -36,32 +34,18 @@ func (r *RiakKv) getStats() (*riakStats, error) {
34 }
35
36 var stats riakStats
39 - if err := r.doOKDecode(req, &stats); err != nil {
37 + if err := r.client().RequestJSON(req, &stats); err != nil {
38 return nil, err
39 }
40
41 return &stats, nil
42 }
43
46 -func (r *RiakKv) doOKDecode(req *http.Request, in interface{}) error {
47 - resp, err := r.httpClient.Do(req)
48 - if err != nil {
49 - return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
50 - }
51 -
52 - defer web.CloseBody(resp)
53 -
54 - if resp.StatusCode != http.StatusOK {
55 - msg := fmt.Sprintf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
44 +func (r *RiakKv) client() *web.Client {
45 + return web.DoHTTP(r.httpClient).OnNokCode(func(resp *http.Response) (bool, error) {
46 if resp.StatusCode == http.StatusNotFound {
57 - msg = fmt.Sprintf("%s (riak_kv_stat is not enabled)", msg)
47 + return false, errors.New("riak_kv_stat is not enabled)")
48 }
59 - return errors.New(msg)
60 - }
61 -
62 - if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
63 - return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
64 - }
65 -
66 - return nil
49 + return false, nil
50 + })
51 }
src/go/plugin/go.d/modules/rspamd/collect.go
+1 -21
@@ -3,9 +3,7 @@
3 package rspamd
4
5 import (
6 - "encoding/json"
6 "fmt"
8 - "net/http"
7
8 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm"
9 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
@@ -55,7 +53,7 @@ func (r *Rspamd) queryRspamdStats() (*rspamdStats, error) {
53 }
54
55 var stats rspamdStats
58 - if err := r.doOKDecode(req, &stats); err != nil {
56 + if err := web.DoHTTP(r.httpClient).RequestJSON(req, &stats); err != nil {
57 return nil, err
58 }
59
@@ -65,21 +63,3 @@ func (r *Rspamd) queryRspamdStats() (*rspamdStats, error) {
63
64 return &stats, nil
65 }
68 -
69 -func (r *Rspamd) doOKDecode(req *http.Request, in interface{}) error {
70 - resp, err := r.httpClient.Do(req)
71 - if err != nil {
72 - return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
73 - }
74 -
75 - defer web.CloseBody(resp)
76 -
77 - if resp.StatusCode != http.StatusOK {
78 - return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
79 - }
80 -
81 - if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
82 - return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
83 - }
84 - return nil
85 -}
src/go/plugin/go.d/modules/squid/collect.go
+7 -27
@@ -6,7 +6,6 @@ import (
6 "bufio"
7 "fmt"
8 "io"
9 - "net/http"
9 "strconv"
10 "strings"
11
@@ -44,10 +43,10 @@ func (s *Squid) collect() (map[string]int64, error) {
43 func (s *Squid) collectCounters(mx map[string]int64) error {
44 req, err := web.NewHTTPRequestWithPath(s.RequestConfig, urlPathServerStats)
45 if err != nil {
47 - return err
46 + return fmt.Errorf("failed to create '%s' request: %w", urlPathServerStats, err)
47 }
48
50 - if err := s.doOK(req, func(body io.Reader) error {
49 + return web.DoHTTP(s.httpClient).Request(req, func(body io.Reader) error {
50 sc := bufio.NewScanner(body)
51
52 for sc.Scan() {
@@ -70,29 +69,10 @@ func (s *Squid) collectCounters(mx map[string]int64) error {
69
70 mx[key] = v
71 }
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 - }
72
91 - defer web.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)
73 + if len(mx) == 0 {
74 + return fmt.Errorf("unexpected response from '%s': no metrics found", req.URL)
75 + }
76 + return nil
77 + })
78 }
src/go/plugin/go.d/modules/tengine/collect.go
+19 -1
@@ -3,20 +3,38 @@
3 package tengine
4
5 import (
6 + "fmt"
7 + "io"
8 +
9 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm"
10 + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
11 )
12
13 func (t *Tengine) collect() (map[string]int64, error) {
10 - status, err := t.apiClient.getStatus()
14 + req, err := web.NewHTTPRequest(t.RequestConfig)
15 if err != nil {
16 + return nil, fmt.Errorf("failed to create HTTP request: %w", err)
17 + }
18 +
19 + var status *tengineStatus
20 + var perr error
21 +
22 + if err := web.DoHTTP(t.httpClient).Request(req, func(body io.Reader) error {
23 + if status, perr = parseStatus(body); perr != nil {
24 + return perr
25 + }
26 + return nil
27 + }); err != nil {
28 return nil, err
29 }
30
31 mx := make(map[string]int64)
32 +
33 for _, m := range *status {
34 for k, v := range stm.ToMap(m) {
35 mx[k] += v
36 }
37 }
38 +
39 return mx, nil
40 }
src/go/plugin/go.d/modules/tengine/metrics.go deleted
-75
@@ -1,75 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package tengine
4 -
5 -/*
6 -http://tengine.taobao.org/document/http_reqstat.html
7 -
8 -bytes_in total number of bytes received from client
9 -bytes_out total number of bytes sent to client
10 -conn_total total number of accepted connections
11 -req_total total number of processed requests
12 -http_2xx total number of 2xx requests
13 -http_3xx total number of 3xx requests
14 -http_4xx total number of 4xx requests
15 -http_5xx total number of 5xx requests
16 -http_other_status total number of other requests
17 -rt accumulation or rt
18 -ups_req total number of requests calling for upstream
19 -ups_rt accumulation or upstream rt
20 -ups_tries total number of times calling for upstream
21 -http_200 total number of 200 requests
22 -http_206 total number of 206 requests
23 -http_302 total number of 302 requests
24 -http_304 total number of 304 requests
25 -http_403 total number of 403 requests
26 -http_404 total number of 404 requests
27 -http_416 total number of 416 requests
28 -http_499 total number of 499 requests
29 -http_500 total number of 500 requests
30 -http_502 total number of 502 requests
31 -http_503 total number of 503 requests
32 -http_504 total number of 504 requests
33 -http_508 total number of 508 requests
34 -http_other_detail_status total number of requests of other status codes
35 -http_ups_4xx total number of requests of upstream 4xx
36 -http_ups_5xx total number of requests of upstream 5xx
37 -*/
38 -
39 -type (
40 - tengineStatus []metric
41 -
42 - metric struct {
43 - Host string
44 - ServerAddress string
45 - BytesIn *int64 `stm:"bytes_in"`
46 - BytesOut *int64 `stm:"bytes_out"`
47 - ConnTotal *int64 `stm:"conn_total"`
48 - ReqTotal *int64 `stm:"req_total"`
49 - HTTP2xx *int64 `stm:"http_2xx"`
50 - HTTP3xx *int64 `stm:"http_3xx"`
51 - HTTP4xx *int64 `stm:"http_4xx"`
52 - HTTP5xx *int64 `stm:"http_5xx"`
53 - HTTPOtherStatus *int64 `stm:"http_other_status"`
54 - RT *int64 `stm:"rt"`
55 - UpsReq *int64 `stm:"ups_req"`
56 - UpsRT *int64 `stm:"ups_rt"`
57 - UpsTries *int64 `stm:"ups_tries"`
58 - HTTP200 *int64 `stm:"http_200"`
59 - HTTP206 *int64 `stm:"http_206"`
60 - HTTP302 *int64 `stm:"http_302"`
61 - HTTP304 *int64 `stm:"http_304"`
62 - HTTP403 *int64 `stm:"http_403"`
63 - HTTP404 *int64 `stm:"http_404"`
64 - HTTP416 *int64 `stm:"http_416"`
65 - HTTP499 *int64 `stm:"http_499"`
66 - HTTP500 *int64 `stm:"http_500"`
67 - HTTP502 *int64 `stm:"http_502"`
68 - HTTP503 *int64 `stm:"http_503"`
69 - HTTP504 *int64 `stm:"http_504"`
70 - HTTP508 *int64 `stm:"http_508"`
71 - HTTPOtherDetailStatus *int64 `stm:"http_other_detail_status"`
72 - HTTPUps4xx *int64 `stm:"http_ups_4xx"`
73 - HTTPUps5xx *int64 `stm:"http_ups_5xx"`
74 - }
75 -)
src/go/plugin/go.d/modules/tengine/status.go renamed
+71 -42
@@ -6,11 +6,80 @@ import (
6 "bufio"
7 "fmt"
8 "io"
9 - "net/http"
9 "strconv"
10 "strings"
11 +)
12 +
13 +/*
14 +http://tengine.taobao.org/document/http_reqstat.html
15 +
16 +bytes_in total number of bytes received from client
17 +bytes_out total number of bytes sent to client
18 +conn_total total number of accepted connections
19 +req_total total number of processed requests
20 +http_2xx total number of 2xx requests
21 +http_3xx total number of 3xx requests
22 +http_4xx total number of 4xx requests
23 +http_5xx total number of 5xx requests
24 +http_other_status total number of other requests
25 +rt accumulation or rt
26 +ups_req total number of requests calling for upstream
27 +ups_rt accumulation or upstream rt
28 +ups_tries total number of times calling for upstream
29 +http_200 total number of 200 requests
30 +http_206 total number of 206 requests
31 +http_302 total number of 302 requests
32 +http_304 total number of 304 requests
33 +http_403 total number of 403 requests
34 +http_404 total number of 404 requests
35 +http_416 total number of 416 requests
36 +http_499 total number of 499 requests
37 +http_500 total number of 500 requests
38 +http_502 total number of 502 requests
39 +http_503 total number of 503 requests
40 +http_504 total number of 504 requests
41 +http_508 total number of 508 requests
42 +http_other_detail_status total number of requests of other status codes
43 +http_ups_4xx total number of requests of upstream 4xx
44 +http_ups_5xx total number of requests of upstream 5xx
45 +*/
46 +
47 +type (
48 + tengineStatus []metric
49
13 - "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web"
50 + metric struct {
51 + Host string
52 + ServerAddress string
53 + BytesIn *int64 `stm:"bytes_in"`
54 + BytesOut *int64 `stm:"bytes_out"`
55 + ConnTotal *int64 `stm:"conn_total"`
56 + ReqTotal *int64 `stm:"req_total"`
57 + HTTP2xx *int64 `stm:"http_2xx"`
58 + HTTP3xx *int64 `stm:"http_3xx"`
59 + HTTP4xx *int64 `stm:"http_4xx"`
60 + HTTP5xx *int64 `stm:"http_5xx"`
61 + HTTPOtherStatus *int64 `stm:"http_other_status"`
62 + RT *int64 `stm:"rt"`
63 + UpsReq *int64 `stm:"ups_req"`
64 + UpsRT *int64 `stm:"ups_rt"`
65 + UpsTries *int64 `stm:"ups_tries"`
66 + HTTP200 *int64 `stm:"http_200"`
67 + HTTP206 *int64 `stm:"http_206"`
68 + HTTP302 *int64 `stm:"http_302"`
69 + HTTP304 *int64 `stm:"http_304"`
70 + HTTP403 *int64 `stm:"http_403"`
71 + HTTP404 *int64 `stm:"http_404"`
72 + HTTP416 *int64 `stm:"http_416"`
73 + HTTP499 *int64 `stm:"http_499"`
74 + HTTP500 *int64 `stm:"http_500"`
75 + HTTP502 *int64 `stm:"http_502"`
76 + HTTP503 *int64 `stm:"http_503"`
77 + HTTP504 *int64 `stm:"http_504"`
78 + HTTP508 *int64 `stm:"http_508"`
79 + HTTPOtherDetailStatus *int64 `stm:"http_other_detail_status"`
80 + HTTPUps4xx *int64 `stm:"http_ups_4xx"`
81 + HTTPUps5xx *int64 `stm:"http_ups_5xx"`
82 + }
83 )
84
85 const (
@@ -77,46 +146,6 @@ var defaultLineFormat = []string{
146 httpUps5xx,
147 }
148
80 -func newAPIClient(client *http.Client, request web.RequestConfig) *apiClient {
81 - return &apiClient{httpClient: client, request: request}
82 -}
83 -
84 -type apiClient struct {
85 - httpClient *http.Client
86 - request web.RequestConfig
87 -}
88 -
89 -func (a apiClient) getStatus() (*tengineStatus, error) {
90 - req, err := web.NewHTTPRequest(a.request)
91 - if err != nil {
92 - return nil, fmt.Errorf("error on creating request : %v", err)
93 - }
94 -
95 - resp, err := a.doRequestOK(req)
96 - defer web.CloseBody(resp)
97 - if err != nil {
98 - return nil, err
99 - }
100 -
101 - status, err := parseStatus(resp.Body)
102 - if err != nil {
103 - return nil, fmt.Errorf("error on parsing response : %v", err)
104 - }
105 -
106 - return status, nil
107 -}
108 -
109 -func (a apiClient) doRequestOK(req *http.Request) (*http.Response, error) {
110 - resp, err := a.httpClient.Do(req)
111 - if err != nil {
112 - return nil, fmt.Errorf("error on request : %v", err)
113 - }
114 - if resp.StatusCode != http.StatusOK {
115 - return resp, fmt.Errorf("%s returned HTTPConfig code %d", req.URL, resp.StatusCode)
116 - }
117 - return resp, nil
118 -}
119 -
149 func parseStatus(r io.Reader) (*tengineStatus, error) {
150 var status tengineStatus
151
src/go/plugin/go.d/modules/tengine/tengine.go
+12 -7
@@ -5,6 +5,7 @@ package tengine
5 import (
6 _ "embed"
7 "errors"
8 + "net/http"
9 "time"
10
11 "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
@@ -50,7 +51,7 @@ type Tengine struct {
51
52 charts *module.Charts
53
53 - apiClient *apiClient
54 + httpClient *http.Client
55 }
56
57 func (t *Tengine) Configuration() any {
@@ -63,13 +64,12 @@ func (t *Tengine) Init() error {
64 return errors.New("url not set")
65 }
66
66 - client, err := web.NewHTTPClient(t.ClientConfig)
67 + httpClient, err := web.NewHTTPClient(t.ClientConfig)
68 if err != nil {
69 t.Errorf("error on creating http client : %v", err)
70 return err
71 }
71 -
72 - t.apiClient = newAPIClient(client, t.RequestConfig)
72 + t.httpClient = httpClient
73
74 t.Debugf("using URL: %s", t.URL)
75 t.Debugf("using timeout: %s", t.Timeout)
@@ -83,9 +83,11 @@ func (t *Tengine) Check() error {
83 t.Error(err)
84 return err
85 }
86 +
87 if len(mx) == 0 {
88 return errors.New("no metrics collected")
89 }
90 +
91 return nil
92 }
93
@@ -95,17 +97,20 @@ func (t *Tengine) Charts() *module.Charts {
97
98 func (t *Tengine) Collect() map[string]int64 {
99 mx, err := t.collect()
98 -
100 if err != nil {
101 t.Error(err)
102 return nil
103 }
104
105 + if len(mx) == 0 {
106 + return nil
107 + }
108 +
109 return mx
110 }
111
112 func (t *Tengine) Cleanup() {
108 - if t.apiClient != nil && t.apiClient.httpClient != nil {
109 - t.apiClient.httpClient.CloseIdleConnections()
113 + if t.httpClient != nil {
114 + t.httpClient.CloseIdleConnections()
115 }
116 }
src/go/plugin/go.d/modules/tengine/tengine_test.go
-1
@@ -43,7 +43,6 @@ func TestTengine_Init(t *testing.T) {
43 job := New()
44
45 require.NoError(t, job.Init())
46 - assert.NotNil(t, job.apiClient)
46 }
47
48 func TestTengine_Check(t *testing.T) {
src/go/plugin/go.d/modules/tomcat/collect.go
+1 -23
@@ -3,10 +3,7 @@
3 package tomcat
4
5 import (
6 - "encoding/xml"
6 "errors"
8 - "fmt"
9 - "net/http"
7 "net/url"
8 "strings"
9
@@ -95,28 +92,9 @@ func (t *Tomcat) queryServerStatus() (*serverStatusResponse, error) {
92 req.URL.RawQuery = urlQueryServerStatus
93
94 var status serverStatusResponse
98 -
99 - if err := t.doOKDecode(req, &status); err != nil {
95 + if err := web.DoHTTP(t.httpClient).RequestXML(req, &status); err != nil {
96 return nil, err
97 }
98
99 return &status, nil
100 }
105 -
106 -func (t *Tomcat) doOKDecode(req *http.Request, in interface{}) error {
107 - resp, err := t.httpClient.Do(req)
108 - if err != nil {
109 - return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
110 - }
111 - defer web.CloseBody(resp)
112 -
113 - if resp.StatusCode != http.StatusOK {
114 - return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
115 - }
116 -
117 - if err := xml.NewDecoder(resp.Body).Decode(in); err != nil {
118 - return fmt.Errorf("error decoding XML response from '%s': %v", req.URL, err)
119 - }
120 -
121 - return nil
122 -}
src/go/plugin/go.d/modules/typesense/collect.go
+8 -25
@@ -59,7 +59,7 @@ func (ts *Typesense) collectHealth(mx map[string]int64) error {
59 }
60
61 var resp healthResponse
62 - if err := ts.doOKDecode(req, &resp); err != nil {
62 + if err := ts.client().RequestJSON(req, &resp); err != nil {
63 return err
64 }
65
@@ -95,8 +95,8 @@ func (ts *Typesense) collectStats(mx map[string]int64) error {
95 req.Header.Set("X-TYPESENSE-API-KEY", ts.APIKey)
96
97 var resp statsResponse
98 - if err := ts.doOKDecode(req, &resp); err != nil {
99 - if !isStatusUnauthorized(err) {
98 + if err := ts.client().RequestJSON(req, &resp); err != nil {
99 + if !strings.Contains(err.Error(), "code: 401") {
100 return err
101 }
102
@@ -115,32 +115,15 @@ func (ts *Typesense) collectStats(mx map[string]int64) error {
115 return nil
116 }
117
118 -func (ts *Typesense) doOKDecode(req *http.Request, in interface{}) error {
119 - resp, err := ts.httpClient.Do(req)
120 - if err != nil {
121 - return fmt.Errorf("error on HTTP request '%s': %v", req.URL, err)
122 - }
123 -
124 - defer web.CloseBody(resp)
125 -
126 - if resp.StatusCode != http.StatusOK {
118 +func (ts *Typesense) client() *web.Client {
119 + return web.DoHTTP(ts.httpClient).OnNokCode(func(resp *http.Response) (bool, error) {
120 // {"message": "Forbidden - a valid `x-typesense-api-key` header must be sent."}
121 var msg struct {
122 Msg string `json:"message"`
123 }
124 if err := json.NewDecoder(resp.Body).Decode(&msg); err == nil && msg.Msg != "" {
132 - return fmt.Errorf("'%s' returned HTTP status code: %d (msg: '%s')",
133 - req.URL, resp.StatusCode, msg.Msg)
125 + return false, fmt.Errorf("msg: '%s'", msg.Msg)
126 }
135 - return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
136 - }
137 -
138 - if err := json.NewDecoder(resp.Body).Decode(in); err != nil {
139 - return fmt.Errorf("error on decoding response from '%s': %v", req.URL, err)
140 - }
141 - return nil
142 -}
143 -
144 -func isStatusUnauthorized(err error) bool {
145 - return strings.Contains(err.Error(), "code: 401")
127 + return false, nil
128 + })
129 }
src/go/plugin/go.d/pkg/prometheus/client.go
+1 -4
@@ -123,10 +123,7 @@ func (p *prometheus) fetch(w io.Writer) error {
123 return err
124 }
125
126 - defer func() {
127 - _, _ = io.Copy(io.Discard, resp.Body)
128 - _ = resp.Body.Close()
129 - }()
126 + defer web.CloseBody(resp)
127
128 if resp.StatusCode != http.StatusOK {
129 return fmt.Errorf("server '%s' returned HTTP status code %d (%s)", req.URL, resp.StatusCode, resp.Status)
src/go/plugin/go.d/pkg/web/client.go new
+86
@@ -0,0 +1,86 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +package web
4 +
5 +import (
6 + "encoding/json"
7 + "encoding/xml"
8 + "fmt"
9 + "io"
10 + "net/http"
11 +)
12 +
13 +type Client struct {
14 + httpClient *http.Client
15 + onNokCode func(resp *http.Response) (bool, error)
16 +}
17 +
18 +func DoHTTP(cl *http.Client) *Client {
19 + return &Client{
20 + httpClient: cl,
21 + }
22 +}
23 +
24 +func (c *Client) OnNokCode(fn func(resp *http.Response) (bool, error)) *Client {
25 + c.onNokCode = fn
26 + return c
27 +}
28 +
29 +func (c *Client) RequestJSON(req *http.Request, in any) error {
30 + return c.Request(req, func(body io.Reader) error {
31 + return json.NewDecoder(body).Decode(in)
32 + })
33 +}
34 +
35 +func (c *Client) RequestXML(req *http.Request, in any, opts ...func(dec *xml.Decoder)) error {
36 + return c.Request(req, func(body io.Reader) error {
37 + dec := xml.NewDecoder(body)
38 + for _, opt := range opts {
39 + opt(dec)
40 + }
41 + return dec.Decode(in)
42 + })
43 +}
44 +
45 +func (c *Client) Request(req *http.Request, parse func(body io.Reader) error) error {
46 + resp, err := c.httpClient.Do(req)
47 + if err != nil {
48 + return fmt.Errorf("error on HTTP request to '%s': %w", req.URL, err)
49 + }
50 +
51 + defer CloseBody(resp)
52 +
53 + if resp.StatusCode != http.StatusOK {
54 + if err := c.handleNokCode(req, resp); err != nil {
55 + return err
56 + }
57 + }
58 +
59 + if parse != nil {
60 + if err := parse(resp.Body); err != nil {
61 + return fmt.Errorf("error on parsing response from '%s': %w", req.URL, err)
62 + }
63 + }
64 +
65 + return nil
66 +}
67 +
68 +func (c *Client) handleNokCode(req *http.Request, resp *http.Response) error {
69 + if c.onNokCode != nil {
70 + handled, err := c.onNokCode(resp)
71 + if err != nil {
72 + return fmt.Errorf("'%s' returned HTTP status code: %d (%w)", req.URL, resp.StatusCode, err)
73 + }
74 + if handled {
75 + return nil
76 + }
77 + }
78 + return fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode)
79 +}
80 +
81 +func CloseBody(resp *http.Response) {
82 + if resp != nil && resp.Body != nil {
83 + _, _ = io.Copy(io.Discard, resp.Body)
84 + _ = resp.Body.Close()
85 + }
86 +}
src/go/plugin/go.d/pkg/web/client_config.go
-8
@@ -5,7 +5,6 @@ package web
5 import (
6 "errors"
7 "fmt"
8 - "io"
8 "net"
9 "net/http"
10 "net/url"
@@ -66,13 +65,6 @@ func NewHTTPClient(cfg ClientConfig) (*http.Client, error) {
65 }, nil
66 }
67
69 -func CloseBody(resp *http.Response) {
70 - if resp != nil && resp.Body != nil {
71 - _, _ = io.Copy(io.Discard, resp.Body)
72 - _ = resp.Body.Close()
73 - }
74 -}
75 -
68 func redirectFunc(notFollowRedirect bool) func(req *http.Request, via []*http.Request) error {
69 if follow := !notFollowRedirect; follow {
70 return nil
src/go/plugin/go.d/pkg/web/config.go renamed