| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package haproxy |
| 4 | |
| 5 | import ( |
| 6 | "errors" |
| 7 | "strings" |
| 8 | |
| 9 | "github.com/netdata/netdata/go/plugins/pkg/prometheus" |
| 10 | "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi" |
| 11 | ) |
| 12 | |
| 13 | const ( |
| 14 | metricBackendSessionsTotal = "haproxy_backend_sessions_total" |
| 15 | metricBackendCurrentSessions = "haproxy_backend_current_sessions" |
| 16 | metricBackendHTTPResponsesTotal = "haproxy_backend_http_responses_total" |
| 17 | metricBackendResponseTimeAverageSeconds = "haproxy_backend_response_time_average_seconds" |
| 18 | metricBackendCurrentQueue = "haproxy_backend_current_queue" |
| 19 | metricBackendQueueTimeAverageSeconds = "haproxy_backend_queue_time_average_seconds" |
| 20 | metricBackendBytesInTotal = "haproxy_backend_bytes_in_total" |
| 21 | metricBackendBytesOutTotal = "haproxy_backend_bytes_out_total" |
| 22 | ) |
| 23 | |
| 24 | func isHaproxyMetrics(pms prometheus.Series) bool { |
| 25 | for _, pm := range pms { |
| 26 | if strings.HasPrefix(pm.Name(), "haproxy_") { |
| 27 | return true |
| 28 | } |
| 29 | } |
| 30 | return false |
| 31 | } |
| 32 | |
| 33 | func (c *Collector) collect() (map[string]int64, error) { |
| 34 | pms, err := c.prom.ScrapeSeries() |
| 35 | if err != nil { |
| 36 | return nil, err |
| 37 | } |
| 38 | |
| 39 | if c.validateMetrics && !isHaproxyMetrics(pms) { |
| 40 | return nil, errors.New("unexpected metrics (not HAProxy)") |
| 41 | } |
| 42 | c.validateMetrics = false |
| 43 | |
| 44 | mx := make(map[string]int64) |
| 45 | for _, pm := range pms { |
| 46 | proxy := pm.Labels.Get("proxy") |
| 47 | if proxy == "" { |
| 48 | continue |
| 49 | } |
| 50 | |
| 51 | if !c.proxies[proxy] { |
| 52 | c.proxies[proxy] = true |
| 53 | c.addProxyToCharts(proxy) |
| 54 | } |
| 55 | |
| 56 | mx[dimID(pm)] = int64(pm.Value * multiplier(pm)) |
| 57 | } |
| 58 | |
| 59 | return mx, nil |
| 60 | } |
| 61 | |
| 62 | func (c *Collector) addProxyToCharts(proxy string) { |
| 63 | c.addDimToChart(chartBackendCurrentSessions.ID, &collectorapi.Dim{ |
| 64 | ID: proxyDimID(metricBackendCurrentSessions, proxy), |
| 65 | Name: proxy, |
| 66 | }) |
| 67 | c.addDimToChart(chartBackendSessions.ID, &collectorapi.Dim{ |
| 68 | ID: proxyDimID(metricBackendSessionsTotal, proxy), |
| 69 | Name: proxy, |
| 70 | Algo: collectorapi.Incremental, |
| 71 | }) |
| 72 | |
| 73 | c.addDimToChart(chartBackendResponseTimeAverage.ID, &collectorapi.Dim{ |
| 74 | ID: proxyDimID(metricBackendResponseTimeAverageSeconds, proxy), |
| 75 | Name: proxy, |
| 76 | }) |
| 77 | if err := c.Charts().Add(newChartBackendHTTPResponses(proxy)); err != nil { |
| 78 | c.Warning(err) |
| 79 | } |
| 80 | |
| 81 | c.addDimToChart(chartBackendCurrentQueue.ID, &collectorapi.Dim{ |
| 82 | ID: proxyDimID(metricBackendCurrentQueue, proxy), |
| 83 | Name: proxy, |
| 84 | }) |
| 85 | c.addDimToChart(chartBackendQueueTimeAverage.ID, &collectorapi.Dim{ |
| 86 | ID: proxyDimID(metricBackendQueueTimeAverageSeconds, proxy), |
| 87 | Name: proxy, |
| 88 | }) |
| 89 | |
| 90 | if err := c.Charts().Add(newChartBackendNetworkIO(proxy)); err != nil { |
| 91 | c.Warning(err) |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | func (c *Collector) addDimToChart(chartID string, dim *collectorapi.Dim) { |
| 96 | chart := c.Charts().Get(chartID) |
| 97 | if chart == nil { |
| 98 | c.Warningf("error on adding '%s' dimension: can not find '%s' chart", dim.ID, chartID) |
| 99 | return |
| 100 | } |
| 101 | if err := chart.AddDim(dim); err != nil { |
| 102 | c.Warning(err) |
| 103 | return |
| 104 | } |
| 105 | chart.MarkNotCreated() |
| 106 | } |
| 107 | |
| 108 | func multiplier(pm prometheus.SeriesSample) float64 { |
| 109 | switch pm.Name() { |
| 110 | case metricBackendResponseTimeAverageSeconds, |
| 111 | metricBackendQueueTimeAverageSeconds: |
| 112 | // to milliseconds |
| 113 | return 1000 |
| 114 | } |
| 115 | return 1 |
| 116 | } |
| 117 | |
| 118 | func dimID(pm prometheus.SeriesSample) string { |
| 119 | proxy := pm.Labels.Get("proxy") |
| 120 | if proxy == "" { |
| 121 | return "" |
| 122 | } |
| 123 | |
| 124 | name := cleanMetricName(pm.Name()) |
| 125 | if pm.Name() == metricBackendHTTPResponsesTotal { |
| 126 | name += "_" + pm.Labels.Get("code") |
| 127 | } |
| 128 | return proxyDimID(name, proxy) |
| 129 | } |
| 130 | |
| 131 | func proxyDimID(metric, proxy string) string { |
| 132 | return cleanMetricName(metric) + "_proxy_" + proxy |
| 133 | } |
| 134 | |
| 135 | func cleanMetricName(name string) string { |
| 136 | if strings.HasSuffix(name, "_total") { |
| 137 | return name[:len(name)-6] |
| 138 | } |
| 139 | if strings.HasSuffix(name, "_seconds") { |
| 140 | return name[:len(name)-8] |
| 141 | } |
| 142 | return name |
| 143 | } |