forked from bakins/php-fpm-exporter
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcollector.go
237 lines (203 loc) · 5.98 KB
/
collector.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
package exporter
import (
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strconv"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
fcgiclient "github.com/tomasen/fcgi_client"
"go.uber.org/zap"
)
var (
statusLineRegexp = regexp.MustCompile(`(?m)^(.*):\s+(.*)$`)
)
type collector struct {
exporter *Exporter
up *prometheus.Desc
acceptedConn *prometheus.Desc
listenQueue *prometheus.Desc
maxListenQueue *prometheus.Desc
listenQueueLength *prometheus.Desc
phpProcesses *prometheus.Desc
maxActiveProcesses *prometheus.Desc
maxChildrenReached *prometheus.Desc
slowRequests *prometheus.Desc
scrapeFailures *prometheus.Desc
failureCount int
}
const metricsNamespace = "phpfpm"
func newFuncMetric(metricName string, docString string, labels []string) *prometheus.Desc {
return prometheus.NewDesc(
prometheus.BuildFQName(metricsNamespace, "", metricName),
docString, labels, nil,
)
}
func (e *Exporter) newCollector() *collector {
return &collector{
exporter: e,
up: newFuncMetric("up", "able to contact php-fpm", nil),
acceptedConn: newFuncMetric("accepted_connections_total", "Total number of accepted connections", nil),
listenQueue: newFuncMetric("listen_queue_connections", "Number of connections that have been initiated but not yet accepted", nil),
maxListenQueue: newFuncMetric("listen_queue_max_connections", "Max number of connections the listen queue has reached since FPM start", nil),
listenQueueLength: newFuncMetric("listen_queue_length_connections", "The length of the socket queue, dictating maximum number of pending connections", nil),
phpProcesses: newFuncMetric("processes_total", "process count", []string{"state"}),
maxActiveProcesses: newFuncMetric("active_max_processes", "Maximum active process count", nil),
maxChildrenReached: newFuncMetric("max_children_reached_total", "Number of times the process limit has been reached", nil),
slowRequests: newFuncMetric("slow_requests_total", "Number of requests that exceed request_slowlog_timeout", nil),
scrapeFailures: newFuncMetric("scrape_failures_total", "Number of errors while scraping php_fpm", nil),
}
}
func (c *collector) Describe(ch chan<- *prometheus.Desc) {
ch <- c.up
ch <- c.scrapeFailures
ch <- c.acceptedConn
ch <- c.listenQueue
ch <- c.maxListenQueue
ch <- c.listenQueueLength
ch <- c.phpProcesses
ch <- c.maxActiveProcesses
ch <- c.maxChildrenReached
ch <- c.slowRequests
}
func getDataFastcgi(u *url.URL) ([]byte, error) {
path := u.Path
host := u.Host
if path == "" || u.Scheme == "unix" {
path = "/status"
}
if u.Scheme == "unix" {
host = u.Path
}
env := map[string]string{
"SCRIPT_FILENAME": path,
"SCRIPT_NAME": path,
}
fcgi, err := fcgiclient.Dial(u.Scheme, host)
if err != nil {
return nil, errors.Wrap(err, "fastcgi dial failed")
}
defer fcgi.Close()
resp, err := fcgi.Get(env)
if err != nil {
return nil, errors.Wrap(err, "fastcgi get failed")
}
defer resp.Body.Close()
if resp.StatusCode != 200 && resp.StatusCode != 0 {
return nil, errors.Errorf("unexpected status: %d", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "failed to read fastcgi body")
}
return body, nil
}
func getDataHTTP(u *url.URL) ([]byte, error) {
req := http.Request{
Method: "GET",
URL: u,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
Host: u.Host,
}
resp, err := http.DefaultClient.Do(&req)
if err != nil {
return nil, errors.Wrap(err, "HTTP request failed")
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, errors.Errorf("unexpected HTTP status: %d", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "failed to read http body")
}
return body, nil
}
func (c *collector) Collect(ch chan<- prometheus.Metric) {
up := 1.0
var (
body []byte
err error
)
if c.exporter.fcgiEndpoint != nil && c.exporter.fcgiEndpoint.String() != "" {
body, err = getDataFastcgi(c.exporter.fcgiEndpoint)
} else {
body, err = getDataHTTP(c.exporter.endpoint)
}
if err != nil {
up = 0.0
c.exporter.logger.Error("failed to get php-fpm status", zap.Error(err))
c.failureCount++
}
ch <- prometheus.MustNewConstMetric(
c.up,
prometheus.GaugeValue,
up,
)
ch <- prometheus.MustNewConstMetric(
c.scrapeFailures,
prometheus.CounterValue,
float64(c.failureCount),
)
if up == 0.0 {
return
}
matches := statusLineRegexp.FindAllStringSubmatch(string(body), -1)
for _, match := range matches {
key := match[1]
value, err := strconv.Atoi(match[2])
if err != nil {
continue
}
var desc *prometheus.Desc
var valueType prometheus.ValueType
labels := []string{}
switch key {
case "accepted conn":
desc = c.acceptedConn
valueType = prometheus.CounterValue
case "listen queue":
desc = c.listenQueue
valueType = prometheus.GaugeValue
case "max listen queue":
desc = c.maxListenQueue
valueType = prometheus.CounterValue
case "listen queue len":
desc = c.listenQueueLength
valueType = prometheus.GaugeValue
case "idle processes":
desc = c.phpProcesses
valueType = prometheus.GaugeValue
labels = append(labels, "idle")
case "active processes":
desc = c.phpProcesses
valueType = prometheus.GaugeValue
labels = append(labels, "active")
case "max active processes":
desc = c.maxActiveProcesses
valueType = prometheus.CounterValue
case "max children reached":
desc = c.maxChildrenReached
valueType = prometheus.CounterValue
case "slow requests":
desc = c.slowRequests
valueType = prometheus.CounterValue
default:
continue
}
m, err := prometheus.NewConstMetric(desc, valueType, float64(value), labels...)
if err != nil {
c.exporter.logger.Error(
"failed to create metrics",
zap.String("key", key),
zap.Error(err),
)
continue
}
ch <- m
}
}