github.com/grafana/pyroscope@v1.18.0/pkg/test/integration/cluster/metrics.go (about)

     1  package cluster
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"math"
     8  	"strings"
     9  
    10  	"github.com/prometheus/client_golang/prometheus"
    11  	pm "github.com/prometheus/client_model/go"
    12  )
    13  
    14  type gatherCheck struct {
    15  	g          prometheus.Gatherer
    16  	conditions []gatherCoditions
    17  }
    18  
    19  func matchValue(exp float64) func(float64) error {
    20  	return func(value float64) error {
    21  		if value == exp {
    22  			return nil
    23  		}
    24  		return fmt.Errorf("expected %f, got %f", exp, value)
    25  	}
    26  }
    27  
    28  //nolint:unparam
    29  func (c *gatherCheck) addExpectValue(value float64, metricName string, labelPairs ...string) *gatherCheck {
    30  	c.conditions = append(c.conditions, gatherCoditions{
    31  		metricName: metricName,
    32  		labelPairs: labelPairs,
    33  		valueCheck: matchValue(value),
    34  	})
    35  	return c
    36  }
    37  
    38  func retrieveValue(ch chan float64) func(float64) error {
    39  	return func(value float64) error {
    40  		ch <- value
    41  		return nil
    42  	}
    43  }
    44  
    45  func (c *gatherCheck) addRetrieveValue(valueCh chan float64, metricName string, labelPairs ...string) *gatherCheck {
    46  	c.conditions = append(c.conditions, gatherCoditions{
    47  		metricName: metricName,
    48  		labelPairs: labelPairs,
    49  		valueCheck: retrieveValue(valueCh),
    50  	})
    51  	return c
    52  }
    53  
    54  type gatherCoditions struct {
    55  	metricName string
    56  	labelPairs []string
    57  	valueCheck func(float64) error
    58  }
    59  
    60  func (c *gatherCoditions) String() string {
    61  	b := strings.Builder{}
    62  	b.WriteString(c.metricName)
    63  	b.WriteRune('{')
    64  	for i := 0; i < len(c.labelPairs); i += 2 {
    65  		b.WriteString(c.labelPairs[i])
    66  		b.WriteRune('=')
    67  		b.WriteString(c.labelPairs[i+1])
    68  		b.WriteRune(',')
    69  	}
    70  	s := b.String()
    71  	return s[:len(s)-1] + "}"
    72  }
    73  
    74  func (c *gatherCoditions) matches(pairs []*pm.LabelPair) bool {
    75  outer:
    76  	for i := 0; i < len(c.labelPairs); i += 2 {
    77  		for _, l := range pairs {
    78  			if l.GetName() != c.labelPairs[i] {
    79  				continue
    80  			}
    81  			if l.GetValue() == c.labelPairs[i+1] {
    82  				continue outer // match move to next pair
    83  			}
    84  			return false // value wrong
    85  		}
    86  		return false // label not found
    87  	}
    88  	return true
    89  }
    90  
    91  func (comp *Component) checkMetrics() *gatherCheck {
    92  	return &gatherCheck{
    93  		g: comp.reg,
    94  	}
    95  }
    96  
    97  func (g *gatherCheck) run(ctx context.Context) error {
    98  	actualValues := make([]float64, len(g.conditions))
    99  
   100  	// maps from metric name to condition index
   101  	nameMap := make(map[string][]int)
   102  	for idx, c := range g.conditions {
   103  		// not a number
   104  		actualValues[idx] = math.NaN()
   105  		nameMap[c.metricName] = append(nameMap[c.metricName], idx)
   106  	}
   107  
   108  	// now gather actual metrics
   109  	metrics, err := g.g.Gather()
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	for _, m := range metrics {
   115  		if ctx.Err() != nil {
   116  			return ctx.Err()
   117  		}
   118  
   119  		conditions, ok := nameMap[m.GetName()]
   120  		if !ok {
   121  			continue
   122  		}
   123  
   124  		// now iterate over all label pairs
   125  		for _, sm := range m.GetMetric() {
   126  			// check for each condition if it matches with he labels
   127  			for _, condIdx := range conditions {
   128  				if g.conditions[condIdx].matches(sm.Label) {
   129  					v := -1.0 // -1.0 is an invalid value, when metric type is not gauge or counter
   130  					if g := sm.GetGauge(); g != nil {
   131  						v = g.GetValue()
   132  					} else if c := sm.GetCounter(); c != nil {
   133  						v = c.GetValue()
   134  					}
   135  					actualValues[condIdx] = v
   136  				}
   137  			}
   138  		}
   139  	}
   140  
   141  	errs := make([]error, len(actualValues))
   142  	for idx, actual := range actualValues {
   143  		cond := g.conditions[idx]
   144  		if math.IsNaN(actual) {
   145  			errs[idx] = fmt.Errorf("metric for %s not found", cond.String())
   146  			continue
   147  		}
   148  		if err := cond.valueCheck(actual); err != nil {
   149  			errs[idx] = fmt.Errorf("unexpected value for %s: %w", cond.String(), err)
   150  		}
   151  	}
   152  
   153  	return errors.Join(errs...)
   154  }