mirror of
				https://github.com/elyby/chrly.git
				synced 2025-05-31 14:11:51 +05:30 
			
		
		
		
	Added new stats reporter to check suitable redis pool size
This commit is contained in:
		| @@ -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. | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
							
								
								
									
										9
									
								
								di/db.go
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								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", | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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())) | ||||
| 				} | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user