phpfpm

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 31, 2026 License: MIT Imports: 20 Imported by: 0

README

phpfpm

A Go library for talking to PHP-FPM: discover running masters, parse their effective configuration, scrape live status over FastCGI, and reload a master safely.

It is the shared layer beneath fpm-exporter, which reads a host, and fpm-tune, which reads and writes one. Neither depends on the other; both depend on this.

go get github.com/cboxdk/phpfpm

Requirements

  • Go 1.26 or newer.
  • A host running PHP-FPM. The library shells out to the php-fpm binary for -tt (parse) and -t (validate), scans /proc to find masters, and signals them — so it runs where php-fpm runs, which in practice means Linux. On other platforms the process scan and the trust checks degrade rather than pretend.
  • The right user. Discovery reads other processes' details and, before it executes or signals anything, checks that the binary and config are owned by root or by this process. Running as root reads everything and applies the strict checks; running as the php-fpm user reads its own master; running as a stranger sees less, on purpose.

Reading a host

Find every master and scrape the pools it serves:

package main

import (
	"context"
	"fmt"
	"log/slog"
	"time"

	"github.com/cboxdk/phpfpm"
)

func main() {
	log := slog.Default()

	discovered, err := phpfpm.Discover(log)
	if err != nil {
		panic(err)
	}

	targets := make([]phpfpm.Target, 0, len(discovered))
	for _, d := range discovered {
		targets = append(targets, phpfpm.TargetFromDiscovered(d))
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// One outcome per target, in order. A pool that could not be reached comes
	// back with Err set rather than vanishing — it still occupies memory, and
	// dropping it would hand its share to its neighbours.
	outcomes, _ := phpfpm.ScrapeAll(ctx, targets, log)
	for _, o := range outcomes {
		if o.Err != nil {
			fmt.Printf("%s: unreachable: %v\n", o.Name, o.Err)
			continue
		}
		for name, pool := range o.Result.Pools {
			fmt.Printf("%s: %d active, %d accepted, %d workers\n",
				name, pool.ActiveProcesses, pool.AcceptedConnections, len(pool.Processes))
		}
	}
}

Reloading a master safely

Changing a pool's settings means writing configuration and telling the master to re-read it. The order is the whole safety story: validate, then reload, or an invalid file takes the master down and every pool with it.

// 1. Prove php-fpm accepts the configuration before anything is signalled.
if err := phpfpm.Validate(ctx, master.Binary, master.ConfigPath); err != nil {
	return fmt.Errorf("php-fpm rejected the configuration: %w", err)
}

// 2. Reload, and watch the master actually survive it. A daemonized master
//    re-execs into a NEW pid on SIGUSR2 — this follows it, and reports a master
//    that genuinely died rather than one that merely changed number.
newPID, err := phpfpm.ReloadAndWait(ctx, phpfpm.ReloadTarget{
	PID:        master.PID,
	ConfigPath: master.ConfigPath,
}, 2*time.Second, log)
if err != nil {
	return fmt.Errorf("the master did not survive the reload: %w", err)
}

The surface

Task Entry point
Find masters and their pools Discover — process scan plus php-fpm -tt, with each pool's configured size
Find masters only DiscoverMasters — the same scan without parsing any configuration
Parse effective configuration ParseConfig / ParseConfigContext — global settings plus one map per pool
Scrape live status Scrape (one pool), ScrapeAll (many, concurrently) — per-pool counters, and per worker both its own RSS and its whole subtree's (the ffmpeg it spawned)
Read opcache GetOpcacheStatus
Validate a configuration Validatephp-fpm -t, without applying it
Reload a master ReloadAndWait, ReloadMaster (SIGUSR2, scoped to a named config)
Confirm a master's identity VerifyMaster, MasterPID
Invalidate the parse cache InvalidateConfigCache — after you change a configuration

Full reference: pkg.go.dev/github.com/cboxdk/phpfpm. Longer-form documentation is in docs/: guides for reading a host, reloading safely, and the configuration cache, plus the design notes below in full.

Why it is shaped the way it is

The interesting parts of this library are the ones that refuse to do the obvious thing, because the obvious thing is wrong in a way that only shows up in production. Each of these was found against a real master.

Reload, never restart. SIGUSR2 makes the master re-read its configuration and cycle workers gracefully, carrying its listening sockets across so no request is dropped. But a configuration php-fpm rejects does not degrade — the master refuses to come back, and every pool it served goes with it. So a caller changing pm.* must pair the reload with Validate first. Verified: a drop-in naming a pool that no longer exists makes php-fpm -t exit 78, and reloading with that file present kills the master permanently.

A reload does not preserve the pid. SIGUSR2 makes the master re-exec itself. In the foreground — under systemd, or as pid 1 in a container — the pid survives. Daemonized, which is php-fpm's own default, the re-exec produces a new process and the original exits. ReloadAndWait confirms the master rather than the number, returns the pid it came back as, and reports one that genuinely died with nothing taking its place. A consumer that watched the pid it signalled reported a textbook reload as a dead master and rolled a good change back.

Identity before signalling. A pid is a promise about the instant it was read. A master can exit between discovery and the reload, and the kernel can reuse the number for something else — so ReloadAndWait also compares the process's start time across the settle window, and ReloadMaster compares the config path as a path rather than a substring, so on a host running two masters the signal reaches the intended one.

The process table is not a trust boundary. Any local user can start a process whose name matches php-fpm's and whose command line names a config they control, and both are handed to exec. Discovery checks that the binary and config are owned by root or by this process and not writable by others, refuses relative paths (which exec resolves through PATH), and — as root — checks the directories above them, since a root-owned binary inside a world-writable directory can be swapped between the check and the exec.

A master can be found without reading its configuration. Discover parses each master's config to enumerate pools, so a master whose configuration no longer parses is invisible to it — exactly when a consumer trying to repair that configuration most needs to find it. DiscoverMasters reads only the process table and answers regardless.

The configuration cache never expires on its own. ParseConfig forks php-fpm, so its result is cached per binary+config pair for the life of the process. That is right for a scrape loop and wrong for anything that changes the configuration: such a caller must call InvalidateConfigCache after reloading, or it keeps reading the settings it saw at startup. A consumer that missed this reported a pool as configured for 4 workers hours after setting it to 12.

A pool that cannot be scraped still occupies memory. Discovered and Target carry the pool's configured pm.max_children and process manager, so a caller holding a failed scrape still knows how large the pool is. Without it, a site restarting for five seconds looks like a pool needing nothing, and its memory is handed to its neighbours.

A pool can carry a hint the library does not interpret. Discovered and Target also carry Workload — the value of env[FPM_TUNE_WORKLOAD] from the pool's own config, or empty. This package only surfaces it; what "web" or "subprocess-heavy" means is the consumer's business. The config is the one place a per-pool hint can live and be discovered without a second file.

No package-level logger. Every entry point that can log takes a *slog.Logger. A library that owns a global logger cannot be embedded twice with different destinations, and it makes tests order-dependent.

Development

make check        # fmt, tidy, vet, lint, race, vulncheck, license-check
go test ./...     # the suite runs against a real php-fpm when one is installed

Security

This library execs php-fpm and signals masters, so its trust boundaries matter. Found a way past one? See SECURITY.md — please report it privately.

Provenance

Extracted from fpm-exporter's internal/phpfpm, where it could not be imported. The parsing and its fixtures moved unchanged; the control and trust surfaces have been reworked substantially since, under review and against real masters.

License

MIT — see LICENSE.

Documentation

Overview

Package phpfpm is a Go library for talking to PHP-FPM.

It covers the four things a caller needs in order to reason about a running PHP-FPM installation: parsing the effective configuration, discovering running masters and their pools, scraping live status over FastCGI, and controlling the master (validate, reload).

It exists as its own module because two consumers need it and neither should depend on the other: fpm-exporter reads, and fpm-tune both reads and writes. The code began life inside fpm-exporter's internal/phpfpm, where it could not be imported.

Two conventions run through the package:

Nothing here owns a logger. Entry points that need one take a *slog.Logger. A library with a package-level logger cannot be embedded twice with different destinations, and it makes tests order-dependent.

Reload, never restart. Reload sends SIGUSR2, which has the master re-read its configuration and cycle workers gracefully. Callers changing pm.* settings should pair it with Validate first — an invalid drop-in that reaches a reload takes the pool down.

Index

Constants

View Source
const DefaultParseTimeout = 30 * time.Second

DefaultParseTimeout bounds ParseConfig when the caller gives no context.

Generous: `php-fpm -tt` on a host with many pools is not instant, and killing a slow-but-working parse is worse than waiting for it. The point is that there IS a bound.

View Source
const DefaultTimeout = 3 * time.Second

DefaultTimeout bounds a FastCGI dial when a Target does not set one.

Variables

View Source
var ErrNotAMaster = errors.New("not a php-fpm master process")

ErrNotAMaster reports that a pid is not a php-fpm master process.

View Source
var ErrPathMissing = errors.New("path does not exist")

trustedPath reports whether a discovered path is safe to execute or read. ErrPathMissing reports a path that is not there at all, as opposed to one that is there and fails the trust checks.

Functions

func InvalidateConfigCache added in v0.3.0

func InvalidateConfigCache(binary, configPath string)

InvalidateConfigCache forgets the parsed configuration.

Call it after reloading php-fpm with a changed configuration. Without it, a long-running process keeps reporting the settings it saw at startup: a tool that writes pool settings would never observe its own changes, and would show an operator a "currently configured" value that has not been true for hours.

Passing a binary and config path forgets just that pair; passing empty strings forgets everything.

func MasterPID

func MasterPID(pidFile string) (int, error)

MasterPID reads a php-fpm master's pid from its pid file.

Discover finds masters by scanning the process table, which is the right tool when you do not know what is running. When you already know which pool you are reloading, the pid file is the authoritative answer and does not require permission to read another user's /proc entry.

func ParseAddress

func ParseAddress(addr string, path string) (scheme, address, scriptPath string, err error)

func Reload

func Reload(pid int) error

Reload asks a running master to re-read its configuration.

SIGUSR2 is a graceful reload: the master re-reads the config and cycles its workers, letting each finish the request it is serving. It is deliberately not a restart — a restart drops in-flight requests, and on a host serving many sites that is a visible outage for a change that was meant to be routine.

Callers changing pool settings should Validate first. Reload does not check the configuration it is asking the master to adopt.

func ReloadAndWait

func ReloadAndWait(ctx context.Context, target ReloadTarget, settle time.Duration, log *slog.Logger) (int, error)

ReloadAndWait reloads a master and waits for it to come back, returning the pid it came back as.

A reload that kills the master is the failure this exists to detect: Reload returns as soon as the signal is delivered, which is well before the master has re-read anything.

The returned pid may differ from the one signalled, and that is the whole subtlety. SIGUSR2 makes the master re-exec itself. When php-fpm runs in the foreground — under systemd, or as pid 1 in a container — the pid survives. When it runs DAEMONIZED, which is php-fpm's own default, the re-exec produces a new process and the original exits.

Watching the original pid therefore reported a perfectly successful reload as a dead master. Observed on a stock homebrew php-fpm: the log said "using inherited socket" and "ready to handle connections" under a new pid while the caller rolled the change back and told the operator its master had died.

So the confirmation is about the MASTER, not the number: the original pid surviving is one way to see it, and a successor that owns the same config is the other.

func ReloadMaster added in v0.8.1

func ReloadMaster(pid int, configPath string) error

ReloadMaster is Reload for a caller that knows which master it means.

VerifyMaster on its own only establishes that the pid is *a* php-fpm master. That is enough on a host running one, and not enough on a host running several: a master can exit between discovery and the reload, and a pid handed straight back to a different master would be signalled as though it were ours. The config path is in the process title, so checking it costs nothing and makes the answer specific.

func RemoveOpcacheScript

func RemoveOpcacheScript()

RemoveOpcacheScript deletes the generated status script. Safe to call when no script was ever written, and safe to call while scrapes are in flight — the next probe recreates it.

func Validate

func Validate(ctx context.Context, binary, configPath string) error

Validate checks a configuration with `php-fpm -t` without applying it.

This is the guard that makes writing pool configuration survivable. PHP-FPM re-reads its configuration on reload, and a syntax error or an impossible value does not fail gracefully — the master refuses to come back and every pool it served goes down. Validating first turns that outage into a returned error.

The configPath is the master config, not the drop-in: `-t` walks the include tree, so a broken fragment is caught through its parent.

func VerifyMaster added in v0.5.0

func VerifyMaster(pid int) error

VerifyMaster confirms that a pid really is a php-fpm master, immediately before it is signalled.

This replaced a `pid <= 1` refusal that was wrong in both directions.

It refused the most common deployment there is. In the official php:8.3-fpm image the master IS pid 1, so `fpm-tune apply` wrote the configuration, declined to reload, reported the master as dead and rolled the whole change back — verified against that image, where every apply failed this way.

And it did not guard against the thing that actually matters. A pid is only a promise about the instant it was read: between discovery and the reload the master can exit and the kernel can hand the number to something else. SIGUSR2 to a process that did not ask for it terminates it by default, so the old check would happily kill an unrelated program while carefully declining to signal init. Container pid namespaces start at 1 and stay small, which is exactly where recycling is likely.

Checking what the process IS covers both, and costs one /proc read.

Types

type Discovered

type Discovered struct {
	Name         string
	ConfigPath   string
	StatusPath   string
	Binary       string
	Socket       string
	StatusSocket string

	// MaxChildren and ProcessManager are the pool's CONFIGURED settings, read
	// from the effective configuration during discovery.
	//
	// Carried because a caller that cannot reach a pool still needs them. A pool
	// whose socket refuses — restarting, or briefly overloaded — is not a pool
	// that has stopped occupying memory, and a consumer with no idea how large
	// it is will hand its allocation to a neighbour and overcommit the host the
	// moment it comes back. Discovery has already parsed this; throwing it away
	// only to be unable to recover it later is the expensive kind of tidy.
	MaxChildren    int
	ProcessManager string

	// Workload is the value of env[FPM_TUNE_WORKLOAD] in the pool's config, when
	// it set one — a free-form hint a consumer that sizes pools can read to know
	// what the pool does before it has measured it. Empty when unset. This
	// package does not interpret it; it only carries what the config declared,
	// because the config is the one place a per-pool hint can live and be
	// discovered without a second file.
	Workload string

	// PID is the master process serving this pool.
	//
	// Carried from the process scan because the pid file is not a reliable
	// alternative: the official php:8.3-fpm image ships `pid` commented out, so
	// there is no file to read — and a caller that could not identify the master
	// would write pool configuration and never reload it.
	PID int
}

Discovered is one pool found by scanning the process table.

func Discover

func Discover(log *slog.Logger) ([]Discovered, error)

Discover scans the process table for PHP-FPM masters and returns the pools they serve.

It parses each master's effective configuration, which means executing the discovered binary — see trustedPath for why that is gated on ownership.

log may be nil.

func DiscoverContext added in v0.10.0

func DiscoverContext(ctx context.Context, log *slog.Logger) ([]Discovered, error)

DiscoverContext is Discover with a caller-supplied deadline. It forks php-fpm once per master, so a caller in a scrape loop should bound it.

type EffectiveConfig

type EffectiveConfig struct {
	Global map[string]string
	Pools  map[string]map[string]string
}

func ParseConfig

func ParseConfig(FPMBinaryPath string, FPMConfigPath string) (*EffectiveConfig, error)

ParseConfig runs `php-fpm -tt` and parses its report of the effective configuration. Results are cached per binary+config pair.

The cache never expires on its own, which is right for the common case — the parse forks php-fpm and a scrape loop would otherwise do it every few seconds — but it means a caller that CHANGES the configuration has to say so. See InvalidateConfigCache.

func ParseConfigContext added in v0.10.0

func ParseConfigContext(ctx context.Context, FPMBinaryPath string, FPMConfigPath string) (*EffectiveConfig, error)

ParseConfigContext is ParseConfig with a caller-supplied deadline.

type Info

type Info struct {
	Version    string
	Extensions []string
}

func GetPHPStats

func GetPHPStats(ctx context.Context, target Target) (*Info, error)

type Master added in v0.7.0

type Master struct {
	PID        int
	Binary     string
	ConfigPath string
}

Master is a running php-fpm master, identified WITHOUT reading its configuration.

Discover parses each master's effective config, which is what makes it useful — and what makes it useless in the one situation a caller most needs an answer. A master whose config file no longer parses is skipped entirely, so a tool trying to repair exactly that config cannot find the master to repair it for. Observed: a rejected pool fragment left on disk by a run that died, a healthy master still serving from the configuration it loaded before the file appeared, and `fpm-tune apply` reporting "no PHP-FPM pools found" while the fragment sat there waiting for any reload to adopt it.

This carries only what the process table itself provides, so it answers even when the configuration does not.

func DiscoverMasters added in v0.7.0

func DiscoverMasters(log *slog.Logger) ([]Master, error)

DiscoverMasters scans the process table for php-fpm masters.

The binary and config path still go through the trust checks — they are about to be handed to exec — but nothing is executed here and nothing is parsed.

log may be nil.

type Memory

type Memory struct {
	UsedMemory       uint64  `json:"used_memory"`
	FreeMemory       uint64  `json:"free_memory"`
	WastedMemory     uint64  `json:"wasted_memory"`
	CurrentWastedPct float64 `json:"current_wasted_percentage"`
}

type OpcacheStatus

type OpcacheStatus struct {
	Enabled     bool   `json:"opcache_enabled"`
	MemoryUsage Memory `json:"memory_usage"`
	Statistics  Stats  `json:"opcache_statistics"`
}

func GetOpcacheStatus

func GetOpcacheStatus(ctx context.Context, target Target) (*OpcacheStatus, error)

type Pool

type Pool struct {
	Address             string            `json:"address"`
	Path                string            `json:"path"`
	Name                string            `json:"pool"`
	ProcessManager      string            `json:"process manager"`
	StartTime           int64             `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"`
	MemoryPeak          int64             `json:"memory peak"`
	Processes           []PoolProcess     `json:"processes"`
	ProcessesCpu        *float64          `json:"processes_cpu"`
	ProcessesMemory     *float64          `json:"processes_memory"`
	Config              map[string]string `json:"config,omitempty"`
	OpcacheStatus       OpcacheStatus     `json:"opcache_status,omitempty"`
	PhpInfo             Info              `json:"php_info,omitempty"`
}

type PoolOutcome

type PoolOutcome struct {
	// Name is the pool's configured or discovered name, used to label a
	// failure. A successful scrape prefers the name PHP-FPM itself reports.
	Name   string
	Socket string
	Result *Result
	Err    error
}

PoolOutcome is what happened to one configured pool during a scrape: either a Result, or the error that prevented one. Failures are values rather than log lines so the collector can emit up=0 for a pool that did not answer -- a pool that silently vanishes from the output is indistinguishable from a pool that was removed from the configuration.

func Scrape

func Scrape(ctx context.Context, target Target, log *slog.Logger) PoolOutcome

Scrape reads one pool's status page. Its client and response body close at the end of this call rather than at the end of a whole batch.

It takes its own snapshot of the process table to measure worker subtrees; ScrapeAll shares one across pools instead. See scrape.

log may be nil.

func ScrapeAll

func ScrapeAll(ctx context.Context, targets []Target, log *slog.Logger) ([]PoolOutcome, error)

ScrapeAll scrapes every target. It returns one outcome per target, in the order given, and an error only when nothing at all could be collected.

It took fpm-exporter's whole *config.Config before this package was extracted. A library takes the work it is asked to do; deciding which pools exist belongs to the caller.

log may be nil.

type PoolProcess

type PoolProcess struct {
	PID               int     `json:"pid"`
	State             string  `json:"state"`
	StartTime         int64   `json:"start time"`
	StartSince        int64   `json:"start since"`
	Requests          int64   `json:"requests"`
	RequestDuration   int64   `json:"request duration"`
	RequestMethod     string  `json:"request method"`
	RequestURI        string  `json:"request uri"`
	ContentLength     int64   `json:"content length"`
	User              string  `json:"user"`
	Script            string  `json:"script"`
	LastRequestCPU    float64 `json:"last request cpu"`
	LastRequestMemory float64 `json:"last request memory"`
	// CurrentRSS is the worker's resident memory, in bytes.
	//
	// Not part of PHP-FPM's status output — it is filled in from the operating
	// system using PID, because the size of a worker is the number any capacity
	// decision turns on and the status page does not carry it. Zero means the
	// worker could not be read, usually because it exited between the status
	// response and the lookup.
	CurrentRSS int64 `json:"current_rss"`

	// CurrentPSS is the worker's proportional set size, in bytes: like CurrentRSS,
	// but every shared page (the opcache SHM segment, shared libraries, the pages
	// still copy-on-write from the master) is divided by the number of processes
	// mapping it rather than charged in full to each. Summing PSS across a pool's
	// workers yields the memory those workers actually cost the host; summing RSS
	// double-counts everything shared. Read from /proc/<pid>/smaps_rollup.
	//
	// Zero means it could not be read: a kernel without smaps_rollup (pre-4.14), a
	// permission short of PTRACE_MODE_READ, or the worker having exited. A consumer
	// sizing a pool should prefer CurrentPSS when it is non-zero and fall back to
	// CurrentRSS otherwise.
	CurrentPSS int64 `json:"current_pss"`

	// SubtreeRSS is the resident memory of the worker AND every process it
	// spawned — the ffmpeg or imagemagick a request shelled out to, each a
	// separate pid the status page and CurrentRSS both miss. It is always at
	// least CurrentRSS; the difference is what the children cost. Zero means it
	// was not measured (no process snapshot this scrape), which is distinct from
	// a measured subtree that equals CurrentRSS because nothing was spawned.
	//
	// Point-in-time: a child that lived and died between two scrapes is not in
	// it, and one whose worker exited first has reparented away from the subtree.
	// It is the per-worker view; a cgroup's own high-water mark, where there is a
	// cgroup, catches the transients this cannot.
	SubtreeRSS int64 `json:"subtree_rss"`
}

type ReloadTarget added in v0.6.0

type ReloadTarget struct {
	// PID is the master to signal.
	PID int

	// PIDFile and ConfigPath are how the master is found again if it comes back
	// under a different pid. Either is sufficient; both are better.
	PIDFile    string
	ConfigPath string
}

ReloadTarget identifies a master well enough to recognise it after a reload.

The pid alone is not enough, because a reload does not always preserve it.

type Result

type Result struct {
	Timestamp time.Time
	Pools     map[string]Pool
	Global    map[string]string `json:"global_config,omitempty"`
}

type Stats

type Stats struct {
	NumCachedScripts uint64  `json:"num_cached_scripts"`
	Hits             uint64  `json:"hits"`
	Misses           uint64  `json:"misses"`
	BlacklistMisses  uint64  `json:"blacklist_misses"`
	OomRestarts      uint64  `json:"oom_restarts"`
	HashRestarts     uint64  `json:"hash_restarts"`
	ManualRestarts   uint64  `json:"manual_restarts"`
	HitRate          float64 `json:"opcache_hit_rate"`
}

type Target

type Target struct {
	// Name identifies the pool in results when the pool itself could not be
	// reached. A successful scrape uses the name PHP-FPM reports; this is the
	// fallback so a failing pool is still labelled with something meaningful.
	Name string `mapstructure:"name"`

	// Socket is the pool's FastCGI address ("unix:///run/php-fpm.sock",
	// "tcp://127.0.0.1:9000", or a bare path).
	Socket string `mapstructure:"socket"`

	// StatusSocket is where the status page is served, when it differs from
	// Socket. Empty means Socket.
	StatusSocket string `mapstructure:"status_socket"`
	StatusPath   string `mapstructure:"status_path"`

	// ConfigPath and Binary let ParseConfig recover the effective configuration
	// for this pool. Both come from the master's command line during Discover.
	ConfigPath string `mapstructure:"config_path"`
	Binary     string `mapstructure:"binary"`

	// MaxChildren and ProcessManager are the pool's CONFIGURED settings as
	// discovery read them.
	//
	// They matter most when the pool cannot be reached. A pool whose socket
	// refuses is not a pool that has stopped occupying memory, and a caller with
	// no idea how large it is will hand its allocation away and overcommit the
	// host the moment it comes back.
	MaxChildren    int    `mapstructure:"-"`
	ProcessManager string `mapstructure:"-"`

	// Workload is env[FPM_TUNE_WORKLOAD] from the pool's config, when it set one.
	// Carried, not interpreted — a consumer that sizes pools reads it as a hint
	// about what the pool does. Empty when unset.
	Workload string `mapstructure:"workload"`

	// PID is the master serving this pool, when it is known. Zero means unknown,
	// not "no master".
	PID int `mapstructure:"-"`

	// Timeout bounds the FastCGI dial for this pool. Zero means DefaultTimeout.
	Timeout time.Duration `mapstructure:"timeout"`
}

Target describes how to reach one PHP-FPM pool.

It replaces the FPMPoolConfig this package used to take from fpm-exporter's internal config. A library cannot depend on one application's configuration type — the second consumer has a different one, and neither should have to adopt the other's.

The mapstructure tags are kept so a viper-based caller can decode straight into this type rather than maintaining a parallel struct and a copy function.

func TargetFromDiscovered

func TargetFromDiscovered(d Discovered) Target

TargetFromDiscovered builds a scrape target from a discovered pool.

type Unstatused added in v1.1.0

type Unstatused struct {
	Name       string
	ConfigPath string // the master config the pool is defined under
	Binary     string // the master's php-fpm binary
	PID        int    // the master serving it
	Socket     string // the pool's listen socket, where the status page would be served
}

Unstatused is a pool that exists but exposes no pm.status_path, so it cannot be scraped for the live metrics the normal results carry — and is left out of them for exactly that reason.

It is surfaced separately rather than dropped silently: a bare php-fpm ships its default `www` pool with pm.status_path commented out, so a tool that needs the status page finds nothing on a stock install and reports it as "no pools", which sends the operator looking for a master that is running the whole time. Carrying the pool lets a caller say what is actually wrong, and — if it manages the host — turn the status page on rather than asking the operator to hand-edit a file.

func UnstatusedPools added in v1.1.0

func UnstatusedPools(ctx context.Context, log *slog.Logger) ([]Unstatused, error)

UnstatusedPools returns the pools found on the host that have no pm.status_path, so cannot be scraped and are left out of DiscoverContext's results.

It runs the same scan DiscoverContext does — one fork of php-fpm per master — so it is for the two places that need it rather than a hot loop: reporting honestly why nothing was found, and enabling the status page for the pools that lack it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL