github.com/netdata/go.d.plugin@v0.58.1/modules/nginxplus/collect.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package nginxplus
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"time"
     9  )
    10  
    11  func (n *NginxPlus) collect() (map[string]int64, error) {
    12  	if n.apiVersion == 0 {
    13  		v, err := n.queryAPIVersion()
    14  		if err != nil {
    15  			return nil, err
    16  		}
    17  		n.apiVersion = v
    18  	}
    19  
    20  	now := time.Now()
    21  	if now.Sub(n.queryEndpointsTime) > n.queryEndpointsEvery {
    22  		n.queryEndpointsTime = now
    23  		if err := n.queryAvailableEndpoints(); err != nil {
    24  			return nil, err
    25  		}
    26  	}
    27  
    28  	ms := n.queryMetrics()
    29  	if ms.empty() {
    30  		return nil, errors.New("no metrics collected")
    31  	}
    32  
    33  	mx := make(map[string]int64)
    34  	n.cache.resetUpdated()
    35  	n.collectInfo(mx, ms)
    36  	n.collectConnections(mx, ms)
    37  	n.collectSSL(mx, ms)
    38  	n.collectHTTPRequests(mx, ms)
    39  	n.collectHTTPCache(mx, ms)
    40  	n.collectHTTPServerZones(mx, ms)
    41  	n.collectHTTPLocationZones(mx, ms)
    42  	n.collectHTTPUpstreams(mx, ms)
    43  	n.collectStreamServerZones(mx, ms)
    44  	n.collectStreamUpstreams(mx, ms)
    45  	n.collectResolvers(mx, ms)
    46  	n.updateCharts()
    47  
    48  	return mx, nil
    49  }
    50  
    51  func (n *NginxPlus) collectInfo(mx map[string]int64, ms *nginxMetrics) {
    52  	if ms.info == nil {
    53  		return
    54  	}
    55  	mx["uptime"] = int64(ms.info.Timestamp.Sub(ms.info.LoadTimestamp).Seconds())
    56  }
    57  
    58  func (n *NginxPlus) collectConnections(mx map[string]int64, ms *nginxMetrics) {
    59  	if ms.connections == nil {
    60  		return
    61  	}
    62  	mx["connections_accepted"] = ms.connections.Accepted
    63  	mx["connections_dropped"] = ms.connections.Dropped
    64  	mx["connections_active"] = ms.connections.Active
    65  	mx["connections_idle"] = ms.connections.Idle
    66  }
    67  
    68  func (n *NginxPlus) collectSSL(mx map[string]int64, ms *nginxMetrics) {
    69  	if ms.ssl == nil {
    70  		return
    71  	}
    72  	mx["ssl_handshakes"] = ms.ssl.Handshakes
    73  	mx["ssl_handshakes_failed"] = ms.ssl.HandshakesFailed
    74  	mx["ssl_session_reuses"] = ms.ssl.SessionReuses
    75  	mx["ssl_no_common_protocol"] = ms.ssl.NoCommonProtocol
    76  	mx["ssl_no_common_cipher"] = ms.ssl.NoCommonCipher
    77  	mx["ssl_handshake_timeout"] = ms.ssl.HandshakeTimeout
    78  	mx["ssl_peer_rejected_cert"] = ms.ssl.PeerRejectedCert
    79  	mx["ssl_verify_failures_no_cert"] = ms.ssl.VerifyFailures.NoCert
    80  	mx["ssl_verify_failures_expired_cert"] = ms.ssl.VerifyFailures.ExpiredCert
    81  	mx["ssl_verify_failures_revoked_cert"] = ms.ssl.VerifyFailures.RevokedCert
    82  	mx["ssl_verify_failures_hostname_mismatch"] = ms.ssl.VerifyFailures.HostnameMismatch
    83  	mx["ssl_verify_failures_other"] = ms.ssl.VerifyFailures.Other
    84  }
    85  
    86  func (n *NginxPlus) collectHTTPRequests(mx map[string]int64, ms *nginxMetrics) {
    87  	if ms.httpRequests == nil {
    88  		return
    89  	}
    90  	mx["http_requests_total"] = ms.httpRequests.Total
    91  	mx["http_requests_current"] = ms.httpRequests.Current
    92  }
    93  
    94  func (n *NginxPlus) collectHTTPCache(mx map[string]int64, ms *nginxMetrics) {
    95  	if ms.httpCaches == nil {
    96  		return
    97  	}
    98  	for name, cache := range *ms.httpCaches {
    99  		n.cache.putHTTPCache(name)
   100  		px := fmt.Sprintf("http_cache_%s_", name)
   101  		mx[px+"state_cold"] = boolToInt(cache.Cold)
   102  		mx[px+"state_warm"] = boolToInt(!cache.Cold)
   103  		mx[px+"size"] = cache.Size
   104  		mx[px+"served_responses"] = cache.Hit.Responses + cache.Stale.Responses + cache.Updating.Responses + cache.Revalidated.Responses
   105  		mx[px+"written_responses"] = cache.Miss.ResponsesWritten + cache.Expired.ResponsesWritten + cache.Bypass.ResponsesWritten
   106  		mx[px+"bypassed_responses"] = cache.Miss.Responses + cache.Expired.Responses + cache.Bypass.Responses
   107  		mx[px+"served_bytes"] = cache.Hit.Bytes + cache.Stale.Bytes + cache.Updating.Bytes + cache.Revalidated.Bytes
   108  		mx[px+"written_bytes"] = cache.Miss.BytesWritten + cache.Expired.BytesWritten + cache.Bypass.BytesWritten
   109  		mx[px+"bypassed_bytes"] = cache.Miss.Bytes + cache.Expired.Bytes + cache.Bypass.Bytes
   110  	}
   111  }
   112  
   113  func (n *NginxPlus) collectHTTPServerZones(mx map[string]int64, ms *nginxMetrics) {
   114  	if ms.httpServerZones == nil {
   115  		return
   116  	}
   117  	for name, zone := range *ms.httpServerZones {
   118  		n.cache.putHTTPServerZone(name)
   119  
   120  		px := fmt.Sprintf("http_server_zone_%s_", name)
   121  		mx[px+"requests_processing"] = zone.Processing
   122  		mx[px+"requests"] = zone.Requests
   123  		mx[px+"requests_discarded"] = zone.Discarded
   124  		mx[px+"bytes_received"] = zone.Received
   125  		mx[px+"bytes_sent"] = zone.Sent
   126  		mx[px+"responses"] = zone.Responses.Total
   127  		mx[px+"responses_1xx"] = zone.Responses.Class1xx
   128  		mx[px+"responses_2xx"] = zone.Responses.Class2xx
   129  		mx[px+"responses_3xx"] = zone.Responses.Class3xx
   130  		mx[px+"responses_4xx"] = zone.Responses.Class4xx
   131  		mx[px+"responses_5xx"] = zone.Responses.Class5xx
   132  	}
   133  }
   134  
   135  func (n *NginxPlus) collectHTTPLocationZones(mx map[string]int64, ms *nginxMetrics) {
   136  	if ms.httpLocationZones == nil {
   137  		return
   138  	}
   139  	for name, zone := range *ms.httpLocationZones {
   140  		n.cache.putHTTPLocationZone(name)
   141  
   142  		px := fmt.Sprintf("http_location_zone_%s_", name)
   143  		mx[px+"requests"] = zone.Requests
   144  		mx[px+"requests_discarded"] = zone.Discarded
   145  		mx[px+"bytes_received"] = zone.Received
   146  		mx[px+"bytes_sent"] = zone.Sent
   147  		mx[px+"responses"] = zone.Responses.Total
   148  		mx[px+"responses_1xx"] = zone.Responses.Class1xx
   149  		mx[px+"responses_2xx"] = zone.Responses.Class2xx
   150  		mx[px+"responses_3xx"] = zone.Responses.Class3xx
   151  		mx[px+"responses_4xx"] = zone.Responses.Class4xx
   152  		mx[px+"responses_5xx"] = zone.Responses.Class5xx
   153  	}
   154  }
   155  
   156  func (n *NginxPlus) collectHTTPUpstreams(mx map[string]int64, ms *nginxMetrics) {
   157  	if ms.httpUpstreams == nil {
   158  		return
   159  	}
   160  	for name, upstream := range *ms.httpUpstreams {
   161  		n.cache.putHTTPUpstream(name, upstream.Zone)
   162  
   163  		px := fmt.Sprintf("http_upstream_%s_zone_%s_", name, upstream.Zone)
   164  		mx[px+"zombies"] = upstream.Zombies
   165  		mx[px+"keepalive"] = upstream.Keepalive
   166  		mx[px+"peers"] = int64(len(upstream.Peers))
   167  
   168  		for _, peer := range upstream.Peers {
   169  			n.cache.putHTTPUpstreamServer(name, peer.Server, peer.Name, upstream.Zone)
   170  
   171  			px = fmt.Sprintf("http_upstream_%s_server_%s_zone_%s_", name, peer.Server, upstream.Zone)
   172  			mx[px+"active"] = peer.Active
   173  			mx[px+"state_up"] = boolToInt(peer.State == "up")
   174  			mx[px+"state_down"] = boolToInt(peer.State == "down")
   175  			mx[px+"state_draining"] = boolToInt(peer.State == "draining")
   176  			mx[px+"state_unavail"] = boolToInt(peer.State == "unavail")
   177  			mx[px+"state_checking"] = boolToInt(peer.State == "checking")
   178  			mx[px+"state_unhealthy"] = boolToInt(peer.State == "unhealthy")
   179  			mx[px+"bytes_received"] = peer.Received
   180  			mx[px+"bytes_sent"] = peer.Sent
   181  			mx[px+"requests"] = peer.Requests
   182  			mx[px+"responses"] = peer.Responses.Total
   183  			mx[px+"responses_1xx"] = peer.Responses.Class1xx
   184  			mx[px+"responses_2xx"] = peer.Responses.Class2xx
   185  			mx[px+"responses_3xx"] = peer.Responses.Class3xx
   186  			mx[px+"responses_4xx"] = peer.Responses.Class4xx
   187  			mx[px+"responses_5xx"] = peer.Responses.Class5xx
   188  			mx[px+"response_time"] = peer.ResponseTime
   189  			mx[px+"header_time"] = peer.HeaderTime
   190  			mx[px+"downtime"] = peer.Downtime / 1000
   191  		}
   192  	}
   193  }
   194  
   195  func (n *NginxPlus) collectStreamServerZones(mx map[string]int64, ms *nginxMetrics) {
   196  	if ms.streamServerZones == nil {
   197  		return
   198  	}
   199  	for name, zone := range *ms.streamServerZones {
   200  		n.cache.putStreamServerZone(name)
   201  
   202  		px := fmt.Sprintf("stream_server_zone_%s_", name)
   203  		mx[px+"connections"] = zone.Connections
   204  		mx[px+"connections_processing"] = zone.Processing
   205  		mx[px+"connections_discarded"] = zone.Discarded
   206  		mx[px+"bytes_received"] = zone.Received
   207  		mx[px+"bytes_sent"] = zone.Sent
   208  		mx[px+"sessions"] = zone.Sessions.Total
   209  		mx[px+"sessions_2xx"] = zone.Sessions.Class2xx
   210  		mx[px+"sessions_4xx"] = zone.Sessions.Class4xx
   211  		mx[px+"sessions_5xx"] = zone.Sessions.Class5xx
   212  	}
   213  }
   214  
   215  func (n *NginxPlus) collectStreamUpstreams(mx map[string]int64, ms *nginxMetrics) {
   216  	if ms.streamUpstreams == nil {
   217  		return
   218  	}
   219  	for name, upstream := range *ms.streamUpstreams {
   220  		n.cache.putStreamUpstream(name, upstream.Zone)
   221  
   222  		px := fmt.Sprintf("stream_upstream_%s_zone_%s_", name, upstream.Zone)
   223  		mx[px+"zombies"] = upstream.Zombies
   224  		mx[px+"peers"] = int64(len(upstream.Peers))
   225  
   226  		for _, peer := range upstream.Peers {
   227  			n.cache.putStreamUpstreamServer(name, peer.Server, peer.Name, upstream.Zone)
   228  
   229  			px = fmt.Sprintf("stream_upstream_%s_server_%s_zone_%s_", name, peer.Server, upstream.Zone)
   230  			mx[px+"active"] = peer.Active
   231  			mx[px+"connections"] = peer.Connections
   232  			mx[px+"state_up"] = boolToInt(peer.State == "up")
   233  			mx[px+"state_down"] = boolToInt(peer.State == "down")
   234  			mx[px+"state_unavail"] = boolToInt(peer.State == "unavail")
   235  			mx[px+"state_checking"] = boolToInt(peer.State == "checking")
   236  			mx[px+"state_unhealthy"] = boolToInt(peer.State == "unhealthy")
   237  			mx[px+"bytes_received"] = peer.Received
   238  			mx[px+"bytes_sent"] = peer.Sent
   239  			mx[px+"downtime"] = peer.Downtime / 1000
   240  		}
   241  	}
   242  }
   243  
   244  func (n *NginxPlus) collectResolvers(mx map[string]int64, ms *nginxMetrics) {
   245  	if ms.resolvers == nil {
   246  		return
   247  	}
   248  	for name, zone := range *ms.resolvers {
   249  		n.cache.putResolver(name)
   250  
   251  		px := fmt.Sprintf("resolver_zone_%s_", name)
   252  		mx[px+"requests_name"] = zone.Requests.Name
   253  		mx[px+"requests_srv"] = zone.Requests.Srv
   254  		mx[px+"requests_addr"] = zone.Requests.Addr
   255  		mx[px+"responses_noerror"] = zone.Responses.NoError
   256  		mx[px+"responses_formerr"] = zone.Responses.Formerr
   257  		mx[px+"responses_servfail"] = zone.Responses.Servfail
   258  		mx[px+"responses_nxdomain"] = zone.Responses.Nxdomain
   259  		mx[px+"responses_notimp"] = zone.Responses.Notimp
   260  		mx[px+"responses_refused"] = zone.Responses.Refused
   261  		mx[px+"responses_timedout"] = zone.Responses.TimedOut
   262  		mx[px+"responses_unknown"] = zone.Responses.Unknown
   263  	}
   264  }
   265  
   266  func (n *NginxPlus) updateCharts() {
   267  	const notSeenLimit = 3
   268  
   269  	for key, v := range n.cache.httpCaches {
   270  		if v.updated && !v.hasCharts {
   271  			v.hasCharts = true
   272  			n.addHTTPCacheCharts(v.name)
   273  			continue
   274  		}
   275  		if !v.updated {
   276  			if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit {
   277  				delete(n.cache.httpCaches, key)
   278  				n.removeHTTPCacheCharts(v.name)
   279  			}
   280  		}
   281  	}
   282  	for key, v := range n.cache.httpServerZones {
   283  		if v.updated && !v.hasCharts {
   284  			v.hasCharts = true
   285  			n.addHTTPServerZoneCharts(v.zone)
   286  			continue
   287  		}
   288  		if !v.updated {
   289  			if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit {
   290  				delete(n.cache.httpServerZones, key)
   291  				n.removeHTTPServerZoneCharts(v.zone)
   292  			}
   293  		}
   294  	}
   295  	for key, v := range n.cache.httpLocationZones {
   296  		if v.updated && !v.hasCharts {
   297  			v.hasCharts = true
   298  			n.addHTTPLocationZoneCharts(v.zone)
   299  			continue
   300  		}
   301  		if !v.updated {
   302  			if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit {
   303  				delete(n.cache.httpLocationZones, key)
   304  				n.removeHTTPLocationZoneCharts(v.zone)
   305  			}
   306  		}
   307  	}
   308  	for key, v := range n.cache.httpUpstreams {
   309  		if v.updated && !v.hasCharts {
   310  			v.hasCharts = true
   311  			n.addHTTPUpstreamCharts(v.name, v.zone)
   312  			continue
   313  		}
   314  		if !v.updated {
   315  			if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit {
   316  				delete(n.cache.httpUpstreams, key)
   317  				n.removeHTTPUpstreamCharts(v.name, v.zone)
   318  			}
   319  		}
   320  	}
   321  	for key, v := range n.cache.httpUpstreamServers {
   322  		if v.updated && !v.hasCharts {
   323  			v.hasCharts = true
   324  			n.addHTTPUpstreamServerCharts(v.name, v.serverAddr, v.serverName, v.zone)
   325  			continue
   326  		}
   327  		if !v.updated {
   328  			if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit {
   329  				delete(n.cache.httpUpstreamServers, key)
   330  				n.removeHTTPUpstreamServerCharts(v.name, v.serverAddr, v.zone)
   331  			}
   332  		}
   333  	}
   334  	for key, v := range n.cache.streamServerZones {
   335  		if v.updated && !v.hasCharts {
   336  			v.hasCharts = true
   337  			n.addStreamServerZoneCharts(v.zone)
   338  			continue
   339  		}
   340  		if !v.updated {
   341  			if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit {
   342  				delete(n.cache.streamServerZones, key)
   343  				n.removeStreamServerZoneCharts(v.zone)
   344  			}
   345  		}
   346  	}
   347  	for key, v := range n.cache.streamUpstreams {
   348  		if v.updated && !v.hasCharts {
   349  			v.hasCharts = true
   350  			n.addStreamUpstreamCharts(v.name, v.zone)
   351  			continue
   352  		}
   353  		if !v.updated {
   354  			if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit {
   355  				delete(n.cache.streamUpstreams, key)
   356  				n.removeStreamUpstreamCharts(v.name, v.zone)
   357  			}
   358  		}
   359  	}
   360  	for key, v := range n.cache.streamUpstreamServers {
   361  		if v.updated && !v.hasCharts {
   362  			v.hasCharts = true
   363  			n.addStreamUpstreamServerCharts(v.name, v.serverAddr, v.serverName, v.zone)
   364  			continue
   365  		}
   366  		if !v.updated {
   367  			if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit {
   368  				delete(n.cache.streamUpstreamServers, key)
   369  				n.removeStreamUpstreamServerCharts(v.name, v.serverAddr, v.zone)
   370  			}
   371  		}
   372  	}
   373  	for key, v := range n.cache.resolvers {
   374  		if v.updated && !v.hasCharts {
   375  			v.hasCharts = true
   376  			n.addResolverZoneCharts(v.zone)
   377  			continue
   378  		}
   379  		if !v.updated {
   380  			if v.notSeenTimes++; v.notSeenTimes >= notSeenLimit {
   381  				delete(n.cache.resolvers, key)
   382  				n.removeResolverZoneCharts(v.zone)
   383  			}
   384  		}
   385  	}
   386  }
   387  
   388  func boolToInt(v bool) int64 {
   389  	if v {
   390  		return 1
   391  	}
   392  	return 0
   393  }