Skip to content

Commit a39a405

Browse files
authored
Add zcache key metrics (#101)
* feat: add metrics for existing and deleted cache items * test: gen. TaskMetrics mocks * test: add tests for cleanup metrics * fix: use consistent metric names * test: update tests & fix race condition in the test * fix: add error logs for updating metrics * chore: add mock generation process to README * fix: move readme to the correct location
1 parent 3318ca0 commit a39a405

File tree

5 files changed

+287
-15
lines changed

5 files changed

+287
-15
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# golem
2+
23
Zondax's opinionated golang lib for projects
34

45
## Test
56

67
The project contains unit tests you can execute them by running the following command:
78

8-
`go test -v [TEST FOLDER PATH]`
9+
`go test -v [TEST FOLDER PATH]`

pkg/metrics/readme.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,25 @@ metricsServer.IncrementMetric("my_gauge")
111111

112112
// Decrement a gauge
113113
metricsServer.DecrementMetric("my_gauge")
114-
```
114+
```
115+
116+
### Mocking Support
117+
118+
Use MockTaskMetrics for testing.
119+
120+
```go
121+
func TestMetrics(t *testing.T) {
122+
tm := &metrics.MockTaskMetrics{}
123+
tm.On("RegisterMetric", "local_cache_cleanup_errors", mock.Anything, []string{"error_type"}, mock.Anything).Once().Return(nil)
124+
// Use tm in your tests
125+
}
126+
127+
```
128+
129+
To generate mocks:
130+
131+
- install: https://github.com/vektra/mockery
132+
- pull the repo with the correct version of the interface you want to mock
133+
- ` mockery --name TaskMetrics --dir ./pkg/metrics --output . --filename task_metrics_mock.go --structname MockTaskMetrics --inpackage`
134+
- For usage in tests
135+

pkg/metrics/task_metrics_mock.go

Lines changed: 158 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/zcache/local_cache.go

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,28 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"time"
9+
810
"github.com/allegro/bigcache/v3"
911
"github.com/zondax/golem/pkg/metrics"
1012
"github.com/zondax/golem/pkg/metrics/collectors"
1113
"go.uber.org/zap"
12-
"time"
1314
)
1415

1516
const (
16-
neverExpires = -1
17-
errorTypeLabel = "error_type"
18-
cleanupErrorMetricKey = "localCacheCleanupErrors"
19-
iterationErrorLabel = "iteration_error"
20-
unmarshalErrorLabel = "unmarshal_error"
21-
deletionErrorLabel = "deletion_error"
17+
neverExpires = -1
18+
19+
errorTypeLabel = "error_type"
20+
itemCountLabel = "item_count"
21+
residentItemCountLabel = "resident_item_count"
22+
deletedItemCountLabel = "deleted_item_count"
23+
iterationErrorLabel = "iteration_error"
24+
unmarshalErrorLabel = "unmarshal_error"
25+
deletionErrorLabel = "deletion_error"
26+
27+
cleanupItemCountMetricKey = "local_cache_cleanup_item_count"
28+
cleanupDeletedItemCountMetricKey = "local_cache_cleanup_deleted_item_count"
29+
cleanupErrorMetricKey = "local_cache_cleanup_errors"
2230
)
2331

