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 }