From aabf54e318ec19ae3e70e3ad7c103987950b3985 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Fri, 1 May 2020 02:46:12 +0300 Subject: [PATCH] Added new stats reporter to check suitable redis pool size --- CHANGELOG.md | 3 +++ db/redis/redis.go | 4 +++ db/redis/redis_integration_test.go | 5 ++++ di/db.go | 9 +++++++ di/handlers.go | 5 ++++ di/logger.go | 7 ++++++ eventsubscribers/stats_reporter.go | 33 +++++++++++++++++++++++++ eventsubscribers/stats_reporter_test.go | 28 +++++++++++++++++++++ 8 files changed, 94 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4a4976..52047b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Mojang API base addresses. - New health checker, that ensures that response for textures provider from Mojang's API is valid. - `dev` Docker images now have the `--cpuprofile` flag, which allows you to run the program with CPU profiling. +- New StatsD metrics: + - Gauges: + - `ely.skinsystem.{hostname}.app.redis.pool.available` ### Fixed - Handle the case when there is no textures property in Mojang's response. diff --git a/db/redis/redis.go b/db/redis/redis.go index 54146bf..154b791 100644 --- a/db/redis/redis.go +++ b/db/redis/redis.go @@ -243,6 +243,10 @@ func (db *Redis) Ping() error { return nil } +func (db *Redis) Avail() int { + return db.pool.Avail() +} + func buildUsernameKey(username string) string { return "username:" + strings.ToLower(username) } diff --git a/db/redis/redis_integration_test.go b/db/redis/redis_integration_test.go index 5532972..0e8dd0e 100644 --- a/db/redis/redis_integration_test.go +++ b/db/redis/redis_integration_test.go @@ -377,3 +377,8 @@ func (suite *redisTestSuite) TestPing() { err := suite.Redis.Ping() suite.Require().Nil(err) } + +func (suite *redisTestSuite) TestAvail() { + avail := suite.Redis.Avail() + suite.Require().True(avail > 0) +} diff --git a/di/db.go b/di/db.go index 33bae47..31d1ab9 100644 --- a/di/db.go +++ b/di/db.go @@ -1,8 +1,10 @@ package di import ( + "context" "fmt" "path" + "time" "github.com/goava/di" "github.com/spf13/viper" @@ -23,6 +25,7 @@ var db = di.Options( di.Provide(newRedis, di.As(new(http.SkinsRepository)), di.As(new(mojangtextures.UUIDsStorage)), + di.As(new(es.RedisPoolCheckable)), ), di.Provide(newFSFactory, di.As(new(http.CapesRepository)), @@ -43,6 +46,12 @@ func newRedis(container *di.Container, config *viper.Viper) (*redis.Redis, error return nil, err } + if err := container.Provide(func() es.ReporterFunc { + return es.AvailableRedisPoolSizeReporter(conn, time.Second, context.Background()) + }, di.As(new(es.Reporter))); err != nil { + return nil, err + } + if err := container.Provide(func() *namedHealthChecker { return &namedHealthChecker{ Name: "redis", diff --git a/di/handlers.go b/di/handlers.go index 2c21bb2..ea2f9dd 100644 --- a/di/handlers.go +++ b/di/handlers.go @@ -74,6 +74,11 @@ func newHandlerFactory( mount(router, "/api", apiRouter) } + err := container.Invoke(enableReporters) + if err != nil { + return nil, err + } + // Resolve health checkers last, because all the services required by the application // must first be initialized and each of them can publish its own checkers var healthCheckers []*namedHealthChecker diff --git a/di/logger.go b/di/logger.go index 68f5ae9..6f479d9 100644 --- a/di/logger.go +++ b/di/logger.go @@ -13,6 +13,7 @@ import ( "github.com/mono83/slf/wd" "github.com/spf13/viper" + "github.com/elyby/chrly/eventsubscribers" "github.com/elyby/chrly/version" ) @@ -95,3 +96,9 @@ func newStatsReporter(config *viper.Viper) (slf.StatsReporter, error) { return wd.Custom("", "", dispatcher), nil } + +func enableReporters(reporter slf.StatsReporter, factories []eventsubscribers.Reporter) { + for _, factory := range factories { + factory.Enable(reporter) + } +} diff --git a/eventsubscribers/stats_reporter.go b/eventsubscribers/stats_reporter.go index 926b1c0..286014e 100644 --- a/eventsubscribers/stats_reporter.go +++ b/eventsubscribers/stats_reporter.go @@ -1,6 +1,7 @@ package eventsubscribers import ( + "context" "net/http" "strings" "sync" @@ -19,6 +20,17 @@ type StatsReporter struct { timersMutex sync.Mutex } +type Reporter interface { + Enable(reporter slf.StatsReporter) +} + +type ReporterFunc func(reporter slf.StatsReporter) + +func (f ReporterFunc) Enable(reporter slf.StatsReporter) { + f(reporter) +} + +// TODO: rework all reporters in the same style as AvailableRedisPoolSizeReporter func (s *StatsReporter) ConfigureWithDispatcher(d Subscriber) { s.timersMap = make(map[string]time.Time) @@ -175,3 +187,24 @@ func (s *StatsReporter) finalizeTimeRecording(timeKey string, statName string) { s.RecordTimer(statName, time.Since(startedAt)) } + +type RedisPoolCheckable interface { + Avail() int +} + +func AvailableRedisPoolSizeReporter(pool RedisPoolCheckable, d time.Duration, stop context.Context) ReporterFunc { + return func(reporter slf.StatsReporter) { + go func() { + ticker := time.NewTicker(d) + for { + select { + case <-stop.Done(): + ticker.Stop() + return + case <-ticker.C: + reporter.UpdateGauge("redis.pool.available", int64(pool.Avail())) + } + } + }() + } +} diff --git a/eventsubscribers/stats_reporter_test.go b/eventsubscribers/stats_reporter_test.go index df6288e..4fedb77 100644 --- a/eventsubscribers/stats_reporter_test.go +++ b/eventsubscribers/stats_reporter_test.go @@ -1,6 +1,7 @@ package eventsubscribers import ( + "context" "errors" "net/http/httptest" "testing" @@ -392,3 +393,30 @@ func TestStatsReporter(t *testing.T) { }) } } + +type redisPoolCheckableMock struct { + mock.Mock +} + +func (r *redisPoolCheckableMock) Avail() int { + return r.Called().Int(0) +} + +func TestAvailableRedisPoolSizeReporter(t *testing.T) { + poolMock := &redisPoolCheckableMock{} + poolMock.On("Avail").Return(5).Times(3) + reporterMock := &StatsReporterMock{} + reporterMock.On("UpdateGauge", "redis.pool.available", int64(5)).Times(3) + + ctx, cancel := context.WithCancel(context.Background()) + + creator := AvailableRedisPoolSizeReporter(poolMock, 10*time.Millisecond, ctx) + creator(reporterMock) + + time.Sleep(35 * time.Millisecond) + + cancel() + + poolMock.AssertExpectations(t) + reporterMock.AssertExpectations(t) +}