php-fpm-exporter/phpfpm/phpfpm.go

248 lines
6.8 KiB
Go
Raw Normal View History

2018-02-17 12:42:58 +00:00
// Copyright © 2018 Enrico Stahn <enrico.stahn@gmail.com>
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2018-02-19 01:12:34 +00:00
// Package phpfpm provides convenient access to PHP-FPM pool data
2018-02-17 12:42:58 +00:00
package phpfpm
import (
"encoding/json"
"fmt"
2018-02-18 23:53:19 +00:00
"io/ioutil"
"net/url"
2018-02-17 12:42:58 +00:00
"strconv"
"sync"
2018-02-18 23:53:19 +00:00
"time"
2018-02-24 23:08:12 +00:00
"github.com/tomasen/fcgi_client"
2018-02-17 12:42:58 +00:00
)
2018-02-24 13:41:46 +00:00
// PoolProcessRequestIdle defines a process that is idle.
const PoolProcessRequestIdle string = "Idle"
2018-02-24 13:41:46 +00:00
// PoolProcessRequestIdle defines a process that is active.
const PoolProcessRequestActive string = "Running"
// PoolProcessRequestFinishing defines a process that is about to finish.
const PoolProcessRequestFinishing string = "Finishing"
// PoolProcessRequestReadingHeaders defines a process that is reading headers.
const PoolProcessRequestReadingHeaders string = "Reading headers"
// PoolProcessRequestInfo defines a process that is getting request information.
const PoolProcessRequestInfo string = "Getting request informations"
// PoolProcessRequestFinishing defines a process that is about to end.
const PoolProcessRequestEnding string = "Ending"
2018-02-17 12:42:58 +00:00
var log logger
type logger interface {
Info(ar ...interface{})
Infof(string, ...interface{})
Debug(ar ...interface{})
2018-02-17 12:42:58 +00:00
Debugf(string, ...interface{})
Error(ar ...interface{})
Errorf(string, ...interface{})
2018-02-17 12:42:58 +00:00
}
2018-02-19 00:45:35 +00:00
// PoolManager manages all configured Pools
2018-02-17 12:42:58 +00:00
type PoolManager struct {
2018-02-19 00:45:35 +00:00
Pools []Pool `json:"pools"`
2018-02-17 12:42:58 +00:00
}
2018-02-19 00:45:35 +00:00
// Pool describes a single PHP-FPM pool that can be reached via a Socket or TCP address
2018-02-17 12:42:58 +00:00
type Pool struct {
// The address of the pool, e.g. tcp://127.0.0.1:9000 or unix:///tmp/php-fpm.sock
2018-02-19 00:45:35 +00:00
Address string `json:"-"`
ScrapeError error `json:"-"`
ScrapeFailures int64 `json:"-"`
2018-02-17 12:42:58 +00:00
Name string `json:"pool"`
ProcessManager string `json:"process manager"`
2018-02-19 00:45:35 +00:00
StartTime timestamp `json:"start time"`
StartSince int64 `json:"start since"`
AcceptedConnections int64 `json:"accepted conn"`
ListenQueue int64 `json:"listen queue"`
MaxListenQueue int64 `json:"max listen queue"`
ListenQueueLength int64 `json:"listen queue len"`
IdleProcesses int64 `json:"idle processes"`
ActiveProcesses int64 `json:"active processes"`
TotalProcesses int64 `json:"total processes"`
MaxActiveProcesses int64 `json:"max active processes"`
MaxChildrenReached int64 `json:"max children reached"`
SlowRequests int64 `json:"slow requests"`
2018-02-17 12:42:58 +00:00
Processes []PoolProcess `json:"processes"`
}
2018-02-19 00:45:35 +00:00
// PoolProcess describes a single PHP-FPM process. A pool can have multiple processes.
2018-02-17 12:42:58 +00:00
type PoolProcess struct {
PID int64 `json:"pid"`
2018-02-17 12:42:58 +00:00
State string `json:"state"`
StartTime int64 `json:"start time"`
StartSince int64 `json:"start since"`
Requests int64 `json:"requests"`
RequestDuration int64 `json:"request duration"`
2018-02-17 12:42:58 +00:00
RequestMethod string `json:"request method"`
RequestURI string `json:"request uri"`
ContentLength int64 `json:"content length"`
2018-02-17 12:42:58 +00:00
User string `json:"user"`
Script string `json:"script"`
LastRequestCPU float64 `json:"last request cpu"`
2018-02-17 12:42:58 +00:00
LastRequestMemory int `json:"last request memory"`
}
// PoolProcessScoreboard holds the calculated metrics for pool processes.
type PoolProcessScoreboard struct {
Active int64
Idle int64
Finishing int64
ReadingHeaders int64
Info int64
Ending int64
Unknown int64
Total int64
}
2018-02-19 00:45:35 +00:00
// Add will add a pool to the pool manager based on the given URI.
2018-02-17 12:42:58 +00:00
func (pm *PoolManager) Add(uri string) Pool {
p := Pool{Address: uri}
2018-02-19 00:45:35 +00:00
pm.Pools = append(pm.Pools, p)
2018-02-17 12:42:58 +00:00
return p
}
2018-02-19 00:45:35 +00:00
// Update will run the pool.Update() method concurrently on all Pools.
2018-02-17 12:42:58 +00:00
func (pm *PoolManager) Update() (err error) {
wg := &sync.WaitGroup{}
started := time.Now()
2018-02-19 00:45:35 +00:00
for idx := range pm.Pools {
2018-02-17 12:42:58 +00:00
wg.Add(1)
go func(p *Pool) {
defer wg.Done()
p.Update()
2018-02-19 00:45:35 +00:00
}(&pm.Pools[idx])
2018-02-17 12:42:58 +00:00
}
wg.Wait()
ended := time.Now()
2018-02-19 00:45:35 +00:00
log.Debugf("Updated %v pool(s) in %v", len(pm.Pools), ended.Sub(started))
2018-02-17 12:42:58 +00:00
return nil
}
2018-02-19 00:45:35 +00:00
// Update will connect to PHP-FPM and retrieve the latest data for the pool.
2018-02-17 12:42:58 +00:00
func (p *Pool) Update() (err error) {
p.ScrapeError = nil
env := map[string]string{
"SCRIPT_FILENAME": "/status",
"SCRIPT_NAME": "/status",
"SERVER_SOFTWARE": "go / php-fpm_exporter",
"REMOTE_ADDR": "127.0.0.1",
"QUERY_STRING": "json&full",
}
2018-02-17 12:42:58 +00:00
uri, err := url.Parse(p.Address)
if err != nil {
return p.error(err)
}
fcgi, err := fcgiclient.DialTimeout(uri.Scheme, uri.Hostname()+":"+uri.Port(), time.Duration(3)*time.Second)
if err != nil {
return p.error(err)
}
defer fcgi.Close()
2018-02-17 12:42:58 +00:00
resp, err := fcgi.Get(env)
if err != nil {
return p.error(err)
}
defer resp.Body.Close()
2018-02-17 12:42:58 +00:00
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
return p.error(err)
}
log.Debugf("Pool[%v]: %v", p.Address, string(content))
2018-02-17 12:42:58 +00:00
if err = json.Unmarshal(content, &p); err != nil {
return p.error(err)
}
return nil
}
2018-02-18 23:53:19 +00:00
func (p *Pool) error(err error) error {
p.ScrapeError = err
p.ScrapeFailures++
2018-02-17 12:42:58 +00:00
log.Error(err)
return err
}
func CalculateProcessScoreboard(p Pool) PoolProcessScoreboard {
pps := PoolProcessScoreboard{}
for idx := range p.Processes {
switch p.Processes[idx].State {
case PoolProcessRequestActive:
pps.Active++
case PoolProcessRequestIdle:
pps.Idle++
case PoolProcessRequestEnding:
pps.Ending++
case PoolProcessRequestFinishing:
pps.Finishing++
case PoolProcessRequestInfo:
pps.Info++
case PoolProcessRequestReadingHeaders:
pps.ReadingHeaders++
default:
pps.Unknown++
log.Errorf("Unknown process state '%v'", p.Processes[idx].State)
}
}
pps.Total = pps.Active + pps.Idle + pps.Ending + pps.Finishing + pps.Info + pps.ReadingHeaders + pps.Unknown
return pps
}
2018-02-19 00:45:35 +00:00
type timestamp time.Time
2018-02-17 12:42:58 +00:00
2018-02-19 00:45:35 +00:00
// MarshalJSON customise JSON for timestamp
func (t *timestamp) MarshalJSON() ([]byte, error) {
2018-02-17 12:42:58 +00:00
ts := time.Time(*t).Unix()
stamp := fmt.Sprint(ts)
return []byte(stamp), nil
}
2018-02-19 00:45:35 +00:00
// UnmarshalJSON customise JSON for timestamp
func (t *timestamp) UnmarshalJSON(b []byte) error {
2018-02-17 12:42:58 +00:00
ts, err := strconv.Atoi(string(b))
if err != nil {
return err
}
2018-02-19 00:45:35 +00:00
*t = timestamp(time.Unix(int64(ts), 0))
2018-02-17 12:42:58 +00:00
return nil
}
2018-02-19 00:45:35 +00:00
// SetLogger configures the used logger
2018-02-18 23:53:19 +00:00
func SetLogger(logger logger) {
2018-02-17 12:42:58 +00:00
log = logger
2018-02-18 23:53:19 +00:00
}