master
go 214 lines 5.55 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package traefik
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 metricEntrypointRequestsTotal = "traefik_entrypoint_requests_total"
15 metricEntrypointRequestDurationSecondsSum = "traefik_entrypoint_request_duration_seconds_sum"
16 metricEntrypointRequestDurationSecondsCount = "traefik_entrypoint_request_duration_seconds_count"
17 metricEntrypointOpenConnections = "traefik_entrypoint_open_connections"
18 )
19
20 const (
21 prefixEntrypointRequests = "entrypoint_requests_"
22 prefixEntrypointReqDurAvg = "entrypoint_request_duration_average_"
23 prefixEntrypointOpenConn = "entrypoint_open_connections_"
24 )
25
26 func isTraefikMetrics(pms prometheus.Series) bool {
27 for _, pm := range pms {
28 if strings.HasPrefix(pm.Name(), "traefik_") {
29 return true
30 }
31 }
32 return false
33 }
34
35 func (c *Collector) collect() (map[string]int64, error) {
36 pms, err := c.prom.ScrapeSeries()
37 if err != nil {
38 return nil, err
39 }
40
41 if c.checkMetrics && !isTraefikMetrics(pms) {
42 return nil, errors.New("unexpected metrics (not Traefik)")
43 }
44 c.checkMetrics = false
45
46 mx := make(map[string]int64)
47
48 c.collectEntrypointRequestsTotal(mx, pms)
49 c.collectEntrypointRequestDuration(mx, pms)
50 c.collectEntrypointOpenConnections(mx, pms)
51 c.updateCodeClassMetrics(mx)
52
53 return mx, nil
54 }
55
56 func (c *Collector) collectEntrypointRequestsTotal(mx map[string]int64, pms prometheus.Series) {
57 if pms = pms.FindByName(metricEntrypointRequestsTotal); pms.Len() == 0 {
58 return
59 }
60
61 for _, pm := range pms {
62 code := pm.Labels.Get("code")
63 ep := pm.Labels.Get("entrypoint")
64 proto := pm.Labels.Get("protocol")
65 codeClass := getCodeClass(code)
66 if code == "" || ep == "" || proto == "" || codeClass == "" {
67 continue
68 }
69
70 key := prefixEntrypointRequests + ep + "_" + proto + "_" + codeClass
71 mx[key] += int64(pm.Value)
72
73 id := ep + "_" + proto
74 ce := c.cacheGetOrPutEntrypoint(id)
75 if ce.requests == nil {
76 chart := newChartEntrypointRequests(ep, proto)
77 ce.requests = chart
78 if err := c.Charts().Add(chart); err != nil {
79 c.Warning(err)
80 }
81 }
82 }
83 }
84
85 func (c *Collector) collectEntrypointRequestDuration(mx map[string]int64, pms prometheus.Series) {
86 if pms = pms.FindByNames(
87 metricEntrypointRequestDurationSecondsCount,
88 metricEntrypointRequestDurationSecondsSum,
89 ); pms.Len() == 0 {
90 return
91 }
92
93 for _, pm := range pms {
94 code := pm.Labels.Get("code")
95 ep := pm.Labels.Get("entrypoint")
96 proto := pm.Labels.Get("protocol")
97 codeClass := getCodeClass(code)
98 if code == "" || ep == "" || proto == "" || codeClass == "" {
99 continue
100 }
101
102 id := ep + "_" + proto
103 ce := c.cacheGetOrPutEntrypoint(id)
104 v := ce.reqDurData[codeClass]
105 if pm.Name() == metricEntrypointRequestDurationSecondsSum {
106 v.cur.secs += pm.Value
107 } else {
108 v.cur.reqs += pm.Value
109 }
110 ce.reqDurData[codeClass] = v
111 }
112
113 for id, ce := range c.cache.entrypoints {
114 if ce.reqDur == nil {
115 chart := newChartEntrypointRequestDuration(ce.name, ce.proto)
116 ce.reqDur = chart
117 if err := c.Charts().Add(chart); err != nil {
118 c.Warning(err)
119 }
120 }
121 for codeClass, v := range ce.reqDurData {
122 secs, reqs, seen := v.cur.secs-v.prev.secs, v.cur.reqs-v.prev.reqs, v.seen
123 v.prev.secs, v.prev.reqs, v.seen = v.cur.secs, v.cur.reqs, true
124 v.cur.secs, v.cur.reqs = 0, 0
125 ce.reqDurData[codeClass] = v
126
127 key := prefixEntrypointReqDurAvg + id + "_" + codeClass
128 if secs <= 0 || reqs <= 0 || !seen {
129 mx[key] = 0
130 } else {
131 mx[key] = int64(secs * 1000 / reqs)
132 }
133 }
134 }
135 }
136
137 func (c *Collector) collectEntrypointOpenConnections(mx map[string]int64, pms prometheus.Series) {
138 if pms = pms.FindByName(metricEntrypointOpenConnections); pms.Len() == 0 {
139 return
140 }
141
142 for _, pm := range pms {
143 method := pm.Labels.Get("method")
144 ep := pm.Labels.Get("entrypoint")
145 proto := pm.Labels.Get("protocol")
146 if method == "" || ep == "" || proto == "" {
147 continue
148 }
149
150 key := prefixEntrypointOpenConn + ep + "_" + proto + "_" + method
151 mx[key] += int64(pm.Value)
152
153 id := ep + "_" + proto
154 ce := c.cacheGetOrPutEntrypoint(id)
155 if ce.openConn == nil {
156 chart := newChartEntrypointOpenConnections(ep, proto)
157 ce.openConn = chart
158 if err := c.Charts().Add(chart); err != nil {
159 c.Warning(err)
160 }
161 }
162
163 if !ce.openConnMethods[method] {
164 ce.openConnMethods[method] = true
165 dim := &collectorapi.Dim{ID: key, Name: method}
166 if err := ce.openConn.AddDim(dim); err != nil {
167 c.Warning(err)
168 }
169 ce.openConn.MarkNotCreated()
170 }
171 }
172 }
173
174 var httpRespCodeClasses = []string{"1xx", "2xx", "3xx", "4xx", "5xx"}
175
176 func (c *Collector) updateCodeClassMetrics(mx map[string]int64) {
177 for id, ce := range c.cache.entrypoints {
178 if ce.requests != nil {
179 for _, c := range httpRespCodeClasses {
180 key := prefixEntrypointRequests + id + "_" + c
181 mx[key] += 0
182 }
183 }
184 if ce.reqDur != nil {
185 for _, c := range httpRespCodeClasses {
186 key := prefixEntrypointReqDurAvg + id + "_" + c
187 mx[key] += 0
188 }
189 }
190 }
191 }
192
193 func getCodeClass(code string) string {
194 if len(code) != 3 {
195 return ""
196 }
197 return string(code[0]) + "xx"
198 }
199
200 func (c *Collector) cacheGetOrPutEntrypoint(id string) *cacheEntrypoint {
201 if _, ok := c.cache.entrypoints[id]; !ok {
202 name, proto := id, id
203 if idx := strings.LastIndexByte(id, '_'); idx != -1 {
204 name, proto = id[:idx], id[idx+1:]
205 }
206 c.cache.entrypoints[id] = &cacheEntrypoint{
207 name: name,
208 proto: proto,
209 reqDurData: make(map[string]cacheEntrypointReqDur),
210 openConnMethods: make(map[string]bool),
211 }
212 }
213 return c.cache.entrypoints[id]
214 }