2432
type CacheItem struct {
@@ -164,6 +172,8 @@ func (c *localCache) startCleanupProcess(interval time.Duration) {
164172
func (c *localCache) cleanupExpiredKeys() {
165173
iterator := c.client.Iterator()
166174
var keysToDelete []string
175+
var totalDeleted int
176+
var totalResident int
167177

168178
for iterator.SetNext() {
169179
entry, err := iterator.Value()
@@ -178,34 +188,55 @@ func (c *localCache) cleanupExpiredKeys() {
178188
continue
179189
}
180190

191+
totalResident++
192+
181193
if cachedItem.IsExpired() {
182194
keysToDelete = append(keysToDelete, entry.Key())
183195
}
184196

185197
if len(keysToDelete) >= c.batchSize {
186-
c.deleteKeysInBatch(keysToDelete)
198+
totalDeleted += c.deleteKeysInBatch(keysToDelete)
187199
keysToDelete = keysToDelete[:0]
188200
time.Sleep(c.throttleTime)
189201
}
190202
}
191203

192204
if len(keysToDelete) > 0 {
193-
c.deleteKeysInBatch(keysToDelete)
205+
totalDeleted += c.deleteKeysInBatch(keysToDelete)
206+
}
207+
208+
// update metrics
209+
if err := c.metricsServer.UpdateMetric(cleanupItemCountMetricKey, float64(totalResident-totalDeleted), residentItemCountLabel); err != nil {
210+
c.logger.Error("Failed to update cleanup item count metric", zap.Error(err))
211+
}
212+
if err := c.metricsServer.UpdateMetric(cleanupDeletedItemCountMetricKey, float64(totalDeleted), deletedItemCountLabel); err != nil {
213+
c.logger.Error("Failed to update deletion cleanup deleted item count metric", zap.Error(err))
194214
}
195215
}
196216

197-
func (c *localCache) deleteKeysInBatch(keys []string) {
217+
func (c *localCache) deleteKeysInBatch(keys []string) (deleted int) {
198218
for _, key := range keys {
199219
if err := c.client.Delete(key); err != nil {
200220
if err = c.metricsServer.UpdateMetric(cleanupErrorMetricKey, 1, deletionErrorLabel); err != nil {
201221
c.logger.Error("Failed to update deletion error metric", zap.Error(err))
202222
}
223+
continue
203224
}
225+
deleted++
204226
}
227+
return
205228
}
206229

207230
func (c *localCache) registerCleanupMetrics() {
208231
if err := c.metricsServer.RegisterMetric(cleanupErrorMetricKey, "Counts different types of errors occurred during cache cleanup process", []string{errorTypeLabel}, &collectors.Counter{}); err != nil {
209232
c.logger.Error("Failed to register cleanup metrics", zap.Error(err))
210233
}
234+
235+
if err := c.metricsServer.RegisterMetric(cleanupItemCountMetricKey, "Counts the valid items in the cache during cache cleanup process", []string{itemCountLabel}, &collectors.Gauge{}); err != nil {
236+
c.logger.Error("Failed to register cleanup metrics", zap.Error(err))
237+
}
238+
239+
if err := c.metricsServer.RegisterMetric(cleanupDeletedItemCountMetricKey, "Counts the expired (deleted) items in the cache during cache cleanup process", []string{itemCountLabel}, &collectors.Gauge{}); err != nil {
240+
c.logger.Error("Failed to register cleanup metrics", zap.Error(err))
241+
}
211242
}

pkg/zcache/local_cache_test.go

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"github.com/allegro/bigcache/v3"
8-
"github.com/stretchr/testify/suite"
9-
"github.com/zondax/golem/pkg/metrics"
107
"os"
8+
"sync"
119
"testing"
1210
"time"
11+
12+
"github.com/allegro/bigcache/v3"
13+
"github.com/stretchr/testify/mock"
14+
"github.com/stretchr/testify/suite"
15+
"github.com/zondax/golem/pkg/metrics"
1316
)
1417

1518
const (
@@ -165,3 +168,61 @@ func (suite *LocalCacheTestSuite) TestCleanupProcessItemDoesNotExpire() {
165168
suite.NoError(err, "Did not expect an error when retrieving a non-expiring item")
166169
suite.Equal(value, result, "The retrieved value should match the original value")
167170
}
171+
172+
// insert 1 persistent key and 1 key with a ttl.
173+
// after cleanup, there will be 1 key in the cache and 1 deleted expired key.
174+
func (suite *LocalCacheTestSuite) TestCleanupProcessMetrics() {
175+
cleanupInterval := 1 * time.Second
176+
ttl := 10 * time.Millisecond
177+
178+
// label:count
179+
expected := map[string]int{
180+
"resident_item_count": 1,
181+
"deleted_item_count": 1,
182+
}
183+
got := sync.Map{}
184+
185+
tm := &metrics.MockTaskMetrics{}
186+
tm.On("RegisterMetric", "local_cache_cleanup_errors", mock.Anything, []string{"error_type"}, mock.Anything).Once().
187+
Return(nil)
188+
tm.On("RegisterMetric", "local_cache_cleanup_item_count", mock.Anything, []string{"item_count"}, mock.Anything).Once().
189+
Return(nil)
190+
tm.On("RegisterMetric", "local_cache_cleanup_deleted_item_count", mock.Anything, []string{"item_count"}, mock.Anything).Once().
191+
Return(nil)
192+
193+
tm.On("UpdateMetric", "local_cache_cleanup_errors", mock.Anything, mock.Anything).Return(nil)
194+
tm.On("UpdateMetric", "local_cache_cleanup_item_count", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
195+
total := args.Get(1).(float64)
196+
label := args.Get(2).(string)
197+
got.Store(label, int(total))
198+
}).Return(nil)
199+
tm.On("UpdateMetric", "local_cache_cleanup_deleted_item_count", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
200+
total := args.Get(1).(float64)
201+
label := args.Get(2).(string)
202+
got.Store(label, int(total))
203+
}).Return(nil)
204+
205+
cache, err := NewLocalCache(&LocalConfig{
206+
Prefix: "test",
207+
CleanupInterval: cleanupInterval,
208+
MetricServer: tm,
209+
})
210+
suite.NoError(err)
211+
212+
ctx := context.Background()
213+
key := "permanentKey"
214+
value := "thisValueShouldPersist"
215+
216+
err = cache.Set(ctx, key, value, ttl)
217+
suite.NoError(err)
218+
err = cache.Set(ctx, key+"2", value, neverExpires)
219+
suite.NoError(err)
220+
221+
time.Sleep(2 * cleanupInterval)
222+
for k, v := range expected {
223+
gotV, ok := got.Load(k)
224+
suite.Assert().True(ok)
225+
226+
suite.Assert().Equal(v, gotV)
227+
}
228+
}

0 commit comments

Comments
 (0)