diff options
Diffstat (limited to '')
-rw-r--r-- | src/go/collectors/go.d.plugin/modules/nginxplus/nginx_http_api_query.go | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/nginxplus/nginx_http_api_query.go b/src/go/collectors/go.d.plugin/modules/nginxplus/nginx_http_api_query.go new file mode 100644 index 000000000..b05ce1d7b --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/nginxplus/nginx_http_api_query.go @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package nginxplus + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "sync" + + "github.com/netdata/netdata/go/go.d.plugin/pkg/web" +) + +const ( + urlPathAPIVersions = "/api/" + urlPathAPIEndpointsRoot = "/api/%d" + urlPathAPINginx = "/api/%d/nginx" + urlPathAPIEndpointsHTTP = "/api/%d/http" + urlPathAPIEndpointsStream = "/api/%d/stream" + urlPathAPIConnections = "/api/%d/connections" + urlPathAPISSL = "/api/%d/ssl" + urlPathAPIResolvers = "/api/%d/resolvers" + urlPathAPIHTTPRequests = "/api/%d/http/requests" + urlPathAPIHTTPServerZones = "/api/%d/http/server_zones" + urlPathAPIHTTPLocationZones = "/api/%d/http/location_zones" + urlPathAPIHTTPUpstreams = "/api/%d/http/upstreams" + urlPathAPIHTTPCaches = "/api/%d/http/caches" + urlPathAPIStreamServerZones = "/api/%d/stream/server_zones" + urlPathAPIStreamUpstreams = "/api/%d/stream/upstreams" +) + +type nginxMetrics struct { + info *nginxInfo + connections *nginxConnections + ssl *nginxSSL + httpRequests *nginxHTTPRequests + httpServerZones *nginxHTTPServerZones + httpLocationZones *nginxHTTPLocationZones + httpUpstreams *nginxHTTPUpstreams + httpCaches *nginxHTTPCaches + streamServerZones *nginxStreamServerZones + streamUpstreams *nginxStreamUpstreams + resolvers *nginxResolvers +} + +func (n *NginxPlus) queryAPIVersion() (int64, error) { + req, _ := web.NewHTTPRequest(n.Request.Copy()) + req.URL.Path = urlPathAPIVersions + + var versions nginxAPIVersions + if err := n.doWithDecode(&versions, req); err != nil { + return 0, err + } + + if len(versions) == 0 { + return 0, fmt.Errorf("'%s' returned no data", req.URL) + } + + return versions[len(versions)-1], nil +} + +func (n *NginxPlus) queryAvailableEndpoints() error { + req, _ := web.NewHTTPRequest(n.Request.Copy()) + req.URL.Path = fmt.Sprintf(urlPathAPIEndpointsRoot, n.apiVersion) + + var endpoints []string + if err := n.doWithDecode(&endpoints, req); err != nil { + return err + } + + n.Debugf("discovered root endpoints: %v", endpoints) + var hasHTTP, hasStream bool + for _, v := range endpoints { + switch v { + case "nginx": + n.endpoints.nginx = true + case "connections": + n.endpoints.connections = true + case "ssl": + n.endpoints.ssl = true + case "resolvers": + n.endpoints.resolvers = true + case "http": + hasHTTP = true + case "stream": + hasStream = true + } + } + + if hasHTTP { + endpoints = endpoints[:0] + req, _ = web.NewHTTPRequest(n.Request.Copy()) + req.URL.Path = fmt.Sprintf(urlPathAPIEndpointsHTTP, n.apiVersion) + + if err := n.doWithDecode(&endpoints, req); err != nil { + return err + } + + n.Debugf("discovered http endpoints: %v", endpoints) + for _, v := range endpoints { + switch v { + case "requests": + n.endpoints.httpRequest = true + case "server_zones": + n.endpoints.httpServerZones = true + case "location_zones": + n.endpoints.httpLocationZones = true + case "caches": + n.endpoints.httpCaches = true + case "upstreams": + n.endpoints.httpUpstreams = true + } + } + } + + if hasStream { + endpoints = endpoints[:0] + req, _ = web.NewHTTPRequest(n.Request.Copy()) + req.URL.Path = fmt.Sprintf(urlPathAPIEndpointsStream, n.apiVersion) + + if err := n.doWithDecode(&endpoints, req); err != nil { + return err + } + + n.Debugf("discovered stream endpoints: %v", endpoints) + for _, v := range endpoints { + switch v { + case "server_zones": + n.endpoints.streamServerZones = true + case "upstreams": + n.endpoints.streamUpstreams = true + } + } + } + + return nil +} + +func (n *NginxPlus) queryMetrics() *nginxMetrics { + ms := &nginxMetrics{} + wg := &sync.WaitGroup{} + + for _, task := range []struct { + do bool + fn func(*nginxMetrics) + }{ + {do: n.endpoints.nginx, fn: n.queryNginxInfo}, + {do: n.endpoints.connections, fn: n.queryConnections}, + {do: n.endpoints.ssl, fn: n.querySSL}, + {do: n.endpoints.httpRequest, fn: n.queryHTTPRequests}, + {do: n.endpoints.httpServerZones, fn: n.queryHTTPServerZones}, + {do: n.endpoints.httpLocationZones, fn: n.queryHTTPLocationZones}, + {do: n.endpoints.httpUpstreams, fn: n.queryHTTPUpstreams}, + {do: n.endpoints.httpCaches, fn: n.queryHTTPCaches}, + {do: n.endpoints.streamServerZones, fn: n.queryStreamServerZones}, + {do: n.endpoints.streamUpstreams, fn: n.queryStreamUpstreams}, + {do: n.endpoints.resolvers, fn: n.queryResolvers}, + } { + task := task + if task.do { + wg.Add(1) + go func() { task.fn(ms); wg.Done() }() + } + } + + wg.Wait() + + return ms +} + +func (n *NginxPlus) queryNginxInfo(ms *nginxMetrics) { + req, _ := web.NewHTTPRequest(n.Request.Copy()) + req.URL.Path = fmt.Sprintf(urlPathAPINginx, n.apiVersion) + + var v nginxInfo + + if err := n.doWithDecode(&v, req); err != nil { + n.endpoints.nginx = !errors.Is(err, errPathNotFound) + n.Warning(err) + return + } + + ms.info = &v +} + +func (n *NginxPlus) queryConnections(ms *nginxMetrics) { + req, _ := web.NewHTTPRequest(n.Request.Copy()) + req.URL.Path = fmt.Sprintf(urlPathAPIConnections, n.apiVersion) + + var v nginxConnections + + if err := n.doWithDecode(&v, req); err != nil { + n.endpoints.connections = !errors.Is(err, errPathNotFound) + n.Warning(err) + return + } + + ms.connections = &v +} + +func (n *NginxPlus) querySSL(ms *nginxMetrics) { + req, _ := web.NewHTTPRequest(n.Request.Copy()) + req.URL.Path = fmt.Sprintf(urlPathAPISSL, n.apiVersion) + + var v nginxSSL + + if err := n.doWithDecode(&v, req); err != nil { + n.endpoints.ssl = !errors.Is(err, errPathNotFound) + n.Warning(err) + return + } + + ms.ssl = &v +} + +func (n *NginxPlus) queryHTTPRequests(ms *nginxMetrics) { + req, _ := web.NewHTTPRequest(n.Request.Copy()) + req.URL.Path = fmt.Sprintf(urlPathAPIHTTPRequests, n.apiVersion) + + var v nginxHTTPRequests + + if err := n.doWithDecode(&v, req); err != nil { + n.endpoints.httpRequest = !errors.Is(err, errPathNotFound) + n.Warning(err) + return + } + + ms.httpRequests = &v +} + +func (n *NginxPlus) queryHTTPServerZones(ms *nginxMetrics) { + req, _ := web.NewHTTPRequest(n.Request.Copy()) + req.URL.Path = fmt.Sprintf(urlPathAPIHTTPServerZones, n.apiVersion) + + var v nginxHTTPServerZones + + if err := n.doWithDecode(&v, req); err != nil { + n.endpoints.httpServerZones = !errors.Is(err, errPathNotFound) + n.Warning(err) + return + } + + ms.httpServerZones = &v +} + +func (n *NginxPlus) queryHTTPLocationZones(ms *nginxMetrics) { + req, _ := web.NewHTTPRequest(n.Request.Copy()) + req.URL.Path = fmt.Sprintf(urlPathAPIHTTPLocationZones, n.apiVersion) + + var v nginxHTTPLocationZones + + if err := n.doWithDecode(&v, req); err != nil { + n.endpoints.httpLocationZones = !errors.Is(err, errPathNotFound) + n.Warning(err) + return + } + + ms.httpLocationZones = &v +} + +func (n *NginxPlus) queryHTTPUpstreams(ms *nginxMetrics) { + req, _ := web.NewHTTPRequest(n.Request.Copy()) + req.URL.Path = fmt.Sprintf(urlPathAPIHTTPUpstreams, n.apiVersion) + + var v nginxHTTPUpstreams + + if err := n.doWithDecode(&v, req); err != nil { + n.endpoints.httpUpstreams = !errors.Is(err, errPathNotFound) + n.Warning(err) + return + } + + ms.httpUpstreams = &v +} + +func (n *NginxPlus) queryHTTPCaches(ms *nginxMetrics) { + req, _ := web.NewHTTPRequest(n.Request.Copy()) + req.URL.Path = fmt.Sprintf(urlPathAPIHTTPCaches, n.apiVersion) + + var v nginxHTTPCaches + + if err := n.doWithDecode(&v, req); err != nil { + n.endpoints.httpCaches = !errors.Is(err, errPathNotFound) + n.Warning(err) + return + } + + ms.httpCaches = &v +} + +func (n *NginxPlus) queryStreamServerZones(ms *nginxMetrics) { + req, _ := web.NewHTTPRequest(n.Request.Copy()) + req.URL.Path = fmt.Sprintf(urlPathAPIStreamServerZones, n.apiVersion) + + var v nginxStreamServerZones + + if err := n.doWithDecode(&v, req); err != nil { + n.endpoints.streamServerZones = !errors.Is(err, errPathNotFound) + n.Warning(err) + return + } + + ms.streamServerZones = &v +} + +func (n *NginxPlus) queryStreamUpstreams(ms *nginxMetrics) { + req, _ := web.NewHTTPRequest(n.Request.Copy()) + req.URL.Path = fmt.Sprintf(urlPathAPIStreamUpstreams, n.apiVersion) + + var v nginxStreamUpstreams + + if err := n.doWithDecode(&v, req); err != nil { + n.endpoints.streamUpstreams = !errors.Is(err, errPathNotFound) + n.Warning(err) + return + } + + ms.streamUpstreams = &v +} + +func (n *NginxPlus) queryResolvers(ms *nginxMetrics) { + req, _ := web.NewHTTPRequest(n.Request.Copy()) + req.URL.Path = fmt.Sprintf(urlPathAPIResolvers, n.apiVersion) + + var v nginxResolvers + + if err := n.doWithDecode(&v, req); err != nil { + n.endpoints.resolvers = !errors.Is(err, errPathNotFound) + n.Warning(err) + return + } + + ms.resolvers = &v +} + +var ( + errPathNotFound = errors.New("path not found") +) + +func (n *NginxPlus) doWithDecode(dst interface{}, req *http.Request) error { + n.Debugf("executing %s '%s'", req.Method, req.URL) + resp, err := n.httpClient.Do(req) + if err != nil { + return err + } + defer closeBody(resp) + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("%s returned %d status code (%w)", req.URL, resp.StatusCode, errPathNotFound) + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("%s returned %d status code (%s)", req.URL, resp.StatusCode, resp.Status) + } + + content, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("error on reading response from %s : %v", req.URL, err) + } + + if err := json.Unmarshal(content, dst); err != nil { + return fmt.Errorf("error on parsing response from %s : %v", req.URL, err) + } + + return nil +} + +func closeBody(resp *http.Response) { + if resp != nil && resp.Body != nil { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + } +} + +func (n *nginxMetrics) empty() bool { + return n.info != nil && + n.connections == nil && + n.ssl == nil && + n.httpRequests == nil && + n.httpServerZones == nil && + n.httpLocationZones == nil && + n.httpUpstreams == nil && + n.httpCaches == nil && + n.streamServerZones == nil && + n.streamUpstreams == nil && + n.resolvers != nil +} |