Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
404
plugins/inputs/phpfpm/phpfpm.go
Normal file
404
plugins/inputs/phpfpm/phpfpm.go
Normal file
|
@ -0,0 +1,404 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
package phpfpm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/internal/globpath"
|
||||
"github.com/influxdata/telegraf/plugins/common/tls"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
const (
|
||||
pfPool = "pool"
|
||||
pfStartSince = "start since"
|
||||
pfAcceptedConn = "accepted conn"
|
||||
pfListenQueue = "listen queue"
|
||||
pfMaxListenQueue = "max listen queue"
|
||||
pfListenQueueLen = "listen queue len"
|
||||
pfIdleProcesses = "idle processes"
|
||||
pfActiveProcesses = "active processes"
|
||||
pfTotalProcesses = "total processes"
|
||||
pfMaxActiveProcesses = "max active processes"
|
||||
pfMaxChildrenReached = "max children reached"
|
||||
pfSlowRequests = "slow requests"
|
||||
)
|
||||
|
||||
type Phpfpm struct {
|
||||
Format string `toml:"format"`
|
||||
Timeout config.Duration `toml:"timeout"`
|
||||
Urls []string `toml:"urls"`
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
tls.ClientConfig
|
||||
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
type jsonMetrics struct {
|
||||
Pool string `json:"pool"`
|
||||
ProcessManager string `json:"process manager"`
|
||||
StartTime int `json:"start time"`
|
||||
StartSince int `json:"start since"`
|
||||
AcceptedConn int `json:"accepted conn"`
|
||||
ListenQueue int `json:"listen queue"`
|
||||
MaxListenQueue int `json:"max listen queue"`
|
||||
ListenQueueLen int `json:"listen queue len"`
|
||||
IdleProcesses int `json:"idle processes"`
|
||||
ActiveProcesses int `json:"active processes"`
|
||||
TotalProcesses int `json:"total processes"`
|
||||
MaxActiveProcesses int `json:"max active processes"`
|
||||
MaxChildrenReached int `json:"max children reached"`
|
||||
SlowRequests int `json:"slow requests"`
|
||||
Processes []struct {
|
||||
Pid int `json:"pid"`
|
||||
State string `json:"state"`
|
||||
StartTime int `json:"start time"`
|
||||
StartSince int `json:"start since"`
|
||||
Requests int `json:"requests"`
|
||||
RequestDuration int `json:"request duration"`
|
||||
RequestMethod string `json:"request method"`
|
||||
RequestURI string `json:"request uri"`
|
||||
ContentLength int `json:"content length"`
|
||||
User string `json:"user"`
|
||||
Script string `json:"script"`
|
||||
LastRequestCPU float64 `json:"last request cpu"`
|
||||
LastRequestMemory float64 `json:"last request memory"`
|
||||
} `json:"processes"`
|
||||
}
|
||||
|
||||
type metricStat map[string]int64
|
||||
type poolStat map[string]metricStat
|
||||
|
||||
func (*Phpfpm) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (p *Phpfpm) Init() error {
|
||||
if len(p.Urls) == 0 {
|
||||
p.Urls = []string{"http://127.0.0.1/status"}
|
||||
}
|
||||
|
||||
tlsCfg, err := p.ClientConfig.TLSConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch p.Format {
|
||||
case "":
|
||||
p.Format = "status"
|
||||
case "status", "json":
|
||||
// both valid
|
||||
default:
|
||||
return fmt.Errorf("invalid format: %s", p.Format)
|
||||
}
|
||||
|
||||
p.client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsCfg,
|
||||
},
|
||||
Timeout: time.Duration(p.Timeout),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Phpfpm) Gather(acc telegraf.Accumulator) error {
|
||||
var wg sync.WaitGroup
|
||||
for _, serv := range expandUrls(acc, p.Urls) {
|
||||
wg.Add(1)
|
||||
go func(serv string) {
|
||||
defer wg.Done()
|
||||
acc.AddError(p.gatherServer(serv, acc))
|
||||
}(serv)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Request status page to get stat raw data and import it
|
||||
func (p *Phpfpm) gatherServer(addr string, acc telegraf.Accumulator) error {
|
||||
if strings.HasPrefix(addr, "http://") || strings.HasPrefix(addr, "https://") {
|
||||
return p.gatherHTTP(addr, acc)
|
||||
}
|
||||
|
||||
var (
|
||||
fcgi *conn
|
||||
socketPath string
|
||||
statusPath string
|
||||
)
|
||||
|
||||
var err error
|
||||
if strings.HasPrefix(addr, "fcgi://") || strings.HasPrefix(addr, "cgi://") {
|
||||
u, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable parse server address %q: %w", addr, err)
|
||||
}
|
||||
socketAddr := strings.Split(u.Host, ":")
|
||||
if len(socketAddr) < 2 {
|
||||
return fmt.Errorf("url does not follow required 'address:port' format: %s", u.Host)
|
||||
}
|
||||
fcgiIP := socketAddr[0]
|
||||
fcgiPort, err := strconv.Atoi(socketAddr[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse server port %q: %w", socketAddr[1], err)
|
||||
}
|
||||
fcgi, err = newFcgiClient(time.Duration(p.Timeout), fcgiIP, fcgiPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(u.Path) > 1 {
|
||||
statusPath = strings.Trim(u.Path, "/")
|
||||
} else {
|
||||
statusPath = "status"
|
||||
}
|
||||
} else {
|
||||
socketPath, statusPath = unixSocketPaths(addr)
|
||||
if statusPath == "" {
|
||||
statusPath = "status"
|
||||
}
|
||||
fcgi, err = newFcgiClient(time.Duration(p.Timeout), "unix", socketPath)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.gatherFcgi(fcgi, statusPath, acc, addr)
|
||||
}
|
||||
|
||||
// Gather stat using fcgi protocol
|
||||
func (p *Phpfpm) gatherFcgi(fcgi *conn, statusPath string, acc telegraf.Accumulator, addr string) error {
|
||||
fpmOutput, fpmErr, err := fcgi.request(map[string]string{
|
||||
"SCRIPT_NAME": "/" + statusPath,
|
||||
"SCRIPT_FILENAME": statusPath,
|
||||
"REQUEST_METHOD": "GET",
|
||||
"CONTENT_LENGTH": "0",
|
||||
"SERVER_PROTOCOL": "HTTP/1.0",
|
||||
"SERVER_SOFTWARE": "go / fcgiclient ",
|
||||
"REMOTE_ADDR": "127.0.0.1",
|
||||
}, "/"+statusPath)
|
||||
|
||||
if len(fpmErr) == 0 && err == nil {
|
||||
p.importMetric(bytes.NewReader(fpmOutput), acc, addr)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unable parse phpfpm status, error: %s; %w", string(fpmErr), err)
|
||||
}
|
||||
|
||||
// Gather stat using http protocol
|
||||
func (p *Phpfpm) gatherHTTP(addr string, acc telegraf.Accumulator) error {
|
||||
u, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable parse server address %q: %w", addr, err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create new request %q: %w", addr, err)
|
||||
}
|
||||
|
||||
res, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to connect to phpfpm status page %q: %w", addr, err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
return fmt.Errorf("unable to get valid stat result from %q: %w", addr, err)
|
||||
}
|
||||
|
||||
p.importMetric(res.Body, acc, addr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Import stat data into Telegraf system
|
||||
func (p *Phpfpm) importMetric(r io.Reader, acc telegraf.Accumulator, addr string) {
|
||||
if p.Format == "json" {
|
||||
p.parseJSON(r, acc, addr)
|
||||
} else {
|
||||
parseLines(r, acc, addr)
|
||||
}
|
||||
}
|
||||
|
||||
func parseLines(r io.Reader, acc telegraf.Accumulator, addr string) {
|
||||
stats := make(poolStat)
|
||||
var currentPool string
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
statLine := scanner.Text()
|
||||
keyvalue := strings.Split(statLine, ":")
|
||||
|
||||
if len(keyvalue) < 2 {
|
||||
continue
|
||||
}
|
||||
fieldName := strings.Trim(keyvalue[0], " ")
|
||||
// We start to gather data for a new pool here
|
||||
if fieldName == pfPool {
|
||||
currentPool = strings.Trim(keyvalue[1], " ")
|
||||
stats[currentPool] = make(metricStat)
|
||||
continue
|
||||
}
|
||||
|
||||
// Start to parse metric for current pool
|
||||
switch fieldName {
|
||||
case pfStartSince,
|
||||
pfAcceptedConn,
|
||||
pfListenQueue,
|
||||
pfMaxListenQueue,
|
||||
pfListenQueueLen,
|
||||
pfIdleProcesses,
|
||||
pfActiveProcesses,
|
||||
pfTotalProcesses,
|
||||
pfMaxActiveProcesses,
|
||||
pfMaxChildrenReached,
|
||||
pfSlowRequests:
|
||||
fieldValue, err := strconv.ParseInt(strings.Trim(keyvalue[1], " "), 10, 64)
|
||||
if err == nil {
|
||||
stats[currentPool][fieldName] = fieldValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, we push the pool metric
|
||||
for pool := range stats {
|
||||
tags := map[string]string{
|
||||
"pool": pool,
|
||||
"url": addr,
|
||||
}
|
||||
fields := make(map[string]interface{})
|
||||
for k, v := range stats[pool] {
|
||||
fields[strings.ReplaceAll(k, " ", "_")] = v
|
||||
}
|
||||
acc.AddFields("phpfpm", fields, tags)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Phpfpm) parseJSON(r io.Reader, acc telegraf.Accumulator, addr string) {
|
||||
var metrics jsonMetrics
|
||||
if err := json.NewDecoder(r).Decode(&metrics); err != nil {
|
||||
p.Log.Errorf("Unable to decode JSON response: %s", err)
|
||||
return
|
||||
}
|
||||
timestamp := time.Now()
|
||||
|
||||
tags := map[string]string{
|
||||
"pool": metrics.Pool,
|
||||
"url": addr,
|
||||
}
|
||||
fields := map[string]any{
|
||||
"start_since": metrics.StartSince,
|
||||
"accepted_conn": metrics.AcceptedConn,
|
||||
"listen_queue": metrics.ListenQueue,
|
||||
"max_listen_queue": metrics.MaxListenQueue,
|
||||
"listen_queue_len": metrics.ListenQueueLen,
|
||||
"idle_processes": metrics.IdleProcesses,
|
||||
"active_processes": metrics.ActiveProcesses,
|
||||
"total_processes": metrics.TotalProcesses,
|
||||
"max_active_processes": metrics.MaxActiveProcesses,
|
||||
"max_children_reached": metrics.MaxChildrenReached,
|
||||
"slow_requests": metrics.SlowRequests,
|
||||
}
|
||||
acc.AddFields("phpfpm", fields, tags, timestamp)
|
||||
|
||||
for _, process := range metrics.Processes {
|
||||
tags := map[string]string{
|
||||
"pool": metrics.Pool,
|
||||
"url": addr,
|
||||
"user": process.User,
|
||||
"request_uri": process.RequestURI,
|
||||
"request_method": process.RequestMethod,
|
||||
"script": process.Script,
|
||||
}
|
||||
fields := map[string]any{
|
||||
"pid": process.Pid,
|
||||
"state": process.State,
|
||||
"start_time": process.StartTime,
|
||||
"requests": process.Requests,
|
||||
"request_duration": process.RequestDuration,
|
||||
"content_length": process.ContentLength,
|
||||
"last_request_cpu": process.LastRequestCPU,
|
||||
"last_request_memory": process.LastRequestMemory,
|
||||
}
|
||||
acc.AddFields("phpfpm_process", fields, tags, timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
func expandUrls(acc telegraf.Accumulator, urls []string) []string {
|
||||
addrs := make([]string, 0, len(urls))
|
||||
for _, address := range urls {
|
||||
if isNetworkURL(address) {
|
||||
addrs = append(addrs, address)
|
||||
continue
|
||||
}
|
||||
paths, err := globUnixSocket(address)
|
||||
if err != nil {
|
||||
acc.AddError(err)
|
||||
continue
|
||||
}
|
||||
addrs = append(addrs, paths...)
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
func globUnixSocket(address string) ([]string, error) {
|
||||
pattern, status := unixSocketPaths(address)
|
||||
glob, err := globpath.Compile(pattern)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not compile glob %q: %w", pattern, err)
|
||||
}
|
||||
paths := glob.Match()
|
||||
if len(paths) == 0 {
|
||||
return nil, fmt.Errorf("socket doesn't exist %q", pattern)
|
||||
}
|
||||
|
||||
addresses := make([]string, 0, len(paths))
|
||||
for _, path := range paths {
|
||||
if status != "" {
|
||||
path = path + ":" + status
|
||||
}
|
||||
addresses = append(addresses, path)
|
||||
}
|
||||
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
func unixSocketPaths(addr string) (socketPath, statusPath string) {
|
||||
socketAddr := strings.Split(addr, ":")
|
||||
if len(socketAddr) >= 2 {
|
||||
socketPath = socketAddr[0]
|
||||
statusPath = socketAddr[1]
|
||||
} else {
|
||||
socketPath = socketAddr[0]
|
||||
statusPath = ""
|
||||
}
|
||||
|
||||
return socketPath, statusPath
|
||||
}
|
||||
|
||||
func isNetworkURL(addr string) bool {
|
||||
return strings.HasPrefix(addr, "http://") || strings.HasPrefix(addr, "https://") || strings.HasPrefix(addr, "fcgi://") || strings.HasPrefix(addr, "cgi://")
|
||||
}
|
||||
|
||||
func init() {
|
||||
inputs.Add("phpfpm", func() telegraf.Input {
|
||||
return &Phpfpm{}
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue