From f09848204fa5283d21ea43e262ee41aa578e1808 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 26 Aug 2024 10:15:24 +0200 Subject: Merging upstream version 1.47.0. Signed-off-by: Daniel Baumann --- src/go/plugin/go.d/modules/phpdaemon/README.md | 1 + src/go/plugin/go.d/modules/phpdaemon/charts.go | 66 ++++ src/go/plugin/go.d/modules/phpdaemon/client.go | 77 +++++ src/go/plugin/go.d/modules/phpdaemon/collect.go | 19 ++ .../go.d/modules/phpdaemon/config_schema.json | 183 +++++++++++ src/go/plugin/go.d/modules/phpdaemon/init.go | 27 ++ .../modules/phpdaemon/integrations/phpdaemon.md | 333 +++++++++++++++++++++ src/go/plugin/go.d/modules/phpdaemon/metadata.yaml | 276 +++++++++++++++++ src/go/plugin/go.d/modules/phpdaemon/metrics.go | 33 ++ src/go/plugin/go.d/modules/phpdaemon/phpdaemon.go | 114 +++++++ .../go.d/modules/phpdaemon/phpdaemon_test.go | 144 +++++++++ .../go.d/modules/phpdaemon/testdata/config.json | 20 ++ .../go.d/modules/phpdaemon/testdata/config.yaml | 17 ++ .../modules/phpdaemon/testdata/fullstatus.json | 10 + 14 files changed, 1320 insertions(+) create mode 120000 src/go/plugin/go.d/modules/phpdaemon/README.md create mode 100644 src/go/plugin/go.d/modules/phpdaemon/charts.go create mode 100644 src/go/plugin/go.d/modules/phpdaemon/client.go create mode 100644 src/go/plugin/go.d/modules/phpdaemon/collect.go create mode 100644 src/go/plugin/go.d/modules/phpdaemon/config_schema.json create mode 100644 src/go/plugin/go.d/modules/phpdaemon/init.go create mode 100644 src/go/plugin/go.d/modules/phpdaemon/integrations/phpdaemon.md create mode 100644 src/go/plugin/go.d/modules/phpdaemon/metadata.yaml create mode 100644 src/go/plugin/go.d/modules/phpdaemon/metrics.go create mode 100644 src/go/plugin/go.d/modules/phpdaemon/phpdaemon.go create mode 100644 src/go/plugin/go.d/modules/phpdaemon/phpdaemon_test.go create mode 100644 src/go/plugin/go.d/modules/phpdaemon/testdata/config.json create mode 100644 src/go/plugin/go.d/modules/phpdaemon/testdata/config.yaml create mode 100644 src/go/plugin/go.d/modules/phpdaemon/testdata/fullstatus.json (limited to 'src/go/plugin/go.d/modules/phpdaemon') diff --git a/src/go/plugin/go.d/modules/phpdaemon/README.md b/src/go/plugin/go.d/modules/phpdaemon/README.md new file mode 120000 index 00000000..2f2fca9f --- /dev/null +++ b/src/go/plugin/go.d/modules/phpdaemon/README.md @@ -0,0 +1 @@ +integrations/phpdaemon.md \ No newline at end of file diff --git a/src/go/plugin/go.d/modules/phpdaemon/charts.go b/src/go/plugin/go.d/modules/phpdaemon/charts.go new file mode 100644 index 00000000..e96a209b --- /dev/null +++ b/src/go/plugin/go.d/modules/phpdaemon/charts.go @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package phpdaemon + +import "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" + +type ( + // Charts is an alias for module.Charts + Charts = module.Charts + // Chart is an alias for module.Chart + Chart = module.Chart + // Dims is an alias for module.Dims + Dims = module.Dims +) + +var charts = Charts{ + { + ID: "workers", + Title: "Workers", + Units: "workers", + Fam: "workers", + Ctx: "phpdaemon.workers", + Type: module.Stacked, + Dims: Dims{ + {ID: "alive"}, + {ID: "shutdown"}, + }, + }, + { + ID: "alive_workers", + Title: "Alive Workers State", + Units: "workers", + Fam: "workers", + Ctx: "phpdaemon.alive_workers", + Type: module.Stacked, + Dims: Dims{ + {ID: "idle"}, + {ID: "busy"}, + {ID: "reloading"}, + }, + }, + { + ID: "idle_workers", + Title: "Idle Workers State", + Units: "workers", + Fam: "workers", + Ctx: "phpdaemon.idle_workers", + Type: module.Stacked, + Dims: Dims{ + {ID: "preinit"}, + {ID: "init"}, + {ID: "initialized"}, + }, + }, +} + +var uptimeChart = Chart{ + ID: "uptime", + Title: "Uptime", + Units: "seconds", + Fam: "uptime", + Ctx: "phpdaemon.uptime", + Dims: Dims{ + {ID: "uptime", Name: "time"}, + }, +} diff --git a/src/go/plugin/go.d/modules/phpdaemon/client.go b/src/go/plugin/go.d/modules/phpdaemon/client.go new file mode 100644 index 00000000..bc54265d --- /dev/null +++ b/src/go/plugin/go.d/modules/phpdaemon/client.go @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package phpdaemon + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web" +) + +type decodeFunc func(dst interface{}, reader io.Reader) error + +func decodeJson(dst interface{}, reader io.Reader) error { return json.NewDecoder(reader).Decode(dst) } + +func newAPIClient(httpClient *http.Client, request web.Request) *client { + return &client{ + httpClient: httpClient, + request: request, + } +} + +type client struct { + httpClient *http.Client + request web.Request +} + +func (c *client) queryFullStatus() (*FullStatus, error) { + var status FullStatus + err := c.doWithDecode(&status, decodeJson, c.request) + if err != nil { + return nil, err + } + + return &status, nil +} + +func (c *client) doWithDecode(dst interface{}, decode decodeFunc, request web.Request) error { + req, err := web.NewHTTPRequest(request) + if err != nil { + return fmt.Errorf("error on creating http request to %s : %v", request.URL, err) + } + + resp, err := c.doOK(req) + defer closeBody(resp) + if err != nil { + return err + } + + if err = decode(dst, resp.Body); err != nil { + return fmt.Errorf("error on parsing response from %s : %v", req.URL, err) + } + + return nil +} + +func (c *client) doOK(req *http.Request) (*http.Response, error) { + resp, err := c.httpClient.Do(req) + if err != nil { + return resp, fmt.Errorf("error on request : %v", err) + } + + if resp.StatusCode != http.StatusOK { + return resp, fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode) + } + + return resp, err +} + +func closeBody(resp *http.Response) { + if resp != nil && resp.Body != nil { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + } +} diff --git a/src/go/plugin/go.d/modules/phpdaemon/collect.go b/src/go/plugin/go.d/modules/phpdaemon/collect.go new file mode 100644 index 00000000..9be718ea --- /dev/null +++ b/src/go/plugin/go.d/modules/phpdaemon/collect.go @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package phpdaemon + +import "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/stm" + +func (p *PHPDaemon) collect() (map[string]int64, error) { + s, err := p.client.queryFullStatus() + + if err != nil { + return nil, err + } + + // https://github.com/kakserpom/phpdaemon/blob/master/PHPDaemon/Core/Daemon.php + // see getStateOfWorkers() + s.Initialized = s.Idle - (s.Init + s.Preinit) + + return stm.ToMap(s), nil +} diff --git a/src/go/plugin/go.d/modules/phpdaemon/config_schema.json b/src/go/plugin/go.d/modules/phpdaemon/config_schema.json new file mode 100644 index 00000000..a154aaa5 --- /dev/null +++ b/src/go/plugin/go.d/modules/phpdaemon/config_schema.json @@ -0,0 +1,183 @@ +{ + "jsonSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "phpDaemon collector configuration.", + "type": "object", + "properties": { + "update_every": { + "title": "Update every", + "description": "Data collection interval, measured in seconds.", + "type": "integer", + "minimum": 1, + "default": 1 + }, + "url": { + "title": "URL", + "description": "The URL of the phpDaemon status page.", + "type": "string", + "default": "http://127.0.0.1:8509/FullStatus", + "format": "uri" + }, + "timeout": { + "title": "Timeout", + "description": "The timeout in seconds for the HTTP request.", + "type": "number", + "minimum": 0.5, + "default": 1 + }, + "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" + } + }, + "required": [ + "url" + ], + "additionalProperties": false, + "patternProperties": { + "^name$": {} + } + }, + "uiSchema": { + "ui:flavour": "tabs", + "ui:options": { + "tabs": [ + { + "title": "Base", + "fields": [ + "update_every", + "url", + "timeout", + "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" + ] + } + ] + }, + "uiOptions": { + "fullPage": true + }, + "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)." + }, + "username": { + "ui:widget": "password" + }, + "proxy_username": { + "ui:widget": "password" + }, + "password": { + "ui:widget": "password" + }, + "proxy_password": { + "ui:widget": "password" + } + } +} diff --git a/src/go/plugin/go.d/modules/phpdaemon/init.go b/src/go/plugin/go.d/modules/phpdaemon/init.go new file mode 100644 index 00000000..ec9925b7 --- /dev/null +++ b/src/go/plugin/go.d/modules/phpdaemon/init.go @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package phpdaemon + +import ( + "errors" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web" +) + +func (p *PHPDaemon) validateConfig() error { + if p.URL == "" { + return errors.New("url not set") + } + if _, err := web.NewHTTPRequest(p.Request); err != nil { + return err + } + return nil +} + +func (p *PHPDaemon) initClient() (*client, error) { + httpClient, err := web.NewHTTPClient(p.Client) + if err != nil { + return nil, err + } + return newAPIClient(httpClient, p.Request), nil +} diff --git a/src/go/plugin/go.d/modules/phpdaemon/integrations/phpdaemon.md b/src/go/plugin/go.d/modules/phpdaemon/integrations/phpdaemon.md new file mode 100644 index 00000000..11445455 --- /dev/null +++ b/src/go/plugin/go.d/modules/phpdaemon/integrations/phpdaemon.md @@ -0,0 +1,333 @@ + + +# phpDaemon + + + + + +Plugin: go.d.plugin +Module: phpdaemon + + + +## Overview + +This collector monitors phpDaemon 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 phpDaemon instance + +These metrics refer to the entire monitored application. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| phpdaemon.workers | alive, shutdown | workers | +| phpdaemon.alive_workers | idle, busy, reloading | workers | +| phpdaemon.idle_workers | preinit, init, initialized | workers | +| phpdaemon.uptime | time | seconds | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Enable phpDaemon's HTTP server + +Statistics expected to be in JSON format. + +
+phpDaemon configuration + +Instruction from [@METAJIJI](https://github.com/METAJIJI). + +To enable `phpd` statistics on http, you must enable the http server and write an application. +Application is important, because standalone application [ServerStatus.php](https://github.com/kakserpom/phpdaemon/blob/master/PHPDaemon/Applications/ServerStatus.php) provides statistics in html format and unusable for `netdata`. + +```php +// /opt/phpdaemon/conf/phpd.conf + +path /opt/phpdaemon/conf/AppResolver.php; +Pool:HTTPServer { + privileged; + listen '127.0.0.1'; + port 8509; +} +``` + +```php +// /opt/phpdaemon/conf/AppResolver.php + +attrs->server['DOCUMENT_URI'], $m)) { + return $m[1]; + } + } +} + +return new MyAppResolver; +``` + +```php +/opt/phpdaemon/conf/PHPDaemon/Applications/FullStatus.php + +header('Content-Type: application/javascript; charset=utf-8'); + + $stat = Daemon::getStateOfWorkers(); + $stat['uptime'] = time() - Daemon::$startTime; + echo json_encode($stat); + } +} +``` + +
+ + + +### Configuration + +#### File + +The configuration file name for this integration is `go.d/phpdaemon.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/phpdaemon.conf +``` +#### Options + +The following options can be defined globally: update_every, autodetection_retry. + + +
Config options + +| 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:8509/FullStatus | yes | +| timeout | HTTP request timeout. | 2 | 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 | + +
+ +#### Examples + +##### Basic + +A basic example configuration. + +
Config + +```yaml +jobs: + - name: local + url: http://127.0.0.1:8509/FullStatus + +``` +
+ +##### HTTP authentication + +HTTP authentication. + +
Config + +```yaml +jobs: + - name: local + url: http://127.0.0.1:8509/FullStatus + username: username + password: password + +``` +
+ +##### HTTPS with self-signed certificate + +HTTPS with self-signed certificate. + +
Config + +```yaml +jobs: + - name: local + url: http://127.0.0.1:8509/FullStatus + tls_skip_verify: yes + +``` +
+ +##### Multi-instance + +> **Note**: When you define multiple jobs, their names must be unique. + +Collecting metrics from local and remote instances. + + +
Config + +```yaml +jobs: + - name: local + url: http://127.0.0.1:8509/FullStatus + + - name: remote + url: http://192.0.2.1:8509/FullStatus + +``` +
+ + + +## Troubleshooting + +### Debug Mode + +**Important**: Debug mode is not supported for data collection jobs created via the UI using the Dyncfg feature. + +To troubleshoot issues with the `phpdaemon` 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 phpdaemon + ``` + +### Getting Logs + +If you're encountering problems with the `phpdaemon` collector, follow these steps to retrieve logs and identify potential issues: + +- **Run the command** specific to your system (systemd, non-systemd, or Docker container). +- **Examine the output** for any warnings or error messages that might indicate issues. These messages should provide clues about the root cause of the problem. + +#### System with systemd + +Use the following command to view logs generated since the last Netdata service restart: + +```bash +journalctl _SYSTEMD_INVOCATION_ID="$(systemctl show --value --property=InvocationID netdata)" --namespace=netdata --grep phpdaemon +``` + +#### System without systemd + +Locate the collector log file, typically at `/var/log/netdata/collector.log`, and use `grep` to filter for collector's name: + +```bash +grep phpdaemon /var/log/netdata/collector.log +``` + +**Note**: This method shows logs from all restarts. Focus on the **latest entries** for troubleshooting current issues. + +#### Docker Container + +If your Netdata runs in a Docker container named "netdata" (replace if different), use this command: + +```bash +docker logs netdata 2>&1 | grep phpdaemon +``` + + diff --git a/src/go/plugin/go.d/modules/phpdaemon/metadata.yaml b/src/go/plugin/go.d/modules/phpdaemon/metadata.yaml new file mode 100644 index 00000000..bd3ae8e5 --- /dev/null +++ b/src/go/plugin/go.d/modules/phpdaemon/metadata.yaml @@ -0,0 +1,276 @@ +plugin_name: go.d.plugin +modules: + - meta: + id: collector-go.d.plugin-phpdaemon + plugin_name: go.d.plugin + module_name: phpdaemon + monitored_instance: + name: phpDaemon + link: https://github.com/kakserpom/phpdaemon + icon_filename: php.svg + categories: + - data-collection.apm + keywords: + - phpdaemon + - php + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + most_popular: false + overview: + data_collection: + metrics_description: | + This collector monitors phpDaemon 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 phpDaemon's HTTP server + description: | + Statistics expected to be in JSON format. + +
+ phpDaemon configuration + + Instruction from [@METAJIJI](https://github.com/METAJIJI). + + To enable `phpd` statistics on http, you must enable the http server and write an application. + Application is important, because standalone application [ServerStatus.php](https://github.com/kakserpom/phpdaemon/blob/master/PHPDaemon/Applications/ServerStatus.php) provides statistics in html format and unusable for `netdata`. + + ```php + // /opt/phpdaemon/conf/phpd.conf + + path /opt/phpdaemon/conf/AppResolver.php; + Pool:HTTPServer { + privileged; + listen '127.0.0.1'; + port 8509; + } + ``` + + ```php + // /opt/phpdaemon/conf/AppResolver.php + + attrs->server['DOCUMENT_URI'], $m)) { + return $m[1]; + } + } + } + + return new MyAppResolver; + ``` + + ```php + /opt/phpdaemon/conf/PHPDaemon/Applications/FullStatus.php + + header('Content-Type: application/javascript; charset=utf-8'); + + $stat = Daemon::getStateOfWorkers(); + $stat['uptime'] = time() - Daemon::$startTime; + echo json_encode($stat); + } + } + ``` + +
+ configuration: + file: + name: go.d/phpdaemon.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:8509/FullStatus + required: true + - name: timeout + description: HTTP request timeout. + default_value: 2 + 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: Basic + description: A basic example configuration. + config: | + jobs: + - name: local + url: http://127.0.0.1:8509/FullStatus + - name: HTTP authentication + description: HTTP authentication. + config: | + jobs: + - name: local + url: http://127.0.0.1:8509/FullStatus + username: username + password: password + - name: HTTPS with self-signed certificate + description: HTTPS with self-signed certificate. + config: | + jobs: + - name: local + url: http://127.0.0.1:8509/FullStatus + tls_skip_verify: yes + - 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://127.0.0.1:8509/FullStatus + + - name: remote + url: http://192.0.2.1:8509/FullStatus + 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: phpdaemon.workers + description: Workers + unit: workers + chart_type: line + dimensions: + - name: alive + - name: shutdown + - name: phpdaemon.alive_workers + description: Alive Workers State + unit: workers + chart_type: line + dimensions: + - name: idle + - name: busy + - name: reloading + - name: phpdaemon.idle_workers + description: Idle Workers State + unit: workers + chart_type: line + dimensions: + - name: preinit + - name: init + - name: initialized + - name: phpdaemon.uptime + description: Uptime + unit: seconds + chart_type: line + dimensions: + - name: time diff --git a/src/go/plugin/go.d/modules/phpdaemon/metrics.go b/src/go/plugin/go.d/modules/phpdaemon/metrics.go new file mode 100644 index 00000000..1be3c0be --- /dev/null +++ b/src/go/plugin/go.d/modules/phpdaemon/metrics.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package phpdaemon + +// https://github.com/kakserpom/phpdaemon/blob/master/PHPDaemon/Core/Daemon.php +// see getStateOfWorkers() + +// WorkerState represents phpdaemon worker state. +type WorkerState struct { + // Alive is sum of Idle, Busy and Reloading + Alive int64 `stm:"alive"` + Shutdown int64 `stm:"shutdown"` + + // Idle that the worker is not in the middle of execution valuable callback (e.g. request) at this moment of time. + // It does not mean that worker not have any pending operations. + // Idle is sum of Preinit, Init and Initialized. + Idle int64 `stm:"idle"` + // Busy means that the worker is in the middle of execution valuable callback. + Busy int64 `stm:"busy"` + Reloading int64 `stm:"reloading"` + + Preinit int64 `stm:"preinit"` + // Init means that worker is starting right now. + Init int64 `stm:"init"` + // Initialized means that the worker is in Idle state. + Initialized int64 `stm:"initialized"` +} + +// FullStatus FullStatus. +type FullStatus struct { + WorkerState `stm:""` + Uptime *int64 `stm:"uptime"` +} diff --git a/src/go/plugin/go.d/modules/phpdaemon/phpdaemon.go b/src/go/plugin/go.d/modules/phpdaemon/phpdaemon.go new file mode 100644 index 00000000..d9af1059 --- /dev/null +++ b/src/go/plugin/go.d/modules/phpdaemon/phpdaemon.go @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package phpdaemon + +import ( + _ "embed" + "errors" + "time" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module" + "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/web" +) + +//go:embed "config_schema.json" +var configSchema string + +func init() { + module.Register("phpdaemon", module.Creator{ + JobConfigSchema: configSchema, + Create: func() module.Module { return New() }, + Config: func() any { return &Config{} }, + }) +} + +func New() *PHPDaemon { + return &PHPDaemon{ + Config: Config{ + HTTP: web.HTTP{ + Request: web.Request{ + URL: "http://127.0.0.1:8509/FullStatus", + }, + Client: web.Client{ + Timeout: web.Duration(time.Second), + }, + }, + }, + charts: charts.Copy(), + } +} + +type Config struct { + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"` + web.HTTP `yaml:",inline" json:""` +} + +type PHPDaemon struct { + module.Base + Config `yaml:",inline" json:""` + + charts *Charts + + client *client +} + +func (p *PHPDaemon) Configuration() any { + return p.Config +} + +func (p *PHPDaemon) Init() error { + if err := p.validateConfig(); err != nil { + p.Error(err) + return err + } + + c, err := p.initClient() + if err != nil { + p.Error(err) + return err + } + p.client = c + + p.Debugf("using URL %s", p.URL) + p.Debugf("using timeout: %s", p.Timeout) + + return nil +} + +func (p *PHPDaemon) Check() error { + mx, err := p.collect() + if err != nil { + p.Error(err) + return err + } + if len(mx) == 0 { + return errors.New("no metrics collected") + } + + if _, ok := mx["uptime"]; ok { + _ = p.charts.Add(uptimeChart.Copy()) + } + + return nil +} + +func (p *PHPDaemon) Charts() *Charts { + return p.charts +} + +func (p *PHPDaemon) Collect() map[string]int64 { + mx, err := p.collect() + + if err != nil { + p.Error(err) + return nil + } + + return mx +} + +func (p *PHPDaemon) Cleanup() { + if p.client != nil && p.client.httpClient != nil { + p.client.httpClient.CloseIdleConnections() + } +} diff --git a/src/go/plugin/go.d/modules/phpdaemon/phpdaemon_test.go b/src/go/plugin/go.d/modules/phpdaemon/phpdaemon_test.go new file mode 100644 index 00000000..e9e35af6 --- /dev/null +++ b/src/go/plugin/go.d/modules/phpdaemon/phpdaemon_test.go @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package phpdaemon + +import ( + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/netdata/netdata/go/plugins/plugin/go.d/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") + + dataFullStatusMetrics, _ = os.ReadFile("testdata/fullstatus.json") +) + +func Test_testDataIsValid(t *testing.T) { + for name, data := range map[string][]byte{ + "dataConfigJSON": dataConfigJSON, + "dataConfigYAML": dataConfigYAML, + "dataFullStatusMetrics": dataFullStatusMetrics, + } { + require.NotNil(t, data, name) + } +} + +func TestPHPDaemon_ConfigurationSerialize(t *testing.T) { + module.TestConfigurationSerialize(t, &PHPDaemon{}, dataConfigJSON, dataConfigYAML) +} + +func TestPHPDaemon_Init(t *testing.T) { + job := New() + + require.NoError(t, job.Init()) + assert.NotNil(t, job.client) +} + +func TestPHPDaemon_Check(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(dataFullStatusMetrics) + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + require.NoError(t, job.Init()) + assert.NoError(t, job.Check()) +} + +func TestPHPDaemon_CheckNG(t *testing.T) { + job := New() + job.URL = "http://127.0.0.1:38001" + require.NoError(t, job.Init()) + assert.Error(t, job.Check()) +} + +func TestPHPDaemon_Charts(t *testing.T) { + job := New() + + assert.NotNil(t, job.Charts()) + assert.False(t, job.charts.Has(uptimeChart.ID)) + + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(dataFullStatusMetrics) + })) + defer ts.Close() + + job.URL = ts.URL + require.NoError(t, job.Init()) + assert.NoError(t, job.Check()) + assert.True(t, job.charts.Has(uptimeChart.ID)) +} + +func TestPHPDaemon_Cleanup(t *testing.T) { + assert.NotPanics(t, New().Cleanup) +} + +func TestPHPDaemon_Collect(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(dataFullStatusMetrics) + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + require.NoError(t, job.Init()) + assert.NoError(t, job.Check()) + + expected := map[string]int64{ + "alive": 350, + "busy": 200, + "idle": 50, + "init": 20, + "initialized": 10, + "preinit": 20, + "reloading": 100, + "shutdown": 500, + "uptime": 15765, + } + + assert.Equal(t, expected, job.Collect()) + +} + +func TestPHPDaemon_InvalidData(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte("hello and goodbye")) + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + require.NoError(t, job.Init()) + assert.Error(t, job.Check()) +} + +func TestPHPDaemon_404(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.Error(t, job.Check()) +} diff --git a/src/go/plugin/go.d/modules/phpdaemon/testdata/config.json b/src/go/plugin/go.d/modules/phpdaemon/testdata/config.json new file mode 100644 index 00000000..984c3ed6 --- /dev/null +++ b/src/go/plugin/go.d/modules/phpdaemon/testdata/config.json @@ -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": true, + "tls_ca": "ok", + "tls_cert": "ok", + "tls_key": "ok", + "tls_skip_verify": true +} diff --git a/src/go/plugin/go.d/modules/phpdaemon/testdata/config.yaml b/src/go/plugin/go.d/modules/phpdaemon/testdata/config.yaml new file mode 100644 index 00000000..8558b61c --- /dev/null +++ b/src/go/plugin/go.d/modules/phpdaemon/testdata/config.yaml @@ -0,0 +1,17 @@ +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 diff --git a/src/go/plugin/go.d/modules/phpdaemon/testdata/fullstatus.json b/src/go/plugin/go.d/modules/phpdaemon/testdata/fullstatus.json new file mode 100644 index 00000000..b7d2a5e7 --- /dev/null +++ b/src/go/plugin/go.d/modules/phpdaemon/testdata/fullstatus.json @@ -0,0 +1,10 @@ +{ + "idle": 50, + "busy": 200, + "alive": 350, + "shutdown": 500, + "preinit": 20, + "init": 20, + "reloading": 100, + "uptime": 15765 +} \ No newline at end of file -- cgit v1.2.3