Added new stats reporter to check suitable redis pool size

This commit is contained in:
ErickSkrauch 2020-05-01 02:46:12 +03:00
parent 5dbe6af1d0
commit aabf54e318
8 changed files with 94 additions and 0 deletions

View File

@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Mojang API base addresses. Mojang API base addresses.
- New health checker, that ensures that response for textures provider from Mojang's API is valid. - 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. - `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 ### Fixed
- Handle the case when there is no textures property in Mojang's response. - Handle the case when there is no textures property in Mojang's response.

View File

@ -243,6 +243,10 @@ func (db *Redis) Ping() error {
return nil return nil
} }
func (db *Redis) Avail() int {
return db.pool.Avail()
}
func buildUsernameKey(username string) string { func buildUsernameKey(username string) string {
return "username:" + strings.ToLower(username) return "username:" + strings.ToLower(username)
} }

View File

@ -377,3 +377,8 @@ func (suite *redisTestSuite) TestPing() {
err := suite.Redis.Ping() err := suite.Redis.Ping()
suite.Require().Nil(err) suite.Require().Nil(err)
} }
func (suite *redisTestSuite) TestAvail() {
avail := suite.Redis.Avail()
suite.Require().True(avail > 0)
}

View File

@ -1,8 +1,10 @@
package di package di
import ( import (
"context"
"fmt" "fmt"
"path" "path"
"time"
"github.com/goava/di" "github.com/goava/di"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -23,6 +25,7 @@ var db = di.Options(
di.Provide(newRedis, di.Provide(newRedis,
di.As(new(http.SkinsRepository)), di.As(new(http.SkinsRepository)),
di.As(new(mojangtextures.UUIDsStorage)), di.As(new(mojangtextures.UUIDsStorage)),
di.As(new(es.RedisPoolCheckable)),
), ),
di.Provide(newFSFactory, di.Provide(newFSFactory,
di.As(new(http.CapesRepository)), di.As(new(http.CapesRepository)),
@ -43,6 +46,12 @@ func newRedis(container *di.Container, config *viper.Viper) (*redis.Redis, error
return nil, err 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 { if err := container.Provide(func() *namedHealthChecker {
return &namedHealthChecker{ return &namedHealthChecker{
Name: "redis", Name: "redis",

View File

@ -74,6 +74,11 @@ func newHandlerFactory(
mount(router, "/api", apiRouter) 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 // 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 // must first be initialized and each of them can publish its own checkers
var healthCheckers []*namedHealthChecker var healthCheckers []*namedHealthChecker

View File

@ -13,6 +13,7 @@ import (
"github.com/mono83/slf/wd" "github.com/mono83/slf/wd"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/elyby/chrly/eventsubscribers"
"github.com/elyby/chrly/version" "github.com/elyby/chrly/version"
) )
@ -95,3 +96,9 @@ func newStatsReporter(config *viper.Viper) (slf.StatsReporter, error) {
return wd.Custom("", "", dispatcher), nil return wd.Custom("", "", dispatcher), nil
} }
func enableReporters(reporter slf.StatsReporter, factories []eventsubscribers.Reporter) {
for _, factory := range factories {
factory.Enable(reporter)
}
}

View File

@ -1,6 +1,7 @@
package eventsubscribers package eventsubscribers
import ( import (
"context"
"net/http" "net/http"
"strings" "strings"
"sync" "sync"
@ -19,6 +20,17 @@ type StatsReporter struct {
timersMutex sync.Mutex 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) { func (s *StatsReporter) ConfigureWithDispatcher(d Subscriber) {
s.timersMap = make(map[string]time.Time) 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)) 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()))
}
}
}()
}
}

View File

@ -1,6 +1,7 @@
package eventsubscribers package eventsubscribers
import ( import (
"context"
"errors" "errors"
"net/http/httptest" "net/http/httptest"
"testing" "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)
}