1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View 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{}
})
}