github.com/netdata/go.d.plugin@v0.58.1/modules/lighttpd/apiclient.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package lighttpd 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 busyWorkers = "BusyWorkers" 18 idleWorkers = "IdleWorkers" 19 20 busyServers = "BusyServers" 21 idleServers = "IdleServers" 22 totalAccesses = "Total Accesses" 23 totalkBytes = "Total kBytes" 24 uptime = "Uptime" 25 scoreBoard = "Scoreboard" 26 ) 27 28 func newAPIClient(client *http.Client, request web.Request) *apiClient { 29 return &apiClient{httpClient: client, request: request} 30 } 31 32 type apiClient struct { 33 httpClient *http.Client 34 request web.Request 35 } 36 37 func (a apiClient) getServerStatus() (*serverStatus, error) { 38 req, err := web.NewHTTPRequest(a.request) 39 40 if err != nil { 41 return nil, fmt.Errorf("error on creating request : %v", err) 42 } 43 44 resp, err := a.doRequestOK(req) 45 46 defer closeBody(resp) 47 48 if err != nil { 49 return nil, err 50 } 51 52 status, err := parseResponse(resp.Body) 53 54 if err != nil { 55 return nil, fmt.Errorf("error on parsing response from %s : %v", req.URL, err) 56 } 57 58 return status, nil 59 } 60 61 func (a apiClient) doRequestOK(req *http.Request) (*http.Response, error) { 62 resp, err := a.httpClient.Do(req) 63 if err != nil { 64 return nil, fmt.Errorf("error on request : %v", err) 65 } 66 if resp.StatusCode != http.StatusOK { 67 return resp, fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode) 68 } 69 return resp, nil 70 } 71 72 func parseResponse(r io.Reader) (*serverStatus, error) { 73 s := bufio.NewScanner(r) 74 var status serverStatus 75 76 for s.Scan() { 77 parts := strings.Split(s.Text(), ":") 78 if len(parts) != 2 { 79 continue 80 } 81 key, value := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) 82 83 switch key { 84 default: 85 case busyWorkers, idleWorkers: 86 return nil, fmt.Errorf("found '%s', apache data", key) 87 case busyServers: 88 status.Servers.Busy = mustParseInt(value) 89 case idleServers: 90 status.Servers.Idle = mustParseInt(value) 91 case totalAccesses: 92 status.Total.Accesses = mustParseInt(value) 93 case totalkBytes: 94 status.Total.KBytes = mustParseInt(value) 95 case uptime: 96 status.Uptime = mustParseInt(value) 97 case scoreBoard: 98 status.Scoreboard = parseScoreboard(value) 99 } 100 } 101 102 return &status, nil 103 } 104 105 func parseScoreboard(value string) *scoreboard { 106 // Descriptions from https://blog.serverdensity.com/monitor-lighttpd/ 107 // 108 // “.” = Opening the TCP connection (connect) 109 // “C” = Closing the TCP connection if no other HTTP request will use it (close) 110 // “E” = hard error 111 // “k” = Keeping the TCP connection open for more HTTP requests from the same client to avoid the TCP handling overhead (keep-alive) 112 // “r” = ReadAsMap the content of the HTTP request (read) 113 // “R” = ReadAsMap the content of the HTTP request (read-POST) 114 // “W” = Write the HTTP response to the socket (write) 115 // “h” = Decide action to take with the request (handle-request) 116 // “q” = Start of HTTP request (request-start) 117 // “Q” = End of HTTP request (request-end) 118 // “s” = Start of the HTTP request response (response-start) 119 // “S” = End of the HTTP request response (response-end) 120 // “_” Waiting for Connection (NOTE: not sure, copied the description from apache score board) 121 122 var sb scoreboard 123 for _, s := range strings.Split(value, "") { 124 switch s { 125 case "_": 126 sb.Waiting++ 127 case ".": 128 sb.Open++ 129 case "C": 130 sb.Close++ 131 case "E": 132 sb.HardError++ 133 case "k": 134 sb.KeepAlive++ 135 case "r": 136 sb.Read++ 137 case "R": 138 sb.ReadPost++ 139 case "W": 140 sb.Write++ 141 case "h": 142 sb.HandleRequest++ 143 case "q": 144 sb.RequestStart++ 145 case "Q": 146 sb.RequestEnd++ 147 case "s": 148 sb.ResponseStart++ 149 case "S": 150 sb.ResponseEnd++ 151 } 152 } 153 154 return &sb 155 } 156 157 func mustParseInt(value string) *int64 { 158 v, err := strconv.ParseInt(value, 10, 64) 159 if err != nil { 160 panic(err) 161 } 162 return &v 163 } 164 165 func closeBody(resp *http.Response) { 166 if resp != nil && resp.Body != nil { 167 _, _ = io.Copy(io.Discard, resp.Body) 168 _ = resp.Body.Close() 169 } 170 }