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