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