diff options
author | Austin S. Hemmelgarn <austin@netdata.cloud> | 2024-02-13 06:56:20 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-13 06:56:20 -0500 |
commit | 3a29b66132f561c910d827e8c7ae82997f7c1f30 (patch) | |
tree | a9306156631b6b188de8877f7c1dbdbe8b067804 /src/go/collectors/go.d.plugin/modules/haproxy | |
parent | 57eec3da0e51baa400037ccc4b547cb839ab6ffa (diff) |
Include Go plugin sources in main repository. (#16997)
* Include Go plugin sources in main repository.
* Fix CI issues.
* Rename source tree.
Diffstat (limited to 'src/go/collectors/go.d.plugin/modules/haproxy')
10 files changed, 1567 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/haproxy/README.md b/src/go/collectors/go.d.plugin/modules/haproxy/README.md new file mode 120000 index 0000000000..2f52cf8463 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/haproxy/README.md @@ -0,0 +1 @@ +integrations/haproxy.md
\ No newline at end of file diff --git a/src/go/collectors/go.d.plugin/modules/haproxy/charts.go b/src/go/collectors/go.d.plugin/modules/haproxy/charts.go new file mode 100644 index 0000000000..8c5f6bef64 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/haproxy/charts.go @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package haproxy + +import ( + "fmt" + + "github.com/netdata/go.d.plugin/agent/module" +) + +var charts = module.Charts{ + chartBackendCurrentSessions.Copy(), + chartBackendSessions.Copy(), + + chartBackendResponseTimeAverage.Copy(), + + chartBackendQueueTimeAverage.Copy(), + chartBackendCurrentQueue.Copy(), +} + +var ( + chartBackendCurrentSessions = module.Chart{ + ID: "backend_current_sessions", + Title: "Current number of active sessions", + Units: "sessions", + Fam: "backend sessions", + Ctx: "haproxy.backend_current_sessions", + } + chartBackendSessions = module.Chart{ + ID: "backend_sessions", + Title: "Sessions rate", + Units: "sessions/s", + Fam: "backend sessions", + Ctx: "haproxy.backend_sessions", + } +) + +var ( + chartBackendResponseTimeAverage = module.Chart{ + ID: "backend_response_time_average", + Title: "Average response time for last 1024 successful connections", + Units: "milliseconds", + Fam: "backend responses", + Ctx: "haproxy.backend_response_time_average", + } + chartTemplateBackendHTTPResponses = module.Chart{ + ID: "backend_http_responses_proxy_%s", + Title: "HTTP responses by code class for <code>%s</code> proxy", + Units: "responses/s", + Fam: "backend responses", + Ctx: "haproxy.backend_http_responses", + Type: module.Stacked, + Dims: module.Dims{ + {ID: "haproxy_backend_http_responses_1xx_proxy_%s", Name: "1xx", Algo: module.Incremental}, + {ID: "haproxy_backend_http_responses_2xx_proxy_%s", Name: "2xx", Algo: module.Incremental}, + {ID: "haproxy_backend_http_responses_3xx_proxy_%s", Name: "3xx", Algo: module.Incremental}, + {ID: "haproxy_backend_http_responses_4xx_proxy_%s", Name: "4xx", Algo: module.Incremental}, + {ID: "haproxy_backend_http_responses_5xx_proxy_%s", Name: "5xx", Algo: module.Incremental}, + {ID: "haproxy_backend_http_responses_other_proxy_%s", Name: "other", Algo: module.Incremental}, + }, + } +) + +var ( + chartBackendQueueTimeAverage = module.Chart{ + ID: "backend_queue_time_average", + Title: "Average queue time for last 1024 successful connections", + Units: "milliseconds", + Fam: "backend queue", + Ctx: "haproxy.backend_queue_time_average", + } + chartBackendCurrentQueue = module.Chart{ + ID: "backend_current_queue", + Title: "Current number of queued requests", + Units: "requests", + Fam: "backend queue", + Ctx: "haproxy.backend_current_queue", + } +) + +var ( + chartTemplateBackendNetworkIO = module.Chart{ + ID: "backend_network_io_proxy_%s", + Title: "Network traffic for <code>%s</code> proxy", + Units: "bytes/s", + Fam: "backend network", + Ctx: "haproxy.backend_network_io", + Type: module.Area, + Dims: module.Dims{ + {ID: "haproxy_backend_bytes_in_proxy_%s", Name: "in", Algo: module.Incremental}, + {ID: "haproxy_backend_bytes_out_proxy_%s", Name: "out", Algo: module.Incremental, Mul: -1}, + }, + } +) + +func newChartBackendHTTPResponses(proxy string) *module.Chart { + return newBackendChartFromTemplate(chartTemplateBackendHTTPResponses, proxy) +} + +func newChartBackendNetworkIO(proxy string) *module.Chart { + return newBackendChartFromTemplate(chartTemplateBackendNetworkIO, proxy) +} + +func newBackendChartFromTemplate(tpl module.Chart, proxy string) *module.Chart { + c := tpl.Copy() + c.ID = fmt.Sprintf(c.ID, proxy) + c.Title = fmt.Sprintf(c.Title, proxy) + for _, d := range c.Dims { + d.ID = fmt.Sprintf(d.ID, proxy) + } + return c +} diff --git a/src/go/collectors/go.d.plugin/modules/haproxy/collect.go b/src/go/collectors/go.d.plugin/modules/haproxy/collect.go new file mode 100644 index 0000000000..10d24a088b --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/haproxy/collect.go @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package haproxy + +import ( + "errors" + "strings" + + "github.com/netdata/go.d.plugin/agent/module" + "github.com/netdata/go.d.plugin/pkg/prometheus" +) + +const ( + metricBackendSessionsTotal = "haproxy_backend_sessions_total" + metricBackendCurrentSessions = "haproxy_backend_current_sessions" + metricBackendHTTPResponsesTotal = "haproxy_backend_http_responses_total" + metricBackendResponseTimeAverageSeconds = "haproxy_backend_response_time_average_seconds" + metricBackendCurrentQueue = "haproxy_backend_current_queue" + metricBackendQueueTimeAverageSeconds = "haproxy_backend_queue_time_average_seconds" + metricBackendBytesInTotal = "haproxy_backend_bytes_in_total" + metricBackendBytesOutTotal = "haproxy_backend_bytes_out_total" +) + +func isHaproxyMetrics(pms prometheus.Series) bool { + for _, pm := range pms { + if strings.HasPrefix(pm.Name(), "haproxy_") { + return true + } + } + return false +} + +func (h *Haproxy) collect() (map[string]int64, error) { + pms, err := h.prom.ScrapeSeries() + if err != nil { + return nil, err + } + + if h.validateMetrics && !isHaproxyMetrics(pms) { + return nil, errors.New("unexpected metrics (not HAProxy)") + } + h.validateMetrics = false + + mx := make(map[string]int64) + for _, pm := range pms { + proxy := pm.Labels.Get("proxy") + if proxy == "" { + continue + } + + if !h.proxies[proxy] { + h.proxies[proxy] = true + h.addProxyToCharts(proxy) + } + + mx[dimID(pm)] = int64(pm.Value * multiplier(pm)) + } + + return mx, nil +} + +func (h *Haproxy) addProxyToCharts(proxy string) { + h.addDimToChart(chartBackendCurrentSessions.ID, &module.Dim{ + ID: proxyDimID(metricBackendCurrentSessions, proxy), + Name: proxy, + }) + h.addDimToChart(chartBackendSessions.ID, &module.Dim{ + ID: proxyDimID(metricBackendSessionsTotal, proxy), + Name: proxy, + Algo: module.Incremental, + }) + + h.addDimToChart(chartBackendResponseTimeAverage.ID, &module.Dim{ + ID: proxyDimID(metricBackendResponseTimeAverageSeconds, proxy), + Name: proxy, + }) + if err := h.Charts().Add(newChartBackendHTTPResponses(proxy)); err != nil { + h.Warning(err) + } + + h.addDimToChart(chartBackendCurrentQueue.ID, &module.Dim{ + ID: proxyDimID(metricBackendCurrentQueue, proxy), + Name: proxy, + }) + h.addDimToChart(chartBackendQueueTimeAverage.ID, &module.Dim{ + ID: proxyDimID(metricBackendQueueTimeAverageSeconds, proxy), + Name: proxy, + }) + + if err := h.Charts().Add(newChartBackendNetworkIO(proxy)); err != nil { + h.Warning(err) + } +} + +func (h *Haproxy) addDimToChart(chartID string, dim *module.Dim) { + chart := h.Charts().Get(chartID) + if chart == nil { + h.Warningf("error on adding '%s' dimension: can not find '%s' chart", dim.ID, chartID) + return + } + if err := chart.AddDim(dim); err != nil { + h.Warning(err) + return + } + chart.MarkNotCreated() +} + +func multiplier(pm prometheus.SeriesSample) float64 { + switch pm.Name() { + case metricBackendResponseTimeAverageSeconds, + metricBackendQueueTimeAverageSeconds: + // to milliseconds + return 1000 + } + return 1 +} + +func dimID(pm prometheus.SeriesSample) string { + proxy := pm.Labels.Get("proxy") + if proxy == "" { + return "" + } + + name := cleanMetricName(pm.Name()) + if pm.Name() == metricBackendHTTPResponsesTotal { + name += "_" + pm.Labels.Get("code") + } + return proxyDimID(name, proxy) +} + +func proxyDimID(metric, proxy string) string { + return cleanMetricName(metric) + "_proxy_" + proxy +} + +func cleanMetricName(name string) string { + if strings.HasSuffix(name, "_total") { + return name[:len(name)-6] + } + if strings.HasSuffix(name, "_seconds") { + return name[:len(name)-8] + } + return name +} diff --git a/src/go/collectors/go.d.plugin/modules/haproxy/config_schema.json b/src/go/collectors/go.d.plugin/modules/haproxy/config_schema.json new file mode 100644 index 0000000000..9fa8cd1114 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/haproxy/config_schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "go.d/haproxy job configuration schema.", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timeout": { + "type": [ + "string", + "integer" + ] + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "proxy_username": { + "type": "string" + }, + "proxy_password": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "not_follow_redirects": { + "type": "boolean" + }, + "tls_ca": { + "type": "string" + }, + "tls_cert": { + "type": "string" + }, + "tls_key": { + "type": "string" + }, + "insecure_skip_verify": { + "type": "boolean" + } + }, + "required": [ + "name", + "url" + ] +} diff --git a/src/go/collectors/go.d.plugin/modules/haproxy/haproxy.go b/src/go/collectors/go.d.plugin/modules/haproxy/haproxy.go new file mode 100644 index 0000000000..ffc936711d --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/haproxy/haproxy.go @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package haproxy + +import ( + _ "embed" + "time" + + "github.com/netdata/go.d.plugin/agent/module" + "github.com/netdata/go.d.plugin/pkg/prometheus" + "github.com/netdata/go.d.plugin/pkg/web" +) + +//go:embed "config_schema.json" +var configSchema string + +func init() { + module.Register("haproxy", module.Creator{ + JobConfigSchema: configSchema, + Create: func() module.Module { return New() }, + }) +} + +func New() *Haproxy { + return &Haproxy{ + Config: Config{ + HTTP: web.HTTP{ + Request: web.Request{ + URL: "http://127.0.0.1:8404/metrics", + }, + Client: web.Client{ + Timeout: web.Duration{Duration: time.Second}, + }, + }, + }, + + charts: charts.Copy(), + proxies: make(map[string]bool), + validateMetrics: true, + } +} + +type Config struct { + web.HTTP `yaml:",inline"` +} + +type Haproxy struct { + module.Base + Config `yaml:",inline"` + + charts *module.Charts + + prom prometheus.Prometheus + validateMetrics bool + proxies map[string]bool +} + +func (h *Haproxy) Init() bool { + if err := h.validateConfig(); err != nil { + h.Errorf("config validation: %v", err) + return false + } + + prom, err := h.initPrometheusClient() + if err != nil { + h.Errorf("prometheus client initialization: %v", err) + return false + } + h.prom = prom + + return true +} + +func (h *Haproxy) Check() bool { + return len(h.Collect()) > 0 +} + +func (h *Haproxy) Charts() *module.Charts { + return h.charts +} + +func (h *Haproxy) Collect() map[string]int64 { + ms, err := h.collect() + if err != nil { + h.Error(err) + return nil + } + + if len(ms) == 0 { + return nil + } + return ms +} + +func (Haproxy) Cleanup() { + // TODO: close http idle connections +} diff --git a/src/go/collectors/go.d.plugin/modules/haproxy/haproxy_test.go b/src/go/collectors/go.d.plugin/modules/haproxy/haproxy_test.go new file mode 100644 index 0000000000..c881c19f37 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/haproxy/haproxy_test.go @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package haproxy + +import ( + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/netdata/go.d.plugin/pkg/tlscfg" + "github.com/netdata/go.d.plugin/pkg/web" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + v2310Metrics, _ = os.ReadFile("testdata/v2.3.10/metrics.txt") +) + +func Test_Testdata(t *testing.T) { + for name, data := range map[string][]byte{ + "v2310Metrics": v2310Metrics, + } { + require.NotNilf(t, data, name) + } +} + +func TestNew(t *testing.T) { + assert.IsType(t, (*Haproxy)(nil), New()) +} + +func TestHaproxy_Init(t *testing.T) { + tests := map[string]struct { + config Config + wantFail bool + }{ + "success on default config": { + config: New().Config, + }, + "fails on unset 'url'": { + wantFail: true, + config: Config{HTTP: web.HTTP{ + Request: web.Request{}, + }}, + }, + "fails on invalid TLSCA": { + wantFail: true, + config: Config{ + HTTP: web.HTTP{ + Client: web.Client{ + TLSConfig: tlscfg.TLSConfig{TLSCA: "testdata/tls"}, + }, + }}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + rdb := New() + rdb.Config = test.config + + if test.wantFail { + assert.False(t, rdb.Init()) + } else { + assert.True(t, rdb.Init()) + } + }) + } +} + +func TestHaproxy_Charts(t *testing.T) { + assert.NotNil(t, New().Charts()) +} + +func TestHaproxy_Cleanup(t *testing.T) { + assert.NotPanics(t, New().Cleanup) +} + +func TestHaproxy_Check(t *testing.T) { + tests := map[string]struct { + wantFail bool + prepare func(t *testing.T) (h *Haproxy, cleanup func()) + }{ + "success on valid response v2.3.1": { + wantFail: false, + prepare: prepareCaseHaproxyV231Metrics, + }, + "fails on response with unexpected metrics (not HAProxy)": { + wantFail: true, + prepare: prepareCaseNotHaproxyMetrics, + }, + "fails on 404 response": { + wantFail: true, + prepare: prepareCase404Response, + }, + "fails on connection refused": { + wantFail: true, + prepare: prepareCaseConnectionRefused, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + h, cleanup := test.prepare(t) + defer cleanup() + + if test.wantFail { + assert.False(t, h.Check()) + } else { + assert.True(t, h.Check()) + } + }) + } +} + +func TestHaproxy_Collect(t *testing.T) { + tests := map[string]struct { + prepare func(t *testing.T) (h *Haproxy, cleanup func()) + wantCollected map[string]int64 + }{ + "success on valid response v2.3.1": { + prepare: prepareCaseHaproxyV231Metrics, + wantCollected: map[string]int64{ + "haproxy_backend_bytes_in_proxy_proxy1": 21057046294, + "haproxy_backend_bytes_in_proxy_proxy2": 2493759083896, + "haproxy_backend_bytes_out_proxy_proxy1": 41352782609, + "haproxy_backend_bytes_out_proxy_proxy2": 5131407558, + "haproxy_backend_current_queue_proxy_proxy1": 1, + "haproxy_backend_current_queue_proxy_proxy2": 1, + "haproxy_backend_current_sessions_proxy_proxy1": 1, + "haproxy_backend_current_sessions_proxy_proxy2": 1322, + "haproxy_backend_http_responses_1xx_proxy_proxy1": 1, + "haproxy_backend_http_responses_1xx_proxy_proxy2": 4130401, + "haproxy_backend_http_responses_2xx_proxy_proxy1": 21338013, + "haproxy_backend_http_responses_2xx_proxy_proxy2": 1, + "haproxy_backend_http_responses_3xx_proxy_proxy1": 10004, + "haproxy_backend_http_responses_3xx_proxy_proxy2": 1, + "haproxy_backend_http_responses_4xx_proxy_proxy1": 10170758, + "haproxy_backend_http_responses_4xx_proxy_proxy2": 1, + "haproxy_backend_http_responses_5xx_proxy_proxy1": 3075, + "haproxy_backend_http_responses_5xx_proxy_proxy2": 1, + "haproxy_backend_http_responses_other_proxy_proxy1": 5657, + "haproxy_backend_http_responses_other_proxy_proxy2": 1, + "haproxy_backend_queue_time_average_proxy_proxy1": 0, + "haproxy_backend_queue_time_average_proxy_proxy2": 0, + "haproxy_backend_response_time_average_proxy_proxy1": 52, + "haproxy_backend_response_time_average_proxy_proxy2": 1, + "haproxy_backend_sessions_proxy_proxy1": 31527507, + "haproxy_backend_sessions_proxy_proxy2": 4131723, + }, + }, + "fails on response with unexpected metrics (not HAProxy)": { + prepare: prepareCaseNotHaproxyMetrics, + }, + "fails on 404 response": { + prepare: prepareCase404Response, + }, + "fails on connection refused": { + prepare: prepareCaseConnectionRefused, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + h, cleanup := test.prepare(t) + defer cleanup() + + ms := h.Collect() + + assert.Equal(t, test.wantCollected, ms) + if len(test.wantCollected) > 0 { + ensureCollectedHasAllChartsDimsVarsIDs(t, h, ms) + } + }) + } +} + +func prepareCaseHaproxyV231Metrics(t *testing.T) (*Haproxy, func()) { + t.Helper() + srv := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(v2310Metrics) + })) + h := New() + h.URL = srv.URL + require.True(t, h.Init()) + + return h, srv.Close +} + +func prepareCaseNotHaproxyMetrics(t *testing.T) (*Haproxy, func()) { + t.Helper() + srv := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte(` +# HELP haproxy_backend_http_responses_total Total number of HTTP responses. +# TYPE haproxy_backend_http_responses_total counter +application_backend_http_responses_total{proxy="infra-traefik-web",code="1xx"} 0 +application_backend_http_responses_total{proxy="infra-vernemq-ws",code="1xx"} 4130401 +application_backend_http_responses_total{proxy="infra-traefik-web",code="2xx"} 21338013 +application_backend_http_responses_total{proxy="infra-vernemq-ws",code="2xx"} 0 +application_backend_http_responses_total{proxy="infra-traefik-web",code="3xx"} 10004 +application_backend_http_responses_total{proxy="infra-vernemq-ws",code="3xx"} 0 +application_backend_http_responses_total{proxy="infra-traefik-web",code="4xx"} 10170758 +application_backend_http_responses_total{proxy="infra-vernemq-ws",code="4xx"} 0 +application_backend_http_responses_total{proxy="infra-traefik-web",code="5xx"} 3075 +application_backend_http_responses_total{proxy="infra-vernemq-ws",code="5xx"} 0 +application_backend_http_responses_total{proxy="infra-traefik-web",code="other"} 5657 +application_backend_http_responses_total{proxy="infra-vernemq-ws",code="other"} 0 +`)) + })) + h := New() + h.URL = srv.URL + require.True(t, h.Init()) + + return h, srv.Close +} + +func prepareCase404Response(t *testing.T) (*Haproxy, func()) { + t.Helper() + srv := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + h := New() + h.URL = srv.URL + require.True(t, h.Init()) + + return h, srv.Close +} + +func prepareCaseConnectionRefused(t *testing.T) (*Haproxy, func()) { + t.Helper() + h := New() + h.URL = "http://127.0.0.1:38001" + require.True(t, h.Init()) + + return h, func() {} +} + +func ensureCollectedHasAllChartsDimsVarsIDs(t *testing.T, h *Haproxy, ms map[string]int64) { + for _, chart := range *h.Charts() { + if chart.Obsolete { + continue + } + for _, dim := range chart.Dims { + _, ok := ms[dim.ID] + assert.Truef(t, ok, "chart '%s' dim '%s': no dim in collected", dim.ID, chart.ID) + } + for _, v := range chart.Vars { + _, ok := ms[v.ID] + assert.Truef(t, ok, "chart '%s' dim '%s': no dim in collected", v.ID, chart.ID) + } + } +} diff --git a/src/go/collectors/go.d.plugin/modules/haproxy/init.go b/src/go/collectors/go.d.plugin/modules/haproxy/init.go new file mode 100644 index 0000000000..30e0c45604 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/haproxy/init.go @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package haproxy + +import ( + "errors" + + "github.com/netdata/go.d.plugin/pkg/prometheus" + "github.com/netdata/go.d.plugin/pkg/prometheus/selector" + "github.com/netdata/go.d.plugin/pkg/web" +) + +func (h Haproxy) validateConfig() error { + if h.URL == "" { + return errors.New("'url' is not set") + } + if _, err := web.NewHTTPRequest(h.Request); err != nil { + return err + } + return nil +} + +func (h Haproxy) initPrometheusClient() (prometheus.Prometheus, error) { + httpClient, err := web.NewHTTPClient(h.Client) + if err != nil { + return nil, err + } + + prom := prometheus.NewWithSelector(httpClient, h.Request, sr) + return prom, nil +} + +var sr, _ = selector.Expr{ + Allow: []string{ + metricBackendHTTPResponsesTotal, + metricBackendCurrentQueue, + metricBackendQueueTimeAverageSeconds, + metricBackendBytesInTotal, + metricBackendResponseTimeAverageSeconds, + metricBackendSessionsTotal, + metricBackendCurrentSessions, + metricBackendBytesOutTotal, + }, +}.Parse() diff --git a/src/go/collectors/go.d.plugin/modules/haproxy/integrations/haproxy.md b/src/go/collectors/go.d.plugin/modules/haproxy/integrations/haproxy.md new file mode 100644 index 0000000000..d4cf4146fc --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/haproxy/integrations/haproxy.md @@ -0,0 +1,241 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/go.d.plugin/edit/master/modules/haproxy/README.md" +meta_yaml: "https://github.com/netdata/go.d.plugin/edit/master/modules/haproxy/metadata.yaml" +sidebar_label: "HAProxy" +learn_status: "Published" +learn_rel_path: "Data Collection/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--> + +# HAProxy + + +<img src="https://netdata.cloud/img/haproxy.svg" width="150"/> + + +Plugin: go.d.plugin +Module: haproxy + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +This collector monitors HAProxy servers. + + + + +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 HAProxy instance + +These metrics refer to the entire monitored application. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| haproxy.backend_current_sessions | a dimension per proxy | sessions | +| haproxy.backend_sessions | a dimension per proxy | sessions/s | +| haproxy.backend_response_time_average | a dimension per proxy | milliseconds | +| haproxy.backend_queue_time_average | a dimension per proxy | milliseconds | +| haproxy.backend_current_queue | a dimension per proxy | requests | + +### Per proxy + +These metrics refer to the Proxy. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| haproxy.backend_http_responses | 1xx, 2xx, 3xx, 4xx, 5xx, other | responses/s | +| haproxy.backend_network_io | in, out | bytes/s | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Enable PROMEX addon. + +To enable PROMEX addon, follow the [official documentation](https://github.com/haproxy/haproxy/tree/master/addons/promex). + + + +### Configuration + +#### File + +The configuration file name for this integration is `go.d/haproxy.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/configure/nodes.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config go.d/haproxy.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 | 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. + +<details><summary>Config</summary> + +```yaml +jobs: + - name: local + url: http://127.0.0.1:8404/metrics + +``` +</details> + +##### HTTP authentication + +Basic HTTP authentication. + +<details><summary>Config</summary> + +```yaml +jobs: + - name: local + url: http://127.0.0.1:8404/metrics + username: username + password: password + +``` +</details> + +##### HTTPS with self-signed certificate + +NGINX Plus with enabled HTTPS and self-signed certificate. + +<details><summary>Config</summary> + +```yaml +jobs: + - name: local + url: https://127.0.0.1:8404/metrics + 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:8404/metrics + + - name: remote + url: http://192.0.2.1:8404/metrics + +``` +</details> + + + +## Troubleshooting + +### Debug Mode + +To troubleshoot issues with the `haproxy` 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 haproxy + ``` + + diff --git a/src/go/collectors/go.d.plugin/modules/haproxy/metadata.yaml b/src/go/collectors/go.d.plugin/modules/haproxy/metadata.yaml new file mode 100644 index 0000000000..adc8796020 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/haproxy/metadata.yaml @@ -0,0 +1,231 @@ +plugin_name: go.d.plugin +modules: + - meta: + |