diff options
Diffstat (limited to 'src/go/plugin/go.d/modules/nginxplus')
29 files changed, 4459 insertions, 0 deletions
diff --git a/src/go/plugin/go.d/modules/nginxplus/README.md b/src/go/plugin/go.d/modules/nginxplus/README.md new file mode 120000 index 00000000..16cb6c1b --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/README.md @@ -0,0 +1 @@ +integrations/nginx_plus.md
\ No newline at end of file diff --git a/src/go/plugin/go.d/modules/nginxplus/cache.go b/src/go/plugin/go.d/modules/nginxplus/cache.go new file mode 100644 index 00000000..af58f3a5 --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/cache.go @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package nginxplus + +func newCache() *cache { + return &cache{ + httpCaches: make(map[string]*cacheHTTPCacheEntry), + httpServerZones: make(map[string]*cacheZoneEntry), + httpLocationZones: make(map[string]*cacheZoneEntry), + httpUpstreams: make(map[string]*cacheUpstreamEntry), + httpUpstreamServers: make(map[string]*cacheUpstreamServerEntry), + streamServerZones: make(map[string]*cacheZoneEntry), + streamUpstreams: make(map[string]*cacheUpstreamEntry), + streamUpstreamServers: make(map[string]*cacheUpstreamServerEntry), + resolvers: make(map[string]*cacheResolverEntry), + } +} + +type ( + cache struct { + httpCaches map[string]*cacheHTTPCacheEntry + httpServerZones map[string]*cacheZoneEntry + httpLocationZones map[string]*cacheZoneEntry + httpUpstreams map[string]*cacheUpstreamEntry + httpUpstreamServers map[string]*cacheUpstreamServerEntry + streamServerZones map[string]*cacheZoneEntry + streamUpstreams map[string]*cacheUpstreamEntry + streamUpstreamServers map[string]*cacheUpstreamServerEntry + resolvers map[string]*cacheResolverEntry + } + cacheEntry struct { + hasCharts bool + updated bool + notSeenTimes int + } + cacheHTTPCacheEntry struct { + name string + cacheEntry + } + cacheResolverEntry struct { + zone string + cacheEntry + } + cacheZoneEntry struct { + zone string + cacheEntry + } + cacheUpstreamEntry struct { + name string + zone string + cacheEntry + } + cacheUpstreamServerEntry struct { + name string + zone string + serverAddr string + serverName string + cacheEntry + } +) + +func (c *cache) resetUpdated() { + for _, v := range c.httpCaches { + v.updated = false + } + for _, v := range c.httpServerZones { + v.updated = false + } + for _, v := range c.httpLocationZones { + v.updated = false + } + for _, v := range c.httpUpstreams { + v.updated = false + } + for _, v := range c.httpUpstreamServers { + v.updated = false + } + for _, v := range c.streamServerZones { + v.updated = false + } + for _, v := range c.streamUpstreams { + v.updated = false + } + for _, v := range c.streamUpstreamServers { + v.updated = false + } + for _, v := range c.resolvers { + v.updated = false + } +} + +func (c *cache) putHTTPCache(cache string) { + v, ok := c.httpCaches[cache] + if !ok { + v = &cacheHTTPCacheEntry{name: cache} + c.httpCaches[cache] = v + } + v.updated, v.notSeenTimes = true, 0 +} + +func (c *cache) putHTTPServerZone(zone string) { + v, ok := c.httpServerZones[zone] + if !ok { + v = &cacheZoneEntry{zone: zone} + c.httpServerZones[zone] = v + } + v.updated, v.notSeenTimes = true, 0 +} + +func (c *cache) putHTTPLocationZone(zone string) { + v, ok := c.httpLocationZones[zone] + if !ok { + v = &cacheZoneEntry{zone: zone} + c.httpLocationZones[zone] = v + } + v.updated, v.notSeenTimes = true, 0 +} + +func (c *cache) putHTTPUpstream(name, zone string) { + v, ok := c.httpUpstreams[name+"_"+zone] + if !ok { + v = &cacheUpstreamEntry{name: name, zone: zone} + c.httpUpstreams[name+"_"+zone] = v + } + v.updated, v.notSeenTimes = true, 0 +} + +func (c *cache) putHTTPUpstreamServer(name, serverAddr, serverName, zone string) { + v, ok := c.httpUpstreamServers[name+"_"+serverAddr+"_"+zone] + if !ok { + v = &cacheUpstreamServerEntry{name: name, zone: zone, serverAddr: serverAddr, serverName: serverName} + c.httpUpstreamServers[name+"_"+serverAddr+"_"+zone] = v + } + v.updated, v.notSeenTimes = true, 0 +} + +func (c *cache) putStreamServerZone(zone string) { + v, ok := c.streamServerZones[zone] + if !ok { + v = &cacheZoneEntry{zone: zone} + c.streamServerZones[zone] = v + } + v.updated, v.notSeenTimes = true, 0 + +} + +func (c *cache) putStreamUpstream(name, zone string) { + v, ok := c.streamUpstreams[name+"_"+zone] + if !ok { + v = &cacheUpstreamEntry{name: name, zone: zone} + c.streamUpstreams[name+"_"+zone] = v + } + v.updated, v.notSeenTimes = true, 0 +} + +func (c *cache) putStreamUpstreamServer(name, serverAddr, serverName, zone string) { + v, ok := c.streamUpstreamServers[name+"_"+serverAddr+"_"+zone] + if !ok { + v = &cacheUpstreamServerEntry{name: name, zone: zone, serverAddr: serverAddr, serverName: serverName} + c.streamUpstreamServers[name+"_"+serverAddr+"_"+zone] = v + } + v.updated, v.notSeenTimes = true, 0 +} + +func (c *cache) putResolver(zone string) { + v, ok := c.resolvers[zone] + if !ok { + v = &cacheResolverEntry{zone: zone} + c.resolvers[zone] = v + } + v.updated, v.notSeenTimes = true, 0 +} diff --git a/src/go/plugin/go.d/modules/nginxplus/charts.go b/src/go/plugin/go.d/modules/nginxplus/charts.go new file mode 100644 index 00000000..6070ee03 --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/charts.go @@ -0,0 +1,981 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package nginxplus + +import ( + "fmt" + "strings" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" +) + +const ( + prioClientConnectionsRate = module.Priority + iota + prioClientConnectionsCount + + prioSSLHandshakesRate + prioSSLHandshakesFailuresRate + prioSSLVerificationErrorsRate + prioSSLSessionReusesRate + + prioHTTPRequestsRate + prioHTTPRequestsCount + prioHTTPServerZoneRequestsRate + prioHTTPLocationZoneRequestsRate + prioHTTPServerZoneRequestsProcessingCount + prioHTTPServerZoneRequestsDiscardedRate + prioHTTPLocationZoneRequestsDiscardedRate + + prioHTTPServerZoneResponsesPerCodeClassRate + prioHTTPLocationZoneResponsesPerCodeClassRate + + prioHTTPServerZoneTrafficRate + prioHTTPLocationZoneTrafficRate + + prioHTTPUpstreamPeersCount + prioHTTPUpstreamZombiesCount + prioHTTPUpstreamKeepaliveCount + + prioHTTPUpstreamServerState + prioHTTPUpstreamServerDowntime + + prioHTTPUpstreamServerConnectionsCount + + prioHTTPUpstreamServerRequestsRate + + prioHTTPUpstreamServerResponsesPerCodeClassRate + + prioHTTPUpstreamServerResponseTime + prioHTTPUpstreamServerResponseHeaderTime + + prioHTTPUpstreamServerTrafficRate + + prioHTTPCacheState + prioHTTPCacheIOPS + prioHTTPCacheIO + prioHTTPCacheSize + + prioStreamServerZoneConnectionsRate + prioStreamServerZoneConnectionsProcessingCount + prioStreamServerZoneConnectionsDiscardedRate + + prioStreamServerZoneSessionsPerCodeClassRate + + prioStreamServerZoneTrafficRate + + prioStreamUpstreamPeersCount + prioStreamUpstreamZombiesCount + + prioStreamUpstreamServerState + prioStreamUpstreamServerDowntime + + prioStreamUpstreamServerConnectionsRate + prioStreamUpstreamServerConnectionsCount + + prioStreamUpstreamServerTrafficRate + + prioResolverZoneRequestsRate + prioResolverZoneResponsesRate + + prioUptime +) + +var ( + baseCharts = module.Charts{ + clientConnectionsRateChart.Copy(), + clientConnectionsCountChart.Copy(), + sslHandshakesRateChart.Copy(), + sslHandshakesFailuresRateChart.Copy(), + sslVerificationErrorsRateChart.Copy(), + sslSessionReusesRateChart.Copy(), + httpRequestsRateChart.Copy(), + httpRequestsCountChart.Copy(), + uptimeChart.Copy(), + } + + clientConnectionsRateChart = module.Chart{ + ID: "client_connections_rate", + Title: "Client connections rate", + Units: "connections/s", + Fam: "connections", + Ctx: "nginxplus.client_connections_rate", + Priority: prioClientConnectionsRate, + Dims: module.Dims{ + {ID: "connections_accepted", Name: "accepted", Algo: module.Incremental}, + {ID: "connections_dropped", Name: "dropped", Algo: module.Incremental}, + }, + } + clientConnectionsCountChart = module.Chart{ + ID: "client_connections_count", + Title: "Client connections", + Units: "connections", + Fam: "connections", + Ctx: "nginxplus.client_connections_count", + Priority: prioClientConnectionsCount, + Dims: module.Dims{ + {ID: "connections_active", Name: "active"}, + {ID: "connections_idle", Name: "idle"}, + }, + } + sslHandshakesRateChart = module.Chart{ + ID: "ssl_handshakes_rate", + Title: "SSL handshakes rate", + Units: "handshakes/s", + Fam: "ssl", + Ctx: "nginxplus.ssl_handshakes_rate", + Priority: prioSSLHandshakesRate, + Dims: module.Dims{ + {ID: "ssl_handshakes", Name: "successful", Algo: module.Incremental}, + {ID: "ssl_handshakes_failed", Name: "failed", Algo: module.Incremental}, + }, + } + sslHandshakesFailuresRateChart = module.Chart{ + ID: "ssl_handshakes_failures_rate", + Title: "SSL handshakes failures rate", + Units: "failures/s", + Fam: "ssl", + Ctx: "nginxplus.ssl_handshakes_failures_rate", + Priority: prioSSLHandshakesFailuresRate, + Type: module.Stacked, + Dims: module.Dims{ + {ID: "ssl_no_common_protocol", Name: "no_common_protocol", Algo: module.Incremental}, + {ID: "ssl_no_common_cipher", Name: "no_common_cipher", Algo: module.Incremental}, + {ID: "ssl_handshake_timeout", Name: "timeout", Algo: module.Incremental}, + {ID: "ssl_peer_rejected_cert", Name: "peer_rejected_cert", Algo: module.Incremental}, + }, + } + sslVerificationErrorsRateChart = module.Chart{ + ID: "ssl_verification_errors_rate", + Title: "SSL verification errors rate", + Units: "errors/s", + Fam: "ssl", + Ctx: "nginxplus.ssl_verification_errors_rate", + Priority: prioSSLVerificationErrorsRate, + Type: module.Stacked, + Dims: module.Dims{ + {ID: "ssl_verify_failures_no_cert", Name: "no_cert", Algo: module.Incremental}, + {ID: "ssl_verify_failures_expired_cert", Name: "expired_cert", Algo: module.Incremental}, + {ID: "ssl_verify_failures_revoked_cert", Name: "revoked_cert", Algo: module.Incremental}, + {ID: "ssl_verify_failures_hostname_mismatch", Name: "hostname_mismatch", Algo: module.Incremental}, + {ID: "ssl_verify_failures_other", Name: "other", Algo: module.Incremental}, + }, + } + sslSessionReusesRateChart = module.Chart{ + ID: "ssl_session_reuses_rate", + Title: "Session reuses during SSL handshake", + Units: "reuses/s", + Fam: "ssl", + Ctx: "nginxplus.ssl_session_reuses_rate", + Priority: prioSSLSessionReusesRate, + Dims: module.Dims{ + {ID: "ssl_session_reuses", Name: "ssl_session", Algo: module.Incremental}, + }, + } + httpRequestsRateChart = module.Chart{ + ID: "http_requests_rate", + Title: "HTTP requests rate", + Units: "requests/s", + Fam: "http requests", + Ctx: "nginxplus.http_requests_rate", + Priority: prioHTTPRequestsRate, + Dims: module.Dims{ + {ID: "http_requests_total", Name: "requests", Algo: module.Incremental}, + }, + } + httpRequestsCountChart = module.Chart{ + ID: "http_requests_count", + Title: "HTTP requests", + Units: "requests", + Fam: "http requests", + Ctx: "nginxplus.http_requests_count", + Priority: prioHTTPRequestsCount, + Dims: module.Dims{ + {ID: "http_requests_current", Name: "requests"}, + }, + } + uptimeChart = module.Chart{ + ID: "uptime", + Title: "Uptime", + Units: "seconds", + Fam: "uptime", + Ctx: "nginxplus.uptime", + Priority: prioUptime, + Dims: module.Dims{ + {ID: "uptime", Name: "uptime"}, + }, + } +) + +var ( + httpServerZoneChartsTmpl = module.Charts{ + httpServerZoneRequestsRateChartTmpl.Copy(), + httpServerZoneResponsesPerCodeClassRateChartTmpl.Copy(), + httpServerZoneTrafficRateChartTmpl.Copy(), + httpServerZoneRequestsProcessingCountChartTmpl.Copy(), + httpServerZoneRequestsDiscardedRateChartTmpl.Copy(), + } + httpServerZoneRequestsRateChartTmpl = module.Chart{ + ID: "http_server_zone_%s_requests_rate", + Title: "HTTP Server Zone requests rate", + Units: "requests/s", + Fam: "http requests", + Ctx: "nginxplus.http_server_zone_requests_rate", + Priority: prioHTTPServerZoneRequestsRate, + Dims: module.Dims{ + {ID: "http_server_zone_%s_requests", Name: "requests", Algo: module.Incremental}, + }, + } + httpServerZoneResponsesPerCodeClassRateChartTmpl = module.Chart{ + ID: "http_server_zone_%s_responses_per_code_class_rate", + Title: "HTTP Server Zone responses rate", + Units: "responses/s", + Fam: "http responses", + Ctx: "nginxplus.http_server_zone_responses_per_code_class_rate", + Priority: prioHTTPServerZoneResponsesPerCodeClassRate, + Type: module.Stacked, + Dims: module.Dims{ + {ID: "http_server_zone_%s_responses_1xx", Name: "1xx", Algo: module.Incremental}, + {ID: "http_server_zone_%s_responses_2xx", Name: "2xx", Algo: module.Incremental}, + {ID: "http_server_zone_%s_responses_3xx", Name: "3xx", Algo: module.Incremental}, + {ID: "http_server_zone_%s_responses_4xx", Name: "4xx", Algo: module.Incremental}, + {ID: "http_server_zone_%s_responses_5xx", Name: "5xx", Algo: module.Incremental}, + }, + } + httpServerZoneTrafficRateChartTmpl = module.Chart{ + ID: "http_server_zone_%s_traffic_rate", + Title: "HTTP Server Zone traffic", + Units: "bytes/s", + Fam: "http traffic", + Ctx: "nginxplus.http_server_zone_traffic_rate", + Priority: prioHTTPServerZoneTrafficRate, + Type: module.Area, + Dims: module.Dims{ + {ID: "http_server_zone_%s_bytes_received", Name: "received", Algo: module.Incremental}, + {ID: "http_server_zone_%s_bytes_sent", Name: "sent", Algo: module.Incremental, Mul: -1}, + }, + } + httpServerZoneRequestsProcessingCountChartTmpl = module.Chart{ + ID: "http_server_zone_%s_requests_processing_count", + Title: "HTTP Server Zone currently processed requests", + Units: "requests", + Fam: "http requests", + Ctx: "nginxplus.http_server_zone_requests_processing_count", + Priority: prioHTTPServerZoneRequestsProcessingCount, + Dims: module.Dims{ + {ID: "http_server_zone_%s_requests_processing", Name: "processing"}, + }, + } + httpServerZoneRequestsDiscardedRateChartTmpl = module.Chart{ + ID: "http_server_zone_%s_requests_discarded_rate", + Title: "HTTP Server Zone requests discarded rate", + Units: "requests/s", + Fam: "http requests", + Ctx: "nginxplus.http_server_zone_requests_discarded_rate", + Priority: prioHTTPServerZoneRequestsDiscardedRate, + Dims: module.Dims{ + {ID: "http_server_zone_%s_requests_discarded", Name: "discarded", Algo: module.Incremental}, + }, + } +) + +var ( + httpLocationZoneChartsTmpl = module.Charts{ + httpLocationZoneRequestsRateChartTmpl.Copy(), + httpLocationZoneRequestsDiscardedRateChartTmpl.Copy(), + httpLocationZoneTrafficRateChartTmpl.Copy(), + httpLocationZoneResponsesPerCodeClassRateChartTmpl.Copy(), + } + httpLocationZoneRequestsRateChartTmpl = module.Chart{ + ID: "http_location_zone_%s_requests_rate", + Title: "HTTP Location Zone requests rate", + Units: "requests/s", + Fam: "http requests", + Ctx: "nginxplus.http_location_zone_requests_rate", + Priority: prioHTTPLocationZoneRequestsRate, + Dims: module.Dims{ + {ID: "http_location_zone_%s_requests", Name: "requests", Algo: module.Incremental}, + }, + } + httpLocationZoneResponsesPerCodeClassRateChartTmpl = module.Chart{ + ID: "http_location_zone_%s_responses_per_code_class_rate", + Title: "HTTP Location Zone responses rate", + Units: "responses/s", + Fam: "http responses", + Ctx: "nginxplus.http_location_zone_responses_per_code_class_rate", + Priority: prioHTTPLocationZoneResponsesPerCodeClassRate, + Type: module.Stacked, + Dims: module.Dims{ + {ID: "http_location_zone_%s_responses_1xx", Name: "1xx", Algo: module.Incremental}, + {ID: "http_location_zone_%s_responses_2xx", Name: "2xx", Algo: module.Incremental}, + {ID: "http_location_zone_%s_responses_3xx", Name: "3xx", Algo: module.Incremental}, + {ID: "http_location_zone_%s_responses_4xx", Name: "4xx", Algo: module.Incremental}, + {ID: "http_location_zone_%s_responses_5xx", Name: "5xx", Algo: module.Incremental}, + }, + } + httpLocationZoneTrafficRateChartTmpl = module.Chart{ + ID: "http_location_zone_%s_traffic_rate", + Title: "HTTP Location Zone traffic rate", + Units: "bytes/s", + Fam: "http traffic", + Ctx: "nginxplus.http_location_zone_traffic_rate", + Priority: prioHTTPLocationZoneTrafficRate, + Type: module.Area, + Dims: module.Dims{ + {ID: "http_location_zone_%s_bytes_received", Name: "received", Algo: module.Incremental}, + {ID: "http_location_zone_%s_bytes_sent", Name: "sent", Algo: module.Incremental, Mul: -1}, + }, + } + httpLocationZoneRequestsDiscardedRateChartTmpl = module.Chart{ + ID: "http_location_zone_%s_requests_discarded_rate", + Title: "HTTP Location Zone requests discarded rate", + Units: "requests/s", + Fam: "http requests", + Ctx: "nginxplus.http_location_zone_requests_discarded_rate", + Priority: prioHTTPLocationZoneRequestsDiscardedRate, + Dims: module.Dims{ + {ID: "http_location_zone_%s_requests_discarded", Name: "discarded", Algo: module.Incremental}, + }, + } +) + +var ( + httpUpstreamChartsTmpl = module.Charts{ + httpUpstreamPeersCountChartTmpl.Copy(), + httpUpstreamZombiesCountChartTmpl.Copy(), + httpUpstreamKeepaliveCountChartTmpl.Copy(), + } + httpUpstreamPeersCountChartTmpl = module.Chart{ + ID: "http_upstream_%s_zone_%s_peers_count", + Title: "HTTP Upstream peers", + Units: "peers", + Fam: "http upstream", + Ctx: "nginxplus.http_upstream_peers_count", + Priority: prioHTTPUpstreamPeersCount, + Dims: module.Dims{ + {ID: "http_upstream_%s_zone_%s_peers", Name: "peers"}, + }, + } + httpUpstreamZombiesCountChartTmpl = module.Chart{ + ID: "http_upstream_%s_zone_%s_zombies_count", + Title: "HTTP Upstream zombies", + Units: "servers", + Fam: "http upstream", + Ctx: "nginxplus.http_upstream_zombies_count", + Priority: prioHTTPUpstreamZombiesCount, + Dims: module.Dims{ + {ID: "http_upstream_%s_zone_%s_zombies", Name: "zombie"}, + }, + } + httpUpstreamKeepaliveCountChartTmpl = module.Chart{ + ID: "http_upstream_%s_zone_%s_keepalive_count", + Title: "HTTP Upstream keepalive", + Units: "connections", + Fam: "http upstream", + Ctx: "nginxplus.http_upstream_keepalive_count", + Priority: prioHTTPUpstreamKeepaliveCount, + Dims: module.Dims{ + {ID: "http_upstream_%s_zone_%s_keepalive", Name: "keepalive"}, + }, + } + + httpUpstreamServerChartsTmpl = module.Charts{ + httpUpstreamServerRequestsRateChartTmpl.Copy(), + httpUpstreamServerResponsesPerCodeClassRateChartTmpl.Copy(), + httpUpstreamServerResponseTimeChartTmpl.Copy(), + httpUpstreamServerResponseHeaderTimeChartTmpl.Copy(), + httpUpstreamServerTrafficRateChartTmpl.Copy(), + httpUpstreamServerStateChartTmpl.Copy(), + httpUpstreamServerDowntimeChartTmpl.Copy(), + httpUpstreamServerConnectionsCountChartTmpl.Copy(), + } + httpUpstreamServerRequestsRateChartTmpl = module.Chart{ + ID: "http_upstream_%s_server_%s_zone_%s_requests_rate", + Title: "HTTP Upstream Server requests", + Units: "requests/s", + Fam: "http upstream requests", + Ctx: "nginxplus.http_upstream_server_requests_rate", + Priority: prioHTTPUpstreamServerRequestsRate, + Dims: module.Dims{ + {ID: "http_upstream_%s_server_%s_zone_%s_requests", Name: "requests", Algo: module.Incremental}, + }, + } + httpUpstreamServerResponsesPerCodeClassRateChartTmpl = module.Chart{ + ID: "http_upstream_%s_server_%s_zone_%s_responses_per_code_class_rate", + Title: "HTTP Upstream Server responses", + Units: "responses/s", + Fam: "http upstream responses", + Ctx: "nginxplus.http_upstream_server_responses_per_code_class_rate", + Priority: prioHTTPUpstreamServerResponsesPerCodeClassRate, + Type: module.Stacked, + Dims: module.Dims{ + {ID: "http_upstream_%s_server_%s_zone_%s_responses_1xx", Name: "1xx", Algo: module.Incremental}, + {ID: "http_upstream_%s_server_%s_zone_%s_responses_2xx", Name: "2xx", Algo: module.Incremental}, + {ID: "http_upstream_%s_server_%s_zone_%s_responses_3xx", Name: "3xx", Algo: module.Incremental}, + {ID: "http_upstream_%s_server_%s_zone_%s_responses_4xx", Name: "4xx", Algo: module.Incremental}, + {ID: "http_upstream_%s_server_%s_zone_%s_responses_5xx", Name: "5xx", Algo: module.Incremental}, + }, + } + httpUpstreamServerResponseTimeChartTmpl = module.Chart{ + ID: "http_upstream_%s_server_%s_zone_%s_response_time", + Title: "HTTP Upstream Server average response time", + Units: "milliseconds", + Fam: "http upstream response time", + Ctx: "nginxplus.http_upstream_server_response_time", + Priority: prioHTTPUpstreamServerResponseTime, + Dims: module.Dims{ + {ID: "http_upstream_%s_server_%s_zone_%s_response_time", Name: "response"}, + }, + } + httpUpstreamServerResponseHeaderTimeChartTmpl = module.Chart{ + ID: "http_upstream_%s_server_%s_zone_%s_response_header_time", + Title: "HTTP Upstream Server average response header time", + Units: "milliseconds", + Fam: "http upstream response time", + Ctx: "nginxplus.http_upstream_server_response_header_time", + Priority: prioHTTPUpstreamServerResponseHeaderTime, + Dims: module.Dims{ + {ID: "http_upstream_%s_server_%s_zone_%s_header_time", Name: "header"}, + }, + } + httpUpstreamServerTrafficRateChartTmpl = module.Chart{ + ID: "http_upstream_%s_server_%s_zone_%s_traffic_rate", + Title: "HTTP Upstream Server traffic rate", + Units: "bytes/s", + Fam: "http upstream traffic", + Ctx: "nginxplus.http_upstream_server_traffic_rate", + Priority: prioHTTPUpstreamServerTrafficRate, + Type: module.Area, + Dims: module.Dims{ + {ID: "http_upstream_%s_server_%s_zone_%s_bytes_received", Name: "received", Algo: module.Incremental}, + {ID: "http_upstream_%s_server_%s_zone_%s_bytes_sent", Name: "sent", Algo: module.Incremental, Mul: -1}, + }, + } + httpUpstreamServerStateChartTmpl = module.Chart{ + ID: "http_upstream_%s_server_%s_zone_%s_state", + Title: "HTTP Upstream Server state", + Units: "state", + Fam: "http upstream state", + Ctx: "nginxplus.http_upstream_server_state", + Priority: prioHTTPUpstreamServerState, + Dims: module.Dims{ + {ID: "http_upstream_%s_server_%s_zone_%s_state_up", Name: "up"}, + {ID: "http_upstream_%s_server_%s_zone_%s_state_down", Name: "down"}, + {ID: "http_upstream_%s_server_%s_zone_%s_state_draining", Name: "draining"}, + {ID: "http_upstream_%s_server_%s_zone_%s_state_unavail", Name: "unavail"}, + {ID: "http_upstream_%s_server_%s_zone_%s_state_checking", Name: "checking"}, + {ID: "http_upstream_%s_server_%s_zone_%s_state_unhealthy", Name: "unhealthy"}, + }, + } + httpUpstreamServerConnectionsCountChartTmpl = module.Chart{ + ID: "http_upstream_%s_server_%s_zone_%s_connection_count", + Title: "HTTP Upstream Server connections", + Units: "connections", + Fam: "http upstream connections", + Ctx: "nginxplus.http_upstream_server_connections_count", + Priority: prioHTTPUpstreamServerConnectionsCount, + Dims: module.Dims{ + {ID: "http_upstream_%s_server_%s_zone_%s_active", Name: "active"}, + }, + } + httpUpstreamServerDowntimeChartTmpl = module.Chart{ + ID: "http_upstream_%s_server_%s_zone_%s_downtime", + Title: "HTTP Upstream Server downtime", + Units: "seconds", + Fam: "http upstream state", + Ctx: "nginxplus.http_upstream_server_downtime", + Priority: prioHTTPUpstreamServerDowntime, + Dims: module.Dims{ + {ID: "http_upstream_%s_server_%s_zone_%s_downtime", Name: "downtime"}, + }, + } +) + +var ( + httpCacheChartsTmpl = module.Charts{ + httpCacheStateChartTmpl.Copy(), + httpCacheIOPSChartTmpl.Copy(), + httpCacheIOChartTmpl.Copy(), + httpCacheSizeChartTmpl.Copy(), + } + httpCacheStateChartTmpl = module.Chart{ + ID: "http_cache_%s_state", + Title: "HTTP Cache state", + Units: "state", + Fam: "http cache", + Ctx: "nginxplus.http_cache_state", + Priority: prioHTTPCacheState, + Dims: module.Dims{ + {ID: "http_cache_%s_state_warm", Name: "warm"}, + {ID: "http_cache_%s_state_cold", Name: "cold"}, + }, + } + httpCacheSizeChartTmpl = module.Chart{ + ID: "http_cache_%s_size", + Title: "HTTP Cache size", + Units: "bytes", + Fam: "http cache", + Ctx: "nginxplus.http_cache_size", + Priority: prioHTTPCacheSize, + Dims: module.Dims{ + {ID: "http_cache_%s_size", Name: "size"}, + }, + } + httpCacheIOPSChartTmpl = module.Chart{ + ID: "http_cache_%s_iops", + Title: "HTTP Cache IOPS", + Units: "responses/s", + Fam: "http cache", + Ctx: "nginxplus.http_cache_iops", + Priority: prioHTTPCacheIOPS, + Dims: module.Dims{ + {ID: "http_cache_%s_served_responses", Name: "served", Algo: module.Incremental}, + {ID: "http_cache_%s_written_responses", Name: "written", Algo: module.Incremental}, + {ID: "http_cache_%s_bypassed_responses", Name: "bypassed", Algo: module.Incremental}, + }, + } + httpCacheIOChartTmpl = module.Chart{ + ID: "http_cache_%s_io", + Title: "HTTP Cache IO", + Units: "bytes/s", + Fam: "http cache", + Ctx: "nginxplus.http_cache_io", + Priority: prioHTTPCacheIO, + Dims: module.Dims{ + {ID: "http_cache_%s_served_bytes", Name: "served", Algo: module.Incremental}, + {ID: "http_cache_%s_written_bytes", Name: "written", Algo: module.Incremental}, + {ID: "http_cache_%s_bypassed_bytes", Name: "bypassed", Algo: module.Incremental}, + }, + } +) + +var ( + streamServerZoneChartsTmpl = module.Charts{ + streamServerZoneConnectionsRateChartTmpl.Copy(), + streamServerZoneTrafficRateChartTmpl.Copy(), + streamServerZoneSessionsPerCodeClassRateChartTmpl.Copy(), + streamServerZoneConnectionsProcessingCountRateChartTmpl.Copy(), + streamServerZoneConnectionsDiscardedRateChartTmpl.Copy(), + } + streamServerZoneConnectionsRateChartTmpl = module.Chart{ + ID: "stream_server_zone_%s_connections_rate", + Title: "Stream Server Zone connections rate", + Units: "connections/s", + Fam: "stream connections", + Ctx: "nginxplus.stream_server_zone_connections_rate", + Priority: prioStreamServerZoneConnectionsRate, + Dims: module.Dims{ + {ID: "stream_server_zone_%s_connections", Name: "accepted", Algo: module.Incremental}, + }, + } + streamServerZoneSessionsPerCodeClassRateChartTmpl = module.Chart{ + ID: "stream_server_zone_%s_sessions_per_code_class_rate", + Title: "Stream Server Zone sessions rate", + Units: "sessions/s", + Fam: "stream sessions", + Ctx: "nginxplus.stream_server_zone_sessions_per_code_class_rate", + Priority: prioStreamServerZoneSessionsPerCodeClassRate, + Type: module.Stacked, + Dims: module.Dims{ + {ID: "stream_server_zone_%s_sessions_2xx", Name: "2xx", Algo: module.Incremental}, + {ID: "stream_server_zone_%s_sessions_4xx", Name: "4xx", Algo: module.Incremental}, + {ID: "stream_server_zone_%s_sessions_5xx", Name: "5xx", Algo: module.Incremental}, + }, + } + streamServerZoneTrafficRateChartTmpl = module.Chart{ + ID: "stream_server_zone_%s_traffic_rate", + Title: "Stream Server Zone traffic rate", + Units: "bytes/s", + Fam: "stream traffic", + Ctx: "nginxplus.stream_server_zone_traffic_rate", + Priority: prioStreamServerZoneTrafficRate, + Type: module.Area, + Dims: module.Dims{ + {ID: "stream_server_zone_%s_bytes_received", Name: "received", Algo: module.Incremental}, + {ID: "stream_server_zone_%s_bytes_sent", Name: "sent", Algo: module.Incremental, Mul: -1}, + }, + } + streamServerZoneConnectionsProcessingCountRateChartTmpl = module.Chart{ + ID: "stream_server_zone_%s_connections_processing_count", + Title: "Stream Server Zone connections processed", + Units: "connections", + Fam: "stream connections", + Ctx: "nginxplus.stream_server_zone_connections_processing_count", + Priority: prioStreamServerZoneConnectionsProcessingCount, + Dims: module.Dims{ + {ID: "stream_server_zone_%s_connections_processing", Name: "processing"}, + }, + } + streamServerZoneConnectionsDiscardedRateChartTmpl = module.Chart{ + ID: "stream_server_zone_%s_connections_discarded_rate", + Title: "Stream Server Zone connections discarded", + Units: "connections/s", + Fam: "stream connections", + Ctx: "nginxplus.stream_server_zone_connections_discarded_rate", + Priority: prioStreamServerZoneConnectionsDiscardedRate, + Dims: module.Dims{ + {ID: "stream_server_zone_%s_connections_discarded", Name: "discarded", Algo: module.Incremental}, + }, + } +) + +var ( + streamUpstreamChartsTmpl = module.Charts{ + streamUpstreamPeersCountChartTmpl.Copy(), + streamUpstreamZombiesCountChartTmpl.Copy(), + } + streamUpstreamPeersCountChartTmpl = module.Chart{ + ID: "stream_upstream_%s_zone_%s_peers_count", + Title: "Stream Upstream peers", + Units: "peers", + Fam: "stream upstream", + Ctx: "nginxplus.stream_upstream_peers_count", + Priority: prioStreamUpstreamPeersCount, + Dims: module.Dims{ + {ID: "stream_upstream_%s_zone_%s_peers", Name: "peers"}, + }, + } + streamUpstreamZombiesCountChartTmpl = module.Chart{ + ID: "stream_upstream_%s_zone_%s_zombies_count", + Title: "Stream Upstream zombies", + Units: "servers", + Fam: "stream upstream", + Ctx: "nginxplus.stream_upstream_zombies_count", + Priority: prioStreamUpstreamZombiesCount, + Dims: module.Dims{ + {ID: "stream_upstream_%s_zone_%s_zombies", Name: "zombie"}, + }, + } + + streamUpstreamServerChartsTmpl = module.Charts{ + streamUpstreamServerConnectionsRateChartTmpl.Copy(), + streamUpstreamServerTrafficRateChartTmpl.Copy(), + streamUpstreamServerConnectionsCountChartTmpl.Copy(), + streamUpstreamServerStateChartTmpl.Copy(), + streamUpstreamServerDowntimeChartTmpl.Copy(), + } + streamUpstreamServerConnectionsRateChartTmpl = module.Chart{ + ID: "stream_upstream_%s_server_%s_zone_%s_connection_rate", + Title: "Stream Upstream Server connections", + Units: "connections/s", + Fam: "stream upstream connections", + Ctx: "nginxplus.stream_upstream_server_connections_rate", + Priority: prioStreamUpstreamServerConnectionsRate, + Dims: module.Dims{ + {ID: "stream_upstream_%s_server_%s_zone_%s_connections", Name: "forwarded", Algo: module.Incremental}, + }, + } + streamUpstreamServerTrafficRateChartTmpl = module.Chart{ + ID: "stream_upstream_%s_server_%s_zone_%s_traffic_rate", + Title: "Stream Upstream Server traffic rate", + Units: "bytes/s", + Fam: "stream upstream traffic", + Ctx: "nginxplus.stream_upstream_server_traffic_rate", + Priority: prioStreamUpstreamServerTrafficRate, + Type: module.Area, + Dims: module.Dims{ + {ID: "stream_upstream_%s_server_%s_zone_%s_bytes_received", Name: "received", Algo: module.Incremental}, + {ID: "stream_upstream_%s_server_%s_zone_%s_bytes_sent", Name: "sent", Algo: module.Incremental, Mul: -1}, + }, + } + streamUpstreamServerStateChartTmpl = module.Chart{ + ID: "stream_upstream_%s_server_%s_zone_%s_state", + Title: "Stream Upstream Server state", + Units: "state", + Fam: "stream upstream state", + Ctx: "nginxplus.stream_upstream_server_state", + Priority: prioStreamUpstreamServerState, + Dims: module.Dims{ + {ID: "stream_upstream_%s_server_%s_zone_%s_state_up", Name: "up"}, + {ID: "stream_upstream_%s_server_%s_zone_%s_state_down", Name: "down"}, + {ID: "stream_upstream_%s_server_%s_zone_%s_state_unavail", Name: "unavail"}, + {ID: "stream_upstream_%s_server_%s_zone_%s_state_checking", Name: "checking"}, + {ID: "stream_upstream_%s_server_%s_zone_%s_state_unhealthy", Name: "unhealthy"}, + }, + } + streamUpstreamServerDowntimeChartTmpl = module.Chart{ + ID: "stream_upstream_%s_server_%s_zone_%s_downtime", + Title: "Stream Upstream Server downtime", + Units: "seconds", + Fam: "stream upstream state", + Ctx: "nginxplus.stream_upstream_server_downtime", + Priority: prioStreamUpstreamServerDowntime, + Dims: module.Dims{ + {ID: "stream_upstream_%s_server_%s_zone_%s_downtime", Name: "downtime"}, + }, + } + streamUpstreamServerConnectionsCountChartTmpl = module.Chart{ + ID: "stream_upstream_%s_server_%s_zone_%s_connection_count", + Title: "Stream Upstream Server connections", + Units: "connections", + Fam: "stream upstream connections", + Ctx: "nginxplus.stream_upstream_server_connections_count", + Priority: prioStreamUpstreamServerConnectionsCount, + Dims: module.Dims{ + {ID: "stream_upstream_%s_server_%s_zone_%s_active", Name: "active"}, + }, + } +) + +var ( + resolverZoneChartsTmpl = module.Charts{ + resolverZoneRequestsRateChartTmpl.Copy(), + resolverZoneResponsesRateChartTmpl.Copy(), + } + resolverZoneRequestsRateChartTmpl = module.Chart{ + ID: "resolver_zone_%s_requests_rate", + Title: "Resolver requests rate", + Units: "requests/s", + Fam: "resolver requests", + Ctx: "nginxplus.resolver_zone_requests_rate", + Priority: prioResolverZoneRequestsRate, + Type: module.Stacked, + Dims: module.Dims{ + {ID: "resolver_zone_%s_requests_name", Name: "name", Algo: module.Incremental}, + {ID: "resolver_zone_%s_requests_srv", Name: "srv", Algo: module.Incremental}, + {ID: "resolver_zone_%s_requests_addr", Name: "addr", Algo: module.Incremental}, + }, + } + resolverZoneResponsesRateChartTmpl = module.Chart{ + ID: "resolver_zone_%s_responses_rate", + Title: "Resolver responses rate", + Units: "responses/s", + Fam: "resolver responses", + Ctx: "nginxplus.resolver_zone_responses_rate", + Priority: prioResolverZoneResponsesRate, + Type: module.Stacked, + Dims: module.Dims{ + {ID: "resolver_zone_%s_responses_noerror", Name: "noerror", Algo: module.Incremental}, + {ID: "resolver_zone_%s_responses_formerr", Name: "formerr", Algo: module.Incremental}, + {ID: "resolver_zone_%s_responses_servfail", Name: "servfail", Algo: module.Incremental}, + {ID: "resolver_zone_%s_responses_nxdomain", Name: "nxdomain", Algo: module.Incremental}, + {ID: "resolver_zone_%s_responses_notimp", Name: "notimp", Algo: module.Incremental}, + {ID: "resolver_zone_%s_responses_refused", Name: "refused", Algo: module.Incremental}, + {ID: "resolver_zone_%s_responses_timedout", Name: "timedout", Algo: module.Incremental}, + {ID: "resolver_zone_%s_responses_unknown", Name: "unknown", Algo: module.Incremental}, + }, + } +) + +func (n *NginxPlus) addHTTPCacheCharts(name string) { + charts := httpCacheChartsTmpl.Copy() + + for _, chart := range *charts { + chart.ID = fmt.Sprintf(chart.ID, name) + chart.Labels = []module.Label{ + {Key: "http_cache", Value: name}, + } + for _, dim := range chart.Dims { + dim.ID = fmt.Sprintf(dim.ID, name) + } + } + + if err := n.Charts().Add(*charts...); err != nil { + n.Warning(err) + } +} + +func (n *NginxPlus) removeHTTPCacheCharts(name string) { + px := fmt.Sprintf("http_cache_%s_", name) + n.removeCharts(px) +} + +func (n *NginxPlus) addHTTPServerZoneCharts(zone string) { + charts := httpServerZoneChartsTmpl.Copy() + + for _, chart := range *charts { + chart.ID = fmt.Sprintf(chart.ID, zone) + chart.Labels = []module.Label{ + {Key: "http_server_zone", Value: zone}, + } + for _, dim := range chart.Dims { + dim.ID = fmt.Sprintf(dim.ID, zone) + } + } + + if err := n.Charts().Add(*charts...); err != nil { + n.Warning(err) + } +} + +func (n *NginxPlus) removeHTTPServerZoneCharts(zone string) { + px := fmt.Sprintf("http_server_zone_%s_", zone) + n.removeCharts(px) +} + +func (n *NginxPlus) addHTTPLocationZoneCharts(zone string) { + charts := httpLocationZoneChartsTmpl.Copy() + + for _, chart := range *charts { + chart.ID = fmt.Sprintf(chart.ID, zone) + chart.Labels = []module.Label{ + {Key: "http_location_zone", Value: zone}, + } + for _, dim := range chart.Dims { + dim.ID = fmt.Sprintf(dim.ID, zone) + } + } + + if err := n.Charts().Add(*charts...); err != nil { + n.Warning(err) + } +} + +func (n *NginxPlus) removeHTTPLocationZoneCharts(zone string) { + px := fmt.Sprintf("http_location_zone_%s_", zone) + n.removeCharts(px) +} + +func (n *NginxPlus) addHTTPUpstreamCharts(name, zone string) { + charts := httpUpstreamChartsTmpl.Copy() + + for _, chart := range *charts { + chart.ID = fmt.Sprintf(chart.ID, name, zone) + chart.Labels = []module.Label{ + {Key: "http_upstream_name", Value: name}, + {Key: "http_upstream_zone", Value: zone}, + } + for _, dim := range chart.Dims { + dim.ID = fmt.Sprintf(dim.ID, name, zone) + } + } + + if err := n.Charts().Add(*charts...); err != nil { + n.Warning(err) + } +} + +func (n *NginxPlus) removeHTTPUpstreamCharts(name, zone string) { + px := fmt.Sprintf("http_upstream_%s_zone_%s", name, zone) + n.removeCharts(px) +} + +func (n *NginxPlus) addHTTPUpstreamServerCharts(name, serverAddr, serverName, zone string) { + charts := httpUpstreamServerChartsTmpl.Copy() + + for _, chart := range *charts { + chart.ID = fmt.Sprintf(chart.ID, name, serverAddr, zone) + chart.Labels = []module.Label{ + {Key: "http_upstream_name", Value: name}, + {Key: "http_upstream_zone", Value: zone}, + {Key: "http_upstream_server_address", Value: serverAddr}, + {Key: "http_upstream_server_name", Value: serverName}, + } + for _, dim := range chart.Dims { + dim.ID = fmt.Sprintf(dim.ID, name, serverAddr, zone) + } + } + + if err := n.Charts().Add(*charts...); err != nil { + n.Warning(err) + } +} + +func (n *NginxPlus) removeHTTPUpstreamServerCharts(name, serverAddr, zone string) { + px := fmt.Sprintf("http_upstream_%s_server_%s_zone_%s_", name, zone, serverAddr) + n.removeCharts(px) +} + +func (n *NginxPlus) addStreamServerZoneCharts(zone string) { + charts := streamServerZoneChartsTmpl.Copy() + + for _, chart := range *charts { + chart.ID = fmt.Sprintf(chart.ID, zone) + chart.Labels = []module.Label{ + {Key: "stream_server_zone", Value: zone}, + } + for _, dim := range chart.Dims { + dim.ID = fmt.Sprintf(dim.ID, zone) + } + } + + if err := n.Charts().Add(*charts...); err != nil { + n.Warning(err) + } +} + +func (n *NginxPlus) removeStreamServerZoneCharts(zone string) { + px := fmt.Sprintf("stream_server_zone_%s_", zone) + n.removeCharts(px) +} + +func (n *NginxPlus) addStreamUpstreamCharts(zone, name string) { + charts := streamUpstreamChartsTmpl.Copy() + + for _, chart := range *charts { + chart.ID = fmt.Sprintf(chart.ID, zone, name) + chart.Labels = []module.Label{ + {Key: "stream_upstream_zone", Value: name}, + {Key: "stream_upstream_zone", Value: zone}, + } + for _, dim := range chart.Dims { + dim.ID = fmt.Sprintf(dim.ID, zone, name) + } + } + + if err := n.Charts().Add(*charts...); err != nil { + n.Warning(err) + } +} + +func (n *NginxPlus) removeStreamUpstreamCharts(name, zone string) { + px := fmt.Sprintf("stream_upstream_%s_zone_%s_", name, zone) + n.removeCharts(px) +} + +func (n *NginxPlus) addStreamUpstreamServerCharts(name, serverAddr, serverName, zone string) { + charts := streamUpstreamServerChartsTmpl.Copy() + + for _, chart := range *charts { + chart.ID = fmt.Sprintf(chart.ID, name, serverAddr, zone) + chart.Labels = []module.Label{ + {Key: "stream_upstream_name", Value: name}, + {Key: "stream_upstream_zone", Value: zone}, + {Key: "stream_upstream_server_address", Value: serverAddr}, + {Key: "stream_upstream_server_name", Value: serverName}, + } + for _, dim := range chart.Dims { + dim.ID = fmt.Sprintf(dim.ID, name, serverAddr, zone) + } + } + + if err := n.Charts().Add(*charts...); err != nil { + n.Warning(err) + } +} + +func (n *NginxPlus) removeStreamUpstreamServerCharts(name, serverAddr, zone string) { + px := fmt.Sprintf("stream_upstream_%s_server_%s_zone_%s", name, serverAddr, zone) + n.removeCharts(px) +} + +func (n *NginxPlus) addResolverZoneCharts(zone string) { + charts := resolverZoneChartsTmpl.Copy() + + for _, chart := range *charts { + chart.ID = fmt.Sprintf(chart.ID, zone) + chart.Labels = []module.Label{ + {Key: "resolver_zone", Value: zone}, + } + for _, dim := range chart.Dims { + dim.ID = fmt.Sprintf(dim.ID, zone) + } + } + + if err := n.Charts().Add(*charts...); err != nil { + n.Warning(err) + } +} + +func (n *NginxPlus) removeResolverZoneCharts(zone string) { + px := fmt.Sprintf("resolver_zone_%s_", zone) + n.removeCharts(px) +} + +func (n *NginxPlus) removeCharts(prefix string) { + for _, chart := range *n.Charts() { + if strings.HasPrefix(chart.ID, prefix) { + chart.MarkRemove() + chart.MarkNotCreated() + } + } +} diff --git a/src/go/plugin/go.d/modules/nginxplus/collect.go b/src/go/plugin/go.d/modules/nginxplus/collect.go new file mode 100644 index 00000000..f986778b --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/collect.go @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package nginxplus + +import ( + "errors" + "fmt" + "time" +) + +func (n *NginxPlus) collect() (map[string]int64, error) { + if n.apiVersion == 0 { + v, err := n.queryAPIVersion() + if err != nil { + return nil, err + } + n.apiVersion = v + } + + now := time.Now() + if now.Sub(n.queryEndpointsTime) > n.queryEndpointsEvery { + n.queryEndpointsTime = now + if err := n.queryAvailableEndpoints(); err != nil { + return nil, err + } + } + + ms := n.queryMetrics() + if ms.empty() { + return nil, errors.New("no metrics collected") + } + + mx := make(map[string]int64) + n.cache.resetUpdated() + n.collectInfo(mx, ms) + n.collectConnections(mx, ms) + n.collectSSL(mx, ms) + n.collectHTTPRequests(mx, ms) + n.collectHTTPCache(mx, ms) + n.collectHTTPServerZones(mx, ms) + n.collectHTTPLocationZones(mx, ms) + n.collectHTTPUpstreams(mx, ms) + n.collectStreamServerZones(mx, ms) + n.collectStreamUpstreams(mx, ms) + n.collectResolvers(mx, ms) + n.updateCharts() + + return mx, nil +} + +func (n *NginxPlus) collectInfo(mx map[string]int64, ms *nginxMetrics) { + if ms.info == nil { + return + } + mx["uptime"] = int64(ms.info.Timestamp.Sub(ms.info.LoadTimestamp).Seconds()) +} + +func (n *NginxPlus) collectConnections(mx map[string]int64, ms *nginxMetrics) { + if ms.connections == nil { + return + } + mx["connections_accepted"] = ms.connections.Accepted + mx["connections_dropped"] = ms.connections.Dropped + mx["connections_active"] = ms.connections.Active + mx["connections_idle"] = ms.connections.Idle +} + +func (n *NginxPlus) collectSSL(mx map[string]int64, ms *nginxMetrics) { + if ms.ssl == nil { + return + } + mx["ssl_handshakes"] = ms.ssl.Handshakes + mx["ssl_handshakes_failed"] = ms.ssl.HandshakesFailed + mx["ssl_session_reuses"] = ms.ssl.SessionReuses + mx["ssl_no_common_protocol"] = ms.ssl.NoCommonProtocol + mx["ssl_no_common_cipher"] = ms.ssl.NoCommonCipher + mx["ssl_handshake_timeout"] = ms.ssl.HandshakeTimeout + mx["ssl_peer_rejected_cert"] = ms.ssl.PeerRejectedCert + mx["ssl_verify_failures_no_cert"] = ms.ssl.VerifyFailures.NoCert + mx["ssl_verify_failures_expired_cert"] = ms.ssl.VerifyFailures.ExpiredCert + mx["ssl_verify_failures_revoked_cert"] = ms.ssl.VerifyFailures.RevokedCert + mx["ssl_verify_failures_hostname_mismatch"] = ms.ssl.VerifyFailures.HostnameMismatch + mx["ssl_verify_failures_other"] = ms.ssl.VerifyFailures.Other +} + +func (n *NginxPlus) collectHTTPRequests(mx map[string]int64, ms *nginxMetrics) { + if ms.httpRequests == nil { + return + } + mx["http_requests_total"] = ms.httpRequests.Total + mx["http_requests_current"] = ms.httpRequests.Current +} + +func (n *NginxPlus) collectHTTPCache(mx map[string]int64, ms *nginxMetrics) { + if ms.httpCaches == nil { + return + } + for name, cache := range *ms.httpCaches { + n.cache.putHTTPCache(name) + px := fmt.Sprintf("http_cache_%s_", name) + mx[px+"state_cold"] = boolToInt(cache.Cold) + mx[px+"state_warm"] = boolToInt(!cache.Cold) + mx[px+"size"] = cache.Size + mx[px+"served_responses"] = cache.Hit.Responses + cache.Stale.Responses + cache.Updating.Responses + cache.Revalidated.Responses + mx[px+"written_responses"] = cache.Miss.ResponsesWritten + cache.Expired.ResponsesWritten + cache.Bypass.ResponsesWritten + mx[px+"bypassed_responses"] = cache.Miss.Responses + cache.Expired.Responses + cache.Bypass.Responses + mx[px+"served_bytes"] = cache.Hit.Bytes + cache.Stale.Bytes + cache.Updating.Bytes + cache.Revalidated.Bytes + mx[px+"written_bytes"] = cache.Miss.BytesWritten + cache.Expired.BytesWritten + cache.Bypass.BytesWritten + mx[px+"bypassed_bytes"] = cache.Miss.Bytes + cache.Expired.Bytes + cache.Bypass.Bytes + } +} + +func (n *NginxPlus) collectHTTPServerZones(mx map[string]int64, ms *nginxMetrics) { + if ms.httpServerZones == nil { + return + } + for name, zone := range *ms.httpServerZones { + n.cache.putHTTPServerZone(name) + + px := fmt.Sprintf("http_server_zone_%s_", name) + mx[px+"requests_processing"] = zone.Processing + mx[px+"requests"] = zone.Requests + mx[px+"requests_discarded"] = zone.Discarded + mx[px+"bytes_received"] = zone.Received + mx[px+"bytes_sent"] = zone.Sent + mx[px+"responses"] = zone.Responses.Total + mx[px+"responses_1xx"] = zone.Responses.Class1xx + mx[px+"responses_2xx"] = zone.Responses.Class2xx + mx[px+"responses_3xx"] = zone.Responses.Class3xx + mx[px+"responses_4xx"] = zone.Responses.Class4xx + mx[px+"responses_5xx"] = zone.Responses.Class5xx + } +} + +func (n *NginxPlus) collectHTTPLocationZones(mx map[string]int64, ms *nginxMetrics) { + if ms.httpLocationZones == nil { + return + } + for name, zone := range *ms.httpLocationZones { + n.cache.putHTTPLocationZone(name) + + px := fmt.Sprintf("http_location_zone_%s_", name) + mx[px+"requests"] = zone.Requests + mx[px+"requests_discarded"] = zone.Discarded + mx[px+"bytes_received"] = zone.Received + mx[px+"bytes_sent"] = zone.Sent + mx[px+"responses"] = zone.Responses.Total + mx[px+"responses_1xx"] = zone.Responses.Class1xx + mx[px+"responses_2xx"] = zone.Responses.Class2xx + mx[px+"responses_3xx"] = zone.Responses.Class3xx + mx[px+"responses_4xx"] = zone.Responses.Class4xx + mx[px+"responses_5xx"] = zone.Responses.Class5xx + } +} + +func (n *NginxPlus) collectHTTPUpstreams(mx map[string]int64, ms *nginxMetrics) { + if ms.httpUpstreams == nil { + return + } + for name, upstream := range *ms.httpUpstreams { + n.cache.putHTTPUpstream(name, upstream.Zone) + + px := fmt.Sprintf("http_upstream_%s_zone_%s_", name, upstream.Zone) + mx[px+"zombies"] = upstream.Zombies + mx[px+"keepalive"] = upstream.Keepalive + mx[px+"peers"] = int64(len(upstream.Peers)) + + for _, peer := range upstream.Peers { + n.cache.putHTTPUpstreamServer(name, peer.Server, peer.Name, upstream.Zone) + + px = fmt.Sprintf("http_upstream_%s_server_%s_zone_%s_", name, peer.Server, upstream.Zone) + mx[px+"active"] = peer.Active + mx[px+"state_up"] = boolToInt(peer.State == "up") + mx[px+"state_down"] = boolToInt(peer.State == "down") + mx[px+"state_draining"] = boolToInt(peer.State == "draining") + mx[px+"state_unavail"] = boolToInt(peer.State == "unavail") + mx[px+"state_checking"] = boolToInt(peer.State == "checking") + mx[px+"state_unhealthy"] = boolToInt(peer.State == "unhealthy") + mx[px+"bytes_received"] = peer.Received + mx[px+"bytes_sent"] = peer.Sent + mx[px+"requests"] = peer.Requests + mx[px+"responses"] = peer.Responses.Total + mx[px+"responses_1xx"] = peer.Responses.Class1xx + mx[px+"responses_2xx"] = peer.Responses.Class2xx + mx[px+"responses_3xx"] = peer.Responses.Class3xx + mx[px+"responses_4xx"] = peer.Responses.Class4xx + mx[px+"responses_5xx"] = peer.Responses.Class5xx + mx[px+"response_time"] = peer.ResponseTime + mx[px+"header_time"] = peer.HeaderTime + mx[px+"downtime"] = peer.Downtime / 1000 + } + } +} + +func (n *NginxPlus) collectStreamServerZones(mx map[string]int64, ms *nginxMetrics) { + if ms.streamServerZones == nil { + return + } + for name, zone := range *ms.streamServerZones { + n.cache.putStreamServerZone(name) + + px := fmt.Sprintf("stream_server_zone_%s_", name) + mx[px+"connections"] = zone.Connections + mx[px+"connections_processing"] = zone.Processing + mx[px+"connections_discarded"] = zone.Discarded + mx[px+"bytes_received"] = zone.Received + mx[px+"bytes_sent"] = zone.Sent + mx[px+"sessions"] = zone.Sessions.Total + mx[px+"sessions_2xx"] = zone.Sessions.Class2xx + mx[px+"sessions_4xx"] = zone.Sessions.Class4xx + mx[px+"sessions_5xx"] = zone.Sessions.Class5xx + } +} + +func (n *NginxPlus) collectStreamUpstreams(mx map[string]int64, ms *nginxMetrics) { + if ms.streamUpstreams == nil { + return + } + for name, upstream := range *ms.streamUpstreams { + n.cache.putStreamUpstream(name, upstream.Zone) + + px := fmt.Sprintf("stream_upstream_%s_zone_%s_", name, upstream.Zone) + mx[px+"zombies"] = upstream.Zombies + mx[px+"peers"] = int64(len(upstream.Peers)) + + for _, peer := range upstream.Peers { + n.cache.putStreamUpstreamServer(name, peer.Server, peer.Name, upstream.Zone) + + px = fmt.Sprintf("stream_upstream_%s_server_%s_zone_%s_", name, peer.Server, upstream.Zone) + mx[px+"active"] = peer.Active + mx[px+"connections"] = peer.Connections + mx[px+"state_up"] = boolToInt(peer.State == "up") + mx[px+"state_down"] = boolToInt(peer.State == "down") + mx[px+"state_unavail"] = boolToInt(peer.State == "unavail") + mx[px+"state_checking"] = boolToInt(peer.State == "checking") + mx[px+"state_unhealthy"] = boolToInt(peer.State == "unhealthy") + mx[px+"bytes_received"] = peer.Received + mx[px+"bytes_sent"] = peer.Sent + mx[px+"downtime"] = peer.Downtime / 1000 + } + } +} + +func (n *NginxPlus) collectResolvers(mx map[string]int64, ms *nginxMetrics) { + if ms.resolvers == nil { + return + } + for name, zone := range *ms.resolvers { + n.cache.putResolver(name) + + px := fmt.Sprintf("resolver_zone_%s_", name) + mx[px+"requests_name"] = zone.Requests.Name + mx[px+"requests_srv"] = zone.Requests.Srv + mx[px+"requests_addr"] = zone.Requests.Addr + mx[px+"responses_noerror"] = zone.Responses.NoError + mx[px+"responses_formerr"] = zone.Responses.Formerr + mx[px+"responses_servfail"] = zone.Responses.Servfail + mx[px+"responses_nxdomain"] = zone.Responses.Nxdomain + mx[px+"responses_notimp"] = zone.Responses.Notimp + mx[px+"responses_refused"] = zone.Responses.Refused + mx[px+"responses_timedout"] = zone.Responses.TimedOut + mx[px+"responses_unknown"] = zone.Responses.Unknown + } +} + +func (n *NginxPlus) updateCharts() { + const notSeenLimit = 3 + + for key, v := range n.cache.httpCaches { + if v.updated && !v.hasCharts { + v.hasCharts = true + n.addHTTPCacheCharts(v.name) + continue + } + if !v.updated { + if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit { + delete(n.cache.httpCaches, key) + n.removeHTTPCacheCharts(v.name) + } + } + } + for key, v := range n.cache.httpServerZones { + if v.updated && !v.hasCharts { + v.hasCharts = true + n.addHTTPServerZoneCharts(v.zone) + continue + } + if !v.updated { + if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit { + delete(n.cache.httpServerZones, key) + n.removeHTTPServerZoneCharts(v.zone) + } + } + } + for key, v := range n.cache.httpLocationZones { + if v.updated && !v.hasCharts { + v.hasCharts = true + n.addHTTPLocationZoneCharts(v.zone) + continue + } + if !v.updated { + if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit { + delete(n.cache.httpLocationZones, key) + n.removeHTTPLocationZoneCharts(v.zone) + } + } + } + for key, v := range n.cache.httpUpstreams { + if v.updated && !v.hasCharts { + v.hasCharts = true + n.addHTTPUpstreamCharts(v.name, v.zone) + continue + } + if !v.updated { + if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit { + delete(n.cache.httpUpstreams, key) + n.removeHTTPUpstreamCharts(v.name, v.zone) + } + } + } + for key, v := range n.cache.httpUpstreamServers { + if v.updated && !v.hasCharts { + v.hasCharts = true + n.addHTTPUpstreamServerCharts(v.name, v.serverAddr, v.serverName, v.zone) + continue + } + if !v.updated { + if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit { + delete(n.cache.httpUpstreamServers, key) + n.removeHTTPUpstreamServerCharts(v.name, v.serverAddr, v.zone) + } + } + } + for key, v := range n.cache.streamServerZones { + if v.updated && !v.hasCharts { + v.hasCharts = true + n.addStreamServerZoneCharts(v.zone) + continue + } + if !v.updated { + if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit { + delete(n.cache.streamServerZones, key) + n.removeStreamServerZoneCharts(v.zone) + } + } + } + for key, v := range n.cache.streamUpstreams { + if v.updated && !v.hasCharts { + v.hasCharts = true + n.addStreamUpstreamCharts(v.name, v.zone) + continue + } + if !v.updated { + if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit { + delete(n.cache.streamUpstreams, key) + n.removeStreamUpstreamCharts(v.name, v.zone) + } + } + } + for key, v := range n.cache.streamUpstreamServers { + if v.updated && !v.hasCharts { + v.hasCharts = true + n.addStreamUpstreamServerCharts(v.name, v.serverAddr, v.serverName, v.zone) + continue + } + if !v.updated { + if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit { + delete(n.cache.streamUpstreamServers, key) + n.removeStreamUpstreamServerCharts(v.name, v.serverAddr, v.zone) + } + } + } + for key, v := range n.cache.resolvers { + if v.updated && !v.hasCharts { + v.hasCharts = true + n.addResolverZoneCharts(v.zone) + continue + } + if !v.updated { + if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit { + delete(n.cache.resolvers, key) + n.removeResolverZoneCharts(v.zone) + } + } + } +} + +func boolToInt(v bool) int64 { + if v { + return 1 + } + return 0 +} diff --git a/src/go/plugin/go.d/modules/nginxplus/config_schema.json b/src/go/plugin/go.d/modules/nginxplus/config_schema.json new file mode 100644 index 00000000..fd4c38ef --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/config_schema.json @@ -0,0 +1,183 @@ +{ + "jsonSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NGINX Plus collector configuration.", + "type": "object", + "properties": { + "update_every": { + "title": "Update every", + "description": "Data collection interval, measured in seconds.", + "type": "integer", + "minimum": 1, + "default": 1 + }, + "url": { + "title": "URL", + "description": "The base URL of the NGINX Plus webserver.", + "type": "string", + "default": "http://127.0.0.1:80", + "format": "uri" + }, + "timeout": { + "title": "Timeout", + "description": "The timeout in seconds for the HTTP request.", + "type": "number", + "minimum": 0.5, + "default": 1 + }, + "not_follow_redirects": { + "title": "Not follow redirects", + "description": "If set, the client will not follow HTTP redirects automatically.", + "type": "boolean" + }, + "username": { + "title": "Username", + "description": "The username for basic authentication.", + "type": "string", + "sensitive": true + }, + "password": { + "title": "Password", + "description": "The password for basic authentication.", + "type": "string", + "sensitive": true + }, + "proxy_url": { + "title": "Proxy URL", + "description": "The URL of the proxy server.", + "type": "string" + }, + "proxy_username": { + "title": "Proxy username", + "description": "The username for proxy authentication.", + "type": "string", + "sensitive": true + }, + "proxy_password": { + "title": "Proxy password", + "description": "The password for proxy authentication.", + "type": "string", + "sensitive": true + }, + "headers": { + "title": "Headers", + "description": "Additional HTTP headers to include in the request.", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "tls_skip_verify": { + "title": "Skip TLS verification", + "description": "If set, TLS certificate verification will be skipped.", + "type": "boolean" + }, + "tls_ca": { + "title": "TLS CA", + "description": "The path to the CA certificate file for TLS verification.", + "type": "string", + "pattern": "^$|^/" + }, + "tls_cert": { + "title": "TLS certificate", + "description": "The path to the client certificate file for TLS authentication.", + "type": "string", + "pattern": "^$|^/" + }, + "tls_key": { + "title": "TLS key", + "description": "The path to the client key file for TLS authentication.", + "type": "string", + "pattern": "^$|^/" + }, + "body": { + "title": "Body", + "type": "string" + }, + "method": { + "title": "Method", + "type": "string" + } + }, + "required": [ + "url" + ], + "additionalProperties": false, + "patternProperties": { + "^name$": {} + } + }, + "uiSchema": { + "ui:flavour": "tabs", + "ui:options": { + "tabs": [ + { + "title": "Base", + "fields": [ + "update_every", + "url", + "timeout", + "not_follow_redirects" + ] + }, + { + "title": "Auth", + "fields": [ + "username", + "password" + ] + }, + { + "title": "TLS", + "fields": [ + "tls_skip_verify", + "tls_ca", + "tls_cert", + "tls_key" + ] + }, + { + "title": "Proxy", + "fields": [ + "proxy_url", + "proxy_username", + "proxy_password" + ] + }, + { + "title": "Headers", + "fields": [ + "headers" + ] + } + ] + }, + "uiOptions": { + "fullPage": true + }, + "body": { + "ui:widget": "hidden" + }, + "method": { + "ui:widget": "hidden" + }, + "timeout": { + "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)." + }, + "username": { + "ui:widget": "password" + }, + "proxy_username": { + "ui:widget": "password" + }, + "password": { + "ui:widget": "password" + }, + "proxy_password": { + "ui:widget": "password" + } + } +} diff --git a/src/go/plugin/go.d/modules/nginxplus/integrations/nginx_plus.md b/src/go/plugin/go.d/modules/nginxplus/integrations/nginx_plus.md new file mode 100644 index 00000000..9ebb4b19 --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/integrations/nginx_plus.md @@ -0,0 +1,448 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/go/plugin/go.d/modules/nginxplus/README.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/go/plugin/go.d/modules/nginxplus/metadata.yaml" +sidebar_label: "NGINX Plus" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/Web Servers and Web Proxies" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# NGINX Plus + + +<img src="https://netdata.cloud/img/nginxplus.svg" width="150"/> + + +Plugin: go.d.plugin +Module: nginxplus + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +This collector monitors NGINX Plus servers. + + + + +This collector is supported on all platforms. + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + + +### Default Behavior + +#### Auto-Detection + +This integration doesn't support auto-detection. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +The default configuration for this integration is not expected to impose a significant performance impact on the system. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per NGINX Plus instance + +These metrics refer to the entire monitored application. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| nginxplus.client_connections_rate | accepted, dropped | connections/s | +| nginxplus.client_connections_count | active, idle | connections | +| nginxplus.ssl_handshakes_rate | successful, failed | handshakes/s | +| nginxplus.ssl_handshakes_failures_rate | no_common_protocol, no_common_cipher, timeout, peer_rejected_cert | failures/s | +| nginxplus.ssl_verification_errors_rate | no_cert, expired_cert, revoked_cert, hostname_mismatch, other | errors/s | +| nginxplus.ssl_session_reuses_rate | ssl_session | reuses/s | +| nginxplus.http_requests_rate | requests | requests/s | +| nginxplus.http_requests_count | requests | requests | +| nginxplus.uptime | uptime | seconds | + +### Per http server zone + +These metrics refer to the HTTP server zone. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| http_server_zone | HTTP server zone name | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| nginxplus.http_server_zone_requests_rate | requests | requests/s | +| nginxplus.http_server_zone_responses_per_code_class_rate | 1xx, 2xx, 3xx, 4xx, 5xx | responses/s | +| nginxplus.http_server_zone_traffic_rate | received, sent | bytes/s | +| nginxplus.http_server_zone_requests_processing_count | processing | requests | +| nginxplus.http_server_zone_requests_discarded_rate | discarded | requests/s | + +### Per http location zone + +These metrics refer to the HTTP location zone. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| http_location_zone | HTTP location zone name | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| nginxplus.http_location_zone_requests_rate | requests | requests/s | +| nginxplus.http_location_zone_responses_per_code_class_rate | 1xx, 2xx, 3xx, 4xx, 5xx | responses/s | +| nginxplus.http_location_zone_traffic_rate | received, sent | bytes/s | +| nginxplus.http_location_zone_requests_discarded_rate | discarded | requests/s | + +### Per http upstream + +These metrics refer to the HTTP upstream. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| http_upstream_name | HTTP upstream name | +| http_upstream_zone | HTTP upstream zone name | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| nginxplus.http_upstream_peers_count | peers | peers | +| nginxplus.http_upstream_zombies_count | zombie | servers | +| nginxplus.http_upstream_keepalive_count | keepalive | connections | + +### Per http upstream server + +These metrics refer to the HTTP upstream server. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| http_upstream_name | HTTP upstream name | +| http_upstream_zone | HTTP upstream zone name | +| http_upstream_server_address | HTTP upstream server address (e.g. 127.0.0.1:81) | +| http_upstream_server_name | HTTP upstream server name | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| nginxplus.http_upstream_server_requests_rate | requests | requests/s | +| nginxplus.http_upstream_server_responses_per_code_class_rate | 1xx, 2xx, 3xx, 4xx, 5xx | responses/s | +| nginxplus.http_upstream_server_response_time | response | milliseconds | +| nginxplus.http_upstream_server_response_header_time | header | milliseconds | +| nginxplus.http_upstream_server_traffic_rate | received, sent | bytes/s | +| nginxplus.http_upstream_server_state | up, down, draining, unavail, checking, unhealthy | state | +| nginxplus.http_upstream_server_connections_count | active | connections | +| nginxplus.http_upstream_server_downtime | downtime | seconds | + +### Per http cache + +These metrics refer to the HTTP cache. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| http_cache | HTTP cache name | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| nginxplus.http_cache_state | warm, cold | state | +| nginxplus.http_cache_iops | served, written, bypass | responses/s | +| nginxplus.http_cache_io | served, written, bypass | bytes/s | +| nginxplus.http_cache_size | size | bytes | + +### Per stream server zone + +These metrics refer to the Stream server zone. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| stream_server_zone | Stream server zone name | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| nginxplus.stream_server_zone_connections_rate | accepted | connections/s | +| nginxplus.stream_server_zone_sessions_per_code_class_rate | 2xx, 4xx, 5xx | sessions/s | +| nginxplus.stream_server_zone_traffic_rate | received, sent | bytes/s | +| nginxplus.stream_server_zone_connections_processing_count | processing | connections | +| nginxplus.stream_server_zone_connections_discarded_rate | discarded | connections/s | + +### Per stream upstream + +These metrics refer to the Stream upstream. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| stream_upstream_name | Stream upstream name | +| stream_upstream_zone | Stream upstream zone name | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| nginxplus.stream_upstream_peers_count | peers | peers | +| nginxplus.stream_upstream_zombies_count | zombie | servers | + +### Per stream upstream server + +These metrics refer to the Stream upstream server. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| stream_upstream_name | Stream upstream name | +| stream_upstream_zone | Stream upstream zone name | +| stream_upstream_server_address | Stream upstream server address (e.g. 127.0.0.1:12346) | +| stream_upstream_server_name | Stream upstream server name | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| nginxplus.stream_upstream_server_connections_rate | forwarded | connections/s | +| nginxplus.stream_upstream_server_traffic_rate | received, sent | bytes/s | +| nginxplus.stream_upstream_server_state | up, down, unavail, checking, unhealthy | state | +| nginxplus.stream_upstream_server_downtime | downtime | seconds | +| nginxplus.stream_upstream_server_connections_count | active | connections | + +### Per resolver zone + +These metrics refer to the resolver zone. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| resolver_zone | resolver zone name | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| nginxplus.resolver_zone_requests_rate | name, srv, addr | requests/s | +| nginxplus.resolver_zone_responses_rate | noerror, formerr, servfail, nxdomain, notimp, refused, timedout, unknown | responses/s | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Config API + +To configure API, see the [official documentation](https://docs.nginx.com/nginx/admin-guide/monitoring/live-activity-monitoring/#configuring-the-api). + + + +### Configuration + +#### File + +The configuration file name for this integration is `go.d/nginxplus.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config go.d/nginxplus.conf +``` +#### Options + +The following options can be defined globally: update_every, autodetection_retry. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update_every | Data collection frequency. | 1 | no | +| autodetection_retry | Recheck interval in seconds. Zero means no recheck will be scheduled. | 0 | no | +| url | Server URL. | http://127.0.0.1 | yes | +| timeout | HTTP request timeout. | 1 | no | +| username | Username for basic HTTP authentication. | | no | +| password | Password for basic HTTP authentication. | | no | +| proxy_url | Proxy URL. | | no | +| proxy_username | Username for proxy basic HTTP authentication. | | no | +| proxy_password | Password for proxy basic HTTP authentication. | | no | +| method | HTTP request method. | GET | no | +| body | HTTP request body. | | no | +| headers | HTTP request headers. | | no | +| not_follow_redirects | Redirect handling policy. Controls whether the client follows redirects. | no | no | +| tls_skip_verify | Server certificate chain and hostname validation policy. Controls whether the client performs this check. | no | no | +| tls_ca | Certification authority that the client uses when verifying the server's certificates. | | no | +| tls_cert | Client TLS certificate. | | no | +| tls_key | Client TLS key. | | no | + +</details> + +#### Examples + +##### Basic + +A basic example configuration. + +<details open><summary>Config</summary> + +```yaml +jobs: + - name: local + url: http://127.0.0.1 + +``` +</details> + +##### HTTP authentication + +Basic HTTP authentication. + +<details open><summary>Config</summary> + +```yaml +jobs: + - name: local + url: http://127.0.0.1 + username: username + password: password + +``` +</details> + +##### HTTPS with self-signed certificate + +NGINX Plus with enabled HTTPS and self-signed certificate. + +<details open><summary>Config</summary> + +```yaml +jobs: + - name: local + url: https://127.0.0.1 + tls_skip_verify: yes + +``` +</details> + +##### Multi-instance + +> **Note**: When you define multiple jobs, their names must be unique. + +Collecting metrics from local and remote instances. + + +<details open><summary>Config</summary> + +```yaml +jobs: + - name: local + url: http://127.0.0.1 + + - name: remote + url: http://192.0.2.1 + +``` +</details> + + + +## Troubleshooting + +### Debug Mode + +**Important**: Debug mode is not supported for data collection jobs created via the UI using the Dyncfg feature. + +To troubleshoot issues with the `nginxplus` collector, run the `go.d.plugin` with the debug option enabled. The output +should give you clues as to why the collector isn't working. + +- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on + your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`. + + ```bash + cd /usr/libexec/netdata/plugins.d/ + ``` + +- Switch to the `netdata` user. + + ```bash + sudo -u netdata -s + ``` + +- Run the `go.d.plugin` to debug the collector: + + ```bash + ./go.d.plugin -d -m nginxplus + ``` + +### Getting Logs + +If you're encountering problems with the `nginxplus` collector, follow these steps to retrieve logs and identify potential issues: + +- **Run the command** specific to your system (systemd, non-systemd, or Docker container). +- **Examine the output** for any warnings or error messages that might indicate issues. These messages should provide clues about the root cause of the problem. + +#### System with systemd + +Use the following command to view logs generated since the last Netdata service restart: + +```bash +journalctl _SYSTEMD_INVOCATION_ID="$(systemctl show --value --property=InvocationID netdata)" --namespace=netdata --grep nginxplus +``` + +#### System without systemd + +Locate the collector log file, typically at `/var/log/netdata/collector.log`, and use `grep` to filter for collector's name: + +```bash +grep nginxplus /var/log/netdata/collector.log +``` + +**Note**: This method shows logs from all restarts. Focus on the **latest entries** for troubleshooting current issues. + +#### Docker Container + +If your Netdata runs in a Docker container named "netdata" (replace if different), use this command: + +```bash +docker logs netdata 2>&1 | grep nginxplus +``` + + diff --git a/src/go/plugin/go.d/modules/nginxplus/metadata.yaml b/src/go/plugin/go.d/modules/nginxplus/metadata.yaml new file mode 100644 index 00000000..6bc3a29b --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/metadata.yaml @@ -0,0 +1,584 @@ +plugin_name: go.d.plugin +modules: + - meta: + id: collector-go.d.plugin-nginxplus + plugin_name: go.d.plugin + module_name: nginxplus + monitored_instance: + name: NGINX Plus + link: https://www.nginx.com/products/nginx/ + icon_filename: nginxplus.svg + categories: + - data-collection.web-servers-and-web-proxies + keywords: + - nginxplus + - nginx + - web + - webserver + - http + - proxy + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + most_popular: false + overview: + data_collection: + metrics_description: | + This collector monitors NGINX Plus servers. + method_description: "" + supported_platforms: + include: [] + exclude: [] + multi_instance: true + additional_permissions: + description: "" + default_behavior: + auto_detection: + description: "" + limits: + description: "" + performance_impact: + description: "" + setup: + prerequisites: + list: + - title: Config API + description: | + To configure API, see the [official documentation](https://docs.nginx.com/nginx/admin-guide/monitoring/live-activity-monitoring/#configuring-the-api). + configuration: + file: + name: go.d/nginxplus.conf + options: + description: | + The following options can be defined globally: update_every, autodetection_retry. + folding: + title: Config options + enabled: true + list: + - name: update_every + description: Data collection frequency. + default_value: 1 + required: false + - name: autodetection_retry + description: Recheck interval in seconds. Zero means no recheck will be scheduled. + default_value: 0 + required: false + - name: url + description: Server URL. + default_value: http://127.0.0.1 + required: true + - name: timeout + description: HTTP request timeout. + default_value: 1 + required: false + - name: username + description: Username for basic HTTP authentication. + default_value: "" + required: false + - name: password + description: Password for basic HTTP authentication. + default_value: "" + required: false + - name: proxy_url + description: Proxy URL. + default_value: "" + required: false + - name: proxy_username + description: Username for proxy basic HTTP authentication. + default_value: "" + required: false + - name: proxy_password + description: Password for proxy basic HTTP authentication. + default_value: "" + required: false + - name: method + description: HTTP request method. + default_value: GET + required: false + - name: body + description: HTTP request body. + default_value: "" + required: false + - name: headers + description: HTTP request headers. + default_value: "" + required: false + - name: not_follow_redirects + description: Redirect handling policy. Controls whether the client follows redirects. + default_value: false + required: false + - name: tls_skip_verify + description: Server certificate chain and hostname validation policy. Controls whether the client performs this check. + default_value: false + required: false + - name: tls_ca + description: Certification authority that the client uses when verifying the server's certificates. + default_value: "" + required: false + - name: tls_cert + description: Client TLS certificate. + default_value: "" + required: false + - name: tls_key + description: Client TLS key. + default_value: "" + required: false + examples: + folding: + title: Config + enabled: true + list: + - name: Basic + description: A basic example configuration. + config: | + jobs: + - name: local + url: http://127.0.0.1 + - name: HTTP authentication + description: Basic HTTP authentication. + config: | + jobs: + - name: local + url: http://127.0.0.1 + username: username + password: password + - name: HTTPS with self-signed certificate + description: NGINX Plus with enabled HTTPS and self-signed certificate. + config: | + jobs: + - name: local + url: https://127.0.0.1 + tls_skip_verify: yes + - name: Multi-instance + description: | + > **Note**: When you define multiple jobs, their names must be unique. + + Collecting metrics from local and remote instances. + config: | + jobs: + - name: local + url: http://127.0.0.1 + + - name: remote + url: http://192.0.2.1 + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: global + description: These metrics refer to the entire monitored application. + labels: [] + metrics: + - name: nginxplus.client_connections_rate + description: Client connections rate + unit: connections/s + chart_type: line + dimensions: + - name: accepted + - name: dropped + - name: nginxplus.client_connections_count + description: Client connections + unit: connections + chart_type: line + dimensions: + - name: active + - name: idle + - name: nginxplus.ssl_handshakes_rate + description: SSL handshakes rate + unit: handshakes/s + chart_type: line + dimensions: + - name: successful + - name: failed + - name: nginxplus.ssl_handshakes_failures_rate + description: SSL handshakes failures rate + unit: failures/s + chart_type: stacked + dimensions: + - name: no_common_protocol + - name: no_common_cipher + - name: timeout + - name: peer_rejected_cert + - name: nginxplus.ssl_verification_errors_rate + description: SSL verification errors rate + unit: errors/s + chart_type: stacked + dimensions: + - name: no_cert + - name: expired_cert + - name: revoked_cert + - name: hostname_mismatch + - name: other + - name: nginxplus.ssl_session_reuses_rate + description: Session reuses during SSL handshak + unit: reuses/s + chart_type: line + dimensions: + - name: ssl_session + - name: nginxplus.http_requests_rate + description: HTTP requests rate + unit: requests/s + chart_type: line + dimensions: + - name: requests + - name: nginxplus.http_requests_count + description: HTTP requests + unit: requests + chart_type: line + dimensions: + - name: requests + - name: nginxplus.uptime + description: Uptime + unit: seconds + chart_type: line + dimensions: + - name: uptime + - name: http server zone + description: These metrics refer to the HTTP server zone. + labels: + - name: http_server_zone + description: HTTP server zone name + metrics: + - name: nginxplus.http_server_zone_requests_rate + description: HTTP Server Zone requests rate + unit: requests/s + chart_type: line + dimensions: + - name: requests + - name: nginxplus.http_server_zone_responses_per_code_class_rate + description: HTTP Server Zone responses rate + unit: responses/s + chart_type: stacked + dimensions: + - name: 1xx + - name: 2xx + - name: 3xx + - name: 4xx + - name: 5xx + - name: nginxplus.http_server_zone_traffic_rate + description: HTTP Server Zone traffic + unit: bytes/s + chart_type: area + dimensions: + - name: received + - name: sent + - name: nginxplus.http_server_zone_requests_processing_count + description: HTTP Server Zone currently processed requests + unit: requests + chart_type: line + dimensions: + - name: processing + - name: nginxplus.http_server_zone_requests_discarded_rate + description: HTTP Server Zone requests discarded rate + unit: requests/s + chart_type: line + dimensions: + - name: discarded + - name: http location zone + description: These metrics refer to the HTTP location zone. + labels: + - name: http_location_zone + description: HTTP location zone name + metrics: + - name: nginxplus.http_location_zone_requests_rate + description: HTTP Location Zone requests rate + unit: requests/s + chart_type: line + dimensions: + - name: requests + - name: nginxplus.http_location_zone_responses_per_code_class_rate + description: HTTP Location Zone responses rate + unit: responses/s + chart_type: stacked + dimensions: + - name: 1xx + - name: 2xx + - name: 3xx + - name: 4xx + - name: 5xx + - name: nginxplus.http_location_zone_traffic_rate + description: HTTP Location Zone traffic rate + unit: bytes/s + chart_type: area + dimensions: + - name: received + - name: sent + - name: nginxplus.http_location_zone_requests_discarded_rate + description: HTTP Location Zone requests discarded rate + unit: requests/s + chart_type: line + dimensions: + - name: discarded + - name: http upstream + description: These metrics refer to the HTTP upstream. + labels: + - name: http_upstream_name + description: HTTP upstream name + - name: http_upstream_zone + description: HTTP upstream zone name + metrics: + - name: nginxplus.http_upstream_peers_count + description: HTTP Upstream peers + unit: peers + chart_type: line + dimensions: + - name: peers + - name: nginxplus.http_upstream_zombies_count + description: HTTP Upstream zombies + unit: servers + chart_type: line + dimensions: + - name: zombie + - name: nginxplus.http_upstream_keepalive_count + description: HTTP Upstream keepalive + unit: connections + chart_type: line + dimensions: + - name: keepalive + - name: http upstream server + description: These metrics refer to the HTTP upstream server. + labels: + - name: http_upstream_name + description: HTTP upstream name + - name: http_upstream_zone + description: HTTP upstream zone name + - name: http_upstream_server_address + description: HTTP upstream server address (e.g. 127.0.0.1:81) + - name: http_upstream_server_name + description: HTTP upstream server name + metrics: + - name: nginxplus.http_upstream_server_requests_rate + description: HTTP Upstream Server requests + unit: requests/s + chart_type: line + dimensions: + - name: requests + - name: nginxplus.http_upstream_server_responses_per_code_class_rate + description: HTTP Upstream Server responses + unit: responses/s + chart_type: stacked + dimensions: + - name: 1xx + - name: 2xx + - name: 3xx + - name: 4xx + - name: 5xx + - name: nginxplus.http_upstream_server_response_time + description: HTTP Upstream Server average response time + unit: milliseconds + chart_type: line + dimensions: + - name: response + - name: nginxplus.http_upstream_server_response_header_time + description: HTTP Upstream Server average response header time + unit: milliseconds + chart_type: line + dimensions: + - name: header + - name: nginxplus.http_upstream_server_traffic_rate + description: HTTP Upstream Server traffic rate + unit: bytes/s + chart_type: area + dimensions: + - name: received + - name: sent + - name: nginxplus.http_upstream_server_state + description: HTTP Upstream Server state + unit: state + chart_type: line + dimensions: + - name: up + - name: down + - name: draining + - name: unavail + - name: checking + - name: unhealthy + - name: nginxplus.http_upstream_server_connections_count + description: HTTP Upstream Server connections + unit: connections + chart_type: line + dimensions: + - name: active + - name: nginxplus.http_upstream_server_downtime + description: HTTP Upstream Server downtime + unit: seconds + chart_type: line + dimensions: + - name: downtime + - name: http cache + description: These metrics refer to the HTTP cache. + labels: + - name: http_cache + description: HTTP cache name + metrics: + - name: nginxplus.http_cache_state + description: HTTP Cache state + unit: state + chart_type: line + dimensions: + - name: warm + - name: cold + - name: nginxplus.http_cache_iops + description: HTTP Cache size + unit: responses/s + chart_type: line + dimensions: + - name: served + - name: written + - name: bypass + - name: nginxplus.http_cache_io + description: HTTP Cache IOPS + unit: bytes/s + chart_type: line + dimensions: + - name: served + - name: written + - name: bypass + - name: nginxplus.http_cache_size + description: HTTP Cache IO + unit: bytes + chart_type: line + dimensions: + - name: size + - name: stream server zone + description: These metrics refer to the Stream server zone. + labels: + - name: stream_server_zone + description: Stream server zone name + metrics: + - name: nginxplus.stream_server_zone_connections_rate + description: Stream Server Zone connections rate + unit: connections/s + chart_type: line + dimensions: + - name: accepted + - name: nginxplus.stream_server_zone_sessions_per_code_class_rate + description: Stream Server Zone sessions rate + unit: sessions/s + chart_type: stacked + dimensions: + - name: 2xx + - name: 4xx + - name: 5xx + - name: nginxplus.stream_server_zone_traffic_rate + description: Stream Server Zone traffic rate + unit: bytes/s + chart_type: area + dimensions: + - name: received + - name: sent + - name: nginxplus.stream_server_zone_connections_processing_count + description: Stream Server Zone connections processed + unit: connections + chart_type: line + dimensions: + - name: processing + - name: nginxplus.stream_server_zone_connections_discarded_rate + description: Stream Server Zone connections discarded + unit: connections/s + chart_type: line + dimensions: + - name: discarded + - name: stream upstream + description: These metrics refer to the Stream upstream. + labels: + - name: stream_upstream_name + description: Stream upstream name + - name: stream_upstream_zone + description: Stream upstream zone name + metrics: + - name: nginxplus.stream_upstream_peers_count + description: Stream Upstream peers + unit: peers + chart_type: line + dimensions: + - name: peers + - name: nginxplus.stream_upstream_zombies_count + description: Stream Upstream zombies + unit: servers + chart_type: line + dimensions: + - name: zombie + - name: stream upstream server + description: These metrics refer to the Stream upstream server. + labels: + - name: stream_upstream_name + description: Stream upstream name + - name: stream_upstream_zone + description: Stream upstream zone name + - name: stream_upstream_server_address + description: Stream upstream server address (e.g. 127.0.0.1:12346) + - name: stream_upstream_server_name + description: Stream upstream server name + metrics: + - name: nginxplus.stream_upstream_server_connections_rate + description: Stream Upstream Server connections + unit: connections/s + chart_type: line + dimensions: + - name: forwarded + - name: nginxplus.stream_upstream_server_traffic_rate + description: Stream Upstream Server traffic rate + unit: bytes/s + chart_type: area + dimensions: + - name: received + - name: sent + - name: nginxplus.stream_upstream_server_state + description: Stream Upstream Server state + unit: state + chart_type: line + dimensions: + - name: up + - name: down + - name: unavail + - name: checking + - name: unhealthy + - name: nginxplus.stream_upstream_server_downtime + description: Stream Upstream Server downtime + unit: seconds + chart_type: line + dimensions: + - name: downtime + - name: nginxplus.stream_upstream_server_connections_count + description: Stream Upstream Server connections + unit: connections + chart_type: line + dimensions: + - name: active + - name: resolver zone + description: These metrics refer to the resolver zone. + labels: + - name: resolver_zone + description: resolver zone name + metrics: + - name: nginxplus.resolver_zone_requests_rate + description: Resolver requests rate + unit: requests/s + chart_type: stacked + dimensions: + - name: name + - name: srv + - name: addr + - name: nginxplus.resolver_zone_responses_rate + description: Resolver responses rate + unit: responses/s + chart_type: stacked + dimensions: + - name: noerror + - name: formerr + - name: servfail + - name: nxdomain + - name: notimp + - name: refused + - name: timedout + - name: unknown diff --git a/src/go/plugin/go.d/modules/nginxplus/nginx_http_api.go b/src/go/plugin/go.d/modules/nginxplus/nginx_http_api.go new file mode 100644 index 00000000..0f7999ac --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/nginx_http_api.go @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package nginxplus + +import "time" + +// https://demo.nginx.com/dashboard.html +// https://demo.nginx.com/swagger-ui/ +// http://nginx.org/en/docs/http/ngx_http_api_module.html + +type nginxAPIVersions []int64 + +type ( + nginxInfo struct { + Version string `json:"version"` + Build string `json:"build"` + Address string `json:"address"` + Generation int `json:"generation"` + LoadTimestamp time.Time `json:"load_timestamp"` + Timestamp time.Time `json:"timestamp"` + } + nginxConnections struct { + Accepted int64 `json:"accepted"` + Dropped int64 `json:"dropped"` + Active int64 `json:"active"` + Idle int64 `json:"idle"` + } + nginxSSL struct { + Handshakes int64 `json:"handshakes"` + HandshakesFailed int64 `json:"handshakes_failed"` + SessionReuses int64 `json:"session_reuses"` + NoCommonProtocol int64 `json:"no_common_protocol"` + NoCommonCipher int64 `json:"no_common_cipher"` + HandshakeTimeout int64 `json:"handshake_timeout"` + PeerRejectedCert int64 `json:"peer_rejected_cert"` + VerifyFailures struct { + NoCert int64 `json:"no_cert"` + ExpiredCert int64 `json:"expired_cert"` + RevokedCert int64 `json:"revoked_cert"` + HostnameMismatch int64 `json:"hostname_mismatch"` + Other int64 `json:"other"` + } `json:"verify_failures"` + } +) + +type ( + nginxHTTPRequests struct { + Total int64 `json:"total"` + Current int64 `json:"current"` + } + nginxHTTPServerZones map[string]struct { + Processing int64 `json:"processing"` + Requests int64 `json:"requests"` + Responses struct { + Class1xx int64 `json:"1xx"` + Class2xx int64 `json:"2xx"` + Class3xx int64 `json:"3xx"` + Class4xx int64 `json:"4xx"` + Class5xx int64 `json:"5xx"` + Total int64 + } `json:"responses"` + Discarded int64 `json:"discarded"` + Received int64 `json:"received"` + Sent int64 `json:"sent"` + } + nginxHTTPLocationZones map[string]struct { + Requests int64 `json:"requests"` + Responses struct { + Class1xx int64 `json:"1xx"` + Class2xx int64 `json:"2xx"` + Class3xx int64 `json:"3xx"` + Class4xx int64 `json:"4xx"` + Class5xx int64 `json:"5xx"` + Total int64 + } `json:"responses"` + Discarded int64 `json:"discarded"` + Received int64 `json:"received"` + Sent int64 `json:"sent"` + } + nginxHTTPUpstreams map[string]struct { + Peers []struct { + Id int64 `json:"id"` + Server string `json:"server"` + Name string `json:"name"` + Backup bool `json:"backup"` + Weight int64 `json:"weight"` + State string `json:"state"` + Active int64 `json:"active"` + Requests int64 `json:"requests"` + HeaderTime int64 `json:"header_time"` + ResponseTime int64 `json:"response_time"` + Responses struct { + Class1xx int64 `json:"1xx"` + Class2xx int64 `json:"2xx"` + Class3xx int64 `json:"3xx"` + Class4xx int64 `json:"4xx"` + Class5xx int64 `json:"5xx"` + Total int64 + } `json:"responses"` + Sent int64 `json:"sent"` + Received int64 `json:"received"` + Fails int64 `json:"fails"` + Unavail int64 `json:"unavail"` + HealthChecks struct { + Checks int64 `json:"checks"` + Fails int64 `json:"fails"` + Unhealthy int64 `json:"unhealthy"` + } `json:"health_checks"` + Downtime int64 `json:"downtime"` + Selected time.Time `json:"selected"` + } `json:"peers"` + Keepalive int64 `json:"keepalive"` + Zombies int64 `json:"zombies"` + Zone string `json:"zone"` + } + nginxHTTPCaches map[string]struct { + Size int64 `json:"size"` + Cold bool `json:"cold"` + Hit struct { + Responses int64 `json:"responses"` + Bytes int64 `json:"bytes"` + } `json:"hit"` + Stale struct { + Responses int64 `json:"responses"` + Bytes int64 `json:"bytes"` + } `json:"stale"` + Updating struct { + Responses int64 `json:"responses"` + Bytes int64 `json:"bytes"` + } `json:"updating"` + Revalidated struct { + Responses int64 `json:"responses"` + Bytes int64 `json:"bytes"` + } `json:"revalidated"` + Miss struct { + Responses int64 `json:"responses"` + Bytes int64 `json:"bytes"` + ResponsesWritten int64 `json:"responses_written"` + BytesWritten int64 `json:"bytes_written"` + } `json:"miss"` + Expired struct { + Responses int64 `json:"responses"` + Bytes int64 `json:"bytes"` + ResponsesWritten int64 `json:"responses_written"` + BytesWritten int64 `json:"bytes_written"` + } `json:"expired"` + Bypass struct { + Responses int64 `json:"responses"` + Bytes int64 `json:"bytes"` + ResponsesWritten int64 `json:"responses_written"` + BytesWritten int64 `json:"bytes_written"` + } `json:"bypass"` + } +) + +type ( + nginxStreamServerZones map[string]struct { + Processing int64 `json:"processing"` + Connections int64 `json:"connections"` + Sessions struct { + Class2xx int64 `json:"2xx"` + Class4xx int64 `json:"4xx"` + Class5xx int64 `json:"5xx"` + Total int64 `json:"total"` + } `json:"sessions"` + Discarded int64 `json:"discarded"` + Received int64 `json:"received"` + Sent int64 `json:"sent"` + } + nginxStreamUpstreams map[string]struct { + Peers []struct { + Id int64 `json:"id"` + Server string `json:"server"` + Name string `json:"name"` + Backup bool `json:"backup"` + Weight int64 `json:"weight"` + State string `json:"state"` + Active int64 `json:"active"` + Connections int64 `json:"connections"` + Sent int64 `json:"sent"` + Received int64 `json:"received"` + Fails int64 `json:"fails"` + Unavail int64 `json:"unavail"` + HealthChecks struct { + Checks int64 `json:"checks"` + Fails int64 `json:"fails"` + Unhealthy int64 `json:"unhealthy"` + } `json:"health_checks"` + Downtime int64 `json:"downtime"` + } `json:"peers"` + Zombies int64 `json:"zombies"` + Zone string `json:"zone"` + } +) + +type nginxResolvers map[string]struct { + Requests struct { + Name int64 `json:"name"` + Srv int64 `json:"srv"` + Addr int64 `json:"addr"` + } `json:"requests"` + Responses struct { + NoError int64 `json:"noerror"` + Formerr int64 `json:"formerr"` + Servfail int64 `json:"servfail"` + Nxdomain int64 `json:"nxdomain"` + Notimp int64 `json:"notimp"` + Refused int64 `json:"refused"` + TimedOut int64 `json:"timedout"` + Unknown int64 `json:"unknown"` + } `json:"responses"` +} diff --git a/src/go/plugin/go.d/modules/nginxplus/nginx_http_api_query.go b/src/go/plugin/go.d/modules/nginxplus/nginx_http_api_query.go new file mode 100644 index 00000000..b54cd142 --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/nginx_http_api_query.go @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package nginxplus + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "sync" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/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.NewHTTPRequestWithPath(n.Request, 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.NewHTTPRequestWithPath(n.Request, 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.NewHTTPRequestWithPath(n.Request, 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.NewHTTPRequestWithPath(n.Request, 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.NewHTTPRequestWithPath(n.Request, 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.NewHTTPRequestWithPath(n.Request, 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.NewHTTPRequestWithPath(n.Request, 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.NewHTTPRequestWithPath(n.Request, 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.NewHTTPRequestWithPath(n.Request, 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.NewHTTPRequestWithPath(n.Request, 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.NewHTTPRequestWithPath(n.Request, 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.NewHTTPRequestWithPath(n.Request, 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.NewHTTPRequestWithPath(n.Request, 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.NewHTTPRequestWithPath(n.Request, 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.NewHTTPRequestWithPath(n.Request, 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 +} diff --git a/src/go/plugin/go.d/modules/nginxplus/nginxplus.go b/src/go/plugin/go.d/modules/nginxplus/nginxplus.go new file mode 100644 index 00000000..f737e681 --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/nginxplus.go @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package nginxplus + +import ( + _ "embed" + "errors" + "net/http" + "time" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web" +) + +//go:embed "config_schema.json" +var configSchema string + +func init() { + module.Register("nginxplus", module.Creator{ + JobConfigSchema: configSchema, + Create: func() module.Module { return New() }, + Config: func() any { return &Config{} }, + }) +} + +func New() *NginxPlus { + return &NginxPlus{ + Config: Config{ + HTTP: web.HTTP{ + Request: web.Request{ + URL: "http://127.0.0.1", + }, + Client: web.Client{ + Timeout: web.Duration(time.Second * 1), + }, + }, + }, + charts: baseCharts.Copy(), + queryEndpointsEvery: time.Minute, + cache: newCache(), + } +} + +type Config struct { + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"` + web.HTTP `yaml:",inline" json:""` +} + +type NginxPlus struct { + module.Base + Config `yaml:",inline" json:""` + + charts *module.Charts + + httpClient *http.Client + + apiVersion int64 + endpoints struct { + nginx bool + connections bool + ssl bool + httpCaches bool + httpRequest bool + httpServerZones bool + httpLocationZones bool + httpUpstreams bool + streamServerZones bool + streamUpstreams bool + resolvers bool + } + queryEndpointsTime time.Time + queryEndpointsEvery time.Duration + cache *cache +} + +func (n *NginxPlus) Configuration() any { + return n.Config +} + +func (n *NginxPlus) Init() error { + if n.URL == "" { + n.Error("config validation: 'url' can not be empty'") + return errors.New("url not set") + } + + client, err := web.NewHTTPClient(n.Client) + if err != nil { + n.Errorf("init HTTP client: %v", err) + return err + } + n.httpClient = client + + return nil +} + +func (n *NginxPlus) Check() error { + mx, err := n.collect() + if err != nil { + n.Error(err) + return err + } + if len(mx) == 0 { + return errors.New("no metrics collected") + } + return nil +} + +func (n *NginxPlus) Charts() *module.Charts { + return n.charts +} + +func (n *NginxPlus) Collect() map[string]int64 { + mx, err := n.collect() + + if err != nil { + n.Error(err) + return nil + } + + return mx +} + +func (n *NginxPlus) Cleanup() { + if n.httpClient != nil { + n.httpClient.CloseIdleConnections() + } +} diff --git a/src/go/plugin/go.d/modules/nginxplus/nginxplus_test.go b/src/go/plugin/go.d/modules/nginxplus/nginxplus_test.go new file mode 100644 index 00000000..2628cc68 --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/nginxplus_test.go @@ -0,0 +1,596 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package nginxplus + +import ( + "fmt" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + dataConfigJSON, _ = os.ReadFile("testdata/config.json") + dataConfigYAML, _ = os.ReadFile("testdata/config.yaml") + + dataAPI8APIVersions, _ = os.ReadFile("testdata/api-8/api_versions.json") + dataAPI8Connections, _ = os.ReadFile("testdata/api-8/connections.json") + dataAPI8EndpointsHTTP, _ = os.ReadFile("testdata/api-8/endpoints_http.json") + dataAPI8EndpointsRoot, _ = os.ReadFile("testdata/api-8/endpoints_root.json") + dataAPI8EndpointsStream, _ = os.ReadFile("testdata/api-8/endpoints_stream.json") + dataAPI8HTTPCaches, _ = os.ReadFile("testdata/api-8/http_caches.json") + dataAPI8HTTPLocationZones, _ = os.ReadFile("testdata/api-8/http_location_zones.json") + dataAPI8HTTPRequests, _ = os.ReadFile("testdata/api-8/http_requests.json") + dataAPI8HTTPServerZones, _ = os.ReadFile("testdata/api-8/http_server_zones.json") + dataAPI8HTTPUpstreams, _ = os.ReadFile("testdata/api-8/http_upstreams.json") + dataAPI8SSL, _ = os.ReadFile("testdata/api-8/ssl.json") + dataAPI8StreamServerZones, _ = os.ReadFile("testdata/api-8/stream_server_zones.json") + dataAPI8StreamUpstreams, _ = os.ReadFile("testdata/api-8/stream_upstreams.json") + dataAPI8Resolvers, _ = os.ReadFile("testdata/api-8/resolvers.json") + data404, _ = os.ReadFile("testdata/404.json") +) + +func Test_testDataIsValid(t *testing.T) { + for name, data := range map[string][]byte{ + "dataConfigJSON": dataConfigJSON, + "dataConfigYAML": dataConfigYAML, + "dataAPI8APIVersions": dataAPI8APIVersions, + "dataAPI8Connections": dataAPI8Connections, + "dataAPI8EndpointsHTTP": dataAPI8EndpointsHTTP, + "dataAPI8EndpointsRoot": dataAPI8EndpointsRoot, + "dataAPI8EndpointsStream": dataAPI8EndpointsStream, + "dataAPI8HTTPCaches": dataAPI8HTTPCaches, + "dataAPI8HTTPLocationZones": dataAPI8HTTPLocationZones, + "dataAPI8HTTPRequests": dataAPI8HTTPRequests, + "dataAPI8HTTPServerZones": dataAPI8HTTPServerZones, + "dataAPI8HTTPUpstreams": dataAPI8HTTPUpstreams, + "dataAPI8SSL": dataAPI8SSL, + "dataAPI8StreamServerZones": dataAPI8StreamServerZones, + "dataAPI8StreamUpstreams": dataAPI8StreamUpstreams, + "dataAPI8Resolvers": dataAPI8Resolvers, + "data404": data404, + } { + require.NotNil(t, data, name) + } +} + +func TestNginxPlus_ConfigurationSerialize(t *testing.T) { + module.TestConfigurationSerialize(t, &NginxPlus{}, dataConfigJSON, dataConfigYAML) +} + +func TestNginxPlus_Init(t *testing.T) { + tests := map[string]struct { + wantFail bool + config Config + }{ + "success with default": { + wantFail: false, + config: New().Config, + }, + "fail when URL not set": { + wantFail: true, + config: Config{ + HTTP: web.HTTP{ + Request: web.Request{URL: ""}, + }, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + nginx := New() + nginx.Config = test.config + + if test.wantFail { + assert.Error(t, nginx.Init()) + } else { + assert.NoError(t, nginx.Init()) + } + }) + } +} + +func TestNginxPlus_Check(t *testing.T) { + tests := map[string]struct { + wantFail bool + prepare func(t *testing.T) (nginx *NginxPlus, cleanup func()) + }{ + "success when all requests OK": { + wantFail: false, + prepare: caseAPI8AllRequestsOK, + }, + "success when all requests except stream OK": { + wantFail: false, + prepare: caseAPI8AllRequestsExceptStreamOK, + }, + "fail on invalid data response": { + wantFail: true, + prepare: caseInvalidDataResponse, + }, + "fail on connection refused": { + wantFail: true, + prepare: caseConnectionRefused, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + nginx, cleanup := test.prepare(t) + defer cleanup() + + if test.wantFail { + assert.Error(t, nginx.Check()) + } else { + assert.NoError(t, nginx.Check()) + } + }) + } +} + +func TestNginxPlus_Collect(t *testing.T) { + tests := map[string]struct { + prepare func(t *testing.T) (nginx *NginxPlus, cleanup func()) + wantNumOfCharts int + wantMetrics map[string]int64 + }{ + "success when all requests OK": { + prepare: caseAPI8AllRequestsOK, + wantNumOfCharts: len(baseCharts) + + len(httpCacheChartsTmpl) + + len(httpServerZoneChartsTmpl) + + len(httpLocationZoneChartsTmpl)*2 + + len(httpUpstreamChartsTmpl) + + len(httpUpstreamServerChartsTmpl)*2 + + len(streamServerZoneChartsTmpl) + + len(streamUpstreamChartsTmpl) + + len(streamUpstreamServerChartsTmpl)*2 + + len(resolverZoneChartsTmpl)*2, + wantMetrics: map[string]int64{ + "connections_accepted": 6079, + "connections_active": 1, + "connections_dropped": 0, + "connections_idle": 8, + "http_cache_cache_backend_bypassed_bytes": 67035, + "http_cache_cache_backend_bypassed_responses": 109, + "http_cache_cache_backend_served_bytes": 0, + "http_cache_cache_backend_served_responses": 0, + "http_cache_cache_backend_size": 0, + "http_cache_cache_backend_state_cold": 0, + "http_cache_cache_backend_state_warm": 1, + "http_cache_cache_backend_written_bytes": 0, + "http_cache_cache_backend_written_responses": 0, + "http_location_zone_server_api_bytes_received": 1854427, + "http_location_zone_server_api_bytes_sent": 4668778, + "http_location_zone_server_api_requests": 9188, + "http_location_zone_server_api_requests_discarded": 0, + "http_location_zone_server_api_responses": 9188, + "http_location_zone_server_api_responses_1xx": 0, + "http_location_zone_server_api_responses_2xx": 9187, + "http_location_zone_server_api_responses_3xx": 0, + "http_location_zone_server_api_responses_4xx": 1, + "http_location_zone_server_api_responses_5xx": 0, + "http_location_zone_server_dashboard_bytes_received": 0, + "http_location_zone_server_dashboard_bytes_sent": 0, + "http_location_zone_server_dashboard_requests": 0, + "http_location_zone_server_dashboard_requests_discarded": 0, + "http_location_zone_server_dashboard_responses": 0, + "http_location_zone_server_dashboard_responses_1xx": 0, + "http_location_zone_server_dashboard_responses_2xx": 0, + "http_location_zone_server_dashboard_responses_3xx": 0, + "http_location_zone_server_dashboard_responses_4xx": 0, + "http_location_zone_server_dashboard_responses_5xx": 0, + "http_requests_current": 1, + "http_requests_total": 8363, + "http_server_zone_server_backend_bytes_received": 1773834, + "http_server_zone_server_backend_bytes_sent": 4585734, + "http_server_zone_server_backend_requests": 8962, + "http_server_zone_server_backend_requests_discarded": 0, + "http_server_zone_server_backend_requests_processing": 1, + "http_server_zone_server_backend_responses": 8961, + "http_server_zone_server_backend_responses_1xx": 0, + "http_server_zone_server_backend_responses_2xx": 8960, + "http_server_zone_server_backend_responses_3xx": 0, + "http_server_zone_server_backend_responses_4xx": 1, + "http_server_zone_server_backend_responses_5xx": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_active": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_bytes_received": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_bytes_sent": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_downtime": 1020, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_header_time": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_requests": 26, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_response_time": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_responses": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_responses_1xx": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_responses_2xx": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_responses_3xx": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_responses_4xx": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_responses_5xx": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_state_checking": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_state_down": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_state_draining": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_state_unavail": 1, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_state_unhealthy": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_state_up": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_active": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_bytes_received": 86496, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_bytes_sent": 9180, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_downtime": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_header_time": 1, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_requests": 102, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_response_time": 1, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_responses": 102, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_responses_1xx": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_responses_2xx": 102, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_responses_3xx": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_responses_4xx": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_responses_5xx": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_state_checking": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_state_down": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_state_draining": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_state_unavail": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_state_unhealthy": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_state_up": 1, + "http_upstream_backend_zone_http_backend_keepalive": 0, + "http_upstream_backend_zone_http_backend_peers": 2, + "http_upstream_backend_zone_http_backend_zombies": 0, + "resolver_zone_resolver-http_requests_addr": 0, + "resolver_zone_resolver-http_requests_name": 0, + "resolver_zone_resolver-http_requests_srv": 2939408, + "resolver_zone_resolver-http_responses_formerr": 0, + "resolver_zone_resolver-http_responses_noerror": 0, + "resolver_zone_resolver-http_responses_notimp": 0, + "resolver_zone_resolver-http_responses_nxdomain": 2939404, + "resolver_zone_resolver-http_responses_refused": 0, + "resolver_zone_resolver-http_responses_servfail": 0, + "resolver_zone_resolver-http_responses_timedout": 4, + "resolver_zone_resolver-http_responses_unknown": 0, + "resolver_zone_resolver-stream_requests_addr": 0, + "resolver_zone_resolver-stream_requests_name": 638797, + "resolver_zone_resolver-stream_requests_srv": 0, + "resolver_zone_resolver-stream_responses_formerr": 0, + "resolver_zone_resolver-stream_responses_noerror": 433136, + "resolver_zone_resolver-stream_responses_notimp": 0, + "resolver_zone_resolver-stream_responses_nxdomain": 40022, + "resolver_zone_resolver-stream_responses_refused": 165639, + "resolver_zone_resolver-stream_responses_servfail": 0, + "resolver_zone_resolver-stream_responses_timedout": 0, + "resolver_zone_resolver-stream_responses_unknown": 0, + "ssl_handshake_timeout": 4, + "ssl_handshakes": 15804607, + "ssl_handshakes_failed": 37862, + "ssl_no_common_cipher": 24, + "ssl_no_common_protocol": 16648, + "ssl_peer_rejected_cert": 0, + "ssl_session_reuses": 13096060, + "ssl_verify_failures_expired_cert": 0, + "ssl_verify_failures_hostname_mismatch": 0, + "ssl_verify_failures_other": 0, + "ssl_verify_failures_no_cert": 0, + "ssl_verify_failures_revoked_cert": 0, + "stream_server_zone_tcp_server_bytes_received": 0, + "stream_server_zone_tcp_server_bytes_sent": 0, + "stream_server_zone_tcp_server_connections": 0, + "stream_server_zone_tcp_server_connections_discarded": 0, + "stream_server_zone_tcp_server_connections_processing": 0, + "stream_server_zone_tcp_server_sessions": 0, + "stream_server_zone_tcp_server_sessions_2xx": 0, + "stream_server_zone_tcp_server_sessions_4xx": 0, + "stream_server_zone_tcp_server_sessions_5xx": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12346_zone_tcp_servers_active": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12346_zone_tcp_servers_bytes_received": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12346_zone_tcp_servers_bytes_sent": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12346_zone_tcp_servers_connections": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12346_zone_tcp_servers_downtime": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12346_zone_tcp_servers_state_checking": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12346_zone_tcp_servers_state_down": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12346_zone_tcp_servers_state_unavail": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12346_zone_tcp_servers_state_unhealthy": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12346_zone_tcp_servers_state_up": 1, + "stream_upstream_stream_backend_server_127.0.0.1:12347_zone_tcp_servers_active": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12347_zone_tcp_servers_bytes_received": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12347_zone_tcp_servers_bytes_sent": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12347_zone_tcp_servers_connections": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12347_zone_tcp_servers_downtime": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12347_zone_tcp_servers_state_checking": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12347_zone_tcp_servers_state_down": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12347_zone_tcp_servers_state_unavail": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12347_zone_tcp_servers_state_unhealthy": 0, + "stream_upstream_stream_backend_server_127.0.0.1:12347_zone_tcp_servers_state_up": 1, + "stream_upstream_stream_backend_zone_tcp_servers_peers": 2, + "stream_upstream_stream_backend_zone_tcp_servers_zombies": 0, + }, + }, + "success when all requests except stream OK": { + prepare: caseAPI8AllRequestsExceptStreamOK, + wantNumOfCharts: len(baseCharts) + + len(httpCacheChartsTmpl) + + len(httpServerZoneChartsTmpl) + + len(httpLocationZoneChartsTmpl)*2 + + len(httpUpstreamChartsTmpl) + + len(httpUpstreamServerChartsTmpl)*2 + + len(resolverZoneChartsTmpl)*2, + wantMetrics: map[string]int64{ + "connections_accepted": 6079, + "connections_active": 1, + "connections_dropped": 0, + "connections_idle": 8, + "http_cache_cache_backend_bypassed_bytes": 67035, + "http_cache_cache_backend_bypassed_responses": 109, + "http_cache_cache_backend_served_bytes": 0, + "http_cache_cache_backend_served_responses": 0, + "http_cache_cache_backend_size": 0, + "http_cache_cache_backend_state_cold": 0, + "http_cache_cache_backend_state_warm": 1, + "http_cache_cache_backend_written_bytes": 0, + "http_cache_cache_backend_written_responses": 0, + "http_location_zone_server_api_bytes_received": 1854427, + "http_location_zone_server_api_bytes_sent": 4668778, + "http_location_zone_server_api_requests": 9188, + "http_location_zone_server_api_requests_discarded": 0, + "http_location_zone_server_api_responses": 9188, + "http_location_zone_server_api_responses_1xx": 0, + "http_location_zone_server_api_responses_2xx": 9187, + "http_location_zone_server_api_responses_3xx": 0, + "http_location_zone_server_api_responses_4xx": 1, + "http_location_zone_server_api_responses_5xx": 0, + "http_location_zone_server_dashboard_bytes_received": 0, + "http_location_zone_server_dashboard_bytes_sent": 0, + "http_location_zone_server_dashboard_requests": 0, + "http_location_zone_server_dashboard_requests_discarded": 0, + "http_location_zone_server_dashboard_responses": 0, + "http_location_zone_server_dashboard_responses_1xx": 0, + "http_location_zone_server_dashboard_responses_2xx": 0, + "http_location_zone_server_dashboard_responses_3xx": 0, + "http_location_zone_server_dashboard_responses_4xx": 0, + "http_location_zone_server_dashboard_responses_5xx": 0, + "http_requests_current": 1, + "http_requests_total": 8363, + "http_server_zone_server_backend_bytes_received": 1773834, + "http_server_zone_server_backend_bytes_sent": 4585734, + "http_server_zone_server_backend_requests": 8962, + "http_server_zone_server_backend_requests_discarded": 0, + "http_server_zone_server_backend_requests_processing": 1, + "http_server_zone_server_backend_responses": 8961, + "http_server_zone_server_backend_responses_1xx": 0, + "http_server_zone_server_backend_responses_2xx": 8960, + "http_server_zone_server_backend_responses_3xx": 0, + "http_server_zone_server_backend_responses_4xx": 1, + "http_server_zone_server_backend_responses_5xx": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_active": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_bytes_received": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_bytes_sent": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_downtime": 1020, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_header_time": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_requests": 26, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_response_time": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_responses": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_responses_1xx": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_responses_2xx": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_responses_3xx": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_responses_4xx": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_responses_5xx": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_state_checking": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_state_down": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_state_draining": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_state_unavail": 1, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_state_unhealthy": 0, + "http_upstream_backend_server_127.0.0.1:81_zone_http_backend_state_up": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_active": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_bytes_received": 86496, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_bytes_sent": 9180, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_downtime": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_header_time": 1, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_requests": 102, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_response_time": 1, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_responses": 102, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_responses_1xx": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_responses_2xx": 102, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_responses_3xx": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_responses_4xx": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_responses_5xx": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_state_checking": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_state_down": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_state_draining": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_state_unavail": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_state_unhealthy": 0, + "http_upstream_backend_server_127.0.0.1:82_zone_http_backend_state_up": 1, + "http_upstream_backend_zone_http_backend_keepalive": 0, + "http_upstream_backend_zone_http_backend_peers": 2, + "http_upstream_backend_zone_http_backend_zombies": 0, + "resolver_zone_resolver-http_requests_addr": 0, + "resolver_zone_resolver-http_requests_name": 0, + "resolver_zone_resolver-http_requests_srv": 2939408, + "resolver_zone_resolver-http_responses_formerr": 0, + "resolver_zone_resolver-http_responses_noerror": 0, + "resolver_zone_resolver-http_responses_notimp": 0, + "resolver_zone_resolver-http_responses_nxdomain": 2939404, + "resolver_zone_resolver-http_responses_refused": 0, + "resolver_zone_resolver-http_responses_servfail": 0, + "resolver_zone_resolver-http_responses_timedout": 4, + "resolver_zone_resolver-http_responses_unknown": 0, + "resolver_zone_resolver-stream_requests_addr": 0, + "resolver_zone_resolver-stream_requests_name": 638797, + "resolver_zone_resolver-stream_requests_srv": 0, + "resolver_zone_resolver-stream_responses_formerr": 0, + "resolver_zone_resolver-stream_responses_noerror": 433136, + "resolver_zone_resolver-stream_responses_notimp": 0, + "resolver_zone_resolver-stream_responses_nxdomain": 40022, + "resolver_zone_resolver-stream_responses_refused": 165639, + "resolver_zone_resolver-stream_responses_servfail": 0, + "resolver_zone_resolver-stream_responses_timedout": 0, + "resolver_zone_resolver-stream_responses_unknown": 0, + "ssl_handshake_timeout": 4, + "ssl_handshakes": 15804607, + "ssl_handshakes_failed": 37862, + "ssl_no_common_cipher": 24, + "ssl_no_common_protocol": 16648, + "ssl_peer_rejected_cert": 0, + "ssl_session_reuses": 13096060, + "ssl_verify_failures_expired_cert": 0, + "ssl_verify_failures_hostname_mismatch": 0, + "ssl_verify_failures_other": 0, + "ssl_verify_failures_no_cert": 0, + "ssl_verify_failures_revoked_cert": 0, + }, + }, + "fail on invalid data response": { + prepare: caseInvalidDataResponse, + wantNumOfCharts: 0, + wantMetrics: nil, + }, + "fail on connection refused": { + prepare: caseConnectionRefused, + wantNumOfCharts: 0, + wantMetrics: nil, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + nginx, cleanup := test.prepare(t) + defer cleanup() + + mx := nginx.Collect() + + require.Equal(t, test.wantMetrics, mx) + if len(test.wantMetrics) > 0 { + assert.Equalf(t, test.wantNumOfCharts, len(*nginx.Charts()), "number of charts") + ensureCollectedHasAllChartsDimsVarsIDs(t, nginx, mx) + } + }) + } +} + +func caseAPI8AllRequestsOK(t *testing.T) (*NginxPlus, func()) { + t.Helper() + srv := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case urlPathAPIVersions: + _, _ = w.Write(dataAPI8APIVersions) + case fmt.Sprintf(urlPathAPIEndpointsRoot, 8): + _, _ = w.Write(dataAPI8EndpointsRoot) + case fmt.Sprintf(urlPathAPIEndpointsHTTP, 8): + _, _ = w.Write(dataAPI8EndpointsHTTP) + case fmt.Sprintf(urlPathAPIEndpointsStream, 8): + _, _ = w.Write(dataAPI8EndpointsStream) + case fmt.Sprintf(urlPathAPIConnections, 8): + _, _ = w.Write(dataAPI8Connections) + case fmt.Sprintf(urlPathAPISSL, 8): + _, _ = w.Write(dataAPI8SSL) + case fmt.Sprintf(urlPathAPIHTTPRequests, 8): + _, _ = w.Write(dataAPI8HTTPRequests) + case fmt.Sprintf(urlPathAPIHTTPServerZones, 8): + _, _ = w.Write(dataAPI8HTTPServerZones) + case fmt.Sprintf(urlPathAPIHTTPLocationZones, 8): + _, _ = w.Write(dataAPI8HTTPLocationZones) + case fmt.Sprintf(urlPathAPIHTTPUpstreams, 8): + _, _ = w.Write(dataAPI8HTTPUpstreams) + case fmt.Sprintf(urlPathAPIHTTPCaches, 8): + _, _ = w.Write(dataAPI8HTTPCaches) + case fmt.Sprintf(urlPathAPIStreamServerZones, 8): + _, _ = w.Write(dataAPI8StreamServerZones) + case fmt.Sprintf(urlPathAPIStreamUpstreams, 8): + _, _ = w.Write(dataAPI8StreamUpstreams) + case fmt.Sprintf(urlPathAPIResolvers, 8): + _, _ = w.Write(dataAPI8Resolvers) + default: + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write(data404) + + } + })) + nginx := New() + nginx.URL = srv.URL + require.NoError(t, nginx.Init()) + + return nginx, srv.Close +} + +func caseAPI8AllRequestsExceptStreamOK(t *testing.T) (*NginxPlus, func()) { + t.Helper() + srv := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case urlPathAPIVersions: + _, _ = w.Write(dataAPI8APIVersions) + case fmt.Sprintf(urlPathAPIEndpointsRoot, 8): + _, _ = w.Write(dataAPI8EndpointsRoot) + case fmt.Sprintf(urlPathAPIEndpointsHTTP, 8): + _, _ = w.Write(dataAPI8EndpointsHTTP) + case fmt.Sprintf(urlPathAPIEndpointsStream, 8): + _, _ = w.Write(dataAPI8EndpointsStream) + case fmt.Sprintf(urlPathAPIConnections, 8): + _, _ = w.Write(dataAPI8Connections) + case fmt.Sprintf(urlPathAPISSL, 8): + _, _ = w.Write(dataAPI8SSL) + case fmt.Sprintf(urlPathAPIHTTPRequests, 8): + _, _ = w.Write(dataAPI8HTTPRequests) + case fmt.Sprintf(urlPathAPIHTTPServerZones, 8): + _, _ = w.Write(dataAPI8HTTPServerZones) + case fmt.Sprintf(urlPathAPIHTTPLocationZones, 8): + _, _ = w.Write(dataAPI8HTTPLocationZones) + case fmt.Sprintf(urlPathAPIHTTPUpstreams, 8): + _, _ = w.Write(dataAPI8HTTPUpstreams) + case fmt.Sprintf(urlPathAPIHTTPCaches, 8): + _, _ = w.Write(dataAPI8HTTPCaches) + case fmt.Sprintf(urlPathAPIResolvers, 8): + _, _ = w.Write(dataAPI8Resolvers) + default: + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write(data404) + + } + })) + nginx := New() + nginx.URL = srv.URL + require.NoError(t, nginx.Init()) + + return nginx, srv.Close +} + +func caseInvalidDataResponse(t *testing.T) (*NginxPlus, func()) { + t.Helper() + srv := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte("hello and\n goodbye")) + })) + nginx := New() + nginx.URL = srv.URL + require.NoError(t, nginx.Init()) + + return nginx, srv.Close +} + +func caseConnectionRefused(t *testing.T) (*NginxPlus, func()) { + t.Helper() + nginx := New() + nginx.URL = "http://127.0.0.1:65001" + require.NoError(t, nginx.Init()) + + return nginx, func() {} +} + +func ensureCollectedHasAllChartsDimsVarsIDs(t *testing.T, n *NginxPlus, mx map[string]int64) { + for _, chart := range *n.Charts() { + if chart.ID == uptimeChart.ID { + continue + } + for _, dim := range chart.Dims { + _, ok := mx[dim.ID] + assert.Truef(t, ok, "collected metrics has no data for dim '%s' chart '%s'", dim.ID, chart.ID) + } + for _, v := range chart.Vars { + _, ok := mx[v.ID] + assert.Truef(t, ok, "collected metrics has no data for var '%s' chart '%s'", v.ID, chart.ID) + } + } +} diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/404.json b/src/go/plugin/go.d/modules/nginxplus/testdata/404.json new file mode 100644 index 00000000..d2ed8c9a --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/404.json @@ -0,0 +1,9 @@ +{ + "error": { + "status": 404, + "text": "path not found", + "code": "PathNotFound" + }, + "request_id": "f0d20aca461d043e787ebaa52f018cb2", + "href": "https://nginx.org/en/docs/http/ngx_http_api_module.html" +} diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/api_versions.json b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/api_versions.json new file mode 100644 index 00000000..9ffc3397 --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/api_versions.json @@ -0,0 +1,10 @@ +[ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 +] diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/connections.json b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/connections.json new file mode 100644 index 00000000..490ca13f --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/connections.json @@ -0,0 +1,6 @@ +{ + "accepted": 6079, + "dropped": 0, + "active": 1, + "idle": 8 +} diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/endpoints_http.json b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/endpoints_http.json new file mode 100644 index 00000000..57c4e4aa --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/endpoints_http.json @@ -0,0 +1,10 @@ +[ + "requests", + "server_zones", + "location_zones", + "caches", + "limit_conns", + "limit_reqs", + "keyvals", + "upstreams" +] diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/endpoints_root.json b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/endpoints_root.json new file mode 100644 index 00000000..b185c55f --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/endpoints_root.json @@ -0,0 +1,10 @@ +[ + "nginx", + "processes", + "connections", + "slabs", + "http", + "stream", + "resolvers", + "ssl" +] diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/endpoints_stream.json b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/endpoints_stream.json new file mode 100644 index 00000000..0da09237 --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/endpoints_stream.json @@ -0,0 +1,6 @@ +[ + "server_zones", + "limit_conns", + "keyvals", + "upstreams" +] diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/http_caches.json b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/http_caches.json new file mode 100644 index 00000000..dd2d03ad --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/http_caches.json @@ -0,0 +1,40 @@ +{ + "cache_backend": { + "size": 0, + "cold": false, + "hit": { + "responses": 0, + "bytes": 0 + }, + "stale": { + "responses": 0, + "bytes": 0 + }, + "updating": { + "responses": 0, + "bytes": 0 + }, + "revalidated": { + "responses": 0, + "bytes": 0 + }, + "miss": { + "responses": 109, + "bytes": 67035, + "responses_written": 0, + "bytes_written": 0 + }, + "expired": { + "responses": 0, + "bytes": 0, + "responses_written": 0, + "bytes_written": 0 + }, + "bypass": { + "responses": 0, + "bytes": 0, + "responses_written": 0, + "bytes_written": 0 + } + } +} diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/http_location_zones.json b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/http_location_zones.json new file mode 100644 index 00000000..8812e6df --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/http_location_zones.json @@ -0,0 +1,35 @@ +{ + "server_api": { + "requests": 9188, + "responses": { + "1xx": 0, + "2xx": 9187, + "3xx": 0, + "4xx": 1, + "5xx": 0, + "codes": { + "200": 9187, + "404": 1 + }, + "total": 9188 + }, + "discarded": 0, + "received": 1854427, + "sent": 4668778 + }, + "server_dashboard": { + "requests": 0, + "responses": { + "1xx": 0, + "2xx": 0, + "3xx": 0, + "4xx": 0, + "5xx": 0, + "codes": {}, + "total": 0 + }, + "discarded": 0, + "received": 0, + "sent": 0 + } +} diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/http_requests.json b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/http_requests.json new file mode 100644 index 00000000..0c2a1750 --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/http_requests.json @@ -0,0 +1,4 @@ +{ + "total": 8363, + "current": 1 +} diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/http_server_zones.json b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/http_server_zones.json new file mode 100644 index 00000000..c2538921 --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/http_server_zones.json @@ -0,0 +1,21 @@ +{ + "server_backend": { + "processing": 1, + "requests": 8962, + "responses": { + "1xx": 0, + "2xx": 8960, + "3xx": 0, + "4xx": 1, + "5xx": 0, + "codes": { + "200": 8960, + "404": 1 + }, + "total": 8961 + }, + "discarded": 0, + "received": 1773834, + "sent": 4585734 + } +} diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/http_upstreams.json b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/http_upstreams.json new file mode 100644 index 00000000..0f7ba713 --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/http_upstreams.json @@ -0,0 +1,76 @@ +{ + "backend": { + "peers": [ + { + "id": 0, + "server": "127.0.0.1:81", + "name": "127.0.0.1:81", + "backup": false, + "weight": 5, + "state": "unavail", + "active": 0, + "requests": 26, + "header_time": 0, + "response_time": 0, + "responses": { + "1xx": 0, + "2xx": 0, + "3xx": 0, + "4xx": 0, + "5xx": 0, + "codes": {}, + "total": 0 + }, + "sent": 0, + "received": 0, + "fails": 26, + "unavail": 1, + "health_checks": { + "checks": 0, + "fails": 0, + "unhealthy": 0 + }, + "downtime": 1020702, + "downstart": "2022-11-18T19:17:09.258Z", + "selected": "2022-11-18T19:33:50Z" + }, + { + "id": 1, + "server": "127.0.0.1:82", + "name": "127.0.0.1:82", + "backup": false, + "weight": 1, + "state": "up", + "active": 0, + "requests": 102, + "header_time": 1, + "response_time": 1, + "responses": { + "1xx": 0, + "2xx": 102, + "3xx": 0, + "4xx": 0, + "5xx": 0, + "codes": { + "200": 102 + }, + "total": 102 + }, + "sent": 9180, + "received": 86496, + "fails": 0, + "unavail": 0, + "health_checks": { + "checks": 0, + "fails": 0, + "unhealthy": 0 + }, + "downtime": 0, + "selected": "2022-11-18T19:34:00Z" + } + ], + "keepalive": 0, + "zombies": 0, + "zone": "http_backend" + } +} diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/nginx.json b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/nginx.json new file mode 100644 index 00000000..4480c2bc --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/nginx.json @@ -0,0 +1,10 @@ +{ + "version": "1.21.6", + "build": "nginx-plus-r27-p1", + "address": "127.0.0.1", + "generation": 1, + "load_timestamp": "2022-11-19T14:38:38.676Z", + "timestamp": "2022-11-19T14:38:57.031Z", + "pid": 2254633, + "ppid": 2254629 +} diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/resolvers.json b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/resolvers.json new file mode 100644 index 00000000..ad66f558 --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/resolvers.json @@ -0,0 +1,36 @@ +{ + "resolver-http": { + "requests": { + "name": 0, + "srv": 2939408, + "addr": 0 + }, + "responses": { + "noerror": 0, + "formerr": 0, + "servfail": 0, + "nxdomain": 2939404, + "notimp": 0, + "refused": 0, + "timedout": 4, + "unknown": 0 + } + }, + "resolver-stream": { + "requests": { + "name": 638797, + "srv": 0, + "addr": 0 + }, + "responses": { + "noerror": 433136, + "formerr": 0, + "servfail": 0, + "nxdomain": 40022, + "notimp": 0, + "refused": 165639, + "timedout": 0, + "unknown": 0 + } + } +} diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/ssl.json b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/ssl.json new file mode 100644 index 00000000..2ca8a6a3 --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/ssl.json @@ -0,0 +1,16 @@ +{ + "handshakes": 15804607, + "session_reuses": 13096060, + "handshakes_failed": 37862, + "no_common_protocol": 16648, + "no_common_cipher": 24, + "handshake_timeout": 4, + "peer_rejected_cert": 0, + "verify_failures": { + "no_cert": 0, + "expired_cert": 0, + "revoked_cert": 0, + "hostname_mismatch": 0, + "other": 0 + } +} diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/stream_server_zones.json b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/stream_server_zones.json new file mode 100644 index 00000000..0c7df787 --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/stream_server_zones.json @@ -0,0 +1,15 @@ +{ + "tcp_server": { + "processing": 0, + "connections": 0, + "sessions": { + "2xx": 0, + "4xx": 0, + "5xx": 0, + "total": 0 + }, + "discarded": 0, + "received": 0, + "sent": 0 + } +} diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/stream_upstreams.json b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/stream_upstreams.json new file mode 100644 index 00000000..707ad4db --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/api-8/stream_upstreams.json @@ -0,0 +1,48 @@ +{ + "stream_backend": { + "peers": [ + { + "id": 0, + "server": "127.0.0.1:12346", + "name": "127.0.0.1:12346", + "backup": false, + "weight": 1, + "state": "up", + "active": 0, + "connections": 0, + "sent": 0, + "received": 0, + "fails": 0, + "unavail": 0, + "health_checks": { + "checks": 0, + "fails": 0, + "unhealthy": 0 + }, + "downtime": 0 + }, + { + "id": 1, + "server": "127.0.0.1:12347", + "name": "127.0.0.1:12347", + "backup": false, + "weight": 1, + "state": "up", + "active": 0, + "connections": 0, + "sent": 0, + "received": 0, + "fails": 0, + "unavail": 0, + "health_checks": { + "checks": 0, + "fails": 0, + "unhealthy": 0 + }, + "downtime": 0 + } + ], + "zombies": 0, + "zone": "tcp_servers" + } +} diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/config.json b/src/go/plugin/go.d/modules/nginxplus/testdata/config.json new file mode 100644 index 00000000..984c3ed6 --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/config.json @@ -0,0 +1,20 @@ +{ + "update_every": 123, + "url": "ok", + "body": "ok", + "method": "ok", + "headers": { + "ok": "ok" + }, + "username": "ok", + "password": "ok", + "proxy_url": "ok", + "proxy_username": "ok", + "proxy_password": "ok", + "timeout": 123.123, + "not_follow_redirects": true, + "tls_ca": "ok", + "tls_cert": "ok", + "tls_key": "ok", + "tls_skip_verify": true +} diff --git a/src/go/plugin/go.d/modules/nginxplus/testdata/config.yaml b/src/go/plugin/go.d/modules/nginxplus/testdata/config.yaml new file mode 100644 index 00000000..8558b61c --- /dev/null +++ b/src/go/plugin/go.d/modules/nginxplus/testdata/config.yaml @@ -0,0 +1,17 @@ +update_every: 123 +url: "ok" +body: "ok" +method: "ok" +headers: + ok: "ok" +username: "ok" +password: "ok" +proxy_url: "ok" +proxy_username: "ok" +proxy_password: "ok" +timeout: 123.123 +not_follow_redirects: yes +tls_ca: "ok" +tls_cert: "ok" +tls_key: "ok" +tls_skip_verify: yes |