Skip to content

Commit

Permalink
Instrumented cache decorator
Browse files Browse the repository at this point in the history
  • Loading branch information
miguelreiswildlife committed Sep 26, 2023
1 parent 2720b21 commit 7494bb2
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 3 deletions.
103 changes: 103 additions & 0 deletions leaderboard/enriching/cache/cache_instrumented.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package cache

import (
"context"
"github.com/opentracing/opentracing-go"
extensions "github.com/topfreegames/extensions/middleware"
"github.com/topfreegames/podium/leaderboard/v2/enriching"
"github.com/topfreegames/podium/leaderboard/v2/model"
"time"
)

const (
enrichmentCacheGetTimingMilli = "enrichment_cache_get_duration_milliseconds"
enrichmentCacheGets = "enrichment_cache_gets"
enrichmentCacheHits = "enrichment_cache_gets"
enrichmentCacheGetErrors = "enrichment_cache_get_errors"
enrichmentCacheSetTimingMilli = "enrichment_cache_set_duration_milliseconds"
enrichmentCacheSets = "enrichment_cache_sets"
enrichmentCacheSetErrors = "enrichment_cache_set_errors"
)

type instrumentedCache struct {
impl enriching.EnricherCache
metricsReporter extensions.MetricsReporter
}

// NewInstrumentedCache returns an EnrichCache implementation wrapped
// with metrics reporting and tracing
func NewInstrumentedCache(
impl enriching.EnricherCache,
metricsReporter extensions.MetricsReporter,
) enriching.EnricherCache {
return &instrumentedCache{
impl: impl,
metricsReporter: metricsReporter,
}
}

func (c *instrumentedCache) Get(
ctx context.Context,
tenantID,
leaderboardID string,
members []*model.Member,
) (map[string]map[string]string, bool, error) {
start := time.Now()

span, ctx := opentracing.StartSpanFromContext(ctx, "podium.enriching_cache", opentracing.Tags{
"tenant_id": tenantID,
"leaderboard_id": leaderboardID,
})
defer span.Finish()

metadata, hit, err := c.impl.Get(ctx, tenantID, leaderboardID, members)

c.metricsReporter.Increment(enrichmentCacheGets)
c.metricsReporter.Timing(enrichmentCacheGetTimingMilli, time.Since(start))

if err != nil {
span.SetTag("error", true)
span.SetTag("error.message", err.Error())

c.metricsReporter.Increment(enrichmentCacheGetErrors)
}

if hit {
c.metricsReporter.Increment(enrichmentCacheHits)
}

return metadata, hit, err
}

func (c *instrumentedCache) Set(
ctx context.Context,
tenantID,
leaderboardID string,
members []*model.Member,
ttl time.Duration,
) error {
start := time.Now()

span, ctx := opentracing.StartSpanFromContext(ctx, "podium.enriching_cache", opentracing.Tags{
"tenant_id": tenantID,
"leaderboard_id": leaderboardID,
"ttl": ttl,
})
defer span.Finish()

err := c.impl.Set(ctx, tenantID, leaderboardID, members, ttl)

c.metricsReporter.Increment(enrichmentCacheSets)
c.metricsReporter.Timing(enrichmentCacheSetTimingMilli, time.Since(start))

if err != nil {
span.SetTag("error", true)
span.SetTag("error.message", err.Error())

c.metricsReporter.Increment(enrichmentCacheSetErrors)
}

return err
}

var _ enriching.EnricherCache = &instrumentedCache{}
124 changes: 124 additions & 0 deletions leaderboard/enriching/cache/cache_instrumented_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package cache

import (
"context"
"errors"
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
mock_extensions "github.com/topfreegames/extensions/middleware/mocks"
mock_enriching "github.com/topfreegames/podium/leaderboard/v2/mocks"
"github.com/topfreegames/podium/leaderboard/v2/model"
)

