github.com/netdata/go.d.plugin@v0.58.1/modules/nginx/apiclient.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package nginx
     4  
     5  import (
     6  	"bufio"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/netdata/go.d.plugin/pkg/web"
    15  )
    16  
    17  const (
    18  	connActive  = "connActive"
    19  	connAccepts = "connAccepts"
    20  	connHandled = "connHandled"
    21  	requests    = "requests"
    22  	requestTime = "requestTime"
    23  	connReading = "connReading"
    24  	connWriting = "connWriting"
    25  	connWaiting = "connWaiting"
    26  )
    27  
    28  var (
    29  	nginxSeq = []string{
    30  		connActive,
    31  		connAccepts,
    32  		connHandled,
    33  		requests,
    34  		connReading,
    35  		connWriting,
    36  		connWaiting,
    37  	}
    38  	tengineSeq = []string{
    39  		connActive,
    40  		connAccepts,
    41  		connHandled,
    42  		requests,
    43  		requestTime,
    44  		connReading,
    45  		connWriting,
    46  		connWaiting,
    47  	}
    48  
    49  	reStatus = regexp.MustCompile(`^Active connections: ([0-9]+)\n[^\d]+([0-9]+) ([0-9]+) ([0-9]+) ?([0-9]+)?\nReading: ([0-9]+) Writing: ([0-9]+) Waiting: ([0-9]+)`)
    50  )
    51  
    52  func newAPIClient(client *http.Client, request web.Request) *apiClient {
    53  	return &apiClient{httpClient: client, request: request}
    54  }
    55  
    56  type apiClient struct {
    57  	httpClient *http.Client
    58  	request    web.Request
    59  }
    60  
    61  func (a apiClient) getStubStatus() (*stubStatus, error) {
    62  	req, err := web.NewHTTPRequest(a.request)
    63  	if err != nil {
    64  		return nil, fmt.Errorf("error on creating request : %v", err)
    65  	}
    66  
    67  	resp, err := a.doRequestOK(req)
    68  	defer closeBody(resp)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	status, err := parseStubStatus(resp.Body)
    74  	if err != nil {
    75  		return nil, fmt.Errorf("error on parsing response : %v", err)
    76  	}
    77  
    78  	return status, nil
    79  }
    80  
    81  func (a apiClient) doRequestOK(req *http.Request) (*http.Response, error) {
    82  	resp, err := a.httpClient.Do(req)
    83  	if err != nil {
    84  		return resp, fmt.Errorf("error on request : %v", err)
    85  	}
    86  
    87  	if resp.StatusCode != http.StatusOK {
    88  		return resp, fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode)
    89  	}
    90  
    91  	return resp, err
    92  }
    93  
    94  func closeBody(resp *http.Response) {
    95  	if resp != nil && resp.Body != nil {
    96  		_, _ = io.Copy(io.Discard, resp.Body)
    97  		_ = resp.Body.Close()
    98  	}
    99  }
   100  
   101  func parseStubStatus(r io.Reader) (*stubStatus, error) {
   102  	sc := bufio.NewScanner(r)
   103  	var lines []string
   104  
   105  	for sc.Scan() {
   106  		lines = append(lines, strings.Trim(sc.Text(), "\r\n "))
   107  	}
   108  
   109  	parsed := reStatus.FindStringSubmatch(strings.Join(lines, "\n"))
   110  
   111  	if len(parsed) == 0 {
   112  		return nil, fmt.Errorf("can't parse '%v'", lines)
   113  	}
   114  
   115  	parsed = parsed[1:]
   116  
   117  	var (
   118  		seq    []string
   119  		status stubStatus
   120  	)
   121  
   122  	switch len(parsed) {
   123  	default:
   124  		return nil, fmt.Errorf("invalid number of fields, got %d, expect %d or %d", len(parsed), len(nginxSeq), len(tengineSeq))
   125  	case len(nginxSeq):
   126  		seq = nginxSeq
   127  	case len(tengineSeq):
   128  		seq = tengineSeq
   129  	}
   130  
   131  	for i, key := range seq {
   132  		strValue := parsed[i]
   133  		if strValue == "" {
   134  			continue
   135  		}
   136  		value := mustParseInt(strValue)
   137  		switch key {
   138  		default:
   139  			return nil, fmt.Errorf("unknown key in seq : %s", key)
   140  		case connActive:
   141  			status.Connections.Active = value
   142  		case connAccepts:
   143  			status.Connections.Accepts = value
   144  		case connHandled:
   145  			status.Connections.Handled = value
   146  		case requests:
   147  			status.Requests.Total = value
   148  		case connReading:
   149  			status.Connections.Reading = value
   150  		case connWriting:
   151  			status.Connections.Writing = value
   152  		case connWaiting:
   153  			status.Connections.Waiting = value
   154  		case requestTime:
   155  			status.Requests.Time = &value
   156  		}
   157  	}
   158  
   159  	return &status, nil
   160  }
   161  
   162  func mustParseInt(value string) int64 {
   163  	v, err := strconv.ParseInt(value, 10, 64)
   164  	if err != nil {
   165  		panic(err)
   166  	}
   167  	return v
   168  }