diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 12:08:03 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 12:08:18 +0000 |
commit | 5da14042f70711ea5cf66e034699730335462f66 (patch) | |
tree | 0f6354ccac934ed87a2d555f45be4c831cf92f4a /src/go/collectors/go.d.plugin/modules/lighttpd | |
parent | Releasing debian version 1.44.3-2. (diff) | |
download | netdata-5da14042f70711ea5cf66e034699730335462f66.tar.xz netdata-5da14042f70711ea5cf66e034699730335462f66.zip |
Merging upstream version 1.45.3+dfsg.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/go/collectors/go.d.plugin/modules/lighttpd')
15 files changed, 1303 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/lighttpd/README.md b/src/go/collectors/go.d.plugin/modules/lighttpd/README.md new file mode 120000 index 000000000..b0d3613bf --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/lighttpd/README.md @@ -0,0 +1 @@ +integrations/lighttpd.md
\ No newline at end of file diff --git a/src/go/collectors/go.d.plugin/modules/lighttpd/apiclient.go b/src/go/collectors/go.d.plugin/modules/lighttpd/apiclient.go new file mode 100644 index 000000000..2d4bf0fc7 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/lighttpd/apiclient.go @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package lighttpd + +import ( + "bufio" + "fmt" + "io" + "net/http" + "strconv" + "strings" + + "github.com/netdata/netdata/go/go.d.plugin/pkg/web" +) + +const ( + busyWorkers = "BusyWorkers" + idleWorkers = "IdleWorkers" + + busyServers = "BusyServers" + idleServers = "IdleServers" + totalAccesses = "Total Accesses" + totalkBytes = "Total kBytes" + uptime = "Uptime" + scoreBoard = "Scoreboard" +) + +func newAPIClient(client *http.Client, request web.Request) *apiClient { + return &apiClient{httpClient: client, request: request} +} + +type apiClient struct { + httpClient *http.Client + request web.Request +} + +func (a apiClient) getServerStatus() (*serverStatus, error) { + req, err := web.NewHTTPRequest(a.request) + + if err != nil { + return nil, fmt.Errorf("error on creating request : %v", err) + } + + resp, err := a.doRequestOK(req) + + defer closeBody(resp) + + if err != nil { + return nil, err + } + + status, err := parseResponse(resp.Body) + + if err != nil { + return nil, fmt.Errorf("error on parsing response from %s : %v", req.URL, err) + } + + return status, nil +} + +func (a apiClient) doRequestOK(req *http.Request) (*http.Response, error) { + resp, err := a.httpClient.Do(req) + if err != nil { + return nil, 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, nil +} + +func parseResponse(r io.Reader) (*serverStatus, error) { + s := bufio.NewScanner(r) + var status serverStatus + + for s.Scan() { + parts := strings.Split(s.Text(), ":") + if len(parts) != 2 { + continue + } + key, value := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) + + switch key { + default: + case busyWorkers, idleWorkers: + return nil, fmt.Errorf("found '%s', apache data", key) + case busyServers: + status.Servers.Busy = mustParseInt(value) + case idleServers: + status.Servers.Idle = mustParseInt(value) + case totalAccesses: + status.Total.Accesses = mustParseInt(value) + case totalkBytes: + status.Total.KBytes = mustParseInt(value) + case uptime: + status.Uptime = mustParseInt(value) + case scoreBoard: + status.Scoreboard = parseScoreboard(value) + } + } + + return &status, nil +} + +func parseScoreboard(value string) *scoreboard { + // Descriptions from https://blog.serverdensity.com/monitor-lighttpd/ + // + // “.” = Opening the TCP connection (connect) + // “C” = Closing the TCP connection if no other HTTP request will use it (close) + // “E” = hard error + // “k” = Keeping the TCP connection open for more HTTP requests from the same client to avoid the TCP handling overhead (keep-alive) + // “r” = ReadAsMap the content of the HTTP request (read) + // “R” = ReadAsMap the content of the HTTP request (read-POST) + // “W” = Write the HTTP response to the socket (write) + // “h” = Decide action to take with the request (handle-request) + // “q” = Start of HTTP request (request-start) + // “Q” = End of HTTP request (request-end) + // “s” = Start of the HTTP request response (response-start) + // “S” = End of the HTTP request response (response-end) + // “_” Waiting for Connection (NOTE: not sure, copied the description from apache score board) + + var sb scoreboard + for _, s := range strings.Split(value, "") { + switch s { + case "_": + sb.Waiting++ + case ".": + sb.Open++ + case "C": + sb.Close++ + case "E": + sb.HardError++ + case "k": + sb.KeepAlive++ + case "r": + sb.Read++ + case "R": + sb.ReadPost++ + case "W": + sb.Write++ + case "h": + sb.HandleRequest++ + case "q": + sb.RequestStart++ + case "Q": + sb.RequestEnd++ + case "s": + sb.ResponseStart++ + case "S": + sb.ResponseEnd++ + } + } + + return &sb +} + +func mustParseInt(value string) *int64 { + v, err := strconv.ParseInt(value, 10, 64) + if err != nil { + panic(err) + } + return &v +} + +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/collectors/go.d.plugin/modules/lighttpd/charts.go b/src/go/collectors/go.d.plugin/modules/lighttpd/charts.go new file mode 100644 index 000000000..293e57414 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/lighttpd/charts.go @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package lighttpd + +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: "requests", + Title: "Requests", + Units: "requests/s", + Fam: "requests", + Ctx: "lighttpd.requests", + Dims: Dims{ + {ID: "total_accesses", Name: "requests", Algo: module.Incremental}, + }, + }, + { + ID: "net", + Title: "Bandwidth", + Units: "kilobits/s", + Fam: "bandwidth", + Ctx: "lighttpd.net", + Type: module.Area, + Dims: Dims{ + {ID: "total_kBytes", Name: "sent", Algo: module.Incremental, Mul: 8}, + }, + }, + { + ID: "servers", + Title: "Servers", + Units: "servers", + Fam: "servers", + Ctx: "lighttpd.workers", + Type: module.Stacked, + Dims: Dims{ + {ID: "idle_servers", Name: "idle"}, + {ID: "busy_servers", Name: "busy"}, + }, + }, + { + ID: "scoreboard", + Title: "ScoreBoard", + Units: "connections", + Fam: "connections", + Ctx: "lighttpd.scoreboard", + Dims: Dims{ + {ID: "scoreboard_waiting", Name: "waiting"}, + {ID: "scoreboard_open", Name: "open"}, + {ID: "scoreboard_close", Name: "close"}, + {ID: "scoreboard_hard_error", Name: "hard error"}, + {ID: "scoreboard_keepalive", Name: "keepalive"}, + {ID: "scoreboard_read", Name: "read"}, + {ID: "scoreboard_read_post", Name: "read post"}, + {ID: "scoreboard_write", Name: "write"}, + {ID: "scoreboard_handle_request", Name: "handle request"}, + {ID: "scoreboard_request_start", Name: "request start"}, + {ID: "scoreboard_request_end", Name: "request end"}, + {ID: "scoreboard_response_start", Name: "response start"}, + {ID: "scoreboard_response_end", Name: "response end"}, + }, + }, + { + ID: "uptime", + Title: "Uptime", + Units: "seconds", + Fam: "uptime", + Ctx: "lighttpd.uptime", + Dims: Dims{ + {ID: "uptime"}, + }, + }, +} diff --git a/src/go/collectors/go.d.plugin/modules/lighttpd/collect.go b/src/go/collectors/go.d.plugin/modules/lighttpd/collect.go new file mode 100644 index 000000000..d6a0f1b85 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/lighttpd/collect.go @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package lighttpd + +import ( + "fmt" + + "github.com/netdata/netdata/go/go.d.plugin/pkg/stm" +) + +func (l *Lighttpd) collect() (map[string]int64, error) { + status, err := l.apiClient.getServerStatus() + + if err != nil { + return nil, err + } + + mx := stm.ToMap(status) + + if len(mx) == 0 { + return nil, fmt.Errorf("nothing was collected from %s", l.URL) + } + + return mx, nil +} diff --git a/src/go/collectors/go.d.plugin/modules/lighttpd/config_schema.json b/src/go/collectors/go.d.plugin/modules/lighttpd/config_schema.json new file mode 100644 index 000000000..f75910ae8 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/lighttpd/config_schema.json @@ -0,0 +1,163 @@ +{ + "jsonSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Lighttpd 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 Lighttpd machine readable [status page](https://redmine.lighttpd.net/projects/lighttpd/wiki/Mod_status).", + "type": "string", + "default": "http://127.0.0.1/server-status?auto", + "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": "^$|^/" + } + }, + "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 + }, + "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" + } + } +} diff --git a/src/go/collectors/go.d.plugin/modules/lighttpd/init.go b/src/go/collectors/go.d.plugin/modules/lighttpd/init.go new file mode 100644 index 000000000..c0dae5e7b --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/lighttpd/init.go @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package lighttpd + +import ( + "errors" + "fmt" + "strings" + + "github.com/netdata/netdata/go/go.d.plugin/pkg/web" +) + +func (l *Lighttpd) validateConfig() error { + if l.URL == "" { + return errors.New("url not set") + } + if !strings.HasSuffix(l.URL, "?auto") { + return fmt.Errorf("bad URL '%s', should ends in '?auto'", l.URL) + } + return nil +} + +func (l *Lighttpd) initApiClient() (*apiClient, error) { + client, err := web.NewHTTPClient(l.Client) + if err != nil { + return nil, err + } + return newAPIClient(client, l.Request), nil +} diff --git a/src/go/collectors/go.d.plugin/modules/lighttpd/integrations/lighttpd.md b/src/go/collectors/go.d.plugin/modules/lighttpd/integrations/lighttpd.md new file mode 100644 index 000000000..955d6a724 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/lighttpd/integrations/lighttpd.md @@ -0,0 +1,231 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/go/collectors/go.d.plugin/modules/lighttpd/README.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/go/collectors/go.d.plugin/modules/lighttpd/metadata.yaml" +sidebar_label: "Lighttpd" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/Web Servers and Web Proxies" +most_popular: True +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# Lighttpd + + +<img src="https://netdata.cloud/img/lighttpd.svg" width="150"/> + + +Plugin: go.d.plugin +Module: lighttpd + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +This collector monitors the activity and performance of Lighttpd servers, and collects metrics such as the number of connections, workers, requests and more. + + +It sends HTTP requests to the Lighttpd location [server-status](https://redmine.lighttpd.net/projects/lighttpd/wiki/Mod_status), +which is a built-in location that provides metrics about the Lighttpd server. + + +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 + +By default, it detects Lighttpd instances running on localhost that are listening on port 80. +On startup, it tries to collect metrics from: + +- http://localhost/server-status?auto +- http://127.0.0.1/server-status?auto + + +#### 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 Lighttpd instance + +These metrics refer to the entire monitored application. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| lighttpd.requests | requests | requests/s | +| lighttpd.net | sent | kilobits/s | +| lighttpd.workers | idle, busy | servers | +| lighttpd.scoreboard | waiting, open, close, hard_error, keepalive, read, read_post, write, handle_request, request_start, request_end | connections | +| lighttpd.uptime | uptime | seconds | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Enable Lighttpd status support + +To enable status support, see the [official documentation](https://redmine.lighttpd.net/projects/lighttpd/wiki/Mod_status). + + + +### Configuration + +#### File + +The configuration file name for this integration is `go.d/lighttpd.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/netdata-agent/configuration.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config go.d/lighttpd.conf +``` +#### Options + +The following options can be defined globally: update_every, autodetection_retry. + + +<details><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/server-status?auto | yes | +| 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 + +##### Basic + +A basic example configuration. + +```yaml +jobs: + - name: local + url: http://127.0.0.1/server-status?auto + +``` +##### HTTP authentication + +Basic HTTP authentication. + +<details><summary>Config</summary> + +```yaml +jobs: + - name: local + url: http://127.0.0.1/server-status?auto + username: username + password: password + +``` +</details> + +##### HTTPS with self-signed certificate + +Lighttpd with enabled HTTPS and self-signed certificate. + +<details><summary>Config</summary> + +```yaml +jobs: + - name: local + url: https://127.0.0.1/server-status?auto + tls_skip_verify: yes + +``` +</details> + +##### Multi-instance + +> **Note**: When you define multiple jobs, their names must be unique. + +Collecting metrics from local and remote instances. + + +<details><summary>Config</summary> + +```yaml +jobs: + - name: local + url: http://127.0.0.1/server-status?auto + + - name: remote + url: http://192.0.2.1/server-status?auto + +``` +</details> + + + +## Troubleshooting + +### Debug Mode + +To troubleshoot issues with the `lighttpd` 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 lighttpd + ``` + + diff --git a/src/go/collectors/go.d.plugin/modules/lighttpd/lighttpd.go b/src/go/collectors/go.d.plugin/modules/lighttpd/lighttpd.go new file mode 100644 index 000000000..7a7ef93ca --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/lighttpd/lighttpd.go @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package lighttpd + +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("lighttpd", module.Creator{ + JobConfigSchema: configSchema, + Create: func() module.Module { return New() }, + }) +} + +func New() *Lighttpd { + return &Lighttpd{Config: Config{ + HTTP: web.HTTP{ + Request: web.Request{ + URL: "http://127.0.0.1/server-status?auto", + }, + Client: web.Client{ + Timeout: web.Duration(time.Second * 2), + }, + }, + }} +} + +type Config struct { + web.HTTP `yaml:",inline" json:""` + UpdateEvery int `yaml:"update_every" json:"update_every"` +} + +type Lighttpd struct { + module.Base + Config `yaml:",inline" json:""` + + apiClient *apiClient +} + +func (l *Lighttpd) Configuration() any { + return l.Config +} + +func (l *Lighttpd) Init() error { + if err := l.validateConfig(); err != nil { + l.Errorf("config validation: %v", err) + return err + } + + client, err := l.initApiClient() + if err != nil { + l.Error(err) + return err + } + l.apiClient = client + + l.Debugf("using URL %s", l.URL) + l.Debugf("using timeout: %s", l.Timeout.Duration()) + + return nil +} + +func (l *Lighttpd) Check() error { + mx, err := l.collect() + if err != nil { + l.Error(err) + return err + } + if len(mx) == 0 { + return errors.New("no metrics collected") + } + return nil +} + +func (l *Lighttpd) Charts() *Charts { + return charts.Copy() +} + +func (l *Lighttpd) Collect() map[string]int64 { + mx, err := l.collect() + + if err != nil { + l.Error(err) + return nil + } + + return mx +} + +func (l *Lighttpd) Cleanup() { + if l.apiClient != nil && l.apiClient.httpClient != nil { + l.apiClient.httpClient.CloseIdleConnections() + } +} diff --git a/src/go/collectors/go.d.plugin/modules/lighttpd/lighttpd_test.go b/src/go/collectors/go.d.plugin/modules/lighttpd/lighttpd_test.go new file mode 100644 index 000000000..8a015c85b --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/lighttpd/lighttpd_test.go @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package lighttpd + +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") + + dataStatusMetrics, _ = os.ReadFile("testdata/status.txt") + dataApacheStatusMetrics, _ = os.ReadFile("testdata/apache-status.txt") +) + +func Test_testDataIsValid(t *testing.T) { + for name, data := range map[string][]byte{ + "dataConfigJSON": dataConfigJSON, + "dataConfigYAML": dataConfigYAML, + } { + require.NotNil(t, data, name) + } +} + +func TestLighttpd_ConfigurationSerialize(t *testing.T) { + module.TestConfigurationSerialize(t, &Lighttpd{}, dataConfigJSON, dataConfigYAML) +} + +func TestLighttpd_Cleanup(t *testing.T) { New().Cleanup() } + +func TestLighttpd_Init(t *testing.T) { + job := New() + + require.NoError(t, job.Init()) + assert.NotNil(t, job.apiClient) +} + +func TestLighttpd_InitNG(t *testing.T) { + job := New() + + job.URL = "" + assert.Error(t, job.Init()) +} + +func TestLighttpd_Check(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(dataStatusMetrics) + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + "/server-status?auto" + require.NoError(t, job.Init()) + assert.NoError(t, job.Check()) +} + +func TestLighttpd_CheckNG(t *testing.T) { + job := New() + + job.URL = "http://127.0.0.1:38001/server-status?auto" + require.NoError(t, job.Init()) + assert.Error(t, job.Check()) +} + +func TestLighttpd_Charts(t *testing.T) { assert.NotNil(t, New().Charts()) } + +func TestLighttpd_Collect(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(dataStatusMetrics) + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + "/server-status?auto" + require.NoError(t, job.Init()) + require.NoError(t, job.Check()) + + expected := map[string]int64{ + "scoreboard_waiting": 125, + "scoreboard_request_end": 0, + "busy_servers": 3, + "scoreboard_keepalive": 1, + "scoreboard_read": 1, + "scoreboard_request_start": 0, + "scoreboard_response_start": 0, + "scoreboard_close": 0, + "scoreboard_open": 0, + "scoreboard_hard_error": 0, + "scoreboard_handle_request": 1, + "idle_servers": 125, + "total_kBytes": 4, + "uptime": 11, + "scoreboard_read_post": 0, + "scoreboard_write": 0, + "scoreboard_response_end": 0, + "total_accesses": 12, + } + + assert.Equal(t, expected, job.Collect()) +} + +func TestLighttpd_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 + "/server-status?auto" + require.NoError(t, job.Init()) + assert.Error(t, job.Check()) +} + +func TestLighttpd_ApacheData(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(dataApacheStatusMetrics) + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + "/server-status?auto" + require.NoError(t, job.Init()) + require.Error(t, job.Check()) +} + +func TestLighttpd_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 + "/server-status?auto" + require.NoError(t, job.Init()) + assert.Error(t, job.Check()) +} diff --git a/src/go/collectors/go.d.plugin/modules/lighttpd/metadata.yaml b/src/go/collectors/go.d.plugin/modules/lighttpd/metadata.yaml new file mode 100644 index 000000000..a90ac05ed --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/lighttpd/metadata.yaml @@ -0,0 +1,231 @@ +plugin_name: go.d.plugin +modules: + - meta: + id: collector-go.d.plugin-lighttpd + plugin_name: go.d.plugin + module_name: lighttpd + monitored_instance: + name: Lighttpd + link: https://www.lighttpd.net/ + icon_filename: lighttpd.svg + categories: + - data-collection.web-servers-and-web-proxies + keywords: + - webserver + related_resources: + integrations: + list: + - plugin_name: go.d.plugin + module_name: weblog + - plugin_name: go.d.plugin + module_name: httpcheck + - plugin_name: apps.plugin + module_name: apps + info_provided_to_referring_integrations: + description: "" + most_popular: true + overview: + data_collection: + metrics_description: | + This collector monitors the activity and performance of Lighttpd servers, and collects metrics such as the number of connections, workers, requests and more. + method_description: | + It sends HTTP requests to the Lighttpd location [server-status](https://redmine.lighttpd.net/projects/lighttpd/wiki/Mod_status), + which is a built-in location that provides metrics about the Lighttpd server. + supported_platforms: + include: [] + exclude: [] + multi_instance: true + additional_permissions: + description: "" + default_behavior: + auto_detection: + description: | + By default, it detects Lighttpd instances running on localhost that are listening on port 80. + On startup, it tries to collect metrics from: + + - http://localhost/server-status?auto + - http://127.0.0.1/server-status?auto + limits: + description: "" + performance_impact: + description: "" + setup: + prerequisites: + list: + - title: Enable Lighttpd status support + description: | + To enable status support, see the [official documentation](https://redmine.lighttpd.net/projects/lighttpd/wiki/Mod_status). + configuration: + file: + name: go.d/lighttpd.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/server-status?auto + required: true + - 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: no + required: false + - name: tls_skip_verify + description: Server certificate chain and hostname validation policy. Controls whether the client performs this check. + default_value: no + 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 + folding: + enabled: false + description: A basic example configuration. + config: | + jobs: + - name: local + url: http://127.0.0.1/server-status?auto + - name: HTTP authentication + description: Basic HTTP authentication. + config: | + jobs: + - name: local + url: http://127.0.0.1/server-status?auto + username: username + password: password + - name: HTTPS with self-signed certificate + description: Lighttpd with enabled HTTPS and self-signed certificate. + config: | + jobs: + - name: local + url: https://127.0.0.1/server-status?auto + 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/server-status?auto + + - name: remote + url: http://192.0.2.1/server-status?auto + 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: lighttpd.requests + description: Requests + unit: requests/s + chart_type: line + dimensions: + - name: requests + - name: lighttpd.net + description: Bandwidth + unit: kilobits/s + chart_type: area + dimensions: + - name: sent + - name: lighttpd.workers + description: Servers + unit: servers + chart_type: stacked + dimensions: + - name: idle + - name: busy + - name: lighttpd.scoreboard + description: ScoreBoard + unit: connections + chart_type: line + dimensions: + - name: waiting + - name: open + - name: close + - name: hard_error + - name: keepalive + - name: read + - name: read_post + - name: write + - name: handle_request + - name: request_start + - name: request_end + - name: lighttpd.uptime + description: Uptime + unit: seconds + chart_type: line + dimensions: + - name: uptime diff --git a/src/go/collectors/go.d.plugin/modules/lighttpd/metrics.go b/src/go/collectors/go.d.plugin/modules/lighttpd/metrics.go new file mode 100644 index 000000000..6c39d2d06 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/lighttpd/metrics.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package lighttpd + +type ( + serverStatus struct { + Total struct { + Accesses *int64 `stm:"accesses"` + KBytes *int64 `stm:"kBytes"` + } `stm:"total"` + Servers struct { + Busy *int64 `stm:"busy_servers"` + Idle *int64 `stm:"idle_servers"` + } `stm:""` + Uptime *int64 `stm:"uptime"` + Scoreboard *scoreboard `stm:"scoreboard"` + } + scoreboard struct { + Waiting int64 `stm:"waiting"` + Open int64 `stm:"open"` + Close int64 `stm:"close"` + HardError int64 `stm:"hard_error"` + KeepAlive int64 `stm:"keepalive"` + Read int64 `stm:"read"` + ReadPost int64 `stm:"read_post"` + Write int64 `stm:"write"` + HandleRequest int64 `stm:"handle_request"` + RequestStart int64 `stm:"request_start"` + RequestEnd int64 `stm:"request_end"` + ResponseStart int64 `stm:"response_start"` + ResponseEnd int64 `stm:"response_end"` + } +) diff --git a/src/go/collectors/go.d.plugin/modules/lighttpd/testdata/apache-status.txt b/src/go/collectors/go.d.plugin/modules/lighttpd/testdata/apache-status.txt new file mode 100644 index 000000000..136b69363 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/lighttpd/testdata/apache-status.txt @@ -0,0 +1,39 @@ +127.0.0.1 +ServerVersion: Apache/2.4.37 (Unix) +ServerMPM: event +Server Built: Oct 23 2018 18:27:46 +CurrentTime: Sunday, 13-Jan-2019 20:39:30 MSK +RestartTime: Sunday, 13-Jan-2019 20:35:13 MSK +ParentServerConfigGeneration: 1 +ParentServerMPMGeneration: 0 +ServerUptimeSeconds: 256 +ServerUptime: 4 minutes 16 seconds +Load1: 1.02 +Load5: 1.30 +Load15: 1.41 +Total Accesses: 9 +Total kBytes: 12 +Total Duration: 1 +CPUUser: 0 +CPUSystem: .01 +CPUChildrenUser: 0 +CPUChildrenSystem: 0 +CPULoad: .00390625 +Uptime: 256 +ReqPerSec: .0351563 +BytesPerSec: 48 +BytesPerReq: 1365.33 +DurationPerReq: .111111 +BusyWorkers: 1 +IdleWorkers: 99 +Processes: 4 +Stopping: 0 +BusyWorkers: 1 +IdleWorkers: 99 +ConnsTotal: 0 +ConnsAsyncWriting: 0 +ConnsAsyncKeepAlive: 0 +ConnsAsyncClosing: 0 +Scoreboard: ____________________________________________________________W_______________________________________............................................................................................................................................................................................................................................................................................................ +Using GnuTLS version: 3.6.5 +Built against GnuTLS version: 3.5.19
\ No newline at end of file diff --git a/src/go/collectors/go.d.plugin/modules/lighttpd/testdata/config.json b/src/go/collectors/go.d.plugin/modules/lighttpd/testdata/config.json new file mode 100644 index 000000000..984c3ed6e --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/lighttpd/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/collectors/go.d.plugin/modules/lighttpd/testdata/config.yaml b/src/go/collectors/go.d.plugin/modules/lighttpd/testdata/config.yaml new file mode 100644 index 000000000..8558b61cc --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/lighttpd/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/collectors/go.d.plugin/modules/lighttpd/testdata/status.txt b/src/go/collectors/go.d.plugin/modules/lighttpd/testdata/status.txt new file mode 100644 index 000000000..07d8e06e8 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/lighttpd/testdata/status.txt @@ -0,0 +1,6 @@ +Total Accesses: 12 +Total kBytes: 4 +Uptime: 11 +BusyServers: 3 +IdleServers: 125 +Scoreboard: khr_____________________________________________________________________________________________________________________________
\ No newline at end of file |