1// Copyright © 2018 Enrico Stahn <enrico.stahn@gmail.com>
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6//     http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14// Package phpfpm provides convenient access to PHP-FPM pool data
15package phpfpm
16
17import (
18	"fmt"
19	"sync"
20
21	"github.com/prometheus/client_golang/prometheus"
22)
23
24const (
25	namespace = "phpfpm"
26)
27
28// Exporter configures and exposes PHP-FPM metrics to Prometheus.
29type Exporter struct {
30	mutex       sync.Mutex
31	PoolManager PoolManager
32
33	CountProcessState bool
34
35	up                       *prometheus.Desc
36	scrapeFailues            *prometheus.Desc
37	startSince               *prometheus.Desc
38	acceptedConnections      *prometheus.Desc
39	listenQueue              *prometheus.Desc
40	maxListenQueue           *prometheus.Desc
41	listenQueueLength        *prometheus.Desc
42	idleProcesses            *prometheus.Desc
43	activeProcesses          *prometheus.Desc
44	totalProcesses           *prometheus.Desc
45	maxActiveProcesses       *prometheus.Desc
46	maxChildrenReached       *prometheus.Desc
47	slowRequests             *prometheus.Desc
48	processRequests          *prometheus.Desc
49	processLastRequestMemory *prometheus.Desc
50	processLastRequestCPU    *prometheus.Desc
51	processRequestDuration   *prometheus.Desc
52	processState             *prometheus.Desc
53}
54
55// NewExporter creates a new Exporter for a PoolManager and configures the necessary metrics.
56func NewExporter(pm PoolManager) *Exporter {
57	return &Exporter{
58		PoolManager: pm,
59
60		CountProcessState: false,
61
62		up: prometheus.NewDesc(
63			prometheus.BuildFQName(namespace, "", "up"),
64			"Could PHP-FPM be reached?",
65			[]string{"pool", "scrape_uri"},
66			nil),
67
68		scrapeFailues: prometheus.NewDesc(
69			prometheus.BuildFQName(namespace, "", "scrape_failures"),
70			"The number of failures scraping from PHP-FPM.",
71			[]string{"pool", "scrape_uri"},
72			nil),
73
74		startSince: prometheus.NewDesc(
75			prometheus.BuildFQName(namespace, "", "start_since"),
76			"The number of seconds since FPM has started.",
77			[]string{"pool", "scrape_uri"},
78			nil),
79
80		acceptedConnections: prometheus.NewDesc(
81			prometheus.BuildFQName(namespace, "", "accepted_connections"),
82			"The number of requests accepted by the pool.",
83			[]string{"pool", "scrape_uri"},
84			nil),
85
86		listenQueue: prometheus.NewDesc(
87			prometheus.BuildFQName(namespace, "", "listen_queue"),
88			"The number of requests in the queue of pending connections.",
89			[]string{"pool", "scrape_uri"},
90			nil),
91
92		maxListenQueue: prometheus.NewDesc(
93			prometheus.BuildFQName(namespace, "", "max_listen_queue"),
94			"The maximum number of requests in the queue of pending connections since FPM has started.",
95			[]string{"pool", "scrape_uri"},
96			nil),
97
98		listenQueueLength: prometheus.NewDesc(
99			prometheus.BuildFQName(namespace, "", "listen_queue_length"),
100			"The size of the socket queue of pending connections.",
101			[]string{"pool", "scrape_uri"},
102			nil),
103
104		idleProcesses: prometheus.NewDesc(
105			prometheus.BuildFQName(namespace, "", "idle_processes"),
106			"The number of idle processes.",
107			[]string{"pool", "scrape_uri"},
108			nil),
109
110		activeProcesses: prometheus.NewDesc(
111			prometheus.BuildFQName(namespace, "", "active_processes"),
112			"The number of active processes.",
113			[]string{"pool", "scrape_uri"},
114			nil),
115
116		totalProcesses: prometheus.NewDesc(
117			prometheus.BuildFQName(namespace, "", "total_processes"),
118			"The number of idle + active processes.",
119			[]string{"pool", "scrape_uri"},
120			nil),
121
122		maxActiveProcesses: prometheus.NewDesc(
123			prometheus.BuildFQName(namespace, "", "max_active_processes"),
124			"The maximum number of active processes since FPM has started.",
125			[]string{"pool", "scrape_uri"},
126			nil),
127
128		maxChildrenReached: prometheus.NewDesc(
129			prometheus.BuildFQName(namespace, "", "max_children_reached"),
130			"The number of times, the process limit has been reached, when pm tries to start more children (works only for pm 'dynamic' and 'ondemand').",
131			[]string{"pool", "scrape_uri"},
132			nil),
133
134		slowRequests: prometheus.NewDesc(
135			prometheus.BuildFQName(namespace, "", "slow_requests"),
136			"The number of requests that exceeded your 'request_slowlog_timeout' value.",
137			[]string{"pool", "scrape_uri"},
138			nil),
139
140		processRequests: prometheus.NewDesc(
141			prometheus.BuildFQName(namespace, "", "process_requests"),
142			"The number of requests the process has served.",
143			[]string{"pool", "child", "scrape_uri"},
144			nil),
145
146		processLastRequestMemory: prometheus.NewDesc(
147			prometheus.BuildFQName(namespace, "", "process_last_request_memory"),
148			"The max amount of memory the last request consumed.",
149			[]string{"pool", "child", "scrape_uri"},
150			nil),
151
152		processLastRequestCPU: prometheus.NewDesc(
153			prometheus.BuildFQName(namespace, "", "process_last_request_cpu"),
154			"The %cpu the last request consumed.",
155			[]string{"pool", "child", "scrape_uri"},
156			nil),
157
158		processRequestDuration: prometheus.NewDesc(
159			prometheus.BuildFQName(namespace, "", "process_request_duration"),
160			"The duration in microseconds of the requests.",
161			[]string{"pool", "child", "scrape_uri"},
162			nil),
163
164		processState: prometheus.NewDesc(
165			prometheus.BuildFQName(namespace, "", "process_state"),
166			"The state of the process (Idle, Running, ...).",
167			[]string{"pool", "child", "state", "scrape_uri"},
168			nil),
169	}
170}
171
172// Collect updates the Pools and sends the collected metrics to Prometheus
173func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
174	e.mutex.Lock()
175	defer e.mutex.Unlock()
176
177	if err := e.PoolManager.Update(); err != nil {
178		log.Error(err)
179	}
180
181	for _, pool := range e.PoolManager.Pools {
182		ch <- prometheus.MustNewConstMetric(e.scrapeFailues, prometheus.CounterValue, float64(pool.ScrapeFailures), pool.Name, pool.Address)
183
184		if pool.ScrapeError != nil {
185			ch <- prometheus.MustNewConstMetric(e.up, prometheus.GaugeValue, 0, pool.Name, pool.Address)
186			log.Errorf("Error scraping PHP-FPM: %v", pool.ScrapeError)
187			continue
188		}
189
190		active, idle, total := CountProcessState(pool.Processes)
191		if !e.CountProcessState && (active != pool.ActiveProcesses || idle != pool.IdleProcesses) {
192			log.Error("Inconsistent active and idle processes reported. Set `--fix-process-count` to have this calculated by php-fpm_exporter instead.")
193		}
194
195		if !e.CountProcessState {
196			active = pool.ActiveProcesses
197			idle = pool.IdleProcesses
198			total = pool.TotalProcesses
199		}
200
201		ch <- prometheus.MustNewConstMetric(e.up, prometheus.GaugeValue, 1, pool.Name, pool.Address)
202		ch <- prometheus.MustNewConstMetric(e.startSince, prometheus.CounterValue, float64(pool.StartSince), pool.Name, pool.Address)
203		ch <- prometheus.MustNewConstMetric(e.acceptedConnections, prometheus.CounterValue, float64(pool.AcceptedConnections), pool.Name, pool.Address)
204		ch <- prometheus.MustNewConstMetric(e.listenQueue, prometheus.GaugeValue, float64(pool.ListenQueue), pool.Name, pool.Address)
205		ch <- prometheus.MustNewConstMetric(e.maxListenQueue, prometheus.CounterValue, float64(pool.MaxListenQueue), pool.Name, pool.Address)
206		ch <- prometheus.MustNewConstMetric(e.listenQueueLength, prometheus.GaugeValue, float64(pool.ListenQueueLength), pool.Name, pool.Address)
207		ch <- prometheus.MustNewConstMetric(e.idleProcesses, prometheus.GaugeValue, float64(idle), pool.Name, pool.Address)
208		ch <- prometheus.MustNewConstMetric(e.activeProcesses, prometheus.GaugeValue, float64(active), pool.Name, pool.Address)
209		ch <- prometheus.MustNewConstMetric(e.totalProcesses, prometheus.GaugeValue, float64(total), pool.Name, pool.Address)
210		ch <- prometheus.MustNewConstMetric(e.maxActiveProcesses, prometheus.CounterValue, float64(pool.MaxActiveProcesses), pool.Name, pool.Address)
211		ch <- prometheus.MustNewConstMetric(e.maxChildrenReached, prometheus.CounterValue, float64(pool.MaxChildrenReached), pool.Name, pool.Address)
212		ch <- prometheus.MustNewConstMetric(e.slowRequests, prometheus.CounterValue, float64(pool.SlowRequests), pool.Name, pool.Address)
213
214		for childNumber, process := range pool.Processes {
215			childName := fmt.Sprintf("%d", childNumber)
216
217			states := map[string]int{
218				PoolProcessRequestIdle:           0,
219				PoolProcessRequestRunning:        0,
220				PoolProcessRequestFinishing:      0,
221				PoolProcessRequestReadingHeaders: 0,
222				PoolProcessRequestInfo:           0,
223				PoolProcessRequestEnding:         0,
224			}
225			states[process.State]++
226
227			for stateName, inState := range states {
228				ch <- prometheus.MustNewConstMetric(e.processState, prometheus.GaugeValue, float64(inState), pool.Name, childName, stateName, pool.Address)
229			}
230			ch <- prometheus.MustNewConstMetric(e.processRequests, prometheus.CounterValue, float64(process.Requests), pool.Name, childName, pool.Address)
231			ch <- prometheus.MustNewConstMetric(e.processLastRequestMemory, prometheus.GaugeValue, float64(process.LastRequestMemory), pool.Name, childName, pool.Address)
232			ch <- prometheus.MustNewConstMetric(e.processLastRequestCPU, prometheus.GaugeValue, process.LastRequestCPU, pool.Name, childName, pool.Address)
233			ch <- prometheus.MustNewConstMetric(e.processRequestDuration, prometheus.GaugeValue, float64(process.RequestDuration), pool.Name, childName, pool.Address)
234		}
235	}
236}
237
238// Describe exposes the metric description to Prometheus
239func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
240	ch <- e.up
241	ch <- e.startSince
242	ch <- e.acceptedConnections
243	ch <- e.listenQueue
244	ch <- e.maxListenQueue
245	ch <- e.listenQueueLength
246	ch <- e.idleProcesses
247	ch <- e.activeProcesses
248	ch <- e.totalProcesses
249	ch <- e.maxActiveProcesses
250	ch <- e.maxChildrenReached
251	ch <- e.slowRequests
252	ch <- e.processState
253	ch <- e.processRequests
254	ch <- e.processLastRequestMemory
255	ch <- e.processLastRequestCPU
256	ch <- e.processRequestDuration
257}
258