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  `