1
0
Fork 0
telegraf/plugins/inputs/nginx_plus_api/nginx_plus_api_metrics.go

670 lines
17 KiB
Go
Raw Normal View History

package nginx_plus_api
import (
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/influxdata/telegraf"
)
var (
// errNotFound signals that the NGINX API routes does not exist.
errNotFound = errors.New("not found")
)
func (n *NginxPlusAPI) gatherMetrics(addr *url.URL, acc telegraf.Accumulator) {
addError(acc, n.gatherProcessesMetrics(addr, acc))
addError(acc, n.gatherConnectionsMetrics(addr, acc))
addError(acc, n.gatherSlabsMetrics(addr, acc))
addError(acc, n.gatherSslMetrics(addr, acc))
addError(acc, n.gatherHTTPRequestsMetrics(addr, acc))
addError(acc, n.gatherHTTPServerZonesMetrics(addr, acc))
addError(acc, n.gatherHTTPUpstreamsMetrics(addr, acc))
addError(acc, n.gatherHTTPCachesMetrics(addr, acc))
addError(acc, n.gatherStreamServerZonesMetrics(addr, acc))
addError(acc, n.gatherStreamUpstreamsMetrics(addr, acc))
if n.APIVersion >= 5 {
addError(acc, n.gatherHTTPLocationZonesMetrics(addr, acc))
addError(acc, n.gatherResolverZonesMetrics(addr, acc))
}
if n.APIVersion >= 6 {
addError(acc, n.gatherHTTPLimitReqsMetrics(addr, acc))
}
}
func addError(acc telegraf.Accumulator, err error) {
// This plugin has hardcoded API resource paths it checks that may not
// be in the nginx.conf. Currently, this is to prevent logging of
// paths that are not configured.
//
// The correct solution is to do a GET to /api to get the available paths
// on the server rather than simply ignore.
if !errors.Is(err, errNotFound) {
acc.AddError(err)
}
}
func (n *NginxPlusAPI) gatherURL(addr *url.URL, path string) ([]byte, error) {
address := fmt.Sprintf("%s/%d/%s", addr.String(), n.APIVersion, path)
resp, err := n.client.Get(address)
if err != nil {
return nil, fmt.Errorf("error making HTTP request to %q: %w", address, err)
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK:
case http.StatusNotFound:
// format as special error to catch and ignore as some nginx API
// features are either optional, or only available in some versions
return nil, errNotFound
default:
return nil, fmt.Errorf("%s returned HTTP status %s", address, resp.Status)
}
contentType := strings.Split(resp.Header.Get("Content-Type"), ";")[0]
switch contentType {
case "application/json":
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
default:
return nil, fmt.Errorf("%s returned unexpected content type %s", address, contentType)
}
}
func (n *NginxPlusAPI) gatherProcessesMetrics(addr *url.URL, acc telegraf.Accumulator) error {
body, err := n.gatherURL(addr, processesPath)
if err != nil {
return err
}
var processes = &processes{}
if err := json.Unmarshal(body, processes); err != nil {
return err
}
acc.AddFields(
"nginx_plus_api_processes",
map[string]interface{}{
"respawned": processes.Respawned,
},
getTags(addr),
)
return nil
}
func (n *NginxPlusAPI) gatherConnectionsMetrics(addr *url.URL, acc telegraf.Accumulator) error {
body, err := n.gatherURL(addr, connectionsPath)
if err != nil {
return err
}
var connections = &connections{}
if err := json.Unmarshal(body, connections); err != nil {
return err
}
acc.AddFields(
"nginx_plus_api_connections",
map[string]interface{}{
"accepted": connections.Accepted,
"dropped": connections.Dropped,
"active": connections.Active,
"idle": connections.Idle,
},
getTags(addr),
)
return nil
}
func (n *NginxPlusAPI) gatherSlabsMetrics(addr *url.URL, acc telegraf.Accumulator) error {
body, err := n.gatherURL(addr, slabsPath)
if err != nil {
return err
}
var slabs slabs
if err := json.Unmarshal(body, &slabs); err != nil {
return err
}
tags := getTags(addr)
for zoneName, slab := range slabs {
slabTags := make(map[string]string, len(tags)+1)
for k, v := range tags {
slabTags[k] = v
}
slabTags["zone"] = zoneName
acc.AddFields(
"nginx_plus_api_slabs_pages",
map[string]interface{}{
"used": slab.Pages.Used,
"free": slab.Pages.Free,
},
slabTags,
)
for slotID, slot := range slab.Slots {
slotTags := make(map[string]string, len(slabTags)+1)
for k, v := range slabTags {
slotTags[k] = v
}
slotTags["slot"] = slotID
acc.AddFields(
"nginx_plus_api_slabs_slots",
map[string]interface{}{
"used": slot.Used,
"free": slot.Free,
"reqs": slot.Reqs,
"fails": slot.Fails,
},
slotTags,
)
}
}
return nil
}
func (n *NginxPlusAPI) gatherSslMetrics(addr *url.URL, acc telegraf.Accumulator) error {
body, err := n.gatherURL(addr, sslPath)
if err != nil {
return err
}
var ssl = &ssl{}
if err := json.Unmarshal(body, ssl); err != nil {
return err
}
acc.AddFields(
"nginx_plus_api_ssl",
map[string]interface{}{
"handshakes": ssl.Handshakes,
"handshakes_failed": ssl.HandshakesFailed,
"session_reuses": ssl.SessionReuses,
},
getTags(addr),
)
return nil
}
func (n *NginxPlusAPI) gatherHTTPRequestsMetrics(addr *url.URL, acc telegraf.Accumulator) error {
body, err := n.gatherURL(addr, httpRequestsPath)
if err != nil {
return err
}
var httpRequests = &httpRequests{}
if err := json.Unmarshal(body, httpRequests); err != nil {
return err
}
acc.AddFields(
"nginx_plus_api_http_requests",
map[string]interface{}{
"total": httpRequests.Total,
"current": httpRequests.Current,
},
getTags(addr),
)
return nil
}
func (n *NginxPlusAPI) gatherHTTPServerZonesMetrics(addr *url.URL, acc telegraf.Accumulator) error {
body, err := n.gatherURL(addr, httpServerZonesPath)
if err != nil {
return err
}
var httpServerZones httpServerZones
if err := json.Unmarshal(body, &httpServerZones); err != nil {
return err
}
tags := getTags(addr)
for zoneName, zone := range httpServerZones {
zoneTags := make(map[string]string, len(tags)+1)
for k, v := range tags {
zoneTags[k] = v
}
zoneTags["zone"] = zoneName
acc.AddFields(
"nginx_plus_api_http_server_zones",
func() map[string]interface{} {
result := map[string]interface{}{
"processing": zone.Processing,
"requests": zone.Requests,
"responses_1xx": zone.Responses.Responses1xx,
"responses_2xx": zone.Responses.Responses2xx,
"responses_3xx": zone.Responses.Responses3xx,
"responses_4xx": zone.Responses.Responses4xx,
"responses_5xx": zone.Responses.Responses5xx,
"responses_total": zone.Responses.Total,
"received": zone.Received,
"sent": zone.Sent,
}
if zone.Discarded != nil {
result["discarded"] = *zone.Discarded
}
return result
}(),
zoneTags,
)
}
return nil
}
// Added in 5 API version
func (n *NginxPlusAPI) gatherHTTPLocationZonesMetrics(addr *url.URL, acc telegraf.Accumulator) error {
body, err := n.gatherURL(addr, httpLocationZonesPath)
if err != nil {
return err
}
var httpLocationZones httpLocationZones
if err := json.Unmarshal(body, &httpLocationZones); err != nil {
return err
}
tags := getTags(addr)
for zoneName, zone := range httpLocationZones {
zoneTags := make(map[string]string, len(tags)+1)
for k, v := range tags {
zoneTags[k] = v
}
zoneTags["zone"] = zoneName
acc.AddFields(
"nginx_plus_api_http_location_zones",
func() map[string]interface{} {
result := map[string]interface{}{
"requests": zone.Requests,
"responses_1xx": zone.Responses.Responses1xx,
"responses_2xx": zone.Responses.Responses2xx,
"responses_3xx": zone.Responses.Responses3xx,
"responses_4xx": zone.Responses.Responses4xx,
"responses_5xx": zone.Responses.Responses5xx,
"responses_total": zone.Responses.Total,
"received": zone.Received,
"sent": zone.Sent,
}
if zone.Discarded != nil {
result["discarded"] = *zone.Discarded
}
return result
}(),
zoneTags,
)
}
return nil
}
func (n *NginxPlusAPI) gatherHTTPUpstreamsMetrics(addr *url.URL, acc telegraf.Accumulator) error {
body, err := n.gatherURL(addr, httpUpstreamsPath)
if err != nil {
return err
}
var httpUpstreams httpUpstreams
if err := json.Unmarshal(body, &httpUpstreams); err != nil {
return err
}
tags := getTags(addr)
for upstreamName, upstream := range httpUpstreams {
upstreamTags := make(map[string]string, len(tags)+1)
for k, v := range tags {
upstreamTags[k] = v
}
upstreamTags["upstream"] = upstreamName
upstreamFields := map[string]interface{}{
"keepalive": upstream.Keepalive,
"zombies": upstream.Zombies,
}
if upstream.Queue != nil {
upstreamFields["queue_size"] = upstream.Queue.Size
upstreamFields["queue_max_size"] = upstream.Queue.MaxSize
upstreamFields["queue_overflows"] = upstream.Queue.Overflows
}
acc.AddFields(
"nginx_plus_api_http_upstreams",
upstreamFields,
upstreamTags,
)
for _, peer := range upstream.Peers {
peerFields := map[string]interface{}{
"backup": peer.Backup,
"weight": peer.Weight,
"state": peer.State,
"active": peer.Active,
"requests": peer.Requests,
"responses_1xx": peer.Responses.Responses1xx,
"responses_2xx": peer.Responses.Responses2xx,
"responses_3xx": peer.Responses.Responses3xx,
"responses_4xx": peer.Responses.Responses4xx,
"responses_5xx": peer.Responses.Responses5xx,
"responses_total": peer.Responses.Total,
"sent": peer.Sent,
"received": peer.Received,
"fails": peer.Fails,
"unavail": peer.Unavail,
"healthchecks_checks": peer.HealthChecks.Checks,
"healthchecks_fails": peer.HealthChecks.Fails,
"healthchecks_unhealthy": peer.HealthChecks.Unhealthy,
"downtime": peer.Downtime,
// "selected": peer.Selected.toInt64,
// "downstart": peer.Downstart.toInt64,
}
if peer.HealthChecks.LastPassed != nil {
peerFields["healthchecks_last_passed"] = *peer.HealthChecks.LastPassed
}
if peer.HeaderTime != nil {
peerFields["header_time"] = *peer.HeaderTime
}
if peer.ResponseTime != nil {
peerFields["response_time"] = *peer.ResponseTime
}
if peer.MaxConns != nil {
peerFields["max_conns"] = *peer.MaxConns
}
peerTags := make(map[string]string, len(upstreamTags)+2)
for k, v := range upstreamTags {
peerTags[k] = v
}
peerTags["upstream_address"] = peer.Server
if peer.ID != nil {
peerTags["id"] = strconv.Itoa(*peer.ID)
}
acc.AddFields("nginx_plus_api_http_upstream_peers", peerFields, peerTags)
}
}
return nil
}
func (n *NginxPlusAPI) gatherHTTPCachesMetrics(addr *url.URL, acc telegraf.Accumulator) error {
body, err := n.gatherURL(addr, httpCachesPath)
if err != nil {
return err
}
var httpCaches httpCaches
if err := json.Unmarshal(body, &httpCaches); err != nil {
return err
}
tags := getTags(addr)
for cacheName, cache := range httpCaches {
cacheTags := make(map[string]string, len(tags)+1)
for k, v := range tags {
cacheTags[k] = v
}
cacheTags["cache"] = cacheName
acc.AddFields(
"nginx_plus_api_http_caches",
map[string]interface{}{
"size": cache.Size,
"max_size": cache.MaxSize,
"cold": cache.Cold,
"hit_responses": cache.Hit.Responses,
"hit_bytes": cache.Hit.Bytes,
"stale_responses": cache.Stale.Responses,
"stale_bytes": cache.Stale.Bytes,
"updating_responses": cache.Updating.Responses,
"updating_bytes": cache.Updating.Bytes,
"revalidated_responses": cache.Revalidated.Responses,
"revalidated_bytes": cache.Revalidated.Bytes,
"miss_responses": cache.Miss.Responses,
"miss_bytes": cache.Miss.Bytes,
"miss_responses_written": cache.Miss.ResponsesWritten,
"miss_bytes_written": cache.Miss.BytesWritten,
"expired_responses": cache.Expired.Responses,
"expired_bytes": cache.Expired.Bytes,
"expired_responses_written": cache.Expired.ResponsesWritten,
"expired_bytes_written": cache.Expired.BytesWritten,
"bypass_responses": cache.Bypass.Responses,
"bypass_bytes": cache.Bypass.Bytes,
"bypass_responses_written": cache.Bypass.ResponsesWritten,
"bypass_bytes_written": cache.Bypass.BytesWritten,
},
cacheTags,
)
}
return nil
}
func (n *NginxPlusAPI) gatherStreamServerZonesMetrics(addr *url.URL, acc telegraf.Accumulator) error {
body, err := n.gatherURL(addr, streamServerZonesPath)
if err != nil {
return err
}
var streamServerZones streamServerZones
if err := json.Unmarshal(body, &streamServerZones); err != nil {
return err
}
tags := getTags(addr)
for zoneName, zone := range streamServerZones {
zoneTags := make(map[string]string, len(tags)+1)
for k, v := range tags {
zoneTags[k] = v
}
zoneTags["zone"] = zoneName
acc.AddFields(
"nginx_plus_api_stream_server_zones",
map[string]interface{}{
"processing": zone.Processing,
"connections": zone.Connections,
"received": zone.Received,
"sent": zone.Sent,
},
zoneTags,
)
}
return nil
}
// Added in 5 API version
func (n *NginxPlusAPI) gatherResolverZonesMetrics(addr *url.URL, acc telegraf.Accumulator) error {
body, err := n.gatherURL(addr, resolverZonesPath)
if err != nil {
return err
}
var resolverZones resolverZones
if err := json.Unmarshal(body, &resolverZones); err != nil {
return err
}
tags := getTags(addr)
for zoneName, resolver := range resolverZones {
zoneTags := make(map[string]string, len(tags)+1)
for k, v := range tags {
zoneTags[k] = v
}
zoneTags["zone"] = zoneName
acc.AddFields(
"nginx_plus_api_resolver_zones",
map[string]interface{}{
"name": resolver.Requests.Name,
"srv": resolver.Requests.Srv,
"addr": resolver.Requests.Addr,
"noerror": resolver.Responses.Noerror,
"formerr": resolver.Responses.Formerr,
"servfail": resolver.Responses.Servfail,
"nxdomain": resolver.Responses.Nxdomain,
"notimp": resolver.Responses.Notimp,
"refused": resolver.Responses.Refused,
"timedout": resolver.Responses.Timedout,
"unknown": resolver.Responses.Unknown,
},
zoneTags,
)
}
return nil
}
func (n *NginxPlusAPI) gatherStreamUpstreamsMetrics(addr *url.URL, acc telegraf.Accumulator) error {
body, err := n.gatherURL(addr, streamUpstreamsPath)
if err != nil {
return err
}
var streamUpstreams streamUpstreams
if err := json.Unmarshal(body, &streamUpstreams); err != nil {
return err
}
tags := getTags(addr)
for upstreamName, upstream := range streamUpstreams {
upstreamTags := make(map[string]string, len(tags)+1)
for k, v := range tags {
upstreamTags[k] = v
}
upstreamTags["upstream"] = upstreamName
acc.AddFields(
"nginx_plus_api_stream_upstreams",
map[string]interface{}{
"zombies": upstream.Zombies,
},
upstreamTags,
)
for _, peer := range upstream.Peers {
peerFields := map[string]interface{}{
"backup": peer.Backup,
"weight": peer.Weight,
"state": peer.State,
"active": peer.Active,
"connections": peer.Connections,
"sent": peer.Sent,
"received": peer.Received,
"fails": peer.Fails,
"unavail": peer.Unavail,
"healthchecks_checks": peer.HealthChecks.Checks,
"healthchecks_fails": peer.HealthChecks.Fails,
"healthchecks_unhealthy": peer.HealthChecks.Unhealthy,
"downtime": peer.Downtime,
}
if peer.HealthChecks.LastPassed != nil {
peerFields["healthchecks_last_passed"] = *peer.HealthChecks.LastPassed
}
if peer.ConnectTime != nil {
peerFields["connect_time"] = *peer.ConnectTime
}
if peer.FirstByteTime != nil {
peerFields["first_byte_time"] = *peer.FirstByteTime
}
if peer.ResponseTime != nil {
peerFields["response_time"] = *peer.ResponseTime
}
peerTags := make(map[string]string, len(upstreamTags)+2)
for k, v := range upstreamTags {
peerTags[k] = v
}
peerTags["upstream_address"] = peer.Server
peerTags["id"] = strconv.Itoa(peer.ID)
acc.AddFields("nginx_plus_api_stream_upstream_peers", peerFields, peerTags)
}
}
return nil
}
// Added in 6 API version
func (n *NginxPlusAPI) gatherHTTPLimitReqsMetrics(addr *url.URL, acc telegraf.Accumulator) error {
body, err := n.gatherURL(addr, httpLimitReqsPath)
if err != nil {
return err
}
var httpLimitReqs httpLimitReqs
if err := json.Unmarshal(body, &httpLimitReqs); err != nil {
return err
}
tags := getTags(addr)
for limitReqName, limit := range httpLimitReqs {
limitReqsTags := make(map[string]string, len(tags)+1)
for k, v := range tags {
limitReqsTags[k] = v
}
limitReqsTags["limit"] = limitReqName
acc.AddFields(
"nginx_plus_api_http_limit_reqs",
map[string]interface{}{
"passed": limit.Passed,
"delayed": limit.Delayed,
"rejected": limit.Rejected,
"delayed_dry_run": limit.DelayedDryRun,
"rejected_dry_run": limit.RejectedDryRun,
},
limitReqsTags,
)
}
return nil
}
func getTags(addr *url.URL) map[string]string {
h := addr.Host
host, port, err := net.SplitHostPort(h)
if err != nil {
host = addr.Host
if addr.Scheme == "http" {
port = "80"
} else if addr.Scheme == "https" {
port = "443"
} else {
port = ""
}
}
return map[string]string{"source": host, "port": port}
}