diff options
Diffstat (limited to 'src/go/collectors/go.d.plugin/modules/phpfpm')
18 files changed, 1850 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/README.md b/src/go/collectors/go.d.plugin/modules/phpfpm/README.md new file mode 120000 index 000000000..2953ff4df --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/README.md @@ -0,0 +1 @@ +integrations/php-fpm.md
\ No newline at end of file diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/charts.go b/src/go/collectors/go.d.plugin/modules/phpfpm/charts.go new file mode 100644 index 000000000..6b69d4c78 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/charts.go @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package phpfpm + +import "github.com/netdata/netdata/go/go.d.plugin/agent/module" + +type ( + // Charts is an alias for module.Charts + Charts = module.Charts + // Dims is an alias for module.Dims + Dims = module.Dims +) + +var charts = Charts{ + { + ID: "connections", + Title: "Active Connections", + Units: "connections", + Fam: "active connections", + Ctx: "phpfpm.connections", + Dims: Dims{ + {ID: "active"}, + {ID: "maxActive", Name: "max active"}, + {ID: "idle"}, + }, + }, + { + ID: "requests", + Title: "Requests", + Units: "requests/s", + Fam: "requests", + Ctx: "phpfpm.requests", + Dims: Dims{ + {ID: "requests", Algo: module.Incremental}, + }, + }, + { + ID: "performance", + Title: "Performance", + Units: "status", + Fam: "performance", + Ctx: "phpfpm.performance", + Dims: Dims{ + {ID: "reached", Name: "max children reached"}, + {ID: "slow", Name: "slow requests"}, + }, + }, + { + ID: "request_duration", + Title: "Requests Duration Among All Idle Processes", + Units: "milliseconds", + Fam: "request duration", + Ctx: "phpfpm.request_duration", + Dims: Dims{ + {ID: "minReqDur", Name: "min", Div: 1000}, + {ID: "maxReqDur", Name: "max", Div: 1000}, + {ID: "avgReqDur", Name: "avg", Div: 1000}, + }, + }, + { + ID: "request_cpu", + Title: "Last Request CPU Usage Among All Idle Processes", + Units: "percentage", + Fam: "request CPU", + Ctx: "phpfpm.request_cpu", + Dims: Dims{ + {ID: "minReqCpu", Name: "min"}, + {ID: "maxReqCpu", Name: "max"}, + {ID: "avgReqCpu", Name: "avg"}, + }, + }, + { + ID: "request_mem", + Title: "Last Request Memory Usage Among All Idle Processes", + Units: "KB", + Fam: "request memory", + Ctx: "phpfpm.request_mem", + Dims: Dims{ + {ID: "minReqMem", Name: "min", Div: 1024}, + {ID: "maxReqMem", Name: "max", Div: 1024}, + {ID: "avgReqMem", Name: "avg", Div: 1024}, + }, + }, +} diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/client.go b/src/go/collectors/go.d.plugin/modules/phpfpm/client.go new file mode 100644 index 000000000..5d72041ed --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/client.go @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package phpfpm + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/netdata/netdata/go/go.d.plugin/logger" + "github.com/netdata/netdata/go/go.d.plugin/pkg/web" + + fcgiclient "github.com/kanocz/fcgi_client" +) + +type ( + status struct { + Active int64 `json:"active processes" stm:"active"` + MaxActive int64 `json:"max active processes" stm:"maxActive"` + Idle int64 `json:"idle processes" stm:"idle"` + Requests int64 `json:"accepted conn" stm:"requests"` + Reached int64 `json:"max children reached" stm:"reached"` + Slow int64 `json:"slow requests" stm:"slow"` + Processes []proc `json:"processes"` + } + proc struct { + PID int64 `json:"pid"` + State string `json:"state"` + Duration requestDuration `json:"request duration"` + CPU float64 `json:"last request cpu"` + Memory int64 `json:"last request memory"` + } + requestDuration int64 +) + +// UnmarshalJSON customise JSON for timestamp. +func (rd *requestDuration) UnmarshalJSON(b []byte) error { + if rdc, err := strconv.Atoi(string(b)); err != nil { + *rd = 0 + } else { + *rd = requestDuration(rdc) + } + return nil +} + +type client interface { + getStatus() (*status, error) +} + +type httpClient struct { + client *http.Client + req web.Request + dec decoder +} + +func newHTTPClient(c *http.Client, r web.Request) (*httpClient, error) { + u, err := url.Parse(r.URL) + if err != nil { + return nil, err + } + + dec := decodeText + if _, ok := u.Query()["json"]; ok { + dec = decodeJSON + } + return &httpClient{ + client: c, + req: r, + dec: dec, + }, nil +} + +func (c *httpClient) getStatus() (*status, error) { + req, err := web.NewHTTPRequest(c.req) + if err != nil { + return nil, fmt.Errorf("error on creating HTTP request: %v", err) + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error on HTTP request to '%s': %v", req.URL, err) + } + defer func() { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + }() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode) + } + + st := &status{} + if err := c.dec(resp.Body, st); err != nil { + return nil, fmt.Errorf("error parsing HTTP response from '%s': %v", req.URL, err) + } + + return st, nil +} + +type socketClient struct { + *logger.Logger + + socket string + timeout time.Duration + env map[string]string +} + +func newSocketClient(log *logger.Logger, socket string, timeout time.Duration, fcgiPath string) *socketClient { + return &socketClient{ + Logger: log, + socket: socket, + timeout: timeout, + env: map[string]string{ + "SCRIPT_NAME": fcgiPath, + "SCRIPT_FILENAME": fcgiPath, + "SERVER_SOFTWARE": "go / fcgiclient ", + "REMOTE_ADDR": "127.0.0.1", + "QUERY_STRING": "json&full", + "REQUEST_METHOD": "GET", + "CONTENT_TYPE": "application/json", + }, + } +} + +func (c *socketClient) getStatus() (*status, error) { + socket, err := fcgiclient.DialTimeout("unix", c.socket, c.timeout) + if err != nil { + return nil, fmt.Errorf("error on connecting to socket '%s': %v", c.socket, err) + } + defer socket.Close() + + if err := socket.SetTimeout(c.timeout); err != nil { + return nil, fmt.Errorf("error on setting socket timeout: %v", err) + } + + resp, err := socket.Get(c.env) + if err != nil { + return nil, fmt.Errorf("error on getting data from socket '%s': %v", c.socket, err) + } + + content, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error on reading response from socket '%s': %v", c.socket, err) + } + + if len(content) == 0 { + return nil, fmt.Errorf("no data returned from socket '%s'", c.socket) + } + + st := &status{} + if err := json.Unmarshal(content, st); err != nil { + c.Debugf("failed to JSON decode data: %s", string(content)) + return nil, fmt.Errorf("error on decoding response from socket '%s': %v", c.socket, err) + } + + return st, nil +} + +type tcpClient struct { + *logger.Logger + + address string + timeout time.Duration + env map[string]string +} + +func newTcpClient(log *logger.Logger, address string, timeout time.Duration, fcgiPath string) *tcpClient { + return &tcpClient{ + Logger: log, + address: address, + timeout: timeout, + env: map[string]string{ + "SCRIPT_NAME": fcgiPath, + "SCRIPT_FILENAME": fcgiPath, + "SERVER_SOFTWARE": "go / fcgiclient ", + "REMOTE_ADDR": "127.0.0.1", + "QUERY_STRING": "json&full", + "REQUEST_METHOD": "GET", + "CONTENT_TYPE": "application/json", + }, + } +} + +func (c *tcpClient) getStatus() (*status, error) { + client, err := fcgiclient.DialTimeout("tcp", c.address, c.timeout) + if err != nil { + return nil, fmt.Errorf("error on connecting to address '%s': %v", c.address, err) + } + defer client.Close() + + resp, err := client.Get(c.env) + if err != nil { + return nil, fmt.Errorf("error on getting data from address '%s': %v", c.address, err) + } + + content, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error on reading response from address '%s': %v", c.address, err) + } + + if len(content) == 0 { + return nil, fmt.Errorf("no data returned from address '%s'", c.address) + } + + st := &status{} + if err := json.Unmarshal(content, st); err != nil { + c.Debugf("failed to JSON decode data: %s", string(content)) + return nil, fmt.Errorf("error on decoding response from address '%s': %v", c.address, err) + } + + return st, nil +} diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/collect.go b/src/go/collectors/go.d.plugin/modules/phpfpm/collect.go new file mode 100644 index 000000000..f720252a8 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/collect.go @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package phpfpm + +import ( + "math" + + "github.com/netdata/netdata/go/go.d.plugin/pkg/stm" +) + +func (p *Phpfpm) collect() (map[string]int64, error) { + st, err := p.client.getStatus() + if err != nil { + return nil, err + } + + mx := stm.ToMap(st) + if !hasIdleProcesses(st.Processes) { + return mx, nil + } + + calcIdleProcessesRequestsDuration(mx, st.Processes) + calcIdleProcessesLastRequestCPU(mx, st.Processes) + calcIdleProcessesLastRequestMemory(mx, st.Processes) + return mx, nil +} + +func calcIdleProcessesRequestsDuration(mx map[string]int64, processes []proc) { + statProcesses(mx, processes, "ReqDur", func(p proc) int64 { return int64(p.Duration) }) +} + +func calcIdleProcessesLastRequestCPU(mx map[string]int64, processes []proc) { + statProcesses(mx, processes, "ReqCpu", func(p proc) int64 { return int64(p.CPU) }) +} + +func calcIdleProcessesLastRequestMemory(mx map[string]int64, processes []proc) { + statProcesses(mx, processes, "ReqMem", func(p proc) int64 { return p.Memory }) +} + +func hasIdleProcesses(processes []proc) bool { + for _, p := range processes { + if p.State == "Idle" { + return true + } + } + return false +} + +type accessor func(p proc) int64 + +func statProcesses(m map[string]int64, processes []proc, met string, acc accessor) { + var sum, count, min, max int64 + for _, proc := range processes { + if proc.State != "Idle" { + continue + } + + val := acc(proc) + sum += val + count += 1 + if count == 1 { + min, max = val, val + continue + } + min = int64(math.Min(float64(min), float64(val))) + max = int64(math.Max(float64(max), float64(val))) + } + + m["min"+met] = min + m["max"+met] = max + m["avg"+met] = sum / count +} diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/config_schema.json b/src/go/collectors/go.d.plugin/modules/phpfpm/config_schema.json new file mode 100644 index 000000000..252d8a083 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/config_schema.json @@ -0,0 +1,205 @@ +{ + "jsonSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PHP-FPM collector configuration.", + "type": "object", + "properties": { + "update_every": { + "title": "Update every", + "description": "Data collection interval, measured in seconds.", + "type": "integer", + "minimum": 1, + "default": 1 + }, + "timeout": { + "title": "Timeout", + "description": "The timeout in seconds for requests.", + "type": "number", + "minimum": 0.5, + "default": 1 + }, + "url": { + "title": "URL", + "description": "The URL of the PHP-FPM [status page](https://www.php.net/manual/en/fpm.status.php).", + "type": "string", + "default": "http://127.0.0.1/status?full&json", + "format": "uri" + }, + "address": { + "title": "Address", + "description": "The PHP-FPM daemon's TCP listening address. This will be preferred over the **URL** if set.", + "type": "string", + "default": "" + }, + "socket": { + "title": "Socket", + "description": "The PHP-FPM daemon's Unix socket. This will be preferred over both the **URL** and **Address** if set.", + "type": "string", + "default": "", + "pattern": "^$|^/" + }, + "fcgi_path": { + "title": "FCGI status path", + "description": "The URI to view the [FPM status page](https://www.php.net/manual/en/fpm.status.php).", + "type": "string", + "default": "/status", + "pattern": "^$|^/" + }, + "not_follow_redirects": { + "title": "Not follow redirects", + "description": "If set, the client will not follow HTTP redirects automatically.", + "type": "boolean" + }, + "username": { + "title": "Username", + "description": "The username for basic authentication.", + "type": "string", + "sensitive": true + }, + "password": { + "title": "Password", + "description": "The password for basic authentication.", + "type": "string", + "sensitive": true + }, + "proxy_url": { + "title": "Proxy URL", + "description": "The URL of the proxy server.", + "type": "string" + }, + "proxy_username": { + "title": "Proxy username", + "description": "The username for proxy authentication.", + "type": "string", + "sensitive": true + }, + "proxy_password": { + "title": "Proxy password", + "description": "The password for proxy authentication.", + "type": "string", + "sensitive": true + }, + "headers": { + "title": "Headers", + "description": "Additional HTTP headers to include in the request.", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "tls_skip_verify": { + "title": "Skip TLS verification", + "description": "If set, TLS certificate verification will be skipped.", + "type": "boolean" + }, + "tls_ca": { + "title": "TLS CA", + "description": "The path to the CA certificate file for TLS verification.", + "type": "string", + "pattern": "^$|^/" + }, + "tls_cert": { + "title": "TLS certificate", + "description": "The path to the client certificate file for TLS authentication.", + "type": "string", + "pattern": "^$|^/" + }, + "tls_key": { + "title": "TLS key", + "description": "The path to the client key file for TLS authentication.", + "type": "string", + "pattern": "^$|^/" + }, + "body": { + "title": "Body", + "type": "string" + }, + "method": { + "title": "Method", + "type": "string" + } + }, + "additionalProperties": false, + "patternProperties": { + "^name$": {} + } + }, + "uiSchema": { + "uiOptions": { + "fullPage": true + }, + "address": { + "ui:placeholder": "127.0.0.1:9000" + }, + "socket": { + "ui:placeholder": "/tmp/php-fpm.sock" + }, + "fcgi_path": { + "ui:widget": "hidden" + }, + "body": { + "ui:widget": "hidden" + }, + "method": { + "ui:widget": "hidden" + }, + "timeout": { + "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)." + }, + "password": { + "ui:widget": "password" + }, + "proxy_password": { + "ui:widget": "password" + }, + "ui:flavour": "tabs", + "ui:options": { + "tabs": [ + { + "title": "Base", + "fields": [ + "update_every", + "timeout", + "url", + "address", + "socket", + "not_follow_redirects" + ] + }, + { + "title": "Auth", + "fields": [ + "username", + "password" + ] + }, + { + "title": "TLS", + "fields": [ + "tls_skip_verify", + "tls_ca", + "tls_cert", + "tls_key" + ] + }, + { + "title": "Proxy", + "fields": [ + "proxy_url", + "proxy_username", + "proxy_password" + ] + }, + { + "title": "Headers", + "fields": [ + "headers" + ] + } + ] + } + } +} diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/decode.go b/src/go/collectors/go.d.plugin/modules/phpfpm/decode.go new file mode 100644 index 000000000..021e1fb4c --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/decode.go @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package phpfpm + +import ( + "bufio" + "encoding/json" + "errors" + "io" + "strconv" + "strings" +) + +type decoder func(r io.Reader, s *status) error + +func decodeJSON(r io.Reader, s *status) error { + return json.NewDecoder(r).Decode(s) +} + +func decodeText(r io.Reader, s *status) error { + parts := readParts(r) + if len(parts) == 0 { + return errors.New("invalid text format") + } + + part, parts := parts[0], parts[1:] + if err := readStatus(part, s); err != nil { + return err + } + + return readProcesses(parts, s) +} + +func readParts(r io.Reader) [][]string { + sc := bufio.NewScanner(r) + + var parts [][]string + var lines []string + for sc.Scan() { + line := strings.Trim(sc.Text(), "\r\n ") + // Split parts by star border + if strings.HasPrefix(line, "***") { + parts = append(parts, lines) + lines = []string{} + continue + } + // Skip empty lines + if line == "" { + continue + } + lines = append(lines, line) + } + + if len(lines) > 0 { + parts = append(parts, lines) + } + return parts +} + +func readStatus(data []string, s *status) error { + for _, line := range data { + key, val, err := parseLine(line) + if err != nil { + return err + } + + switch key { + case "active processes": + s.Active = parseInt(val) + case "max active processes": + s.MaxActive = parseInt(val) + case "idle processes": + s.Idle = parseInt(val) + case "accepted conn": + s.Requests = parseInt(val) + case "max children reached": + s.Reached = parseInt(val) + case "slow requests": + s.Slow = parseInt(val) + } + } + return nil +} + +func readProcesses(procs [][]string, s *status) error { + for _, part := range procs { + var proc proc + for _, line := range part { + key, val, err := parseLine(line) + if err != nil { + return err + } + + switch key { + case "state": + proc.State = val + case "request duration": + proc.Duration = requestDuration(parseInt(val)) + case "last request cpu": + proc.CPU = parseFloat(val) + case "last request memory": + proc.Memory = parseInt(val) + } + } + s.Processes = append(s.Processes, proc) + } + return nil +} + +func parseLine(s string) (string, string, error) { + kv := strings.SplitN(s, ":", 2) + if len(kv) != 2 { + return "", "", errors.New("invalid text format line") + } + return strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1]), nil +} + +func parseInt(s string) int64 { + val, err := strconv.ParseInt(strings.TrimSpace(s), 10, 64) + if err != nil { + return 0 + } + return val +} + +func parseFloat(s string) float64 { + val, err := strconv.ParseFloat(strings.TrimSpace(s), 64) + if err != nil { + return 0 + } + return val +} diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/init.go b/src/go/collectors/go.d.plugin/modules/phpfpm/init.go new file mode 100644 index 000000000..33fbc540d --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/init.go @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package phpfpm + +import ( + "errors" + "fmt" + "os" + + "github.com/netdata/netdata/go/go.d.plugin/pkg/web" +) + +func (p *Phpfpm) initClient() (client, error) { + if p.Socket != "" { + return p.initSocketClient() + } + if p.Address != "" { + return p.initTcpClient() + } + if p.URL != "" { + return p.initHTTPClient() + } + + return nil, errors.New("neither 'socket' nor 'url' set") +} + +func (p *Phpfpm) initHTTPClient() (*httpClient, error) { + c, err := web.NewHTTPClient(p.Client) + if err != nil { + return nil, fmt.Errorf("create HTTP client: %v", err) + } + + p.Debugf("using HTTP client: url='%s', timeout='%s'", p.URL, p.Timeout) + + return newHTTPClient(c, p.Request) +} + +func (p *Phpfpm) initSocketClient() (*socketClient, error) { + if _, err := os.Stat(p.Socket); err != nil { + return nil, fmt.Errorf("the socket '%s' does not exist: %v", p.Socket, err) + } + + p.Debugf("using socket client: socket='%s', timeout='%s', fcgi_path='%s'", p.Socket, p.Timeout, p.FcgiPath) + + return newSocketClient(p.Logger, p.Socket, p.Timeout.Duration(), p.FcgiPath), nil +} + +func (p *Phpfpm) initTcpClient() (*tcpClient, error) { + p.Debugf("using tcp client: address='%s', timeout='%s', fcgi_path='%s'", p.Address, p.Timeout, p.FcgiPath) + + return newTcpClient(p.Logger, p.Address, p.Timeout.Duration(), p.FcgiPath), nil +} diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/integrations/php-fpm.md b/src/go/collectors/go.d.plugin/modules/phpfpm/integrations/php-fpm.md new file mode 100644 index 000000000..33e0d7a93 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/integrations/php-fpm.md @@ -0,0 +1,229 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/go/collectors/go.d.plugin/modules/phpfpm/README.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/go/collectors/go.d.plugin/modules/phpfpm/metadata.yaml" +sidebar_label: "PHP-FPM" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/Web Servers and Web Proxies" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# PHP-FPM + + +<img src="https://netdata.cloud/img/php.svg" width="150"/> + + +Plugin: go.d.plugin +Module: phpfpm + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +This collector monitors PHP-FPM instances. + + + + +This collector is supported on all platforms. + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + + +### Default Behavior + +#### Auto-Detection + +This integration doesn't support auto-detection. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +The default configuration for this integration is not expected to impose a significant performance impact on the system. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per PHP-FPM instance + +These metrics refer to the entire monitored application. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| phpfpm.connections | active, max_active, idle | connections | +| phpfpm.requests | requests | requests/s | +| phpfpm.performance | max_children_reached, slow_requests | status | +| phpfpm.request_duration | min, max, avg | milliseconds | +| phpfpm.request_cpu | min, max, avg | percentage | +| phpfpm.request_mem | min, max, avg | KB | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Enable status page + +Uncomment the `pm.status_path = /status` variable in the `php-fpm` config file. + + + +### Configuration + +#### File + +The configuration file name for this integration is `go.d/phpfpm.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config go.d/phpfpm.conf +``` +#### Options + +The following options can be defined globally: update_every, autodetection_retry. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update_every | Data collection frequency. | 1 | no | +| autodetection_retry | Recheck interval in seconds. Zero means no recheck will be scheduled. | 0 | no | +| url | Server URL. | http://127.0.0.1/status?full&json | yes | +| socket | Server Unix socket. | | no | +| address | Server address in IP:PORT format. | | no | +| fcgi_path | Status path. | /status | no | +| timeout | HTTP request timeout. | 1 | no | +| username | Username for basic HTTP authentication. | | no | +| password | Password for basic HTTP authentication. | | no | +| proxy_url | Proxy URL. | | no | +| proxy_username | Username for proxy basic HTTP authentication. | | no | +| proxy_password | Password for proxy basic HTTP authentication. | | no | +| method | HTTP request method. | GET | no | +| body | HTTP request body. | | no | +| headers | HTTP request headers. | | no | +| not_follow_redirects | Redirect handling policy. Controls whether the client follows redirects. | no | no | +| tls_skip_verify | Server certificate chain and hostname validation policy. Controls whether the client performs this check. | no | no | +| tls_ca | Certification authority that the client uses when verifying the server's certificates. | | no | +| tls_cert | Client TLS certificate. | | no | +| tls_key | Client TLS key. | | no | + +</details> + +#### Examples + +##### HTTP + +Collecting data from a local instance over HTTP. + +<details open><summary>Config</summary> + +```yaml +jobs: + - name: local + url: http://localhost/status?full&json + +``` +</details> + +##### Unix socket + +Collecting data from a local instance over Unix socket. + +<details open><summary>Config</summary> + +```yaml +jobs: + - name: local + socket: '/tmp/php-fpm.sock' + +``` +</details> + +##### TCP socket + +Collecting data from a local instance over TCP socket. + +<details open><summary>Config</summary> + +```yaml +jobs: + - name: local + address: 127.0.0.1:9000 + +``` +</details> + +##### Multi-instance + +> **Note**: When you define multiple jobs, their names must be unique. + +Collecting metrics from local and remote instances. + + +<details open><summary>Config</summary> + +```yaml +jobs: + - name: local + url: http://localhost/status?full&json + + - name: remote + url: http://203.0.113.10/status?full&json + +``` +</details> + + + +## Troubleshooting + +### Debug Mode + +To troubleshoot issues with the `phpfpm` collector, run the `go.d.plugin` with the debug option enabled. The output +should give you clues as to why the collector isn't working. + +- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on + your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`. + + ```bash + cd /usr/libexec/netdata/plugins.d/ + ``` + +- Switch to the `netdata` user. + + ```bash + sudo -u netdata -s + ``` + +- Run the `go.d.plugin` to debug the collector: + + ```bash + ./go.d.plugin -d -m phpfpm + ``` + + diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/metadata.yaml b/src/go/collectors/go.d.plugin/modules/phpfpm/metadata.yaml new file mode 100644 index 000000000..739e7b7b8 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/metadata.yaml @@ -0,0 +1,230 @@ +plugin_name: go.d.plugin +modules: + - meta: + id: collector-go.d.plugin-phpfpm + plugin_name: go.d.plugin + module_name: phpfpm + monitored_instance: + name: PHP-FPM + link: https://php-fpm.org/ + icon_filename: php.svg + categories: + - data-collection.web-servers-and-web-proxies + keywords: + - phpfpm + - php + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + most_popular: false + overview: + data_collection: + metrics_description: | + This collector monitors PHP-FPM instances. + method_description: "" + supported_platforms: + include: [] + exclude: [] + multi_instance: true + additional_permissions: + description: "" + default_behavior: + auto_detection: + description: "" + limits: + description: "" + performance_impact: + description: "" + setup: + prerequisites: + list: + - title: Enable status page + description: | + Uncomment the `pm.status_path = /status` variable in the `php-fpm` config file. + configuration: + file: + name: go.d/phpfpm.conf + options: + description: | + The following options can be defined globally: update_every, autodetection_retry. + folding: + title: Config options + enabled: true + list: + - name: update_every + description: Data collection frequency. + default_value: 1 + required: false + - name: autodetection_retry + description: Recheck interval in seconds. Zero means no recheck will be scheduled. + default_value: 0 + required: false + - name: url + description: Server URL. + default_value: http://127.0.0.1/status?full&json + required: true + - name: socket + description: Server Unix socket. + default_value: "" + required: false + - name: address + description: Server address in IP:PORT format. + default_value: "" + required: false + - name: fcgi_path + description: Status path. + default_value: /status + required: false + - name: timeout + description: HTTP request timeout. + default_value: 1 + required: false + - name: username + description: Username for basic HTTP authentication. + default_value: "" + required: false + - name: password + description: Password for basic HTTP authentication. + default_value: "" + required: false + - name: proxy_url + description: Proxy URL. + default_value: "" + required: false + - name: proxy_username + description: Username for proxy basic HTTP authentication. + default_value: "" + required: false + - name: proxy_password + description: Password for proxy basic HTTP authentication. + default_value: "" + required: false + - name: method + description: HTTP request method. + default_value: GET + required: false + - name: body + description: HTTP request body. + default_value: "" + required: false + - name: headers + description: HTTP request headers. + default_value: "" + required: false + - name: not_follow_redirects + description: Redirect handling policy. Controls whether the client follows redirects. + default_value: false + required: false + - name: tls_skip_verify + description: Server certificate chain and hostname validation policy. Controls whether the client performs this check. + default_value: false + required: false + - name: tls_ca + description: Certification authority that the client uses when verifying the server's certificates. + default_value: "" + required: false + - name: tls_cert + description: Client TLS certificate. + default_value: "" + required: false + - name: tls_key + description: Client TLS key. + default_value: "" + required: false + examples: + folding: + title: Config + enabled: true + list: + - name: HTTP + description: Collecting data from a local instance over HTTP. + config: | + jobs: + - name: local + url: http://localhost/status?full&json + - name: Unix socket + description: Collecting data from a local instance over Unix socket. + config: | + jobs: + - name: local + socket: '/tmp/php-fpm.sock' + - name: TCP socket + description: Collecting data from a local instance over TCP socket. + config: | + jobs: + - name: local + address: 127.0.0.1:9000 + - name: Multi-instance + description: | + > **Note**: When you define multiple jobs, their names must be unique. + + Collecting metrics from local and remote instances. + config: | + jobs: + - name: local + url: http://localhost/status?full&json + + - name: remote + url: http://203.0.113.10/status?full&json + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: global + description: These metrics refer to the entire monitored application. + labels: [] + metrics: + - name: phpfpm.connections + description: Active Connections + unit: connections + chart_type: line + dimensions: + - name: active + - name: max_active + - name: idle + - name: phpfpm.requests + description: Requests + unit: requests/s + chart_type: line + dimensions: + - name: requests + - name: phpfpm.performance + description: Performance + unit: status + chart_type: line + dimensions: + - name: max_children_reached + - name: slow_requests + - name: phpfpm.request_duration + description: Requests Duration Among All Idle Processes + unit: milliseconds + chart_type: line + dimensions: + - name: min + - name: max + - name: avg + - name: phpfpm.request_cpu + description: Last Request CPU Usage Among All Idle Processes + unit: percentage + chart_type: line + dimensions: + - name: min + - name: max + - name: avg + - name: phpfpm.request_mem + description: Last Request Memory Usage Among All Idle Processes + unit: KB + chart_type: line + dimensions: + - name: min + - name: max + - name: avg diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/phpfpm.go b/src/go/collectors/go.d.plugin/modules/phpfpm/phpfpm.go new file mode 100644 index 000000000..bff4d06c1 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/phpfpm.go @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package phpfpm + +import ( + _ "embed" + "errors" + "time" + + "github.com/netdata/netdata/go/go.d.plugin/agent/module" + "github.com/netdata/netdata/go/go.d.plugin/pkg/web" +) + +//go:embed "config_schema.json" +var configSchema string + +func init() { + module.Register("phpfpm", module.Creator{ + JobConfigSchema: configSchema, + Create: func() module.Module { return New() }, + Config: func() any { return &Config{} }, + }) +} + +func New() *Phpfpm { + return &Phpfpm{ + Config: Config{ + HTTP: web.HTTP{ + Request: web.Request{ + URL: "http://127.0.0.1/status?full&json", + }, + Client: web.Client{ + Timeout: web.Duration(time.Second), + }, + }, + FcgiPath: "/status", + }, + } +} + +type Config struct { + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"` + web.HTTP `yaml:",inline" json:""` + Socket string `yaml:"socket,omitempty" json:"socket"` + Address string `yaml:"address,omitempty" json:"address"` + FcgiPath string `yaml:"fcgi_path,omitempty" json:"fcgi_path"` +} + +type Phpfpm struct { + module.Base + Config `yaml:",inline" json:""` + + client client +} + +func (p *Phpfpm) Configuration() any { + return p.Config +} + +func (p *Phpfpm) Init() error { + c, err := p.initClient() + if err != nil { + p.Errorf("init client: %v", err) + return err + } + p.client = c + + return nil +} + +func (p *Phpfpm) Check() error { + mx, err := p.collect() + if err != nil { + p.Error(err) + return err + } + if len(mx) == 0 { + return errors.New("no metrics collected") + } + return nil +} + +func (p *Phpfpm) Charts() *Charts { + return charts.Copy() +} + +func (p *Phpfpm) Collect() map[string]int64 { + mx, err := p.collect() + if err != nil { + p.Error(err) + } + + if len(mx) == 0 { + return nil + } + return mx +} + +func (p *Phpfpm) Cleanup() {} diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/phpfpm_test.go b/src/go/collectors/go.d.plugin/modules/phpfpm/phpfpm_test.go new file mode 100644 index 000000000..8b44c64af --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/phpfpm_test.go @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package phpfpm + +import ( + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/netdata/netdata/go/go.d.plugin/agent/module" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + dataConfigJSON, _ = os.ReadFile("testdata/config.json") + dataConfigYAML, _ = os.ReadFile("testdata/config.yaml") + + dataStatusJSON, _ = os.ReadFile("testdata/status.json") + dataStatusFullJSON, _ = os.ReadFile("testdata/status-full.json") + dataStatusFullNoIdleJSON, _ = os.ReadFile("testdata/status-full-no-idle.json") + dataStatusText, _ = os.ReadFile("testdata/status.txt") + dataStatusFullText, _ = os.ReadFile("testdata/status-full.txt") +) + +func Test_testDataIsValid(t *testing.T) { + for name, data := range map[string][]byte{ + "dataConfigJSON": dataConfigJSON, + "dataConfigYAML": dataConfigYAML, + "dataStatusJSON": dataStatusJSON, + "dataStatusFullJSON": dataStatusFullJSON, + "dataStatusFullNoIdleJSON": dataStatusFullNoIdleJSON, + "dataStatusText": dataStatusText, + "dataStatusFullText": dataStatusFullText, + } { + require.NotNil(t, data, name) + } +} + +func TestPhpfpm_ConfigurationSerialize(t *testing.T) { + module.TestConfigurationSerialize(t, &Phpfpm{}, dataConfigJSON, dataConfigYAML) +} + +func TestPhpfpm_Init(t *testing.T) { + job := New() + + require.NoError(t, job.Init()) + assert.NotNil(t, job.client) +} + +func TestPhpfpm_Check(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(dataStatusText) + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + require.NoError(t, job.Init()) + + assert.NoError(t, job.Check()) +} + +func TestPhpfpm_CheckReturnsFalseOnFailure(t *testing.T) { + job := New() + job.URL = "http://127.0.0.1:38001/us" + require.NoError(t, job.Init()) + + assert.Error(t, job.Check()) +} + +func TestPhpfpm_Charts(t *testing.T) { + job := New() + + assert.NotNil(t, job.Charts()) +} + +func TestPhpfpm_CollectJSON(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(dataStatusJSON) + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + "/?json" + require.NoError(t, job.Init()) + + got := job.Collect() + + want := map[string]int64{ + "active": 1, + "idle": 1, + "maxActive": 1, + "reached": 0, + "requests": 21, + "slow": 0, + } + assert.Equal(t, want, got) +} + +func TestPhpfpm_CollectJSONFull(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(dataStatusFullJSON) + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + "/?json" + require.NoError(t, job.Init()) + + got := job.Collect() + + want := map[string]int64{ + "active": 1, + "idle": 1, + "maxActive": 1, + "reached": 0, + "requests": 22, + "slow": 0, + "minReqCpu": 0, + "maxReqCpu": 10, + "avgReqCpu": 5, + "minReqDur": 0, + "maxReqDur": 919, + "avgReqDur": 459, + "minReqMem": 2093045, + "maxReqMem": 2097152, + "avgReqMem": 2095098, + } + assert.Equal(t, want, got) +} + +func TestPhpfpm_CollectNoIdleProcessesJSONFull(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(dataStatusFullNoIdleJSON) + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + "/?json" + require.NoError(t, job.Init()) + + got := job.Collect() + + want := map[string]int64{ + "active": 1, + "idle": 1, + "maxActive": 1, + "reached": 0, + "requests": 22, + "slow": 0, + } + assert.Equal(t, want, got) +} + +func TestPhpfpm_CollectText(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(dataStatusText) + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + require.NoError(t, job.Init()) + + got := job.Collect() + + want := map[string]int64{ + "active": 1, + "idle": 1, + "maxActive": 1, + "reached": 0, + "requests": 19, + "slow": 0, + } + assert.Equal(t, want, got) +} + +func TestPhpfpm_CollectTextFull(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(dataStatusFullText) + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + require.NoError(t, job.Init()) + + got := job.Collect() + + want := map[string]int64{ + "active": 1, + "idle": 1, + "maxActive": 1, + "reached": 0, + "requests": 20, + "slow": 0, + "minReqCpu": 0, + "maxReqCpu": 10, + "avgReqCpu": 5, + "minReqDur": 0, + "maxReqDur": 536, + "avgReqDur": 268, + "minReqMem": 2093045, + "maxReqMem": 2097152, + "avgReqMem": 2095098, + } + assert.Equal(t, want, got) +} + +func TestPhpfpm_CollectReturnsNothingWhenInvalidData(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte("hello and goodbye\nfrom someone\nfoobar")) + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + require.NoError(t, job.Init()) + + assert.Len(t, job.Collect(), 0) +} + +func TestPhpfpm_CollectReturnsNothingWhenEmptyData(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte{}) + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + require.NoError(t, job.Init()) + + assert.Len(t, job.Collect(), 0) +} + +func TestPhpfpm_CollectReturnsNothingWhenBadStatusCode(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + require.NoError(t, job.Init()) + + assert.Len(t, job.Collect(), 0) +} + +func TestPhpfpm_Cleanup(t *testing.T) { + New().Cleanup() +} diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/config.json b/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/config.json new file mode 100644 index 000000000..458343f74 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/config.json @@ -0,0 +1,23 @@ +{ + "update_every": 123, + "url": "ok", + "body": "ok", + "method": "ok", + "headers": { + "ok": "ok" + }, + "username": "ok", + "password": "ok", + "proxy_url": "ok", + "proxy_username": "ok", + "proxy_password": "ok", + "timeout": 123.123, + "not_follow_redirects": true, + "tls_ca": "ok", + "tls_cert": "ok", + "tls_key": "ok", + "tls_skip_verify": true, + "socket": "ok", + "address": "ok", + "fcgi_path": "ok" +} diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/config.yaml b/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/config.yaml new file mode 100644 index 000000000..6c7bea094 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/config.yaml @@ -0,0 +1,20 @@ +update_every: 123 +url: "ok" +body: "ok" +method: "ok" +headers: + ok: "ok" +username: "ok" +password: "ok" +proxy_url: "ok" +proxy_username: "ok" +proxy_password: "ok" +timeout: 123.123 +not_follow_redirects: yes +tls_ca: "ok" +tls_cert: "ok" +tls_key: "ok" +tls_skip_verify: yes +socket: "ok" +address: "ok" +fcgi_path: "ok" diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/status-full-no-idle.json b/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/status-full-no-idle.json new file mode 100644 index 000000000..e5b63accd --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/status-full-no-idle.json @@ -0,0 +1,63 @@ +{ + "pool": "www", + "process manager": "dynamic", + "start time": 1566371090, + "start since": 1119, + "accepted conn": 22, + "listen queue": 0, + "max listen queue": 0, + "listen queue len": 0, + "idle processes": 1, + "active processes": 1, + "total processes": 2, + "max active processes": 1, + "max children reached": 0, + "slow requests": 0, + "processes": [ + { + "pid": 67858, + "state": "Running", + "start time": 1566371090, + "start since": 1119, + "requests": 11, + "request duration": 834, + "request method": "GET", + "request uri": "/status?json&full", + "content length": 0, + "user": "-", + "script": "-", + "last request cpu": 0, + "last request memory": 0 + }, + { + "pid": 67859, + "state": "Running", + "start time": 1566371090, + "start since": 1119, + "requests": 11, + "request duration": 919, + "request method": "GET", + "request uri": "/status?json", + "content length": 0, + "user": "-", + "script": "-", + "last request cpu": 0, + "last request memory": 2097152 + }, + { + "pid": 67860, + "state": "Running", + "start time": 1566371090, + "start since": 1119, + "requests": 11, + "request duration": 18446744073709551227, + "request method": "GET", + "request uri": "/status?json&full", + "content length": 0, + "user": "-", + "script": "-", + "last request cpu": 10.0, + "last request memory": 2093045 + } + ] +}
\ No newline at end of file diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/status-full.json b/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/status-full.json new file mode 100644 index 000000000..456f6253e --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/status-full.json @@ -0,0 +1,63 @@ +{ + "pool": "www", + "process manager": "dynamic", + "start time": 1566371090, + "start since": 1119, + "accepted conn": 22, + "listen queue": 0, + "max listen queue": 0, + "listen queue len": 0, + "idle processes": 1, + "active processes": 1, + "total processes": 2, + "max active processes": 1, + "max children reached": 0, + "slow requests": 0, + "processes": [ + { + "pid": 67858, + "state": "Running", + "start time": 1566371090, + "start since": 1119, + "requests": 11, + "request duration": 834, + "request method": "GET", + "request uri": "/status?json&full", + "content length": 0, + "user": "-", + "script": "-", + "last request cpu": 0, + "last request memory": 0 + }, + { + "pid": 67859, + "state": "Idle", + "start time": 1566371090, + "start since": 1119, + "requests": 11, + "request duration": 919, + "request method": "GET", + "request uri": "/status?json", + "content length": 0, + "user": "-", + "script": "-", + "last request cpu": 0, + "last request memory": 2097152 + }, + { + "pid": 67860, + "state": "Idle", + "start time": 1566371090, + "start since": 1119, + "requests": 11, + "request duration": 18446744073709551227, + "request method": "GET", + "request uri": "/status?json&full", + "content length": 0, + "user": "-", + "script": "-", + "last request cpu": 10.0, + "last request memory": 2093045 + } + ] +}
\ No newline at end of file diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/status-full.txt b/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/status-full.txt new file mode 100644 index 000000000..a5e90987c --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/status-full.txt @@ -0,0 +1,59 @@ +pool: www +process manager: dynamic +start time: 21/Aug/2019:09:04:50 +0200 +start since: 1079 +accepted conn: 20 +listen queue: 0 +max listen queue: 0 +listen queue len: 0 +idle processes: 1 +active processes: 1 +total processes: 2 +max active processes: 1 +max children reached: 0 +slow requests: 0 + +************************ +pid: 67858 +state: Running +start time: 21/Aug/2019:09:04:50 +0200 +start since: 1079 +requests: 10 +request duration: 697 +request method: GET +request URI: /status?full +content length: 0 +user: - +script: - +last request cpu: 0.00 +last request memory: 0 + +************************ +pid: 67859 +state: Idle +start time: 21/Aug/2019:09:04:50 +0200 +start since: 1079 +requests: 10 +request duration: 536 +request method: GET +request URI: /status +content length: 0 +user: - +script: - +last request cpu: 0.00 +last request memory: 2097152 + +************************ +pid: 67860 +state: Idle +start time: 21/Aug/2019:09:04:50 +0200 +start since: 1079 +requests: 10 +request duration: 18446744073709551227 +request method: GET +request URI: /status?full +content length: 0 +user: - +script: - +last request cpu: 10.00 +last request memory: 2093045
\ No newline at end of file diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/status.json b/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/status.json new file mode 100644 index 000000000..80af3e0bc --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/status.json @@ -0,0 +1,16 @@ +{ + "pool": "www", + "process manager": "dynamic", + "start time": 1566371090, + "start since": 1088, + "accepted conn": 21, + "listen queue": 0, + "max listen queue": 0, + "listen queue len": 0, + "idle processes": 1, + "active processes": 1, + "total processes": 2, + "max active processes": 1, + "max children reached": 0, + "slow requests": 0 +}
\ No newline at end of file diff --git a/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/status.txt b/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/status.txt new file mode 100644 index 000000000..08dc158fb --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/phpfpm/testdata/status.txt @@ -0,0 +1,14 @@ +pool: www +process manager: dynamic +start time: 21/Aug/2019:09:04:50 +0200 +start since: 1066 +accepted conn: 19 +listen queue: 0 +max listen queue: 0 +listen queue len: 0 +idle processes: 1 +active processes: 1 +total processes: 2 +max active processes: 1 +max children reached: 0 +slow requests: 0
\ No newline at end of file |