github.com/netdata/go.d.plugin@v0.58.1/modules/nginxplus/nginx_http_api_query.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package nginxplus 4 5 import ( 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "net/http" 11 "sync" 12 13 "github.com/netdata/go.d.plugin/pkg/web" 14 ) 15 16 const ( 17 urlPathAPIVersions = "/api/" 18 urlPathAPIEndpointsRoot = "/api/%d" 19 urlPathAPINginx = "/api/%d/nginx" 20 urlPathAPIEndpointsHTTP = "/api/%d/http" 21 urlPathAPIEndpointsStream = "/api/%d/stream" 22 urlPathAPIConnections = "/api/%d/connections" 23 urlPathAPISSL = "/api/%d/ssl" 24 urlPathAPIResolvers = "/api/%d/resolvers" 25 urlPathAPIHTTPRequests = "/api/%d/http/requests" 26 urlPathAPIHTTPServerZones = "/api/%d/http/server_zones" 27 urlPathAPIHTTPLocationZones = "/api/%d/http/location_zones" 28 urlPathAPIHTTPUpstreams = "/api/%d/http/upstreams" 29 urlPathAPIHTTPCaches = "/api/%d/http/caches" 30 urlPathAPIStreamServerZones = "/api/%d/stream/server_zones" 31 urlPathAPIStreamUpstreams = "/api/%d/stream/upstreams" 32 ) 33 34 type nginxMetrics struct { 35 info *nginxInfo 36 connections *nginxConnections 37 ssl *nginxSSL 38 httpRequests *nginxHTTPRequests 39 httpServerZones *nginxHTTPServerZones 40 httpLocationZones *nginxHTTPLocationZones 41 httpUpstreams *nginxHTTPUpstreams 42 httpCaches *nginxHTTPCaches 43 streamServerZones *nginxStreamServerZones 44 streamUpstreams *nginxStreamUpstreams 45 resolvers *nginxResolvers 46 } 47 48 func (n *NginxPlus) queryAPIVersion() (int64, error) { 49 req, _ := web.NewHTTPRequest(n.Request.Copy()) 50 req.URL.Path = urlPathAPIVersions 51 52 var versions nginxAPIVersions 53 if err := n.doWithDecode(&versions, req); err != nil { 54 return 0, err 55 } 56 57 if len(versions) == 0 { 58 return 0, fmt.Errorf("'%s' returned no data", req.URL) 59 } 60 61 return versions[len(versions)-1], nil 62 } 63 64 func (n *NginxPlus) queryAvailableEndpoints() error { 65 req, _ := web.NewHTTPRequest(n.Request.Copy()) 66 req.URL.Path = fmt.Sprintf(urlPathAPIEndpointsRoot, n.apiVersion) 67 68 var endpoints []string 69 if err := n.doWithDecode(&endpoints, req); err != nil { 70 return err 71 } 72 73 n.Debugf("discovered root endpoints: %v", endpoints) 74 var hasHTTP, hasStream bool 75 for _, v := range endpoints { 76 switch v { 77 case "nginx": 78 n.endpoints.nginx = true 79 case "connections": 80 n.endpoints.connections = true 81 case "ssl": 82 n.endpoints.ssl = true 83 case "resolvers": 84 n.endpoints.resolvers = true 85 case "http": 86 hasHTTP = true 87 case "stream": 88 hasStream = true 89 } 90 } 91 92 if hasHTTP { 93 endpoints = endpoints[:0] 94 req, _ = web.NewHTTPRequest(n.Request.Copy()) 95 req.URL.Path = fmt.Sprintf(urlPathAPIEndpointsHTTP, n.apiVersion) 96 97 if err := n.doWithDecode(&endpoints, req); err != nil { 98 return err 99 } 100 101 n.Debugf("discovered http endpoints: %v", endpoints) 102 for _, v := range endpoints { 103 switch v { 104 case "requests": 105 n.endpoints.httpRequest = true 106 case "server_zones": 107 n.endpoints.httpServerZones = true 108 case "location_zones": 109 n.endpoints.httpLocationZones = true 110 case "caches": 111 n.endpoints.httpCaches = true 112 case "upstreams": 113 n.endpoints.httpUpstreams = true 114 } 115 } 116 } 117 118 if hasStream { 119 endpoints = endpoints[:0] 120 req, _ = web.NewHTTPRequest(n.Request.Copy()) 121 req.URL.Path = fmt.Sprintf(urlPathAPIEndpointsStream, n.apiVersion) 122 123 if err := n.doWithDecode(&endpoints, req); err != nil { 124 return err 125 } 126 127 n.Debugf("discovered stream endpoints: %v", endpoints) 128 for _, v := range endpoints { 129 switch v { 130 case "server_zones": 131 n.endpoints.streamServerZones = true 132 case "upstreams": 133 n.endpoints.streamUpstreams = true 134 } 135 } 136 } 137 138 return nil 139 } 140 141 func (n *NginxPlus) queryMetrics() *nginxMetrics { 142 ms := &nginxMetrics{} 143 wg := &sync.WaitGroup{} 144 145 for _, task := range []struct { 146 do bool 147 fn func(*nginxMetrics) 148 }{ 149 {do: n.endpoints.nginx, fn: n.queryNginxInfo}, 150 {do: n.endpoints.connections, fn: n.queryConnections}, 151 {do: n.endpoints.ssl, fn: n.querySSL}, 152 {do: n.endpoints.httpRequest, fn: n.queryHTTPRequests}, 153 {do: n.endpoints.httpServerZones, fn: n.queryHTTPServerZones}, 154 {do: n.endpoints.httpLocationZones, fn: n.queryHTTPLocationZones}, 155 {do: n.endpoints.httpUpstreams, fn: n.queryHTTPUpstreams}, 156 {do: n.endpoints.httpCaches, fn: n.queryHTTPCaches}, 157 {do: n.endpoints.streamServerZones, fn: n.queryStreamServerZones}, 158 {do: n.endpoints.streamUpstreams, fn: n.queryStreamUpstreams}, 159 {do: n.endpoints.resolvers, fn: n.queryResolvers}, 160 } { 161 task := task 162 if task.do { 163 wg.Add(1) 164 go func() { task.fn(ms); wg.Done() }() 165 } 166 } 167 168 wg.Wait() 169 170 return ms 171 } 172 173 func (n *NginxPlus) queryNginxInfo(ms *nginxMetrics) { 174 req, _ := web.NewHTTPRequest(n.Request.Copy()) 175 req.URL.Path = fmt.Sprintf(urlPathAPINginx, n.apiVersion) 176 177 var v nginxInfo 178 179 if err := n.doWithDecode(&v, req); err != nil { 180 n.endpoints.nginx = !errors.Is(err, errPathNotFound) 181 n.Warning(err) 182 return 183 } 184 185 ms.info = &v 186 } 187 188 func (n *NginxPlus) queryConnections(ms *nginxMetrics) { 189 req, _ := web.NewHTTPRequest(n.Request.Copy()) 190 req.URL.Path = fmt.Sprintf(urlPathAPIConnections, n.apiVersion) 191 192 var v nginxConnections 193 194 if err := n.doWithDecode(&v, req); err != nil { 195 n.endpoints.connections = !errors.Is(err, errPathNotFound) 196 n.Warning(err) 197 return 198 } 199 200 ms.connections = &v 201 } 202 203 func (n *NginxPlus) querySSL(ms *nginxMetrics) { 204 req, _ := web.NewHTTPRequest(n.Request.Copy()) 205 req.URL.Path = fmt.Sprintf(urlPathAPISSL, n.apiVersion) 206 207 var v nginxSSL 208 209 if err := n.doWithDecode(&v, req); err != nil { 210 n.endpoints.ssl = !errors.Is(err, errPathNotFound) 211 n.Warning(err) 212 return 213 } 214 215 ms.ssl = &v 216 } 217 218 func (n *NginxPlus) queryHTTPRequests(ms *nginxMetrics) { 219 req, _ := web.NewHTTPRequest(n.Request.Copy()) 220 req.URL.Path = fmt.Sprintf(urlPathAPIHTTPRequests, n.apiVersion) 221 222 var v nginxHTTPRequests 223 224 if err := n.doWithDecode(&v, req); err != nil { 225 n.endpoints.httpRequest = !errors.Is(err, errPathNotFound) 226 n.Warning(err) 227 return 228 } 229 230 ms.httpRequests = &v 231 } 232 233 func (n *NginxPlus) queryHTTPServerZones(ms *nginxMetrics) { 234 req, _ := web.NewHTTPRequest(n.Request.Copy()) 235 req.URL.Path = fmt.Sprintf(urlPathAPIHTTPServerZones, n.apiVersion) 236 237 var v nginxHTTPServerZones 238 239 if err := n.doWithDecode(&v, req); err != nil { 240 n.endpoints.httpServerZones = !errors.Is(err, errPathNotFound) 241 n.Warning(err) 242 return 243 } 244 245 ms.httpServerZones = &v 246 } 247 248 func (n *NginxPlus) queryHTTPLocationZones(ms *nginxMetrics) { 249 req, _ := web.NewHTTPRequest(n.Request.Copy()) 250 req.URL.Path = fmt.Sprintf(urlPathAPIHTTPLocationZones, n.apiVersion) 251 252 var v nginxHTTPLocationZones 253 254 if err := n.doWithDecode(&v, req); err != nil { 255 n.endpoints.httpLocationZones = !errors.Is(err, errPathNotFound) 256 n.Warning(err) 257 return 258 } 259 260 ms.httpLocationZones = &v 261 } 262 263 func (n *NginxPlus) queryHTTPUpstreams(ms *nginxMetrics) { 264 req, _ := web.NewHTTPRequest(n.Request.Copy()) 265 req.URL.Path = fmt.Sprintf(urlPathAPIHTTPUpstreams, n.apiVersion) 266 267 var v nginxHTTPUpstreams 268 269 if err := n.doWithDecode(&v, req); err != nil { 270 n.endpoints.httpUpstreams = !errors.Is(err, errPathNotFound) 271 n.Warning(err) 272 return 273 } 274 275 ms.httpUpstreams = &v 276 } 277 278 func (n *NginxPlus) queryHTTPCaches(ms *nginxMetrics) { 279 req, _ := web.NewHTTPRequest(n.Request.Copy()) 280 req.URL.Path = fmt.Sprintf(urlPathAPIHTTPCaches, n.apiVersion) 281 282 var v nginxHTTPCaches 283 284 if err := n.doWithDecode(&v, req); err != nil { 285 n.endpoints.httpCaches = !errors.Is(err, errPathNotFound) 286 n.Warning(err) 287 return 288 } 289 290 ms.httpCaches = &v 291 } 292 293 func (n *NginxPlus) queryStreamServerZones(ms *nginxMetrics) { 294 req, _ := web.NewHTTPRequest(n.Request.Copy()) 295 req.URL.Path = fmt.Sprintf(urlPathAPIStreamServerZones, n.apiVersion) 296 297 var v nginxStreamServerZones 298 299 if err := n.doWithDecode(&v, req); err != nil { 300 n.endpoints.streamServerZones = !errors.Is(err, errPathNotFound) 301 n.Warning(err) 302 return 303 } 304 305 ms.streamServerZones = &v 306 } 307 308 func (n *NginxPlus) queryStreamUpstreams(ms *nginxMetrics) { 309 req, _ := web.NewHTTPRequest(n.Request.Copy()) 310 req.URL.Path = fmt.Sprintf(urlPathAPIStreamUpstreams, n.apiVersion) 311 312 var v nginxStreamUpstreams 313 314 if err := n.doWithDecode(&v, req); err != nil { 315 n.endpoints.streamUpstreams = !errors.Is(err, errPathNotFound) 316 n.Warning(err) 317 return 318 } 319 320 ms.streamUpstreams = &v 321 } 322 323 func (n *NginxPlus) queryResolvers(ms *nginxMetrics) { 324 req, _ := web.NewHTTPRequest(n.Request.Copy()) 325 req.URL.Path = fmt.Sprintf(urlPathAPIResolvers, n.apiVersion) 326 327 var v nginxResolvers 328 329 if err := n.doWithDecode(&v, req); err != nil { 330 n.endpoints.resolvers = !errors.Is(err, errPathNotFound) 331 n.Warning(err) 332 return 333 } 334 335 ms.resolvers = &v 336 } 337 338 var ( 339 errPathNotFound = errors.New("path not found") 340 ) 341 342 func (n *NginxPlus) doWithDecode(dst interface{}, req *http.Request) error { 343 n.Debugf("executing %s '%s'", req.Method, req.URL) 344 resp, err := n.httpClient.Do(req) 345 if err != nil { 346 return err 347 } 348 defer closeBody(resp) 349 350 if resp.StatusCode == http.StatusNotFound { 351 return fmt.Errorf("%s returned %d status code (%w)", req.URL, resp.StatusCode, errPathNotFound) 352 } 353 if resp.StatusCode != http.StatusOK { 354 return fmt.Errorf("%s returned %d status code (%s)", req.URL, resp.StatusCode, resp.Status) 355 } 356 357 content, err := io.ReadAll(resp.Body) 358 if err != nil { 359 return fmt.Errorf("error on reading response from %s : %v", req.URL, err) 360 } 361 362 if err := json.Unmarshal(content, dst); err != nil { 363 return fmt.Errorf("error on parsing response from %s : %v", req.URL, err) 364 } 365 366 return nil 367 } 368 369 func closeBody(resp *http.Response) { 370 if resp != nil && resp.Body != nil { 371 _, _ = io.Copy(io.Discard, resp.Body) 372 _ = resp.Body.Close() 373 } 374 } 375 376 func (n *nginxMetrics) empty() bool { 377 return n.info != nil && 378 n.connections == nil && 379 n.ssl == nil && 380 n.httpRequests == nil && 381 n.httpServerZones == nil && 382 n.httpLocationZones == nil && 383 n.httpUpstreams == nil && 384 n.httpCaches == nil && 385 n.streamServerZones == nil && 386 n.streamUpstreams == nil && 387 n.resolvers != nil 388 }