github.com/netdata/go.d.plugin@v0.58.1/modules/traefik/collect.go (about) 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/go.d.plugin/agent/module" 10 "github.com/netdata/go.d.plugin/pkg/prometheus" 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 (t *Traefik) collect() (map[string]int64, error) { 36 pms, err := t.prom.ScrapeSeries() 37 if err != nil { 38 return nil, err 39 } 40 41 if t.checkMetrics && !isTraefikMetrics(pms) { 42 return nil, errors.New("unexpected metrics (not Traefik)") 43 } 44 t.checkMetrics = false 45 46 mx := make(map[string]int64) 47 48 t.collectEntrypointRequestsTotal(mx, pms) 49 t.collectEntrypointRequestDuration(mx, pms) 50 t.collectEntrypointOpenConnections(mx, pms) 51 t.updateCodeClassMetrics(mx) 52 53 return mx, nil 54 } 55 56 func (t *Traefik) 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 := t.cacheGetOrPutEntrypoint(id) 75 if ce.requests == nil { 76 chart := newChartEntrypointRequests(ep, proto) 77 ce.requests = chart 78 if err := t.Charts().Add(chart); err != nil { 79 t.Warning(err) 80 } 81 } 82 } 83 } 84 85 func (t *Traefik) 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 := t.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 t.cache.entrypoints { 114 if ce.reqDur == nil { 115 chart := newChartEntrypointRequestDuration(ce.name, ce.proto) 116 ce.reqDur = chart 117 if err := t.Charts().Add(chart); err != nil { 118 t.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 (t *Traefik) 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 := t.cacheGetOrPutEntrypoint(id) 155 if ce.openConn == nil { 156 chart := newChartEntrypointOpenConnections(ep, proto) 157 ce.openConn = chart 158 if err := t.Charts().Add(chart); err != nil { 159 t.Warning(err) 160 } 161 } 162 163 if !ce.openConnMethods[method] { 164 ce.openConnMethods[method] = true 165 dim := &module.Dim{ID: key, Name: method} 166 if err := ce.openConn.AddDim(dim); err != nil { 167 t.Warning(err) 168 } 169 } 170 } 171 } 172 173 var httpRespCodeClasses = []string{"1xx", "2xx", "3xx", "4xx", "5xx"} 174 175 func (t Traefik) updateCodeClassMetrics(mx map[string]int64) { 176 for id, ce := range t.cache.entrypoints { 177 if ce.requests != nil { 178 for _, c := range httpRespCodeClasses { 179 key := prefixEntrypointRequests + id + "_" + c 180 mx[key] += 0 181 } 182 } 183 if ce.reqDur != nil { 184 for _, c := range httpRespCodeClasses { 185 key := prefixEntrypointReqDurAvg + id + "_" + c 186 mx[key] += 0 187 } 188 } 189 } 190 } 191 192 func getCodeClass(code string) string { 193 if len(code) != 3 { 194 return "" 195 } 196 return string(code[0]) + "xx" 197 } 198 199 func (t *Traefik) cacheGetOrPutEntrypoint(id string) *cacheEntrypoint { 200 if _, ok := t.cache.entrypoints[id]; !ok { 201 name, proto := id, id 202 if idx := strings.LastIndexByte(id, '_'); idx != -1 { 203 name, proto = id[:idx], id[idx+1:] 204 } 205 t.cache.entrypoints[id] = &cacheEntrypoint{ 206 name: name, 207 proto: proto, 208 reqDurData: make(map[string]cacheEntrypointReqDur), 209 openConnMethods: make(map[string]bool), 210 } 211 } 212 return t.cache.entrypoints[id] 213 }