diff --git a/internal/api/model/instance.go b/internal/api/model/instance.go index 369317e84..aaa01d837 100644 --- a/internal/api/model/instance.go +++ b/internal/api/model/instance.go @@ -17,7 +17,10 @@ package model -import "mime/multipart" +import ( + "mime/multipart" + "time" +) // InstanceSettingsUpdateRequest models an instance update request. // @@ -154,4 +157,5 @@ type RandomStats struct { Statuses int64 TotalUsers int64 MonthlyActiveUsers int64 + Generated time.Time } diff --git a/internal/typeutils/converter.go b/internal/typeutils/converter.go index 574b53e15..4fbe1dfd3 100644 --- a/internal/typeutils/converter.go +++ b/internal/typeutils/converter.go @@ -22,11 +22,10 @@ import ( "math/big" "math/rand" "sync" + "sync/atomic" "time" - "codeberg.org/gruf/go-cache/v3" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/filter/interaction" "github.com/superseriousbusiness/gotosocial/internal/filter/visibility" "github.com/superseriousbusiness/gotosocial/internal/log" @@ -39,64 +38,64 @@ type Converter struct { randAvatars sync.Map visFilter *visibility.Filter intFilter *interaction.Filter - randStatsCache cache.TTLCache[struct{}, apimodel.RandomStats] + randStats atomic.Pointer[apimodel.RandomStats] } func NewConverter(state *state.State) *Converter { - // If randomizing instance stats, create a cache so we - // don't have to generate them every time, and set a 1hr - // ttl on the cache so they're not always the same. - var randStatsCache cache.TTLCache[struct{}, apimodel.RandomStats] - if config.GetInstanceStatsRandomize() { - randStatsCache = cache.NewTTL[struct{}, apimodel.RandomStats](0, 1, 0) - randStatsCache.SetTTL(time.Hour, false) - if !randStatsCache.Start(time.Minute) { - log.Panicf(nil, "couldn't start randStatsCache") - } - } - return &Converter{ state: state, defaultAvatars: populateDefaultAvatars(), visFilter: visibility.NewFilter(state), intFilter: interaction.NewFilter(state), - randStatsCache: randStatsCache, } } // RandomStats returns or generates // and returns random instance stats. func (c *Converter) RandomStats() apimodel.RandomStats { + now := time.Now() + stats := c.randStats.Load() + if stats != nil && time.Since(stats.Generated) < time.Hour { + // Random stats are still + // fresh (less than 1hr old), + // so return them as-is. + return *stats + } + + // Generate new random stats. + newStats := genRandStats() + newStats.Generated = now + c.randStats.Store(&newStats) + return newStats +} + +func genRandStats() apimodel.RandomStats { const ( statusesMax = 10000000 usersMax = 1000000 ) - stats, ok := c.randStatsCache.Get(struct{}{}) - if !ok { - statusesB, err := crand.Int(crand.Reader, big.NewInt(statusesMax)) - if err != nil { - // Only errs if something is buggered with the OS. - log.Panicf(nil, "error randomly generating statuses count: %v", err) - } - - totalUsersB, err := crand.Int(crand.Reader, big.NewInt(usersMax)) - if err != nil { - // Only errs if something is buggered with the OS. - log.Panicf(nil, "error randomly generating users count: %v", err) - } - - totalUsers := totalUsersB.Int64() - activeRatio := rand.Float64() //nolint - mau := int64(float64(totalUsers) * activeRatio) - - stats = apimodel.RandomStats{ - Statuses: statusesB.Int64(), - TotalUsers: totalUsers, - MonthlyActiveUsers: mau, - } - c.randStatsCache.Set(struct{}{}, stats) + statusesB, err := crand.Int(crand.Reader, big.NewInt(statusesMax)) + if err != nil { + // Only errs if something is buggered with the OS. + log.Panicf(nil, "error randomly generating statuses count: %v", err) } - return stats + totalUsersB, err := crand.Int(crand.Reader, big.NewInt(usersMax)) + if err != nil { + // Only errs if something is buggered with the OS. + log.Panicf(nil, "error randomly generating users count: %v", err) + } + + // Monthly users should only ever + // be <= 100% of total users. + totalUsers := totalUsersB.Int64() + activeRatio := rand.Float64() //nolint + mau := int64(float64(totalUsers) * activeRatio) + + return apimodel.RandomStats{ + Statuses: statusesB.Int64(), + TotalUsers: totalUsers, + MonthlyActiveUsers: mau, + } }