github.com/netdata/go.d.plugin@v0.58.1/modules/apache/collect.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package apache 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/stm" 14 "github.com/netdata/go.d.plugin/pkg/web" 15 ) 16 17 func (a *Apache) collect() (map[string]int64, error) { 18 status, err := a.scrapeStatus() 19 if err != nil { 20 return nil, err 21 } 22 23 mx := stm.ToMap(status) 24 if len(mx) == 0 { 25 return nil, fmt.Errorf("nothing was collected from %s", a.URL) 26 } 27 28 a.once.Do(func() { a.charts = newCharts(status) }) 29 30 return mx, nil 31 } 32 33 func (a *Apache) scrapeStatus() (*serverStatus, error) { 34 req, err := web.NewHTTPRequest(a.Request) 35 if err != nil { 36 return nil, err 37 } 38 39 resp, err := a.httpClient.Do(req) 40 if err != nil { 41 return nil, fmt.Errorf("error on HTTP request '%s': %v", req.URL, err) 42 } 43 defer closeBody(resp) 44 45 if resp.StatusCode != http.StatusOK { 46 return nil, fmt.Errorf("'%s' returned HTTP status code: %d", req.URL, resp.StatusCode) 47 } 48 49 return parseResponse(resp.Body) 50 } 51 52 func parseResponse(r io.Reader) (*serverStatus, error) { 53 s := bufio.NewScanner(r) 54 var status serverStatus 55 56 for s.Scan() { 57 parts := strings.Split(s.Text(), ":") 58 if len(parts) != 2 { 59 continue 60 } 61 62 key, value := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) 63 64 switch key { 65 default: 66 case "BusyServers", "IdleServers": 67 return nil, fmt.Errorf("found '%s', Lighttpd data", key) 68 case "BusyWorkers": 69 status.Workers.Busy = parseInt(value) 70 case "IdleWorkers": 71 status.Workers.Idle = parseInt(value) 72 case "ConnsTotal": 73 status.Connections.Total = parseInt(value) 74 case "ConnsAsyncWriting": 75 status.Connections.Async.Writing = parseInt(value) 76 case "ConnsAsyncKeepAlive": 77 status.Connections.Async.KeepAlive = parseInt(value) 78 case "ConnsAsyncClosing": 79 status.Connections.Async.Closing = parseInt(value) 80 case "Total Accesses": 81 status.Total.Accesses = parseInt(value) 82 case "Total kBytes": 83 status.Total.KBytes = parseInt(value) 84 case "Uptime": 85 status.Uptime = parseInt(value) 86 case "ReqPerSec": 87 status.Averages.ReqPerSec = parseFloat(value) 88 case "BytesPerSec": 89 status.Averages.BytesPerSec = parseFloat(value) 90 case "BytesPerReq": 91 status.Averages.BytesPerReq = parseFloat(value) 92 case "Scoreboard": 93 status.Scoreboard = parseScoreboard(value) 94 } 95 } 96 97 return &status, nil 98 } 99 100 func parseScoreboard(line string) *scoreboard { 101 // “_” Waiting for Connection 102 // “S” Starting up 103 // “R” Reading Request 104 // “W” Sending Reply 105 // “K” Keepalive (read) 106 // “D” DNS Lookup 107 // “C” Closing connection 108 // “L” Logging 109 // “G” Gracefully finishing 110 // “I” Idle cleanup of worker 111 // “.” Open slot with no current process 112 var sb scoreboard 113 for _, s := range strings.Split(line, "") { 114 switch s { 115 case "_": 116 sb.Waiting++ 117 case "S": 118 sb.Starting++ 119 case "R": 120 sb.Reading++ 121 case "W": 122 sb.Sending++ 123 case "K": 124 sb.KeepAlive++ 125 case "D": 126 sb.DNSLookup++ 127 case "C": 128 sb.Closing++ 129 case "L": 130 sb.Logging++ 131 case "G": 132 sb.Finishing++ 133 case "I": 134 sb.IdleCleanup++ 135 case ".": 136 sb.Open++ 137 } 138 } 139 return &sb 140 } 141 142 func parseInt(value string) *int64 { 143 v, err := strconv.ParseInt(value, 10, 64) 144 if err != nil { 145 return nil 146 } 147 return &v 148 } 149 150 func parseFloat(value string) *float64 { 151 v, err := strconv.ParseFloat(value, 64) 152 if err != nil { 153 return nil 154 } 155 return &v 156 } 157 158 func closeBody(resp *http.Response) { 159 if resp != nil && resp.Body != nil { 160 _, _ = io.Copy(io.Discard, resp.Body) 161 _ = resp.Body.Close() 162 } 163 }