* Add ability to calculate correct idle/active/total processes #6
* Expose PHP-FPM Pool metrics #4
This commit is contained in:
parent
f3d7296080
commit
62276d310f
5 changed files with 137 additions and 37 deletions
8
Gopkg.lock
generated
8
Gopkg.lock
generated
|
@ -135,6 +135,12 @@
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "8c0189d9f6bbf301e5d055d34268156b317016af"
|
revision = "8c0189d9f6bbf301e5d055d34268156b317016af"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/speps/go-hashids"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "d1d57a886aa7e3ef6092b70ceab077e35ee8e0ce"
|
||||||
|
version = "v1.0.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/spf13/afero"
|
name = "github.com/spf13/afero"
|
||||||
packages = [
|
packages = [
|
||||||
|
@ -211,6 +217,6 @@
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "9d6c96a2ed2824b362a4f9d68ba313678fb7e3f7e38e2fd7058ba9c21a0b2487"
|
inputs-digest = "679d8f3e08c6fb8c5ef8eb95cedf7aec17654ae00051c78638c88c3731b1dc43"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
|
@ -48,3 +48,7 @@
|
||||||
[prune]
|
[prune]
|
||||||
go-tests = true
|
go-tests = true
|
||||||
unused-packages = true
|
unused-packages = true
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/speps/go-hashids"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
|
@ -31,6 +31,7 @@ var (
|
||||||
listeningAddress string
|
listeningAddress string
|
||||||
metricsEndpoint string
|
metricsEndpoint string
|
||||||
scrapeURIs []string
|
scrapeURIs []string
|
||||||
|
fixProcessCount bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// serverCmd represents the server command
|
// serverCmd represents the server command
|
||||||
|
@ -53,6 +54,12 @@ to quickly create a Cobra application.`,
|
||||||
}
|
}
|
||||||
|
|
||||||
exporter := phpfpm.NewExporter(pm)
|
exporter := phpfpm.NewExporter(pm)
|
||||||
|
|
||||||
|
if fixProcessCount {
|
||||||
|
log.Info("Idle/Active/Total Processes will be calculated by php-fpm_exporter.")
|
||||||
|
exporter.CalculateProcessScoreboard = true
|
||||||
|
}
|
||||||
|
|
||||||
prometheus.MustRegister(exporter)
|
prometheus.MustRegister(exporter)
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
|
@ -118,6 +125,7 @@ func init() {
|
||||||
serverCmd.Flags().StringVar(&listeningAddress, "web.listen-address", ":9253", "Address on which to expose metrics and web interface.")
|
serverCmd.Flags().StringVar(&listeningAddress, "web.listen-address", ":9253", "Address on which to expose metrics and web interface.")
|
||||||
serverCmd.Flags().StringVar(&metricsEndpoint, "web.telemetry-path", "/metrics", "Path under which to expose metrics.")
|
serverCmd.Flags().StringVar(&metricsEndpoint, "web.telemetry-path", "/metrics", "Path under which to expose metrics.")
|
||||||
serverCmd.Flags().StringSliceVar(&scrapeURIs, "phpfpm.scrape-uri", []string{"tcp://127.0.0.1:9000/status"}, "FastCGI address, e.g. unix:///tmp/php.sock;/status or tcp://127.0.0.1:9000/status")
|
serverCmd.Flags().StringSliceVar(&scrapeURIs, "phpfpm.scrape-uri", []string{"tcp://127.0.0.1:9000/status"}, "FastCGI address, e.g. unix:///tmp/php.sock;/status or tcp://127.0.0.1:9000/status")
|
||||||
|
serverCmd.Flags().BoolVar(&fixProcessCount, "phpfpm.fix-process-count", false, "Enable to calculate process numbers via php-fpm_exporter since PHP-FPM sporadically reports wrong active/idle/total process numbers.")
|
||||||
|
|
||||||
//viper.BindEnv("web.listen-address", "PHP_FPM_WEB_LISTEN_ADDRESS")
|
//viper.BindEnv("web.listen-address", "PHP_FPM_WEB_LISTEN_ADDRESS")
|
||||||
//viper.BindPFlag("web.listen-address", serverCmd.Flags().Lookup("web.listen-address"))
|
//viper.BindPFlag("web.listen-address", serverCmd.Flags().Lookup("web.listen-address"))
|
||||||
|
@ -128,6 +136,7 @@ func init() {
|
||||||
"PHP_FPM_WEB_LISTEN_ADDRESS": "web.listen-address",
|
"PHP_FPM_WEB_LISTEN_ADDRESS": "web.listen-address",
|
||||||
"PHP_FPM_WEB_TELEMETRY_PATH": "web.telemetry-path",
|
"PHP_FPM_WEB_TELEMETRY_PATH": "web.telemetry-path",
|
||||||
"PHP_FPM_SCRAPE_URI": "phpfpm.scrape-uri",
|
"PHP_FPM_SCRAPE_URI": "phpfpm.scrape-uri",
|
||||||
|
"PHP_FPM_FIX_PROCESS_COUNT": "phpfpm.fix-process-count",
|
||||||
}
|
}
|
||||||
|
|
||||||
for env, flag := range envs {
|
for env, flag := range envs {
|
||||||
|
|
|
@ -15,6 +15,7 @@ package phpfpm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/speps/go-hashids"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,8 +25,10 @@ const (
|
||||||
|
|
||||||
// Exporter configures and exposes PHP-FPM metrics to Prometheus.
|
// Exporter configures and exposes PHP-FPM metrics to Prometheus.
|
||||||
type Exporter struct {
|
type Exporter struct {
|
||||||
PoolManager PoolManager
|
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
|
PoolManager PoolManager
|
||||||
|
|
||||||
|
CalculateProcessScoreboard bool
|
||||||
|
|
||||||
up *prometheus.Desc
|
up *prometheus.Desc
|
||||||
scrapeFailues *prometheus.Desc
|
scrapeFailues *prometheus.Desc
|
||||||
|
@ -40,6 +43,9 @@ type Exporter struct {
|
||||||
maxActiveProcesses *prometheus.Desc
|
maxActiveProcesses *prometheus.Desc
|
||||||
maxChildrenReached *prometheus.Desc
|
maxChildrenReached *prometheus.Desc
|
||||||
slowRequests *prometheus.Desc
|
slowRequests *prometheus.Desc
|
||||||
|
processRequests *prometheus.Desc
|
||||||
|
processLastRequestMemory *prometheus.Desc
|
||||||
|
processLastRequestCPU *prometheus.Desc
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExporter creates a new Exporter for a PoolManager and configures the necessary metrics.
|
// NewExporter creates a new Exporter for a PoolManager and configures the necessary metrics.
|
||||||
|
@ -47,6 +53,8 @@ func NewExporter(pm PoolManager) *Exporter {
|
||||||
return &Exporter{
|
return &Exporter{
|
||||||
PoolManager: pm,
|
PoolManager: pm,
|
||||||
|
|
||||||
|
CalculateProcessScoreboard: false,
|
||||||
|
|
||||||
up: prometheus.NewDesc(
|
up: prometheus.NewDesc(
|
||||||
prometheus.BuildFQName(namespace, "", "up"),
|
prometheus.BuildFQName(namespace, "", "up"),
|
||||||
"Could PHP-FPM be reached?",
|
"Could PHP-FPM be reached?",
|
||||||
|
@ -124,6 +132,24 @@ func NewExporter(pm PoolManager) *Exporter {
|
||||||
"The number of requests that exceeded your 'request_slowlog_timeout' value.",
|
"The number of requests that exceeded your 'request_slowlog_timeout' value.",
|
||||||
[]string{"pool"},
|
[]string{"pool"},
|
||||||
nil),
|
nil),
|
||||||
|
|
||||||
|
processRequests: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "process_requests"),
|
||||||
|
"",
|
||||||
|
[]string{"pool", "pid"},
|
||||||
|
nil),
|
||||||
|
|
||||||
|
processLastRequestMemory: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "process_last_request_memory"),
|
||||||
|
"",
|
||||||
|
[]string{"pool", "pid"},
|
||||||
|
nil),
|
||||||
|
|
||||||
|
processLastRequestCPU: prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(namespace, "", "process_last_request_cpu"),
|
||||||
|
"",
|
||||||
|
[]string{"pool", "pid"},
|
||||||
|
nil),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,18 +169,36 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
active, idle, total := CalculateProcessScoreboard(pool)
|
||||||
|
if active != pool.ActiveProcesses || idle != pool.IdleProcesses {
|
||||||
|
log.Error("Inconsistent active and idle processes reported. Set `--fix-process-count` to have this calculated by php-fpm_exporter instead.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.CalculateProcessScoreboard == false {
|
||||||
|
active = pool.ActiveProcesses
|
||||||
|
idle = pool.IdleProcesses
|
||||||
|
total = pool.TotalProcesses
|
||||||
|
}
|
||||||
|
|
||||||
ch <- prometheus.MustNewConstMetric(e.up, prometheus.GaugeValue, 1, pool.Name)
|
ch <- prometheus.MustNewConstMetric(e.up, prometheus.GaugeValue, 1, pool.Name)
|
||||||
ch <- prometheus.MustNewConstMetric(e.startSince, prometheus.CounterValue, float64(pool.AcceptedConnections), pool.Name)
|
ch <- prometheus.MustNewConstMetric(e.startSince, prometheus.CounterValue, float64(pool.AcceptedConnections), pool.Name)
|
||||||
ch <- prometheus.MustNewConstMetric(e.acceptedConnections, prometheus.CounterValue, float64(pool.StartSince), pool.Name)
|
ch <- prometheus.MustNewConstMetric(e.acceptedConnections, prometheus.CounterValue, float64(pool.StartSince), pool.Name)
|
||||||
ch <- prometheus.MustNewConstMetric(e.listenQueue, prometheus.GaugeValue, float64(pool.ListenQueue), pool.Name)
|
ch <- prometheus.MustNewConstMetric(e.listenQueue, prometheus.GaugeValue, float64(pool.ListenQueue), pool.Name)
|
||||||
ch <- prometheus.MustNewConstMetric(e.maxListenQueue, prometheus.CounterValue, float64(pool.MaxListenQueue), pool.Name)
|
ch <- prometheus.MustNewConstMetric(e.maxListenQueue, prometheus.CounterValue, float64(pool.MaxListenQueue), pool.Name)
|
||||||
ch <- prometheus.MustNewConstMetric(e.listenQueueLength, prometheus.GaugeValue, float64(pool.ListenQueueLength), pool.Name)
|
ch <- prometheus.MustNewConstMetric(e.listenQueueLength, prometheus.GaugeValue, float64(pool.ListenQueueLength), pool.Name)
|
||||||
ch <- prometheus.MustNewConstMetric(e.idleProcesses, prometheus.GaugeValue, float64(pool.IdleProcesses), pool.Name)
|
ch <- prometheus.MustNewConstMetric(e.idleProcesses, prometheus.GaugeValue, float64(idle), pool.Name)
|
||||||
ch <- prometheus.MustNewConstMetric(e.activeProcesses, prometheus.GaugeValue, float64(pool.ActiveProcesses), pool.Name)
|
ch <- prometheus.MustNewConstMetric(e.activeProcesses, prometheus.GaugeValue, float64(active), pool.Name)
|
||||||
ch <- prometheus.MustNewConstMetric(e.totalProcesses, prometheus.GaugeValue, float64(pool.TotalProcesses), pool.Name)
|
ch <- prometheus.MustNewConstMetric(e.totalProcesses, prometheus.GaugeValue, float64(total), pool.Name)
|
||||||
ch <- prometheus.MustNewConstMetric(e.maxActiveProcesses, prometheus.CounterValue, float64(pool.MaxActiveProcesses), pool.Name)
|
ch <- prometheus.MustNewConstMetric(e.maxActiveProcesses, prometheus.CounterValue, float64(pool.MaxActiveProcesses), pool.Name)
|
||||||
ch <- prometheus.MustNewConstMetric(e.maxChildrenReached, prometheus.CounterValue, float64(pool.MaxChildrenReached), pool.Name)
|
ch <- prometheus.MustNewConstMetric(e.maxChildrenReached, prometheus.CounterValue, float64(pool.MaxChildrenReached), pool.Name)
|
||||||
ch <- prometheus.MustNewConstMetric(e.slowRequests, prometheus.CounterValue, float64(pool.SlowRequests), pool.Name)
|
ch <- prometheus.MustNewConstMetric(e.slowRequests, prometheus.CounterValue, float64(pool.SlowRequests), pool.Name)
|
||||||
|
|
||||||
|
for _, process := range pool.Processes {
|
||||||
|
pid := calculateProcessHash(process)
|
||||||
|
ch <- prometheus.MustNewConstMetric(e.processRequests, prometheus.CounterValue, float64(process.Requests), pool.Name, pid)
|
||||||
|
ch <- prometheus.MustNewConstMetric(e.processLastRequestMemory, prometheus.GaugeValue, float64(process.LastRequestMemory), pool.Name, pid)
|
||||||
|
ch <- prometheus.MustNewConstMetric(e.processLastRequestCPU, prometheus.GaugeValue, float64(process.LastRequestCPU), pool.Name, pid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -174,3 +218,14 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
|
||||||
ch <- e.maxChildrenReached
|
ch <- e.maxChildrenReached
|
||||||
ch <- e.slowRequests
|
ch <- e.slowRequests
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calculateProcessHash generates a unique identifier for a process to ensure uniqueness across multiple systems/containers
|
||||||
|
func calculateProcessHash(pp PoolProcess) string {
|
||||||
|
hd := hashids.NewData()
|
||||||
|
hd.Salt = "php-fpm_exporter"
|
||||||
|
hd.MinLength = 12
|
||||||
|
h := hashids.NewWithData(hd)
|
||||||
|
e, _ := h.Encode([]int{int(pp.StartTime), int(pp.PID)})
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
|
@ -25,11 +25,18 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const PoolProcessRequestIdle string = "Idle"
|
||||||
|
const PoolProcessRequestActive string = "Running"
|
||||||
|
|
||||||
var log logger
|
var log logger
|
||||||
|
|
||||||
type logger interface {
|
type logger interface {
|
||||||
|
Info(ar ...interface{})
|
||||||
|
Infof(string, ...interface{})
|
||||||
|
Debug(ar ...interface{})
|
||||||
Debugf(string, ...interface{})
|
Debugf(string, ...interface{})
|
||||||
Error(ar ...interface{})
|
Error(ar ...interface{})
|
||||||
|
Errorf(string, ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// PoolManager manages all configured Pools
|
// PoolManager manages all configured Pools
|
||||||
|
@ -46,34 +53,34 @@ type Pool struct {
|
||||||
Name string `json:"pool"`
|
Name string `json:"pool"`
|
||||||
ProcessManager string `json:"process manager"`
|
ProcessManager string `json:"process manager"`
|
||||||
StartTime timestamp `json:"start time"`
|
StartTime timestamp `json:"start time"`
|
||||||
StartSince int `json:"start since"`
|
StartSince int64 `json:"start since"`
|
||||||
AcceptedConnections int `json:"accepted conn"`
|
AcceptedConnections int64 `json:"accepted conn"`
|
||||||
ListenQueue int `json:"listen queue"`
|
ListenQueue int64 `json:"listen queue"`
|
||||||
MaxListenQueue int `json:"max listen queue"`
|
MaxListenQueue int64 `json:"max listen queue"`
|
||||||
ListenQueueLength int `json:"listen queue len"`
|
ListenQueueLength int64 `json:"listen queue len"`
|
||||||
IdleProcesses int `json:"idle processes"`
|
IdleProcesses int64 `json:"idle processes"`
|
||||||
ActiveProcesses int `json:"active processes"`
|
ActiveProcesses int64 `json:"active processes"`
|
||||||
TotalProcesses int `json:"total processes"`
|
TotalProcesses int64 `json:"total processes"`
|
||||||
MaxActiveProcesses int `json:"max active processes"`
|
MaxActiveProcesses int64 `json:"max active processes"`
|
||||||
MaxChildrenReached int `json:"max children reached"`
|
MaxChildrenReached int64 `json:"max children reached"`
|
||||||
SlowRequests int `json:"slow requests"`
|
SlowRequests int64 `json:"slow requests"`
|
||||||
Processes []PoolProcess `json:"processes"`
|
Processes []PoolProcess `json:"processes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PoolProcess describes a single PHP-FPM process. A pool can have multiple processes.
|
// PoolProcess describes a single PHP-FPM process. A pool can have multiple processes.
|
||||||
type PoolProcess struct {
|
type PoolProcess struct {
|
||||||
PID int `json:"pid"`
|
PID int64 `json:"pid"`
|
||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
StartTime int `json:"start time"`
|
StartTime int64 `json:"start time"`
|
||||||
StartSince int `json:"start since"`
|
StartSince int64 `json:"start since"`
|
||||||
Requests int `json:"requests"`
|
Requests int64 `json:"requests"`
|
||||||
RequestDuration int `json:"request duration"`
|
RequestDuration int64 `json:"request duration"`
|
||||||
RequestMethod string `json:"request method"`
|
RequestMethod string `json:"request method"`
|
||||||
RequestURI string `json:"request uri"`
|
RequestURI string `json:"request uri"`
|
||||||
ContentLength int `json:"content length"`
|
ContentLength int64 `json:"content length"`
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Script string `json:"script"`
|
Script string `json:"script"`
|
||||||
LastRequestCPU float32 `json:"last request cpu"`
|
LastRequestCPU float64 `json:"last request cpu"`
|
||||||
LastRequestMemory int `json:"last request memory"`
|
LastRequestMemory int `json:"last request memory"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +150,7 @@ func (p *Pool) Update() (err error) {
|
||||||
return p.error(err)
|
return p.error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Pool[", p.Address, "]:", string(content))
|
log.Debugf("Pool[%v]: %v", p.Address, string(content))
|
||||||
|
|
||||||
if err = json.Unmarshal(content, &p); err != nil {
|
if err = json.Unmarshal(content, &p); err != nil {
|
||||||
return p.error(err)
|
return p.error(err)
|
||||||
|
@ -159,6 +166,25 @@ func (p *Pool) error(err error) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CalculateProcessScoreboard(p Pool) (active int64, idle int64, total int64) {
|
||||||
|
active = 0
|
||||||
|
idle = 0
|
||||||
|
total = 0
|
||||||
|
|
||||||
|
for idx := range p.Processes {
|
||||||
|
switch p.Processes[idx].State {
|
||||||
|
case PoolProcessRequestActive:
|
||||||
|
active++
|
||||||
|
case PoolProcessRequestIdle:
|
||||||
|
idle++
|
||||||
|
default:
|
||||||
|
log.Errorf("Unknown process state '%v'", p.Processes[idx].State)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return active, idle, active + idle
|
||||||
|
}
|
||||||
|
|
||||||
type timestamp time.Time
|
type timestamp time.Time
|
||||||
|
|
||||||
// MarshalJSON customise JSON for timestamp
|
// MarshalJSON customise JSON for timestamp
|
||||||
|
|
Loading…
Reference in a new issue