From 625cbcf7de9e37490121065da620c3d4e9effff9 Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Thu, 24 Feb 2022 12:50:01 -0500 Subject: [PATCH] feat(dot/state): `gossamer_storage_tries_cached_total` gauge metric (#2272) --- dot/rpc/modules/dev_integration_test.go | 3 +- dot/state/block_finalisation_test.go | 25 +++- dot/state/block_notify_test.go | 34 +++-- dot/state/block_race_test.go | 2 +- dot/state/block_test.go | 22 +++- dot/state/epoch_test.go | 5 +- dot/state/initialize.go | 5 +- dot/state/mock_gauge_test.go | 160 ++++++++++++++++++++++++ dot/state/offline_pruner.go | 5 +- dot/state/service.go | 6 +- dot/state/storage_notify_test.go | 37 +++++- dot/state/storage_test.go | 68 ++++++++-- dot/state/tries.go | 22 +++- dot/state/tries_test.go | 109 ++++++++++------ lib/grandpa/grandpa_test.go | 3 +- 15 files changed, 431 insertions(+), 75 deletions(-) create mode 100644 dot/state/mock_gauge_test.go diff --git a/dot/rpc/modules/dev_integration_test.go b/dot/rpc/modules/dev_integration_test.go index 34c8d0a198..d057ee853f 100644 --- a/dot/rpc/modules/dev_integration_test.go +++ b/dot/rpc/modules/dev_integration_test.go @@ -43,7 +43,8 @@ func newState(t *testing.T) (*state.BlockState, *state.EpochState) { db := state.NewInMemoryDB(t) _, genesisTrie, genesisHeader := genesis.NewTestGenesisWithTrieAndHeader(t) - tries := state.NewTries(genesisTrie) + tries, err := state.NewTries(genesisTrie) + require.NoError(t, err) bs, err := state.NewBlockStateFromGenesis(db, tries, genesisHeader, telemetryMock) require.NoError(t, err) es, err := state.NewEpochStateFromGenesis(db, bs, genesisBABEConfig) diff --git a/dot/state/block_finalisation_test.go b/dot/state/block_finalisation_test.go index b55466c374..5020f46e28 100644 --- a/dot/state/block_finalisation_test.go +++ b/dot/state/block_finalisation_test.go @@ -8,6 +8,9 @@ import ( "testing" "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/trie" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) @@ -61,7 +64,16 @@ func TestHighestRoundAndSetID(t *testing.T) { } func TestBlockState_SetFinalisedHash(t *testing.T) { - bs := newTestBlockState(t, testGenesisHeader, newTriesEmpty()) + ctrl := gomock.NewController(t) + + triesGauge := NewMockGauge(ctrl) + triesGauge.EXPECT().Set(0.00) + tries := &Tries{ + rootToTrie: make(map[common.Hash]*trie.Trie), + triesGauge: triesGauge, + } + + bs := newTestBlockState(t, testGenesisHeader, tries) h, err := bs.GetFinalisedHash(0, 0) require.NoError(t, err) require.Equal(t, testGenesisHeader.Hash(), h) @@ -97,7 +109,16 @@ func TestBlockState_SetFinalisedHash(t *testing.T) { } func TestSetFinalisedHash_setFirstSlotOnFinalisation(t *testing.T) { - bs := newTestBlockState(t, testGenesisHeader, newTriesEmpty()) + ctrl := gomock.NewController(t) + + triesGauge := NewMockGauge(ctrl) + triesGauge.EXPECT().Set(0.00).Times(2) + tries := &Tries{ + rootToTrie: make(map[common.Hash]*trie.Trie), + triesGauge: triesGauge, + } + + bs := newTestBlockState(t, testGenesisHeader, tries) firstSlot := uint64(42069) digest := types.NewDigest() diff --git a/dot/state/block_notify_test.go b/dot/state/block_notify_test.go index f476e8836e..ab12805a63 100644 --- a/dot/state/block_notify_test.go +++ b/dot/state/block_notify_test.go @@ -10,16 +10,18 @@ import ( "time" "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/runtime" runtimemocks "github.com/ChainSafe/gossamer/lib/runtime/mocks" "github.com/ChainSafe/gossamer/lib/trie" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) var testMessageTimeout = time.Second * 3 func TestImportChannel(t *testing.T) { - bs := newTestBlockState(t, testGenesisHeader, NewTries(trie.NewEmptyTrie())) + bs := newTestBlockState(t, testGenesisHeader, newTriesEmpty()) ch := bs.GetImportedBlockNotifierChannel() defer bs.FreeImportedBlockNotifierChannel(ch) @@ -36,7 +38,7 @@ func TestImportChannel(t *testing.T) { } func TestFreeImportedBlockNotifierChannel(t *testing.T) { - bs := newTestBlockState(t, testGenesisHeader, NewTries(trie.NewEmptyTrie())) + bs := newTestBlockState(t, testGenesisHeader, newTriesEmpty()) ch := bs.GetImportedBlockNotifierChannel() require.Equal(t, 1, len(bs.imported)) @@ -45,7 +47,16 @@ func TestFreeImportedBlockNotifierChannel(t *testing.T) { } func TestFinalizedChannel(t *testing.T) { - bs := newTestBlockState(t, testGenesisHeader, NewTries(trie.NewEmptyTrie())) + ctrl := gomock.NewController(t) + + triesGauge := NewMockGauge(ctrl) + triesGauge.EXPECT().Set(0.00).Times(3) + tries := &Tries{ + rootToTrie: make(map[common.Hash]*trie.Trie), + triesGauge: triesGauge, + } + + bs := newTestBlockState(t, testGenesisHeader, tries) ch := bs.GetFinalisedNotifierChannel() @@ -67,7 +78,7 @@ func TestFinalizedChannel(t *testing.T) { } func TestImportChannel_Multi(t *testing.T) { - bs := newTestBlockState(t, testGenesisHeader, NewTries(trie.NewEmptyTrie())) + bs := newTestBlockState(t, testGenesisHeader, newTriesEmpty()) num := 5 chs := make([]chan *types.Block, num) @@ -100,7 +111,16 @@ func TestImportChannel_Multi(t *testing.T) { } func TestFinalizedChannel_Multi(t *testing.T) { - bs := newTestBlockState(t, testGenesisHeader, NewTries(trie.NewEmptyTrie())) + ctrl := gomock.NewController(t) + + triesGauge := NewMockGauge(ctrl) + triesGauge.EXPECT().Set(0.00) + tries := &Tries{ + rootToTrie: make(map[common.Hash]*trie.Trie), + triesGauge: triesGauge, + } + + bs := newTestBlockState(t, testGenesisHeader, tries) num := 5 chs := make([]chan *types.FinalisationInfo, num) @@ -137,7 +157,7 @@ func TestFinalizedChannel_Multi(t *testing.T) { } func TestService_RegisterUnRegisterRuntimeUpdatedChannel(t *testing.T) { - bs := newTestBlockState(t, testGenesisHeader, NewTries(trie.NewEmptyTrie())) + bs := newTestBlockState(t, testGenesisHeader, newTriesEmpty()) ch := make(chan<- runtime.Version) chID, err := bs.RegisterRuntimeUpdatedChannel(ch) require.NoError(t, err) @@ -148,7 +168,7 @@ func TestService_RegisterUnRegisterRuntimeUpdatedChannel(t *testing.T) { } func TestService_RegisterUnRegisterConcurrentCalls(t *testing.T) { - bs := newTestBlockState(t, testGenesisHeader, NewTries(trie.NewEmptyTrie())) + bs := newTestBlockState(t, testGenesisHeader, newTriesEmpty()) go func() { for i := 0; i < 100; i++ { diff --git a/dot/state/block_race_test.go b/dot/state/block_race_test.go index c5bc2c1bc6..80d8aa3657 100644 --- a/dot/state/block_race_test.go +++ b/dot/state/block_race_test.go @@ -28,7 +28,7 @@ func TestConcurrencySetHeader(t *testing.T) { dbs[i] = NewInMemoryDB(t) } - tries := NewTries(trie.NewEmptyTrie()) // not used in this test + tries := (*Tries)(nil) // not used in this test pend := new(sync.WaitGroup) pend.Add(threads) diff --git a/dot/state/block_test.go b/dot/state/block_test.go index 45508e8a53..ad96b93399 100644 --- a/dot/state/block_test.go +++ b/dot/state/block_test.go @@ -262,7 +262,16 @@ func TestAddBlock_BlockNumberToHash(t *testing.T) { } func TestFinalization_DeleteBlock(t *testing.T) { - bs := newTestBlockState(t, testGenesisHeader, newTriesEmpty()) + ctrl := gomock.NewController(t) + + triesGauge := NewMockGauge(ctrl) + triesGauge.EXPECT().Set(0.00).Times(5) + tries := &Tries{ + rootToTrie: make(map[common.Hash]*trie.Trie), + triesGauge: triesGauge, + } + + bs := newTestBlockState(t, testGenesisHeader, tries) AddBlocksToState(t, bs, 5, false) btBefore := bs.bt.DeepCopy() @@ -473,7 +482,16 @@ func TestAddBlockToBlockTree(t *testing.T) { } func TestNumberIsFinalised(t *testing.T) { - bs := newTestBlockState(t, testGenesisHeader, newTriesEmpty()) + ctrl := gomock.NewController(t) + + triesGauge := NewMockGauge(ctrl) + triesGauge.EXPECT().Set(0.00).Times(2) + tries := &Tries{ + rootToTrie: make(map[common.Hash]*trie.Trie), + triesGauge: triesGauge, + } + + bs := newTestBlockState(t, testGenesisHeader, tries) fin, err := bs.NumberIsFinalised(big.NewInt(0)) require.NoError(t, err) require.True(t, fin) diff --git a/dot/state/epoch_test.go b/dot/state/epoch_test.go index de3467f7a2..24946c93c4 100644 --- a/dot/state/epoch_test.go +++ b/dot/state/epoch_test.go @@ -11,7 +11,6 @@ import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/lib/keystore" - "github.com/ChainSafe/gossamer/lib/trie" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" @@ -29,7 +28,7 @@ var genesisBABEConfig = &types.BabeConfiguration{ func newEpochStateFromGenesis(t *testing.T) *EpochState { db := NewInMemoryDB(t) - blockState := newTestBlockState(t, nil, NewTries(trie.NewEmptyTrie())) + blockState := newTestBlockState(t, nil, newTriesEmpty()) s, err := NewEpochStateFromGenesis(db, blockState, genesisBABEConfig) require.NoError(t, err) return s @@ -186,7 +185,7 @@ func TestEpochState_SetAndGetSlotDuration(t *testing.T) { func TestEpochState_GetEpochFromTime(t *testing.T) { s := newEpochStateFromGenesis(t) - s.blockState = newTestBlockState(t, testGenesisHeader, NewTries(trie.NewEmptyTrie())) + s.blockState = newTestBlockState(t, testGenesisHeader, newTriesEmpty()) epochDuration, err := time.ParseDuration( fmt.Sprintf("%dms", diff --git a/dot/state/initialize.go b/dot/state/initialize.go index 7b14afb6f2..3769303423 100644 --- a/dot/state/initialize.go +++ b/dot/state/initialize.go @@ -62,7 +62,10 @@ func (s *Service) Initialise(gen *genesis.Genesis, header *types.Header, t *trie return fmt.Errorf("failed to write genesis values to database: %s", err) } - tries := NewTries(t) + tries, err := NewTries(t) + if err != nil { + return fmt.Errorf("cannot setup tries: %w", err) + } // create block state from genesis block blockState, err := NewBlockStateFromGenesis(db, tries, header, s.Telemetry) diff --git a/dot/state/mock_gauge_test.go b/dot/state/mock_gauge_test.go new file mode 100644 index 0000000000..0cf4274d1b --- /dev/null +++ b/dot/state/mock_gauge_test.go @@ -0,0 +1,160 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/prometheus/client_golang/prometheus (interfaces: Gauge) + +// Package state is a generated GoMock package. +package state + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + prometheus "github.com/prometheus/client_golang/prometheus" + io_prometheus_client "github.com/prometheus/client_model/go" +) + +// MockGauge is a mock of Gauge interface. +type MockGauge struct { + ctrl *gomock.Controller + recorder *MockGaugeMockRecorder +} + +// MockGaugeMockRecorder is the mock recorder for MockGauge. +type MockGaugeMockRecorder struct { + mock *MockGauge +} + +// NewMockGauge creates a new mock instance. +func NewMockGauge(ctrl *gomock.Controller) *MockGauge { + mock := &MockGauge{ctrl: ctrl} + mock.recorder = &MockGaugeMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGauge) EXPECT() *MockGaugeMockRecorder { + return m.recorder +} + +// Add mocks base method. +func (m *MockGauge) Add(arg0 float64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Add", arg0) +} + +// Add indicates an expected call of Add. +func (mr *MockGaugeMockRecorder) Add(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockGauge)(nil).Add), arg0) +} + +// Collect mocks base method. +func (m *MockGauge) Collect(arg0 chan<- prometheus.Metric) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Collect", arg0) +} + +// Collect indicates an expected call of Collect. +func (mr *MockGaugeMockRecorder) Collect(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Collect", reflect.TypeOf((*MockGauge)(nil).Collect), arg0) +} + +// Dec mocks base method. +func (m *MockGauge) Dec() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Dec") +} + +// Dec indicates an expected call of Dec. +func (mr *MockGaugeMockRecorder) Dec() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Dec", reflect.TypeOf((*MockGauge)(nil).Dec)) +} + +// Desc mocks base method. +func (m *MockGauge) Desc() *prometheus.Desc { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Desc") + ret0, _ := ret[0].(*prometheus.Desc) + return ret0 +} + +// Desc indicates an expected call of Desc. +func (mr *MockGaugeMockRecorder) Desc() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Desc", reflect.TypeOf((*MockGauge)(nil).Desc)) +} + +// Describe mocks base method. +func (m *MockGauge) Describe(arg0 chan<- *prometheus.Desc) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Describe", arg0) +} + +// Describe indicates an expected call of Describe. +func (mr *MockGaugeMockRecorder) Describe(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Describe", reflect.TypeOf((*MockGauge)(nil).Describe), arg0) +} + +// Inc mocks base method. +func (m *MockGauge) Inc() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Inc") +} + +// Inc indicates an expected call of Inc. +func (mr *MockGaugeMockRecorder) Inc() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Inc", reflect.TypeOf((*MockGauge)(nil).Inc)) +} + +// Set mocks base method. +func (m *MockGauge) Set(arg0 float64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Set", arg0) +} + +// Set indicates an expected call of Set. +func (mr *MockGaugeMockRecorder) Set(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockGauge)(nil).Set), arg0) +} + +// SetToCurrentTime mocks base method. +func (m *MockGauge) SetToCurrentTime() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetToCurrentTime") +} + +// SetToCurrentTime indicates an expected call of SetToCurrentTime. +func (mr *MockGaugeMockRecorder) SetToCurrentTime() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetToCurrentTime", reflect.TypeOf((*MockGauge)(nil).SetToCurrentTime)) +} + +// Sub mocks base method. +func (m *MockGauge) Sub(arg0 float64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Sub", arg0) +} + +// Sub indicates an expected call of Sub. +func (mr *MockGaugeMockRecorder) Sub(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sub", reflect.TypeOf((*MockGauge)(nil).Sub), arg0) +} + +// Write mocks base method. +func (m *MockGauge) Write(arg0 *io_prometheus_client.Metric) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Write indicates an expected call of Write. +func (mr *MockGaugeMockRecorder) Write(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockGauge)(nil).Write), arg0) +} diff --git a/dot/state/offline_pruner.go b/dot/state/offline_pruner.go index ee45e4d612..3a0e8ca794 100644 --- a/dot/state/offline_pruner.go +++ b/dot/state/offline_pruner.go @@ -41,7 +41,10 @@ func NewOfflinePruner(inputDBPath, prunedDBPath string, bloomSize uint64, return nil, fmt.Errorf("failed to load DB %w", err) } - tries := NewTries(trie.NewEmptyTrie()) + tries, err := NewTries(trie.NewEmptyTrie()) + if err != nil { + return nil, fmt.Errorf("cannot setup tries: %w", err) + } // create blockState state // NewBlockState on pruner execution does not use telemetry diff --git a/dot/state/service.go b/dot/state/service.go index 75b4ae587f..4960a1b6f7 100644 --- a/dot/state/service.go +++ b/dot/state/service.go @@ -114,9 +114,11 @@ func (s *Service) Start() error { return nil } - tries := NewTries(trie.NewEmptyTrie()) + tries, err := NewTries(trie.NewEmptyTrie()) + if err != nil { + return fmt.Errorf("cannot create tries: %w", err) + } - var err error // create block state s.Block, err = NewBlockState(s.db, tries, s.Telemetry) if err != nil { diff --git a/dot/state/storage_notify_test.go b/dot/state/storage_notify_test.go index e7f7a0a943..509681c2d3 100644 --- a/dot/state/storage_notify_test.go +++ b/dot/state/storage_notify_test.go @@ -13,12 +13,23 @@ import ( "github.com/ChainSafe/chaindb" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/trie" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) func TestStorageState_RegisterStorageObserver(t *testing.T) { - ss := newTestStorageState(t) + ctrl := gomock.NewController(t) + + triesGauge := NewMockGauge(ctrl) + triesGauge.EXPECT().Inc().Times(2) + tries := &Tries{ + rootToTrie: make(map[common.Hash]*trie.Trie), + triesGauge: triesGauge, + } + + ss := newTestStorageState(t, tries) ts, err := ss.TrieState(nil) require.NoError(t, err) @@ -53,7 +64,16 @@ func TestStorageState_RegisterStorageObserver(t *testing.T) { } func TestStorageState_RegisterStorageObserver_Multi(t *testing.T) { - ss := newTestStorageState(t) + ctrl := gomock.NewController(t) + + triesGauge := NewMockGauge(ctrl) + triesGauge.EXPECT().Inc().Times(2) + tries := &Tries{ + rootToTrie: make(map[common.Hash]*trie.Trie), + triesGauge: triesGauge, + } + + ss := newTestStorageState(t, tries) ts, err := ss.TrieState(nil) require.NoError(t, err) @@ -105,7 +125,18 @@ func TestStorageState_RegisterStorageObserver_Multi(t *testing.T) { func TestStorageState_RegisterStorageObserver_Multi_Filter(t *testing.T) { t.Skip() // this seems to fail often on CI - ss := newTestStorageState(t) + ctrl := gomock.NewController(t) + + triesGauge := NewMockGauge(ctrl) + // TODO restrict those mock calls once test is fixed. + triesGauge.EXPECT().Inc().AnyTimes() + triesGauge.EXPECT().Set(gomock.Any()).AnyTimes() + tries := &Tries{ + rootToTrie: make(map[common.Hash]*trie.Trie), + triesGauge: triesGauge, + } + + ss := newTestStorageState(t, tries) ts, err := ss.TrieState(nil) require.NoError(t, err) diff --git a/dot/state/storage_test.go b/dot/state/storage_test.go index cc0c756277..676c8e2703 100644 --- a/dot/state/storage_test.go +++ b/dot/state/storage_test.go @@ -21,9 +21,8 @@ import ( "github.com/stretchr/testify/require" ) -func newTestStorageState(t *testing.T) *StorageState { +func newTestStorageState(t *testing.T, tries *Tries) *StorageState { db := NewInMemoryDB(t) - tries := newTriesEmpty() bs := newTestBlockState(t, testGenesisHeader, tries) @@ -33,7 +32,16 @@ func newTestStorageState(t *testing.T) *StorageState { } func TestStorage_StoreAndLoadTrie(t *testing.T) { - storage := newTestStorageState(t) + ctrl := gomock.NewController(t) + + triesGauge := NewMockGauge(ctrl) + triesGauge.EXPECT().Inc() + tries := &Tries{ + rootToTrie: make(map[common.Hash]*trie.Trie), + triesGauge: triesGauge, + } + + storage := newTestStorageState(t, tries) ts, err := storage.TrieState(&trie.EmptyHash) require.NoError(t, err) @@ -53,7 +61,16 @@ func TestStorage_StoreAndLoadTrie(t *testing.T) { } func TestStorage_GetStorageByBlockHash(t *testing.T) { - storage := newTestStorageState(t) + ctrl := gomock.NewController(t) + + triesGauge := NewMockGauge(ctrl) + triesGauge.EXPECT().Inc().Times(2) + tries := &Tries{ + rootToTrie: make(map[common.Hash]*trie.Trie), + triesGauge: triesGauge, + } + + storage := newTestStorageState(t, tries) ts, err := storage.TrieState(&trie.EmptyHash) require.NoError(t, err) @@ -88,7 +105,17 @@ func TestStorage_GetStorageByBlockHash(t *testing.T) { } func TestStorage_TrieState(t *testing.T) { - storage := newTestStorageState(t) + ctrl := gomock.NewController(t) + + triesGauge := NewMockGauge(ctrl) + triesGauge.EXPECT().Inc().Times(3) + triesGauge.EXPECT().Set(1.00).Times(1) + tries := &Tries{ + rootToTrie: make(map[common.Hash]*trie.Trie), + triesGauge: triesGauge, + } + + storage := newTestStorageState(t, tries) ts, err := storage.TrieState(&trie.EmptyHash) require.NoError(t, err) ts.Set([]byte("noot"), []byte("washere")) @@ -108,7 +135,17 @@ func TestStorage_TrieState(t *testing.T) { } func TestStorage_LoadFromDB(t *testing.T) { - storage := newTestStorageState(t) + ctrl := gomock.NewController(t) + + triesGauge := NewMockGauge(ctrl) + triesGauge.EXPECT().Inc().Times(4) + triesGauge.EXPECT().Set(1.00).Times(3) + tries := &Tries{ + rootToTrie: make(map[common.Hash]*trie.Trie), + triesGauge: triesGauge, + } + + storage := newTestStorageState(t, tries) ts, err := storage.TrieState(&trie.EmptyHash) require.NoError(t, err) @@ -153,7 +190,16 @@ func TestStorage_LoadFromDB(t *testing.T) { } func TestStorage_StoreTrie_NotSyncing(t *testing.T) { - storage := newTestStorageState(t) + ctrl := gomock.NewController(t) + + triesGauge := NewMockGauge(ctrl) + triesGauge.EXPECT().Inc().Times(2) + tries := &Tries{ + rootToTrie: make(map[common.Hash]*trie.Trie), + triesGauge: triesGauge, + } + + storage := newTestStorageState(t, tries) ts, err := storage.TrieState(&trie.EmptyHash) require.NoError(t, err) @@ -187,7 +233,13 @@ func TestGetStorageChildAndGetStorageFromChild(t *testing.T) { err = genTrie.PutChild([]byte("keyToChild"), testChildTrie) require.NoError(t, err) - tries := NewTries(genTrie) + triesGauge := NewMockGauge(ctrl) + triesGauge.EXPECT().Inc().Times(2) + triesGauge.EXPECT().Set(0.00) + tries := &Tries{ + rootToTrie: make(map[common.Hash]*trie.Trie), + triesGauge: triesGauge, + } blockState, err := NewBlockStateFromGenesis(db, tries, genHeader, telemetryMock) require.NoError(t, err) diff --git a/dot/state/tries.go b/dot/state/tries.go index 21342f4d67..68fff7aeda 100644 --- a/dot/state/tries.go +++ b/dot/state/tries.go @@ -4,10 +4,13 @@ package state import ( + "errors" + "fmt" "sync" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/trie" + "github.com/prometheus/client_golang/prometheus" ) // Tries is a thread safe map of root hash @@ -15,16 +18,27 @@ import ( type Tries struct { rootToTrie map[common.Hash]*trie.Trie mapMutex sync.RWMutex + triesGauge prometheus.Gauge } // NewTries creates a new thread safe map of root hash // to trie using the trie given as a first trie. -func NewTries(t *trie.Trie) *Tries { +func NewTries(t *trie.Trie) (trs *Tries, err error) { + triesGauge := prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: "gossamer_storage", + Name: "tries_cached_total", + Help: "total number of tries cached in memory", + }) + err = prometheus.Register(triesGauge) + if err != nil && !errors.As(err, &prometheus.AlreadyRegisteredError{}) { + return nil, fmt.Errorf("cannot register tries gauge: %w", err) + } return &Tries{ rootToTrie: map[common.Hash]*trie.Trie{ t.MustHash(): t, }, - } + triesGauge: triesGauge, + }, nil } // softSet sets the given trie at the given root hash @@ -38,6 +52,7 @@ func (t *Tries) softSet(root common.Hash, trie *trie.Trie) { return } + t.triesGauge.Inc() t.rootToTrie[root] = trie } @@ -45,6 +60,9 @@ func (t *Tries) delete(root common.Hash) { t.mapMutex.Lock() defer t.mapMutex.Unlock() delete(t.rootToTrie, root) + // Note we use .Set instead of .Dec in case nothing + // was deleted since nothing existed at the hash given. + t.triesGauge.Set(float64(len(t.rootToTrie))) } // get retrieves the trie corresponding to the root hash given diff --git a/dot/state/tries_test.go b/dot/state/tries_test.go index 5e3b90e7c1..58e0492479 100644 --- a/dot/state/tries_test.go +++ b/dot/state/tries_test.go @@ -9,7 +9,10 @@ import ( "github.com/ChainSafe/gossamer/internal/trie/node" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/trie" + "github.com/golang/mock/gomock" + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func Test_NewTries(t *testing.T) { @@ -17,50 +20,52 @@ func Test_NewTries(t *testing.T) { tr := trie.NewEmptyTrie() - rootToTrie := NewTries(tr) + rootToTrie, err := NewTries(tr) + require.NoError(t, err) expectedTries := &Tries{ rootToTrie: map[common.Hash]*trie.Trie{ tr.MustHash(): tr, }, + triesGauge: prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: "gossamer_storage", + Name: "tries_cached_total", + Help: "total number of tries cached in memory", + }), } assert.Equal(t, expectedTries, rootToTrie) } +//go:generate mockgen -destination=mock_gauge_test.go -package $GOPACKAGE github.com/prometheus/client_golang/prometheus Gauge + func Test_Tries_softSet(t *testing.T) { t.Parallel() testCases := map[string]struct { - tries *Tries - root common.Hash - trie *trie.Trie - expectedTries *Tries + rootToTrie map[common.Hash]*trie.Trie + root common.Hash + trie *trie.Trie + triesGaugeInc bool + expectedRootToTrie map[common.Hash]*trie.Trie }{ "set new in map": { - tries: &Tries{ - rootToTrie: map[common.Hash]*trie.Trie{}, - }, - root: common.Hash{1, 2, 3}, - trie: trie.NewEmptyTrie(), - expectedTries: &Tries{ - rootToTrie: map[common.Hash]*trie.Trie{ - {1, 2, 3}: trie.NewEmptyTrie(), - }, + rootToTrie: map[common.Hash]*trie.Trie{}, + root: common.Hash{1, 2, 3}, + trie: trie.NewEmptyTrie(), + triesGaugeInc: true, + expectedRootToTrie: map[common.Hash]*trie.Trie{ + {1, 2, 3}: trie.NewEmptyTrie(), }, }, "do not override in map": { - tries: &Tries{ - rootToTrie: map[common.Hash]*trie.Trie{ - {1, 2, 3}: {}, - }, + rootToTrie: map[common.Hash]*trie.Trie{ + {1, 2, 3}: {}, }, root: common.Hash{1, 2, 3}, trie: trie.NewEmptyTrie(), - expectedTries: &Tries{ - rootToTrie: map[common.Hash]*trie.Trie{ - {1, 2, 3}: {}, - }, + expectedRootToTrie: map[common.Hash]*trie.Trie{ + {1, 2, 3}: {}, }, }, } @@ -69,10 +74,21 @@ func Test_Tries_softSet(t *testing.T) { testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() + ctrl := gomock.NewController(t) - testCase.tries.softSet(testCase.root, testCase.trie) + triesGauge := NewMockGauge(ctrl) + if testCase.triesGaugeInc { + triesGauge.EXPECT().Inc() + } - assert.Equal(t, testCase.expectedTries, testCase.tries) + tries := &Tries{ + rootToTrie: testCase.rootToTrie, + triesGauge: triesGauge, + } + + tries.softSet(testCase.root, testCase.trie) + + assert.Equal(t, testCase.expectedRootToTrie, tries.rootToTrie) }) } } @@ -81,28 +97,30 @@ func Test_Tries_delete(t *testing.T) { t.Parallel() testCases := map[string]struct { - tries *Tries - root common.Hash - expectedTries *Tries + rootToTrie map[common.Hash]*trie.Trie + root common.Hash + triesGaugeSet float64 + expectedRootToTrie map[common.Hash]*trie.Trie }{ "not found": { - tries: &Tries{ - rootToTrie: map[common.Hash]*trie.Trie{}, + rootToTrie: map[common.Hash]*trie.Trie{ + {3, 4, 5}: {}, }, - root: common.Hash{1, 2, 3}, - expectedTries: &Tries{ - rootToTrie: map[common.Hash]*trie.Trie{}, + root: common.Hash{1, 2, 3}, + triesGaugeSet: 1, + expectedRootToTrie: map[common.Hash]*trie.Trie{ + {3, 4, 5}: {}, }, }, "deleted": { - tries: &Tries{ - rootToTrie: map[common.Hash]*trie.Trie{ - {1, 2, 3}: {}, - }, + rootToTrie: map[common.Hash]*trie.Trie{ + {1, 2, 3}: {}, + {3, 4, 5}: {}, }, - root: common.Hash{1, 2, 3}, - expectedTries: &Tries{ - rootToTrie: map[common.Hash]*trie.Trie{}, + root: common.Hash{1, 2, 3}, + triesGaugeSet: 1, + expectedRootToTrie: map[common.Hash]*trie.Trie{ + {3, 4, 5}: {}, }, }, } @@ -111,10 +129,19 @@ func Test_Tries_delete(t *testing.T) { testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() + ctrl := gomock.NewController(t) + + triesGauge := NewMockGauge(ctrl) + triesGauge.EXPECT().Set(testCase.triesGaugeSet) + + tries := &Tries{ + rootToTrie: testCase.rootToTrie, + triesGauge: triesGauge, + } - testCase.tries.delete(testCase.root) + tries.delete(testCase.root) - assert.Equal(t, testCase.expectedTries, testCase.tries) + assert.Equal(t, testCase.expectedRootToTrie, tries.rootToTrie) }) } } diff --git a/lib/grandpa/grandpa_test.go b/lib/grandpa/grandpa_test.go index 14a60f0714..80488e2ac9 100644 --- a/lib/grandpa/grandpa_test.go +++ b/lib/grandpa/grandpa_test.go @@ -60,7 +60,8 @@ func newTestState(t *testing.T) *state.Service { t.Cleanup(func() { db.Close() }) _, genTrie, _ := genesis.NewTestGenesisWithTrieAndHeader(t) - tries := state.NewTries(genTrie) + tries, err := state.NewTries(genTrie) + require.NoError(t, err) block, err := state.NewBlockStateFromGenesis(db, tries, testGenesisHeader, telemetryMock) require.NoError(t, err)