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

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package tengine
     4  
     5  import (
     6  	"bufio"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/netdata/go.d.plugin/pkg/web"
    14  )
    15  
    16  const (
    17  	bytesIn               = "bytes_in"
    18  	bytesOut              = "bytes_out"
    19  	connTotal             = "conn_total"
    20  	reqTotal              = "req_total"
    21  	http2xx               = "http_2xx"
    22  	http3xx               = "http_3xx"
    23  	http4xx               = "http_4xx"
    24  	http5xx               = "http_5xx"
    25  	httpOtherStatus       = "http_other_status"
    26  	rt                    = "rt"
    27  	upsReq                = "ups_req"
    28  	upsRT                 = "ups_rt"
    29  	upsTries              = "ups_tries"
    30  	http200               = "http_200"
    31  	http206               = "http_206"
    32  	http302               = "http_302"
    33  	http304               = "http_304"
    34  	http403               = "http_403"
    35  	http404               = "http_404"
    36  	http416               = "http_416"
    37  	http499               = "http_499"
    38  	http500               = "http_500"
    39  	http502               = "http_502"
    40  	http503               = "http_503"
    41  	http504               = "http_504"
    42  	http508               = "http_508"
    43  	httpOtherDetailStatus = "http_other_detail_status"
    44  	httpUps4xx            = "http_ups_4xx"
    45  	httpUps5xx            = "http_ups_5xx"
    46  )
    47  
    48  var defaultLineFormat = []string{
    49  	bytesIn,
    50  	bytesOut,
    51  	connTotal,
    52  	reqTotal,
    53  	http2xx,
    54  	http3xx,
    55  	http4xx,
    56  	http5xx,
    57  	httpOtherStatus,
    58  	rt,
    59  	upsReq,
    60  	upsRT,
    61  	upsTries,
    62  	http200,
    63  	http206,
    64  	http302,
    65  	http304,
    66  	http403,
    67  	http404,
    68  	http416,
    69  	http499,
    70  	http500,
    71  	http502,
    72  	http503,
    73  	http504,
    74  	http508,
    75  	httpOtherDetailStatus,
    76  	httpUps4xx,
    77  	httpUps5xx,
    78  }
    79  
    80  func newAPIClient(client *http.Client, request web.Request) *apiClient {
    81  	return &apiClient{httpClient: client, request: request}
    82  }
    83  
    84  type apiClient struct {
    85  	httpClient *http.Client
    86  	request    web.Request
    87  }
    88  
    89  func (a apiClient) getStatus() (*tengineStatus, error) {
    90  	req, err := web.NewHTTPRequest(a.request)
    91  	if err != nil {
    92  		return nil, fmt.Errorf("error on creating request : %v", err)
    93  	}
    94  
    95  	resp, err := a.doRequestOK(req)
    96  	defer closeBody(resp)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	status, err := parseStatus(resp.Body)
   102  	if err != nil {
   103  		return nil, fmt.Errorf("error on parsing response : %v", err)
   104  	}
   105  
   106  	return status, nil
   107  }
   108  
   109  func (a apiClient) doRequestOK(req *http.Request) (*http.Response, error) {
   110  	resp, err := a.httpClient.Do(req)
   111  	if err != nil {
   112  		return nil, fmt.Errorf("error on request : %v", err)
   113  	}
   114  	if resp.StatusCode != http.StatusOK {
   115  		return resp, fmt.Errorf("%s returned HTTP code %d", req.URL, resp.StatusCode)
   116  	}
   117  	return resp, nil
   118  }
   119  
   120  func closeBody(resp *http.Response) {
   121  	if resp != nil && resp.Body != nil {
   122  		_, _ = io.Copy(io.Discard, resp.Body)
   123  		_ = resp.Body.Close()
   124  	}
   125  }
   126  
   127  func parseStatus(r io.Reader) (*tengineStatus, error) {
   128  	var status tengineStatus
   129  
   130  	s := bufio.NewScanner(r)
   131  	for s.Scan() {
   132  		m, err := parseStatusLine(s.Text(), defaultLineFormat)
   133  		if err != nil {
   134  			return nil, err
   135  		}
   136  		status = append(status, *m)
   137  	}
   138  
   139  	return &status, nil
   140  }
   141  
   142  func parseStatusLine(line string, lineFormat []string) (*metric, error) {
   143  	parts := strings.Split(line, ",")
   144  
   145  	// NOTE: only default line format is supported
   146  	// TODO: custom line format?
   147  	// www.example.com,127.0.0.1:80,162,6242,1,1,1,0,0,0,0,10,1,10,1....
   148  	i := findFirstInt(parts)
   149  	if i == -1 {
   150  		return nil, fmt.Errorf("invalid line : %s", line)
   151  	}
   152  	if len(parts[i:]) != len(lineFormat) {
   153  		return nil, fmt.Errorf("invalid line length, got %d, expected %d, line : %s",
   154  			len(parts[i:]), len(lineFormat), line)
   155  	}
   156  
   157  	// skip "$host,$server_addr:$server_port"
   158  	parts = parts[i:]
   159  
   160  	var m metric
   161  	for i, key := range lineFormat {
   162  		value := mustParseInt(parts[i])
   163  		switch key {
   164  		default:
   165  			return nil, fmt.Errorf("unknown line format key: %s", key)
   166  		case bytesIn:
   167  			m.BytesIn = value
   168  		case bytesOut:
   169  			m.BytesOut = value
   170  		case connTotal:
   171  			m.ConnTotal = value
   172  		case reqTotal:
   173  			m.ReqTotal = value
   174  		case http2xx:
   175  			m.HTTP2xx = value
   176  		case http3xx:
   177  			m.HTTP3xx = value
   178  		case http4xx:
   179  			m.HTTP4xx = value
   180  		case http5xx:
   181  			m.HTTP5xx = value
   182  		case httpOtherStatus:
   183  			m.HTTPOtherStatus = value
   184  		case rt:
   185  			m.RT = value
   186  		case upsReq:
   187  			m.UpsReq = value
   188  		case upsRT:
   189  			m.UpsRT = value
   190  		case upsTries:
   191  			m.UpsTries = value
   192  		case http200:
   193  			m.HTTP200 = value
   194  		case http206:
   195  			m.HTTP206 = value
   196  		case http302:
   197  			m.HTTP302 = value
   198  		case http304:
   199  			m.HTTP304 = value
   200  		case http403:
   201  			m.HTTP403 = value
   202  		case http404:
   203  			m.HTTP404 = value
   204  		case http416:
   205  			m.HTTP416 = value
   206  		case http499:
   207  			m.HTTP499 = value
   208  		case http500:
   209  			m.HTTP500 = value
   210  		case http502:
   211  			m.HTTP502 = value
   212  		case http503:
   213  			m.HTTP503 = value
   214  		case http504:
   215  			m.HTTP504 = value
   216  		case http508:
   217  			m.HTTP508 = value
   218  		case httpOtherDetailStatus:
   219  			m.HTTPOtherDetailStatus = value
   220  		case httpUps4xx:
   221  			m.HTTPUps4xx = value
   222  		case httpUps5xx:
   223  			m.HTTPUps5xx = value
   224  		}
   225  	}
   226  	return &m, nil
   227  }
   228  
   229  func findFirstInt(s []string) int {
   230  	for i, v := range s {
   231  		_, err := strconv.ParseInt(v, 10, 64)
   232  		if err != nil {
   233  			continue
   234  		}
   235  		return i
   236  	}
   237  	return -1
   238  }
   239  
   240  func mustParseInt(value string) *int64 {
   241  	v, err := strconv.ParseInt(value, 10, 64)
   242  	if err != nil {
   243  		panic(err)
   244  	}
   245  
   246  	return &v
   247  }