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 }