var _ = Describe("Instrumented enrich cache Get tests", func() {
tenantID := "tenant-id"
leaderboardID := "leaderboard-id"
members := []*model.Member{
{
PublicID: "member1",
},
}

result := map[string]map[string]string{
"member1": {
"field1": "value1",
},
}

It("should send metrics when Get is called successfully with hit", func() {
ctrl := gomock.NewController(GinkgoT())
impl := mock_enriching.NewMockEnricherCache(ctrl)
metricsReporter := mock_extensions.NewMockMetricsReporter(ctrl)

impl.EXPECT().Get(gomock.Any(), tenantID, leaderboardID, members).Return(result, true, nil)
metricsReporter.EXPECT().Increment(enrichmentCacheGets).Return(nil)
metricsReporter.EXPECT().Increment(enrichmentCacheHits).Return(nil)
metricsReporter.EXPECT().Timing(enrichmentCacheGetTimingMilli, gomock.Any()).Return(nil)

instrumentedCache := NewInstrumentedCache(impl, metricsReporter)
res, hit, err := instrumentedCache.Get(context.Background(), tenantID, leaderboardID, members)

Expect(res).To(Equal(result))
Expect(hit).To(BeTrue())
Expect(err).To(BeNil())
})

It("should send metrics when Get is called successfully with miss", func() {
ctrl := gomock.NewController(GinkgoT())
impl := mock_enriching.NewMockEnricherCache(ctrl)
metricsReporter := mock_extensions.NewMockMetricsReporter(ctrl)

impl.EXPECT().Get(gomock.Any(), tenantID, leaderboardID, members).Return(nil, false, nil)
metricsReporter.EXPECT().Increment(enrichmentCacheGets).Return(nil)
metricsReporter.EXPECT().Timing(enrichmentCacheGetTimingMilli, gomock.Any()).Return(nil)

instrumentedCache := NewInstrumentedCache(impl, metricsReporter)
res, hit, err := instrumentedCache.Get(context.Background(), tenantID, leaderboardID, members)
Expect(res).To(BeNil())
Expect(hit).To(BeFalse())
Expect(err).To(BeNil())
})

It("should send metrics when Get is called with error", func() {
ctrl := gomock.NewController(GinkgoT())
impl := mock_enriching.NewMockEnricherCache(ctrl)
metricsReporter := mock_extensions.NewMockMetricsReporter(ctrl)

impl.EXPECT().Get(gomock.Any(), tenantID, leaderboardID, members).Return(nil, false, errors.New("error"))
metricsReporter.EXPECT().Increment(enrichmentCacheGets).Return(nil)
metricsReporter.EXPECT().Increment(enrichmentCacheGetErrors).Return(nil)
metricsReporter.EXPECT().Timing(enrichmentCacheGetTimingMilli, gomock.Any()).Return(nil)

instrumentedCache := NewInstrumentedCache(impl, metricsReporter)
res, hit, err := instrumentedCache.Get(context.Background(), tenantID, leaderboardID, members)

Expect(res).To(BeNil())
Expect(hit).To(BeFalse())
Expect(err).To(HaveOccurred())
})
})

var _ = Describe("Instrumented enrich cache Set tests", func() {
tenantID := "tenant-id"
leaderboardID := "leaderboard-id"
members := []*model.Member{
{
PublicID: "member1",
Metadata: map[string]string{
"field1": "value1",
},
},
}

It("should send metrics when Set is called successfully", func() {
ctrl := gomock.NewController(GinkgoT())
impl := mock_enriching.NewMockEnricherCache(ctrl)
metricsReporter := mock_extensions.NewMockMetricsReporter(ctrl)

impl.EXPECT().Set(gomock.Any(), tenantID, leaderboardID, members, gomock.Any()).Return(nil)
metricsReporter.EXPECT().Increment(enrichmentCacheSets).Return(nil)
metricsReporter.EXPECT().Timing(enrichmentCacheSetTimingMilli, gomock.Any()).Return(nil)

instrumentedCache := NewInstrumentedCache(impl, metricsReporter)
err := instrumentedCache.Set(context.Background(), tenantID, leaderboardID, members, 0)

Expect(err).To(BeNil())
})

It("should send metrics when Set is called with error", func() {
ctrl := gomock.NewController(GinkgoT())
impl := mock_enriching.NewMockEnricherCache(ctrl)
metricsReporter := mock_extensions.NewMockMetricsReporter(ctrl)

impl.EXPECT().Set(gomock.Any(), tenantID, leaderboardID, members, gomock.Any()).Return(errors.New("error"))
metricsReporter.EXPECT().Increment(enrichmentCacheSets).Return(nil)
metricsReporter.EXPECT().Increment(enrichmentCacheSetErrors).Return(nil)
metricsReporter.EXPECT().Timing(enrichmentCacheSetTimingMilli, gomock.Any()).Return(nil)

instrumentedCache := NewInstrumentedCache(impl, metricsReporter)
err := instrumentedCache.Set(context.Background(), tenantID, leaderboardID, members, 0)

Expect(err).To(HaveOccurred())
})
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package enriching
package cloud_save

type CloudSaveGetProfilesRequest struct {
TenantID string `json:"tenant_id"`
Expand Down
5 changes: 3 additions & 2 deletions leaderboard/enriching/enricher.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/topfreegames/podium/leaderboard/v2/enriching/cloud-save"
podium_leaderboard_webhooks_v1 "github.com/topfreegames/podium/leaderboard/v2/enriching/proto/webhook/v1"
"github.com/topfreegames/podium/leaderboard/v2/model"
"go.uber.org/zap"
Expand Down Expand Up @@ -179,7 +180,7 @@ func (e *enricherImpl) enrichWithCloudSave(ctx context.Context, tenantID string,
ids[i] = m.PublicID
}

request := CloudSaveGetProfilesRequest{
request := cloud_save.CloudSaveGetProfilesRequest{
TenantID: tenantID,
IDs: ids,
}
Expand Down Expand Up @@ -210,7 +211,7 @@ func (e *enricherImpl) enrichWithCloudSave(ctx context.Context, tenantID string,
return nil, fmt.Errorf("cloud save returned %s response: %w", raw.Status, ErrEnrichmentCall)
}

res := &CloudSaveGetProfilesResponse{}
res := &cloud_save.CloudSaveGetProfilesResponse{}
if err := json.NewDecoder(raw.Body).Decode(res); err != nil {
return nil, fmt.Errorf("could not unmarshal cloud save response: %w", errors.Join(ErrEnrichmentCall, err))
}
Expand Down
File renamed without changes.

0 comments on commit 7494bb2

Please sign in to comment.