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  }