github.com/prysmaticlabs/prysm@v1.4.4/shared/clientstats/scrapers_test.go (about) 1 package clientstats 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "net/http" 8 "strings" 9 "testing" 10 "time" 11 12 "github.com/prysmaticlabs/prysm/shared/testutil/require" 13 "github.com/sirupsen/logrus" 14 logTest "github.com/sirupsen/logrus/hooks/test" 15 ) 16 17 func init() { 18 logrus.SetLevel(logrus.DebugLevel) 19 } 20 21 type mockRT struct { 22 body string 23 status string 24 statusCode int 25 } 26 27 func (rt *mockRT) RoundTrip(req *http.Request) (*http.Response, error) { 28 return &http.Response{ 29 Status: http.StatusText(http.StatusOK), 30 StatusCode: http.StatusOK, 31 Body: io.NopCloser(strings.NewReader(rt.body)), 32 }, nil 33 } 34 35 var _ http.RoundTripper = &mockRT{} 36 37 func TestBeaconNodeScraper(t *testing.T) { 38 bnScraper := beaconNodeScraper{} 39 bnScraper.tripper = &mockRT{body: prometheusTestBody} 40 r, err := bnScraper.Scrape() 41 require.NoError(t, err, "Unexpected error calling beaconNodeScraper.Scrape") 42 bs := &BeaconNodeStats{} 43 err = json.NewDecoder(r).Decode(bs) 44 require.NoError(t, err, "Unexpected error decoding result of beaconNodeScraper.Scrape") 45 // CommonStats 46 require.Equal(t, int64(225), bs.CPUProcessSecondsTotal) 47 require.Equal(t, int64(1166630912), bs.MemoryProcessBytes) 48 require.Equal(t, int64(1619586241), bs.ClientBuild) 49 require.Equal(t, "v1.3.8-hotfix+6c0942", bs.ClientVersion) 50 require.Equal(t, "prysm", bs.ClientName) 51 52 // BeaconNodeStats 53 require.Equal(t, int64(256552), bs.SyncBeaconHeadSlot) 54 require.Equal(t, true, bs.SyncEth2Synced) 55 require.Equal(t, int64(7365341184), bs.DiskBeaconchainBytesTotal) 56 require.Equal(t, int64(37), bs.NetworkPeersConnected) 57 require.Equal(t, true, bs.SyncEth1Connected) 58 require.Equal(t, true, bs.SyncEth1FallbackConfigured) 59 require.Equal(t, true, bs.SyncEth1FallbackConnected) 60 } 61 62 // helper function to wrap up all the scrape logic so tests can focus on data cases and assertions 63 func scrapeBeaconNodeStats(body string) (*BeaconNodeStats, error) { 64 if !strings.HasSuffix(body, "\n") { 65 return nil, fmt.Errorf("Bad test fixture -- make sure there is a trailing newline unless you want to waste time debugging tests") 66 } 67 bnScraper := beaconNodeScraper{} 68 bnScraper.tripper = &mockRT{body: body} 69 r, err := bnScraper.Scrape() 70 if err != nil { 71 return nil, err 72 } 73 bs := &BeaconNodeStats{} 74 err = json.NewDecoder(r).Decode(bs) 75 return bs, err 76 } 77 78 func TestInvertEth1Metrics(t *testing.T) { 79 cases := []struct { 80 key string 81 body string 82 test func(*BeaconNodeStats) bool 83 }{ 84 { 85 key: "SyncEth1Connected", 86 body: strings.Replace(prometheusTestBody, "powchain_sync_eth1_connected 1", "powchain_sync_eth1_connected 0", 1), 87 test: func(bs *BeaconNodeStats) bool { 88 return bs.SyncEth1Connected == false && bs.SyncEth1FallbackConfigured == true && bs.SyncEth1FallbackConnected == true 89 }, 90 }, 91 { 92 key: "SyncEth1FallbackConfigured", 93 body: strings.Replace(prometheusTestBody, "powchain_sync_eth1_fallback_configured 1", "powchain_sync_eth1_fallback_configured 0", 1), 94 test: func(bs *BeaconNodeStats) bool { 95 return bs.SyncEth1Connected == true && bs.SyncEth1FallbackConfigured == false && bs.SyncEth1FallbackConnected == true 96 }, 97 }, 98 { 99 key: "SyncEth1FallbackConnected", 100 body: strings.Replace(prometheusTestBody, "powchain_sync_eth1_fallback_connected 1", "powchain_sync_eth1_fallback_connected 0", 1), 101 test: func(bs *BeaconNodeStats) bool { 102 return bs.SyncEth1Connected == true && bs.SyncEth1FallbackConfigured == true && bs.SyncEth1FallbackConnected == false 103 }, 104 }, 105 } 106 for _, c := range cases { 107 bs, err := scrapeBeaconNodeStats(c.body) 108 require.NoError(t, err) 109 require.Equal(t, true, c.test(bs), "BeaconNodeStats.%s was not false, with prometheus body=%s", c.key, c.body) 110 } 111 } 112 113 func TestFalseEth2Synced(t *testing.T) { 114 bnScraper := beaconNodeScraper{} 115 eth2NotSynced := strings.Replace(prometheusTestBody, "beacon_head_slot 256552", "beacon_head_slot 256559", 1) 116 bnScraper.tripper = &mockRT{body: eth2NotSynced} 117 r, err := bnScraper.Scrape() 118 require.NoError(t, err, "Unexpected error calling beaconNodeScraper.Scrape") 119 120 bs := &BeaconNodeStats{} 121 err = json.NewDecoder(r).Decode(bs) 122 require.NoError(t, err, "Unexpected error decoding result of beaconNodeScraper.Scrape") 123 124 require.Equal(t, false, bs.SyncEth2Synced) 125 } 126 127 func TestValidatorScraper(t *testing.T) { 128 vScraper := validatorScraper{} 129 vScraper.tripper = &mockRT{body: statusFixtureOneOfEach + prometheusTestBody} 130 r, err := vScraper.Scrape() 131 require.NoError(t, err, "Unexpected error calling validatorScraper.Scrape") 132 vs := &ValidatorStats{} 133 err = json.NewDecoder(r).Decode(vs) 134 require.NoError(t, err, "Unexpected error decoding result of validatorScraper.Scrape") 135 // CommonStats 136 require.Equal(t, int64(225), vs.CPUProcessSecondsTotal) 137 require.Equal(t, int64(1166630912), vs.MemoryProcessBytes) 138 require.Equal(t, int64(1619586241), vs.ClientBuild) 139 require.Equal(t, "v1.3.8-hotfix+6c0942", vs.ClientVersion) 140 require.Equal(t, "prysm", vs.ClientName) 141 require.Equal(t, int64(7), vs.ValidatorTotal) 142 require.Equal(t, int64(1), vs.ValidatorActive) 143 } 144 145 func TestValidatorScraperAllActive(t *testing.T) { 146 vScraper := validatorScraper{} 147 vScraper.tripper = &mockRT{body: statusFixtureAllActive + prometheusTestBody} 148 r, err := vScraper.Scrape() 149 require.NoError(t, err, "Unexpected error calling validatorScraper.Scrape") 150 vs := &ValidatorStats{} 151 err = json.NewDecoder(r).Decode(vs) 152 require.NoError(t, err, "Unexpected error decoding result of validatorScraper.Scrape") 153 // CommonStats 154 require.Equal(t, int64(4), vs.ValidatorTotal) 155 require.Equal(t, int64(4), vs.ValidatorActive) 156 } 157 158 func TestValidatorScraperNoneActive(t *testing.T) { 159 vScraper := validatorScraper{} 160 vScraper.tripper = &mockRT{body: statusFixtureNoneActive + prometheusTestBody} 161 r, err := vScraper.Scrape() 162 require.NoError(t, err, "Unexpected error calling validatorScraper.Scrape") 163 vs := &ValidatorStats{} 164 err = json.NewDecoder(r).Decode(vs) 165 require.NoError(t, err, "Unexpected error decoding result of validatorScraper.Scrape") 166 // CommonStats 167 require.Equal(t, int64(6), vs.ValidatorTotal) 168 require.Equal(t, int64(0), vs.ValidatorActive) 169 } 170 171 func mockNowFunc(fixedTime time.Time) func() time.Time { 172 return func() time.Time { 173 return fixedTime 174 } 175 } 176 177 func TestValidatorAPIMessageDefaults(t *testing.T) { 178 now = mockNowFunc(time.Unix(1619811114, 123456789)) 179 // 1+e6 ns per ms, so 123456789 ns rounded down should be 123 ms 180 nowMillis := int64(1619811114123) 181 vScraper := validatorScraper{} 182 vScraper.tripper = &mockRT{body: statusFixtureOneOfEach + prometheusTestBody} 183 r, err := vScraper.Scrape() 184 require.NoError(t, err, "unexpected error from validatorScraper.Scrape()") 185 186 vs := &ValidatorStats{} 187 err = json.NewDecoder(r).Decode(vs) 188 require.NoError(t, err, "Unexpected error decoding result of validatorScraper.Scrape") 189 190 // CommonStats 191 require.Equal(t, nowMillis, vs.Timestamp, "Unexpected 'timestamp' in client-stats APIMessage struct") 192 require.Equal(t, APIVersion, vs.APIVersion, "Unexpected 'version' in client-stats APIMessage struct") 193 require.Equal(t, ValidatorProcessName, vs.ProcessName, "Unexpected value for 'process' in client-stats APIMessage struct") 194 } 195 196 func TestBeaconNodeAPIMessageDefaults(t *testing.T) { 197 now = mockNowFunc(time.Unix(1619811114, 123456789)) 198 // 1+e6 ns per ms, so 123456789 ns rounded down should be 123 ms 199 nowMillis := int64(1619811114123) 200 bScraper := beaconNodeScraper{} 201 bScraper.tripper = &mockRT{body: prometheusTestBody} 202 r, err := bScraper.Scrape() 203 require.NoError(t, err, "unexpected error from beaconNodeScraper.Scrape()") 204 205 vs := &BeaconNodeStats{} 206 err = json.NewDecoder(r).Decode(vs) 207 require.NoError(t, err, "Unexpected error decoding result of beaconNodeScraper.Scrape") 208 209 // CommonStats 210 require.Equal(t, nowMillis, vs.Timestamp, "Unexpected 'timestamp' in client-stats APIMessage struct") 211 require.Equal(t, APIVersion, vs.APIVersion, "Unexpected 'version' in client-stats APIMessage struct") 212 require.Equal(t, BeaconNodeProcessName, vs.ProcessName, "Unexpected value for 'process' in client-stats APIMessage struct") 213 } 214 215 func TestBadInput(t *testing.T) { 216 hook := logTest.NewGlobal() 217 bnScraper := beaconNodeScraper{} 218 bnScraper.tripper = &mockRT{body: ""} 219 _, err := bnScraper.Scrape() 220 require.NoError(t, err) 221 require.LogsContain(t, hook, "Failed to get prysm_version") 222 } 223 224 var prometheusTestBody = ` 225 # HELP process_cpu_seconds_total Total user and system CPU time spent in seconds. 226 # TYPE process_cpu_seconds_total counter 227 process_cpu_seconds_total 225.09 228 # HELP process_resident_memory_bytes Resident memory size in bytes. 229 # TYPE process_resident_memory_bytes gauge 230 process_resident_memory_bytes 1.166630912e+09 231 # HELP prysm_version 232 # TYPE prysm_version gauge 233 prysm_version{buildDate="1619586241",commit="51eb1540fa838cdbe467bbeb0e36ee667d449377",version="v1.3.8-hotfix+6c0942"} 1 234 # HELP validator_count The total number of validators 235 # TYPE validator_count gauge 236 validator_count{state="Active"} 210301 237 validator_count{state="Exited"} 10 238 validator_count{state="Exiting"} 0 239 validator_count{state="Pending"} 0 240 validator_count{state="Slashed"} 0 241 validator_count{state="Slashing"} 0 242 # HELP beacon_head_slot Slot of the head block of the beacon chain 243 # TYPE beacon_head_slot gauge 244 beacon_head_slot 256552 245 # HELP beacon_clock_time_slot The current slot based on the genesis time and current clock 246 # TYPE beacon_clock_time_slot gauge 247 beacon_clock_time_slot 256552 248 # HELP bcnode_disk_beaconchain_bytes_total Total hard disk space used by the beaconchain database, in bytes. May include mmap. 249 # TYPE bcnode_disk_beaconchain_bytes_total gauge 250 bcnode_disk_beaconchain_bytes_total 7.365341184e+09 251 # HELP p2p_peer_count The number of peers in a given state. 252 # TYPE p2p_peer_count gauge 253 p2p_peer_count{state="Bad"} 1 254 p2p_peer_count{state="Connected"} 37 255 p2p_peer_count{state="Connecting"} 0 256 p2p_peer_count{state="Disconnected"} 62 257 p2p_peer_count{state="Disconnecting"} 0 258 # HELP powchain_sync_eth1_connected Boolean indicating whether a fallback eth1 endpoint is currently connected: 0=false, 1=true. 259 # TYPE powchain_sync_eth1_connected gauge 260 powchain_sync_eth1_connected 1 261 # HELP powchain_sync_eth1_fallback_configured Boolean recording whether a fallback eth1 endpoint was configured: 0=false, 1=true. 262 # TYPE powchain_sync_eth1_fallback_configured gauge 263 powchain_sync_eth1_fallback_configured 1 264 # HELP powchain_sync_eth1_fallback_connected Boolean indicating whether a fallback eth1 endpoint is currently connected: 0=false, 1=true. 265 # TYPE powchain_sync_eth1_fallback_connected gauge 266 powchain_sync_eth1_fallback_connected 1 267 ` 268 269 var statusFixtureOneOfEach = `# HELP validator_statuses validator statuses: 0 UNKNOWN, 1 DEPOSITED, 2 PENDING, 3 ACTIVE, 4 EXITING, 5 SLASHING, 6 EXITED 270 # TYPE validator_statuses gauge 271 validator_statuses{pubkey="pk0"} 0 272 validator_statuses{pubkey="pk1"} 1 273 validator_statuses{pubkey="pk2"} 2 274 validator_statuses{pubkey="pk3"} 3 275 validator_statuses{pubkey="pk4"} 4 276 validator_statuses{pubkey="pk5"} 5 277 validator_statuses{pubkey="pk6"} 6 278 ` 279 280 var statusFixtureAllActive = `# HELP validator_statuses validator statuses: 0 UNKNOWN, 1 DEPOSITED, 2 PENDING, 3 ACTIVE, 4 EXITING, 5 SLASHING, 6 EXITED 281 # TYPE validator_statuses gauge 282 validator_statuses{pubkey="pk0"} 3 283 validator_statuses{pubkey="pk1"} 3 284 validator_statuses{pubkey="pk2"} 3 285 validator_statuses{pubkey="pk3"} 3 286 ` 287 288 var statusFixtureNoneActive = `# HELP validator_statuses validator statuses: 0 UNKNOWN, 1 DEPOSITED, 2 PENDING, 3 ACTIVE, 4 EXITING, 5 SLASHING, 6 EXITED 289 # TYPE validator_statuses gauge 290 validator_statuses{pubkey="pk0"} 0 291 validator_statuses{pubkey="pk1"} 1 292 validator_statuses{pubkey="pk2"} 2 293 validator_statuses{pubkey="pk3"} 4 294 validator_statuses{pubkey="pk4"} 5 295 validator_statuses{pubkey="pk5"} 6 296 `