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 }