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  }