diff --git a/integration/aggregate_stats_test.go b/integration/aggregate_stats_test.go index 8fed02a0651d..8b1b27467fea 100644 --- a/integration/aggregate_stats_test.go +++ b/integration/aggregate_stats_test.go @@ -57,15 +57,13 @@ func TestAggregateCollStats(t *testing.T) { assert.NotZero(t, must.NotFail(storageStats.Get("count"))) assert.NotZero(t, must.NotFail(storageStats.Get("avgObjSize"))) assert.NotZero(t, must.NotFail(storageStats.Get("storageSize"))) - // TODO https://github.com/FerretDB/FerretDB/issues/2447 - // assert.NotZero(t, must.NotFail(storageStats.Get("freeStorageSize"))) + assert.Zero(t, must.NotFail(storageStats.Get("freeStorageSize"))) assert.Equal(t, false, must.NotFail(storageStats.Get("capped"))) assert.NotZero(t, must.NotFail(storageStats.Get("nindexes"))) assert.NotZero(t, must.NotFail(storageStats.Get("totalIndexSize"))) assert.NotZero(t, must.NotFail(storageStats.Get("totalSize"))) assert.NotZero(t, must.NotFail(storageStats.Get("indexSizes"))) - // TODO https://github.com/FerretDB/FerretDB/issues/2447 - // assert.Equal(t, int32(1), must.NotFail(storageStats.Get("scaleFactor"))) + assert.Equal(t, int32(1), must.NotFail(storageStats.Get("scaleFactor"))) } func TestAggregateCollStatsCommandErrors(t *testing.T) { diff --git a/integration/commands_administration_compat_test.go b/integration/commands_administration_compat_test.go index 4b01b3e28860..c32cb63e4446 100644 --- a/integration/commands_administration_compat_test.go +++ b/integration/commands_administration_compat_test.go @@ -221,3 +221,70 @@ func TestCommandsAdministrationCompatDBStatsWithScale(t *testing.T) { }) } } + +func TestCommandsAdministrationCompatDBStatsFreeStorage(t *testing.T) { + t.Parallel() + + s := setup.SetupCompatWithOpts(t, &setup.SetupCompatOpts{ + Providers: []shareddata.Provider{shareddata.DocumentsDocuments}, + AddNonExistentCollection: true, + }) + + ctx, targetCollection, compatCollection := s.Ctx, s.TargetCollections[0], s.CompatCollections[0] + + for name, tc := range map[string]struct { //nolint:vet // for readability + command bson.D // required, command to run + skip string // optional, skip test with a specified reason + }{ + "Unset": { + command: bson.D{{"dbStats", int32(1)}}, + }, + "Int32Zero": { + command: bson.D{{"dbStats", int32(1)}, {"freeStorage", int32(0)}}, + }, + "Int32One": { + command: bson.D{{"dbStats", int32(1)}, {"freeStorage", int32(1)}}, + }, + "Int32Negative": { + command: bson.D{{"dbStats", int32(1)}, {"freeStorage", int32(-1)}}, + }, + "True": { + command: bson.D{{"dbStats", int32(1)}, {"freeStorage", true}}, + }, + "False": { + command: bson.D{{"dbStats", int32(1)}, {"freeStorage", false}}, + }, + "Nil": { + command: bson.D{{"dbStats", int32(1)}, {"freeStorage", nil}}, + }, + } { + name, tc := name, tc + + t.Run(name, func(t *testing.T) { + t.Parallel() + + var targetRes bson.D + targetErr := targetCollection.Database().RunCommand(ctx, tc.command).Decode(&targetRes) + + var compatRes bson.D + compatErr := compatCollection.Database().RunCommand(ctx, tc.command).Decode(&compatRes) + + if targetErr != nil { + t.Logf("Target error: %v", targetErr) + t.Logf("Compat error: %v", compatErr) + + // error messages are intentionally not compared + AssertMatchesCommandError(t, compatErr, targetErr) + + return + } + require.NoError(t, compatErr, "compat error; target returned no error") + + targetDoc := ConvertDocument(t, targetRes) + compatDoc := ConvertDocument(t, compatRes) + + assert.Equal(t, compatDoc.Has("freeStorageSize"), targetDoc.Has("freeStorageSize")) + assert.Equal(t, compatDoc.Has("totalFreeStorageSize"), targetDoc.Has("totalFreeStorageSize")) + }) + } +} diff --git a/integration/commands_administration_test.go b/integration/commands_administration_test.go index f511d43427b7..a1d916131aa0 100644 --- a/integration/commands_administration_test.go +++ b/integration/commands_administration_test.go @@ -690,8 +690,7 @@ func TestCommandsAdministrationCollStatsEmpty(t *testing.T) { assert.EqualValues(t, 0, must.NotFail(doc.Get("size"))) assert.EqualValues(t, 0, must.NotFail(doc.Get("count"))) assert.EqualValues(t, 0, must.NotFail(doc.Get("storageSize"))) - // add assertion for freeStorageSize - // TODO https://github.com/FerretDB/FerretDB/issues/2447 + assert.False(t, doc.Has("freeStorageSize")) assert.EqualValues(t, 0, must.NotFail(doc.Get("nindexes"))) assert.EqualValues(t, 0, must.NotFail(doc.Get("totalIndexSize"))) assert.EqualValues(t, 0, must.NotFail(doc.Get("totalSize"))) @@ -727,8 +726,7 @@ func TestCommandsAdministrationCollStats(t *testing.T) { assert.InDelta(t, 40_000, must.NotFail(doc.Get("size")), 39_900) assert.InDelta(t, 2_400, must.NotFail(doc.Get("avgObjSize")), 2_370) assert.InDelta(t, 40_000, must.NotFail(doc.Get("storageSize")), 39_900) - // add assertion for freeStorageSize - // TODO https://github.com/FerretDB/FerretDB/issues/2447 + assert.EqualValues(t, 0, must.NotFail(doc.Get("freeStorageSize"))) assert.EqualValues(t, 1, must.NotFail(doc.Get("nindexes"))) assert.InDelta(t, 12_000, must.NotFail(doc.Get("totalIndexSize")), 11_000) assert.InDelta(t, 32_000, must.NotFail(doc.Get("totalSize")), 30_000) @@ -768,8 +766,7 @@ func TestCommandsAdministrationCollStatsWithScale(t *testing.T) { assert.InDelta(t, 16, must.NotFail(doc.Get("size")), 16) assert.InDelta(t, 2_400, must.NotFail(doc.Get("avgObjSize")), 2_370) assert.InDelta(t, 24, must.NotFail(doc.Get("storageSize")), 24) - // add assertion for freeStorageSize - // TODO https://github.com/FerretDB/FerretDB/issues/2447 + assert.Zero(t, must.NotFail(doc.Get("freeStorageSize"))) assert.EqualValues(t, 1, must.NotFail(doc.Get("nindexes"))) assert.InDelta(t, 8, must.NotFail(doc.Get("totalIndexSize")), 8) assert.InDelta(t, 24, must.NotFail(doc.Get("totalSize")), 24) @@ -1012,9 +1009,6 @@ func TestCommandsAdministrationDBStats(t *testing.T) { freeStorageSize, _ := doc.Get("freeStorageSize") assert.Nil(t, freeStorageSize) - indexFreeStorageSize, _ := doc.Get("indexFreeStorageSize") - assert.Nil(t, indexFreeStorageSize) - totalFreeStorageSize, _ := doc.Get("totalFreeStorageSize") assert.Nil(t, totalFreeStorageSize) @@ -1109,17 +1103,17 @@ func TestCommandsAdministrationDBStatsFreeStorage(t *testing.T) { ctx, collection := setup.Setup(t, shareddata.DocumentsStrings) - var actual bson.D + var res bson.D command := bson.D{{"dbStats", int32(1)}, {"freeStorage", int32(1)}} - err := collection.Database().RunCommand(ctx, command).Decode(&actual) + err := collection.Database().RunCommand(ctx, command).Decode(&res) require.NoError(t, err) - doc := ConvertDocument(t, actual) + doc := ConvertDocument(t, res) assert.Equal(t, float64(1), doc.Remove("scaleFactor")) assert.Equal(t, float64(1), doc.Remove("ok")) - // assert freeStorageSize, indexFreeStorageSize and totalFreeStorageSize - // TODO https://github.com/FerretDB/FerretDB/issues/2447 + assert.Zero(t, must.NotFail(doc.Get("freeStorageSize"))) + assert.Zero(t, must.NotFail(doc.Get("totalFreeStorageSize"))) } //nolint:paralleltest // we test a global server status diff --git a/internal/backends/collection.go b/internal/backends/collection.go index 91686700fd87..982ae153ee89 100644 --- a/internal/backends/collection.go +++ b/internal/backends/collection.go @@ -240,11 +240,12 @@ type CollectionStatsParams struct { // CollectionStatsResult represents the results of Collection.Stats method. type CollectionStatsResult struct { - CountDocuments int64 - SizeTotal int64 - SizeIndexes int64 - SizeCollection int64 - IndexSizes []IndexSize + CountDocuments int64 + SizeTotal int64 + SizeIndexes int64 + SizeCollection int64 + SizeFreeStorage int64 + IndexSizes []IndexSize } // IndexSize represents the name and the size of an index. diff --git a/internal/backends/database.go b/internal/backends/database.go index 3c0f02908872..c6b8892e2f5d 100644 --- a/internal/backends/database.go +++ b/internal/backends/database.go @@ -207,6 +207,7 @@ type DatabaseStatsResult struct { SizeTotal int64 SizeIndexes int64 SizeCollections int64 + SizeFreeStorage int64 } // Stats returns statistic estimations about the database. diff --git a/internal/backends/postgresql/collection.go b/internal/backends/postgresql/collection.go index ef04c31bd4d7..d215e9b929c9 100644 --- a/internal/backends/postgresql/collection.go +++ b/internal/backends/postgresql/collection.go @@ -434,11 +434,12 @@ func (c *collection) Stats(ctx context.Context, params *backends.CollectionStats } return &backends.CollectionStatsResult{ - CountDocuments: stats.countDocuments, - SizeTotal: stats.sizeTables + stats.sizeIndexes, - SizeIndexes: stats.sizeIndexes, - SizeCollection: stats.sizeTables, - IndexSizes: indexSizes, + CountDocuments: stats.countDocuments, + SizeTotal: stats.sizeTables + stats.sizeIndexes, + SizeIndexes: stats.sizeIndexes, + SizeCollection: stats.sizeTables, + SizeFreeStorage: stats.sizeFreeStorage, + IndexSizes: indexSizes, }, nil } diff --git a/internal/backends/postgresql/database.go b/internal/backends/postgresql/database.go index 36ffb5f1fe22..0f83aa717d77 100644 --- a/internal/backends/postgresql/database.go +++ b/internal/backends/postgresql/database.go @@ -17,8 +17,6 @@ package postgresql import ( "context" - "github.com/AlekSi/pointer" - "github.com/FerretDB/FerretDB/internal/backends" "github.com/FerretDB/FerretDB/internal/backends/postgresql/metadata" "github.com/FerretDB/FerretDB/internal/util/lazyerrors" @@ -156,26 +154,23 @@ func (db *database) Stats(ctx context.Context, params *backends.DatabaseStatsPar // See https://www.postgresql.org/docs/15/functions-admin.html#FUNCTIONS-ADMIN-DBOBJECT. q := ` SELECT - SUM(pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(tablename))) + COALESCE(SUM(pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(tablename))), 0) FROM pg_tables WHERE schemaname = $1` args := []any{db.name} row := p.QueryRow(ctx, q, args...) - var schemaSize *int64 - if err := row.Scan(&schemaSize); err != nil { + var sizeTotal int64 + if err := row.Scan(&sizeTotal); err != nil { return nil, lazyerrors.Error(err) } - if schemaSize == nil { - schemaSize = pointer.ToInt64(0) - } - return &backends.DatabaseStatsResult{ CountDocuments: stats.countDocuments, - SizeTotal: *schemaSize, + SizeTotal: sizeTotal, SizeIndexes: stats.sizeIndexes, SizeCollections: stats.sizeTables, + SizeFreeStorage: stats.sizeFreeStorage, }, nil } diff --git a/internal/backends/postgresql/database_test.go b/internal/backends/postgresql/database_test.go index f16ed4e11fa8..7995b008f76f 100644 --- a/internal/backends/postgresql/database_test.go +++ b/internal/backends/postgresql/database_test.go @@ -89,4 +89,29 @@ func TestDatabaseStats(t *testing.T) { require.Equal(t, int64(1), res.CountDocuments) require.NotZero(t, res.SizeIndexes) }) + + t.Run("FreeStorageSize", func(t *testing.T) { + c, err := db.Collection(cNames[0]) + require.NoError(t, err) + + nInsert, deleteFromIndex, deleteToIndex := 50, 10, 40 + ids := make([]any, nInsert) + toInsert := make([]*types.Document, nInsert) + for i := 0; i < nInsert; i++ { + ids[i] = types.NewObjectID() + toInsert[i] = must.NotFail(types.NewDocument("_id", ids[i], "v", "foo")) + } + + _, err = c.InsertAll(ctx, &backends.InsertAllParams{Docs: toInsert}) + require.NoError(t, err) + + _, err = c.DeleteAll(ctx, &backends.DeleteAllParams{IDs: ids[deleteFromIndex:deleteToIndex]}) + require.NoError(t, err) + + res, err := db.Stats(ctx, new(backends.DatabaseStatsParams)) + require.NoError(t, err) + + t.Logf("freeStorage size: %d", res.SizeFreeStorage) + require.NotZero(t, res.SizeFreeStorage) + }) } diff --git a/internal/backends/postgresql/postgresql.go b/internal/backends/postgresql/postgresql.go index 3fd63e6b55f6..0da6b1461b31 100644 --- a/internal/backends/postgresql/postgresql.go +++ b/internal/backends/postgresql/postgresql.go @@ -32,9 +32,10 @@ import ( // stats represents information about statistics of tables and indexes. type stats struct { - countDocuments int64 - sizeIndexes int64 - sizeTables int64 + countDocuments int64 + sizeIndexes int64 + sizeTables int64 + sizeFreeStorage int64 } // collectionsStats returns statistics about tables and indexes for the given collections. @@ -70,6 +71,8 @@ func collectionsStats(ctx context.Context, p *pgxpool.Pool, dbName string, list // used, however it is not updated immediately after operation such as DELETE // unless VACUUM is called, ANALYZE does not update pg_relation_size in this case. // + // The free storage size is the size of free space map (fsm) of table relation. + // // The smallest difference in size that `pg_relation_size` reports appears to be 8KB. // Because of that inserting or deleting a single small object may not change the size. // @@ -82,6 +85,7 @@ func collectionsStats(ctx context.Context, p *pgxpool.Pool, dbName string, list SELECT COALESCE(SUM(c.reltuples), 0), COALESCE(SUM(pg_relation_size(c.oid,'main')), 0), + COALESCE(SUM(pg_relation_size(c.oid,'fsm')), 0), COALESCE(SUM(pg_indexes_size(c.oid)), 0) FROM pg_tables AS t LEFT JOIN pg_class AS c ON c.relname = t.tablename AND c.relnamespace = quote_ident(t.schemaname)::regnamespace @@ -90,7 +94,7 @@ func collectionsStats(ctx context.Context, p *pgxpool.Pool, dbName string, list ) row := p.QueryRow(ctx, q, args...) - if err := row.Scan(&s.countDocuments, &s.sizeTables, &s.sizeIndexes); err != nil { + if err = row.Scan(&s.countDocuments, &s.sizeTables, &s.sizeFreeStorage, &s.sizeIndexes); err != nil { return nil, lazyerrors.Error(err) } diff --git a/internal/backends/sqlite/collection.go b/internal/backends/sqlite/collection.go index b779203bb29f..61be84d437f9 100644 --- a/internal/backends/sqlite/collection.go +++ b/internal/backends/sqlite/collection.go @@ -393,11 +393,12 @@ func (c *collection) Stats(ctx context.Context, params *backends.CollectionStats } return &backends.CollectionStatsResult{ - CountDocuments: stats.countDocuments, - SizeTotal: stats.sizeTables + stats.sizeIndexes, - SizeIndexes: stats.sizeIndexes, - SizeCollection: stats.sizeTables, - IndexSizes: indexSizes, + CountDocuments: stats.countDocuments, + SizeTotal: stats.sizeTables + stats.sizeIndexes, + SizeIndexes: stats.sizeIndexes, + SizeCollection: stats.sizeTables, + IndexSizes: indexSizes, + SizeFreeStorage: stats.sizeFreeStorage, }, nil } diff --git a/internal/backends/sqlite/database.go b/internal/backends/sqlite/database.go index b14fd82a5e12..dd8f1205ef16 100644 --- a/internal/backends/sqlite/database.go +++ b/internal/backends/sqlite/database.go @@ -152,6 +152,7 @@ func (db *database) Stats(ctx context.Context, params *backends.DatabaseStatsPar SizeTotal: totalSize, SizeIndexes: stats.sizeIndexes, SizeCollections: stats.sizeTables, + SizeFreeStorage: stats.sizeFreeStorage, }, nil } diff --git a/internal/backends/sqlite/database_test.go b/internal/backends/sqlite/database_test.go index 928e77ce702f..b700d6dca8e2 100644 --- a/internal/backends/sqlite/database_test.go +++ b/internal/backends/sqlite/database_test.go @@ -20,6 +20,8 @@ import ( "github.com/stretchr/testify/require" "github.com/FerretDB/FerretDB/internal/backends" + "github.com/FerretDB/FerretDB/internal/types" + "github.com/FerretDB/FerretDB/internal/util/must" "github.com/FerretDB/FerretDB/internal/util/state" "github.com/FerretDB/FerretDB/internal/util/testutil" ) @@ -53,3 +55,69 @@ func TestDatabaseStats(t *testing.T) { require.Zero(t, res.CountDocuments) }) } + +func TestDatabaseStatsFreeStorage(t *testing.T) { + t.Parallel() + ctx := testutil.Ctx(t) + + sp, err := state.NewProvider("") + require.NoError(t, err) + + for name, u := range map[string]string{ + "Memory": "file:./?mode=memory", + "LocalDirectory": "file:./", + } { + name, u := name, u + t.Run(name, func(t *testing.T) { + b, err := NewBackend(&NewBackendParams{URI: u, L: testutil.Logger(t), P: sp}) + require.NoError(t, err) + + t.Cleanup(b.Close) + + dbName := testutil.DatabaseName(t) + db, err := b.Database(dbName) + require.NoError(t, err) + + t.Cleanup(func() { + err = b.DropDatabase(ctx, &backends.DropDatabaseParams{Name: dbName}) + require.NoError(t, err) + }) + + cNames := []string{"collectionOne", "collectionTwo"} + for _, cName := range cNames { + err = db.CreateCollection(ctx, &backends.CreateCollectionParams{Name: cName}) + require.NoError(t, err) + require.NotNil(t, db) + } + + res, err := db.Stats(ctx, new(backends.DatabaseStatsParams)) + require.NoError(t, err) + + t.Logf("freeStorage size: %d", res.SizeFreeStorage) + require.Zero(t, res.SizeFreeStorage) + + c, err := db.Collection(cNames[0]) + require.NoError(t, err) + + nInsert, deleteFromIndex, deleteToIndex := 50, 10, 40 + ids := make([]any, nInsert) + toInsert := make([]*types.Document, nInsert) + for i := 0; i < nInsert; i++ { + ids[i] = types.NewObjectID() + toInsert[i] = must.NotFail(types.NewDocument("_id", ids[i], "v", "foo")) + } + + _, err = c.InsertAll(ctx, &backends.InsertAllParams{Docs: toInsert}) + require.NoError(t, err) + + _, err = c.DeleteAll(ctx, &backends.DeleteAllParams{IDs: ids[deleteFromIndex:deleteToIndex]}) + require.NoError(t, err) + + res, err = db.Stats(ctx, new(backends.DatabaseStatsParams)) + require.NoError(t, err) + + t.Logf("freeStorage size: %d", res.SizeFreeStorage) + require.NotZero(t, res.SizeFreeStorage) + }) + } +} diff --git a/internal/backends/sqlite/sqlite.go b/internal/backends/sqlite/sqlite.go index 8e4c4e1c6562..66a8f8c337c3 100644 --- a/internal/backends/sqlite/sqlite.go +++ b/internal/backends/sqlite/sqlite.go @@ -42,9 +42,10 @@ import ( // stats represents information about statistics of tables and indexes. type stats struct { - countDocuments int64 - sizeIndexes int64 - sizeTables int64 + countDocuments int64 + sizeIndexes int64 + sizeTables int64 + sizeFreeStorage int64 // free storage for the entire database } // collectionsStats returns statistics about tables and indexes for the given collections. @@ -126,5 +127,29 @@ func collectionsStats(ctx context.Context, db *fsql.DB, list []*metadata.Collect return nil, lazyerrors.Error(err) } + // https://www.sqlite.org/pragma.html#pragma_freelist_count + q = `PRAGMA freelist_count` + + var freeListCount int64 + if err = db.QueryRowContext(ctx, q).Scan(&freeListCount); err != nil { + return nil, lazyerrors.Error(err) + } + + // https://www.sqlite.org/pragma.html#pragma_page_size + q = `PRAGMA page_size` + + var pageSize int64 + if err = db.QueryRowContext(ctx, q).Scan(&pageSize); err != nil { + return nil, lazyerrors.Error(err) + } + + // SQLite is unable to provide free storage size per collection, + // hence compute it for the entire database. + // It reports free storage regardless of file or in memory database. + // + // https://www.sqlite.org/fileformat.html + // > All pages within the same database are the same size. + stats.sizeFreeStorage = freeListCount * pageSize + return stats, nil } diff --git a/internal/handlers/sqlite/msg_aggregate.go b/internal/handlers/sqlite/msg_aggregate.go index fe57000eb508..d11e45d53c55 100644 --- a/internal/handlers/sqlite/msg_aggregate.go +++ b/internal/handlers/sqlite/msg_aggregate.go @@ -442,12 +442,13 @@ func processStagesStats(ctx context.Context, closer *iterator.MultiCloser, p *st "count", collStats.CountDocuments, "avgObjSize", avgObjSize, "storageSize", collStats.SizeCollection, - // TODO https://github.com/FerretDB/FerretDB/issues/2447 - "freeStorageSize", int64(0), + "freeStorageSize", collStats.SizeFreeStorage, "capped", cInfo.Capped(), "nindexes", nIndexes, - "indexDetails", must.NotFail(types.NewDocument()), // TODO https://github.com/FerretDB/FerretDB/issues/2342 - "indexBuilds", must.NotFail(types.NewDocument()), // TODO https://github.com/FerretDB/FerretDB/issues/2342 + // TODO https://github.com/FerretDB/FerretDB/issues/2447 + "indexDetails", must.NotFail(types.NewDocument()), + // TODO https://github.com/FerretDB/FerretDB/issues/2447 + "indexBuilds", must.NotFail(types.NewDocument()), "totalIndexSize", collStats.SizeIndexes, "totalSize", collStats.SizeTotal, "indexSizes", indexSizes, diff --git a/internal/handlers/sqlite/msg_collstats.go b/internal/handlers/sqlite/msg_collstats.go index 88965cb99b96..254765644cd6 100644 --- a/internal/handlers/sqlite/msg_collstats.go +++ b/internal/handlers/sqlite/msg_collstats.go @@ -83,8 +83,11 @@ func (h *Handler) MsgCollStats(ctx context.Context, msg *wire.OpMsg) (*wire.OpMs return nil, lazyerrors.Error(err) } + var i int + var found bool var cInfo backends.CollectionInfo - if i, found := slices.BinarySearchFunc(collections.Collections, collection, func(e backends.CollectionInfo, t string) int { + + if i, found = slices.BinarySearchFunc(collections.Collections, collection, func(e backends.CollectionInfo, t string) int { return cmp.Compare(e.Name, t) }); found { cInfo = collections.Collections[i] @@ -126,13 +129,19 @@ func (h *Handler) MsgCollStats(ctx context.Context, msg *wire.OpMsg) (*wire.OpMs indexSizes.Set(indexSize.Name, indexSize.Size/scale) } - // add freeStorageSize - // TODO https://github.com/FerretDB/FerretDB/issues/2447 - // MongoDB uses "numbers" that could be int32 or int64, // FerretDB always returns int64 for simplicity. pairs = append(pairs, "storageSize", stats.SizeCollection/scale, + ) + + if found { + pairs = append(pairs, + "freeStorageSize", stats.SizeFreeStorage/scale, + ) + } + + pairs = append(pairs, "nindexes", int64(len(indexes.Indexes)), "totalIndexSize", stats.SizeIndexes/scale, "totalSize", stats.SizeTotal/scale, diff --git a/internal/handlers/sqlite/msg_dbstats.go b/internal/handlers/sqlite/msg_dbstats.go index d77ee0aeb152..d877f6280dd4 100644 --- a/internal/handlers/sqlite/msg_dbstats.go +++ b/internal/handlers/sqlite/msg_dbstats.go @@ -51,6 +51,14 @@ func (h *Handler) MsgDBStats(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, } } + var freeStorage bool + + if v, _ := document.Get("freeStorage"); v != nil { + if freeStorage, err = commonparams.GetBoolOptionalParam("freeStorage", v); err != nil { + return nil, err + } + } + db, err := h.b.Database(dbName) if err != nil { if backends.ErrorCodeIs(err, backends.ErrorCodeDatabaseNameIsInvalid) { @@ -115,15 +123,36 @@ func (h *Handler) MsgDBStats(ctx context.Context, msg *wire.OpMsg) (*wire.OpMsg, pairs = append(pairs, "avgObjSize", stats.SizeCollections/stats.CountDocuments) } - // add freeStorageSize, indexFreeStorageSize and totalFreeStorageSize when freeStorage parameter is 1 - // TODO https://github.com/FerretDB/FerretDB/issues/2447 - pairs = append(pairs, "dataSize", stats.SizeCollections/scale, "storageSize", stats.SizeCollections/scale, + ) + + if freeStorage { + pairs = append(pairs, + "freeStorageSize", stats.SizeFreeStorage/scale, + ) + } + + pairs = append(pairs, "indexes", nIndexes, "indexSize", stats.SizeIndexes/scale, + ) + + // add indexFreeStorageSize + // TODO https://github.com/FerretDB/FerretDB/issues/2447 + + pairs = append(pairs, "totalSize", stats.SizeTotal/scale, + ) + + if freeStorage { + pairs = append(pairs, + "totalFreeStorageSize", (stats.SizeFreeStorage)/scale, + ) + } + + pairs = append(pairs, "scaleFactor", float64(scale), "ok", float64(1), )