github.com/prysmaticlabs/prysm@v1.4.4/endtoend/evaluators/metrics.go (about)

     1  package evaluators
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"regexp"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/pkg/errors"
    14  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p"
    15  	e2e "github.com/prysmaticlabs/prysm/endtoend/params"
    16  	"github.com/prysmaticlabs/prysm/endtoend/policies"
    17  	"github.com/prysmaticlabs/prysm/endtoend/types"
    18  	eth "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    19  	"github.com/prysmaticlabs/prysm/shared/p2putils"
    20  	"google.golang.org/grpc"
    21  	"google.golang.org/protobuf/types/known/emptypb"
    22  )
    23  
    24  const maxMemStatsBytes = 2000000000 // 2 GiB.
    25  
    26  // MetricsCheck performs a check on metrics to make sure caches are functioning, and
    27  // overall health is good. Not checking the first epoch so the sample size isn't too small.
    28  var MetricsCheck = types.Evaluator{
    29  	Name:       "metrics_check_epoch_%d",
    30  	Policy:     policies.AfterNthEpoch(0),
    31  	Evaluation: metricsTest,
    32  }
    33  
    34  type equalityTest struct {
    35  	name  string
    36  	topic string
    37  	value int
    38  }
    39  
    40  type comparisonTest struct {
    41  	name               string
    42  	topic1             string
    43  	topic2             string
    44  	expectedComparison float64
    45  }
    46  
    47  var metricLessThanTests = []equalityTest{
    48  	{
    49  		name:  "memory usage",
    50  		topic: "go_memstats_alloc_bytes",
    51  		value: maxMemStatsBytes,
    52  	},
    53  }
    54  
    55  const (
    56  	p2pFailValidationTopic = "p2p_message_failed_validation_total{topic=\"%s/ssz_snappy\"}"
    57  	p2pReceivedTotalTopic  = "p2p_message_received_total{topic=\"%s/ssz_snappy\"}"
    58  )
    59  
    60  var metricComparisonTests = []comparisonTest{
    61  	{
    62  		name:               "beacon aggregate and proof",
    63  		topic1:             fmt.Sprintf(p2pFailValidationTopic, p2p.AggregateAndProofSubnetTopicFormat),
    64  		topic2:             fmt.Sprintf(p2pReceivedTotalTopic, p2p.AggregateAndProofSubnetTopicFormat),
    65  		expectedComparison: 0.8,
    66  	},
    67  	{
    68  		name:               "committee index beacon attestations",
    69  		topic1:             fmt.Sprintf(p2pFailValidationTopic, formatTopic(p2p.AttestationSubnetTopicFormat)),
    70  		topic2:             fmt.Sprintf(p2pReceivedTotalTopic, formatTopic(p2p.AttestationSubnetTopicFormat)),
    71  		expectedComparison: 0.15,
    72  	},
    73  	{
    74  		name:               "committee cache",
    75  		topic1:             "committee_cache_miss",
    76  		topic2:             "committee_cache_hit",
    77  		expectedComparison: 0.01,
    78  	},
    79  	{
    80  		name:               "hot state cache",
    81  		topic1:             "hot_state_cache_miss",
    82  		topic2:             "hot_state_cache_hit",
    83  		expectedComparison: 0.01,
    84  	},
    85  }
    86  
    87  func metricsTest(conns ...*grpc.ClientConn) error {
    88  	genesis, err := eth.NewNodeClient(conns[0]).GetGenesis(context.Background(), &emptypb.Empty{})
    89  	if err != nil {
    90  		return err
    91  	}
    92  	forkDigest, err := p2putils.CreateForkDigest(time.Unix(genesis.GenesisTime.Seconds, 0), genesis.GenesisValidatorsRoot)
    93  	if err != nil {
    94  		return err
    95  	}
    96  	for i := 0; i < len(conns); i++ {
    97  		response, err := http.Get(fmt.Sprintf("http://localhost:%d/metrics", e2e.TestParams.BeaconNodeMetricsPort+i))
    98  		if err != nil {
    99  			// Continue if the connection fails, regular flake.
   100  			continue
   101  		}
   102  		dataInBytes, err := ioutil.ReadAll(response.Body)
   103  		if err != nil {
   104  			return err
   105  		}
   106  		pageContent := string(dataInBytes)
   107  		if err = response.Body.Close(); err != nil {
   108  			return err
   109  		}
   110  		time.Sleep(connTimeDelay)
   111  
   112  		chainHead, err := eth.NewBeaconChainClient(conns[i]).GetChainHead(context.Background(), &emptypb.Empty{})
   113  		if err != nil {
   114  			return err
   115  		}
   116  		timeSlot, err := valueOfTopic(pageContent, "beacon_clock_time_slot")
   117  		if err != nil {
   118  			return err
   119  		}
   120  		if uint64(chainHead.HeadSlot) != uint64(timeSlot) {
   121  			return fmt.Errorf("expected metrics slot to equal chain head slot, expected %d, received %d", chainHead.HeadSlot, timeSlot)
   122  		}
   123  
   124  		for _, test := range metricLessThanTests {
   125  			topic := test.topic
   126  			if strings.Contains(topic, "%x") {
   127  				topic = fmt.Sprintf(topic, forkDigest)
   128  			}
   129  			if err = metricCheckLessThan(pageContent, topic, test.value); err != nil {
   130  				return errors.Wrapf(err, "failed %s check", test.name)
   131  			}
   132  		}
   133  		for _, test := range metricComparisonTests {
   134  			topic1 := test.topic1
   135  			if strings.Contains(topic1, "%x") {
   136  				topic1 = fmt.Sprintf(topic1, forkDigest)
   137  			}
   138  			topic2 := test.topic2
   139  			if strings.Contains(topic2, "%x") {
   140  				topic2 = fmt.Sprintf(topic2, forkDigest)
   141  			}
   142  			if err = metricCheckComparison(pageContent, topic1, topic2, test.expectedComparison); err != nil {
   143  				return err
   144  			}
   145  		}
   146  	}
   147  	return nil
   148  }
   149  
   150  func metricCheckLessThan(pageContent, topic string, value int) error {
   151  	topicValue, err := valueOfTopic(pageContent, topic)
   152  	if err != nil {
   153  		return err
   154  	}
   155  	if topicValue >= value {
   156  		return fmt.Errorf(
   157  			"unexpected result for metric %s, expected less than %d, received %d",
   158  			topic,
   159  			value,
   160  			topicValue,
   161  		)
   162  	}
   163  	return nil
   164  }
   165  
   166  func metricCheckComparison(pageContent, topic1, topic2 string, comparison float64) error {
   167  	topic2Value, err := valueOfTopic(pageContent, topic2)
   168  	// If we can't find the first topic (error metrics), then assume the test passes.
   169  	if topic2Value != -1 {
   170  		return nil
   171  	}
   172  	if err != nil {
   173  		return err
   174  	}
   175  	topic1Value, err := valueOfTopic(pageContent, topic1)
   176  	if topic1Value != -1 {
   177  		return nil
   178  	}
   179  	if err != nil {
   180  		return err
   181  	}
   182  	topicComparison := float64(topic1Value) / float64(topic2Value)
   183  	if topicComparison >= comparison {
   184  		return fmt.Errorf(
   185  			"unexpected result for comparison between metric %s and metric %s, expected comparison to be %.2f, received %.2f",
   186  			topic1,
   187  			topic2,
   188  			comparison,
   189  			topicComparison,
   190  		)
   191  	}
   192  	return nil
   193  }
   194  
   195  func valueOfTopic(pageContent, topic string) (int, error) {
   196  	regexExp, err := regexp.Compile(topic + " ")
   197  	if err != nil {
   198  		return -1, errors.Wrap(err, "could not create regex expression")
   199  	}
   200  	indexesFound := regexExp.FindAllStringIndex(pageContent, 8)
   201  	if indexesFound == nil {
   202  		return -1, fmt.Errorf("no strings found for %s", topic)
   203  	}
   204  	var result float64
   205  	for i, stringIndex := range indexesFound {
   206  		// Only performing every third result found since theres 2 comments above every metric.
   207  		if i == 0 || i%2 != 0 {
   208  			continue
   209  		}
   210  		startOfValue := stringIndex[1]
   211  		endOfValue := strings.Index(pageContent[startOfValue:], "\n")
   212  		if endOfValue == -1 {
   213  			return -1, fmt.Errorf("could not find next space in %s", pageContent[startOfValue:])
   214  		}
   215  		metricValue := pageContent[startOfValue : startOfValue+endOfValue]
   216  		floatResult, err := strconv.ParseFloat(metricValue, 64)
   217  		if err != nil {
   218  			return -1, errors.Wrapf(err, "could not parse %s for int", metricValue)
   219  		}
   220  		result += floatResult
   221  	}
   222  	return int(result), nil
   223  }
   224  
   225  func formatTopic(topic string) string {
   226  	replacedD := strings.Replace(topic, "%d", "\\w*", 1)
   227  	return replacedD
   228  }