diff --git a/api4/usage.go b/api4/usage.go index a1d86a5777..10581291c0 100644 --- a/api4/usage.go +++ b/api4/usage.go @@ -45,7 +45,7 @@ func getStorageUsage(c *Context, w http.ResponseWriter, r *http.Request) { return } - usage = utils.RoundOffToZeroes(float64(usage)) + usage = utils.RoundOffToZeroesResolution(float64(usage), 8) json, err := json.Marshal(&model.StorageUsage{Bytes: usage}) if err != nil { c.Err = model.NewAppError("Api4.getStorageUsage", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError) diff --git a/app/usage.go b/app/usage.go index 761d398994..0abe3ab03e 100644 --- a/app/usage.go +++ b/app/usage.go @@ -49,7 +49,7 @@ func (a *App) GetPostsUsage() (int64, *model.AppError) { return 0, model.NewAppError("GetPostsUsage", "app.post.analytics_posts_count.app_error", nil, err.Error(), http.StatusInternalServerError) } - return utils.RoundOffToZeroes(float64(count)), nil + return utils.RoundOffToZeroesResolution(float64(count), 3), nil } // GetStorageUsage returns the sum of files' sizes stored on this instance diff --git a/services/telemetry/telemetry.go b/services/telemetry/telemetry.go index a06b697f4c..d499394c59 100644 --- a/services/telemetry/telemetry.go +++ b/services/telemetry/telemetry.go @@ -357,7 +357,7 @@ func (ts *TelemetryService) trackActivity() { if usage, err := ts.dbStore.FileInfo().GetStorageUsage(true, false); err == nil { tmpStorage = usage } - activity["storage_bytes"] = utils.RoundOffToZeroes(float64(tmpStorage)) + activity["storage_bytes"] = utils.RoundOffToZeroesResolution(float64(tmpStorage), 8) } ts.SendTelemetry(TrackActivity, activity) diff --git a/utils/utils.go b/utils/utils.go index bc83343847..9fad79e6d7 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -229,3 +229,35 @@ func RoundOffToZeroes(n float64) int64 { firstDigit := int64(n) / tens return firstDigit * tens } + +func min(a, b int) int { + if a < b { + return a + } + return b +} +func max(a, b int) int { + if a > b { + return a + } + return b +} + +// RoundOffToZeroesResolution truncates off at most minResolution zero places. +// It implicitly sets the lowest minResolution to 0. +// e.g. 0 reports 1s, 1 reports 10s, 2 reports 100s, 3 reports 1000s +func RoundOffToZeroesResolution(n float64, minResolution int) int64 { + resolution := max(0, minResolution) + if n >= -9 && n <= 9 { + if resolution == 0 { + return int64(n) + } + return 0 + } + + zeroes := int(math.Log10(math.Abs(n))) + resolution = min(zeroes, resolution) + tens := int64(math.Pow10(resolution)) + significantDigits := int64(n) / tens + return significantDigits * tens +} diff --git a/utils/utils_test.go b/utils/utils_test.go index 31b6e90ffd..6f275dd23a 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -247,3 +247,132 @@ func TestRoundOffToZeroes(t *testing.T) { }) } } + +func TestRoundOffToZeroesResolution(t *testing.T) { + messageGranularity := 3 + storageGranularity := 8 + testCases := []struct { + desc string + n float64 + minResolution int + expected int64 + }{ + { + desc: "returns 0 when n is 0", + n: 0, + expected: 0, + }, + { + desc: "resolution of 0 does not round", + n: 12345, + expected: 12345, + minResolution: 0, + }, + { + desc: "resolution of 1 truncates to 10s: 9 -> 0", + n: 9, + expected: 0, + minResolution: 1, + }, + { + desc: "resolution of 1 truncates to 10s: 10 -> 10", + n: 10, + expected: 10, + minResolution: 1, + }, + { + desc: "resolution of 1 truncates to 10s: 11 -> 10", + n: 11, + expected: 10, + minResolution: 1, + }, + { + desc: "resolution of 1 truncates to 10s: 19 -> 10", + n: 19, + expected: 10, + minResolution: 1, + }, + { + desc: "supports message usage granularity (1000s): 9 -> 0", + n: 9, + expected: 0, + minResolution: messageGranularity, + }, + { + desc: "supports message usage granularity (1000s): 123 -> 100", + n: 9, + expected: 0, + minResolution: messageGranularity, + }, + { + desc: "supports message usage granularity (1000s): 1234 -> 1000", + n: 1234, + expected: 1000, + minResolution: messageGranularity, + }, + { + desc: "supports message usage granularity (1000s): 1500 -> 1000", + n: 1500, + expected: 1000, + minResolution: messageGranularity, + }, + { + desc: "supports file storage usage granularity (~100s of MiB): 13GiB -> 12.94GiB", + n: 13 * 1024 * 1024 * 1024, + expected: 13_900_000_000, + minResolution: storageGranularity, + }, + { + desc: "supports file storage usage granularity (~100s of MiB): 10GiB -> 9.965GiB", + n: 10 * 1024 * 1024 * 1024, + expected: 10_700_000_000, + minResolution: storageGranularity, + }, + { + // first number at which usage reports as in excess of 10GiB. + // Evaluates to 10299.5992MiB + // Should be close enough for notifying of being over limit. + desc: "supports file storage usage granularity (~100s of MiB): 10.0583GiB -> 10.0583GiB", + n: 10.0583 * 1024 * 1024 * 1024, + expected: 10_800_000_000, + minResolution: storageGranularity, + }, + { + desc: "supports file storage usage granularity (~100s of MiB): 953.67MiB -> 953.67MiB", + n: 1_000_000_000, + expected: 1_000_000_000, + minResolution: storageGranularity, + }, + { + desc: "supports file storage usage granularity (~100s of MiB): 1GiB -> 953.67MiB", + n: 1 * 1024 * 1024 * 1024, + expected: 1_000_000_000, + minResolution: storageGranularity, + }, + { + desc: "supports file storage usage granularity (~100s of MiB): 1049.04MiB -> 1049.04MiB", + n: 1_100_000_000, + expected: 1_100_000_000, + minResolution: storageGranularity, + }, + { + desc: "supports file storage usage granularity (smaller amounts): 104.904MiB -> 104.904MiB", + n: 100_000_000, + expected: 100_000_000, + minResolution: storageGranularity, + }, + { + desc: "supports file storage usage granularity (smaller amounts): 10.4904MiB -> 10.4904MiB", + n: 10_000_000, + expected: 10_000_000, + minResolution: storageGranularity, + }, + } + for _, tc := range testCases { + tc := tc + t.Run(tc.desc, func(t *testing.T) { + res := RoundOffToZeroesResolution(tc.n, tc.minResolution) + assert.Equal(t, tc.expected, res) + }) + } +}