* Add ability to calculate correct idle/active/total processes #6

* Expose PHP-FPM Pool metrics #4
This commit is contained in:
Enrico Stahn 2018-02-25 00:36:13 +11:00
parent f3d7296080
commit 62276d310f
No known key found for this signature in database
GPG key ID: 5263621C269A50DE
5 changed files with 137 additions and 37 deletions

8
Gopkg.lock generated
View file

@ -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

View file

@ -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"

View file

@ -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 {

View file

@ -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,22 +25,27 @@ 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
up *prometheus.Desc CalculateProcessScoreboard bool
scrapeFailues *prometheus.Desc
startSince *prometheus.Desc up *prometheus.Desc
acceptedConnections *prometheus.Desc scrapeFailues *prometheus.Desc
listenQueue *prometheus.Desc startSince *prometheus.Desc
maxListenQueue *prometheus.Desc acceptedConnections *prometheus.Desc
listenQueueLength *prometheus.Desc listenQueue *prometheus.Desc
idleProcesses *prometheus.Desc maxListenQueue *prometheus.Desc
activeProcesses *prometheus.Desc listenQueueLength *prometheus.Desc
totalProcesses *prometheus.Desc idleProcesses *prometheus.Desc
maxActiveProcesses *prometheus.Desc activeProcesses *prometheus.Desc
maxChildrenReached *prometheus.Desc totalProcesses *prometheus.Desc
slowRequests *prometheus.Desc maxActiveProcesses *prometheus.Desc
maxChildrenReached *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
}

View file

@ -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