diff --git a/api/object.go b/api/object.go index 80b24d341..4548eae8a 100644 --- a/api/object.go +++ b/api/object.go @@ -113,11 +113,13 @@ type ( // ObjectsStatsResponse is the response type for the /bus/stats/objects endpoint. ObjectsStatsResponse struct { - NumObjects uint64 `json:"numObjects"` // number of objects - MinHealth float64 `json:"minHealth"` // minimum health of all objects - TotalObjectsSize uint64 `json:"totalObjectsSize"` // size of all objects - TotalSectorsSize uint64 `json:"totalSectorsSize"` // uploaded size of all objects - TotalUploadedSize uint64 `json:"totalUploadedSize"` // uploaded size of all objects including redundant sectors + NumObjects uint64 `json:"numObjects"` // number of objects + NumUnfinishedObjects uint64 `json:"numUnfinishedObjects"` // number of unfinished objects + MinHealth float64 `json:"minHealth"` // minimum health of all objects + TotalObjectsSize uint64 `json:"totalObjectsSize"` // size of all objects + TotalUnfinishedObjectsSize uint64 `json:"totalUnfinishedObjectsSize"` // size of all unfinished objects + TotalSectorsSize uint64 `json:"totalSectorsSize"` // uploaded size of all objects + TotalUploadedSize uint64 `json:"totalUploadedSize"` // uploaded size of all objects including redundant sectors } ) diff --git a/internal/testing/cluster_test.go b/internal/testing/cluster_test.go index e5836a67b..ac599ebda 100644 --- a/internal/testing/cluster_test.go +++ b/internal/testing/cluster_test.go @@ -1978,6 +1978,19 @@ func TestMultipartUploads(t *testing.T) { t.Fatal("unexpected part:", part3) } + // Check objects stats. + os, err := b.ObjectsStats() + tt.OK(err) + if os.NumObjects != 0 { + t.Fatalf("expected 0 object, got %v", os.NumObjects) + } else if os.TotalObjectsSize != 0 { + t.Fatalf("expected object size of 0, got %v", os.TotalObjectsSize) + } else if os.NumUnfinishedObjects != 1 { + t.Fatalf("expected 1 unfinished object, got %v", os.NumUnfinishedObjects) + } else if os.TotalUnfinishedObjectsSize != uint64(size) { + t.Fatalf("expected unfinished object size of %v, got %v", size, os.TotalUnfinishedObjectsSize) + } + // Complete upload ui, err := b.CompleteMultipartUpload(context.Background(), api.DefaultBucketName, objPath, mpr.UploadID, []api.MultipartCompletedPart{ { @@ -2023,6 +2036,19 @@ func TestMultipartUploads(t *testing.T) { } else if expectedData := data1[:1]; !bytes.Equal(data, expectedData) { t.Fatal("unexpected data:", cmp.Diff(data, expectedData)) } + + // Check objects stats. + os, err = b.ObjectsStats() + tt.OK(err) + if os.NumObjects != 1 { + t.Fatalf("expected 1 object, got %v", os.NumObjects) + } else if os.TotalObjectsSize != uint64(size) { + t.Fatalf("expected object size of %v, got %v", size, os.TotalObjectsSize) + } else if os.NumUnfinishedObjects != 0 { + t.Fatalf("expected 0 unfinished object, got %v", os.NumUnfinishedObjects) + } else if os.TotalUnfinishedObjectsSize != 0 { + t.Fatalf("expected unfinished object size of 0, got %v", os.TotalUnfinishedObjectsSize) + } } func TestWalletSendUnconfirmed(t *testing.T) { diff --git a/stores/metadata.go b/stores/metadata.go index 56f5e8aac..c86b9f81d 100644 --- a/stores/metadata.go +++ b/stores/metadata.go @@ -636,7 +636,7 @@ func (s *SQLStore) ListBuckets(ctx context.Context) ([]api.Bucket, error) { // reduce locking and make sure all results are consistent, everything is done // within a single transaction. func (s *SQLStore) ObjectsStats(ctx context.Context) (api.ObjectsStatsResponse, error) { - // Number of objects. + // number of objects var objInfo struct { NumObjects uint64 MinHealth float64 @@ -651,6 +651,28 @@ func (s *SQLStore) ObjectsStats(ctx context.Context) (api.ObjectsStatsResponse, return api.ObjectsStatsResponse{}, err } + // number of unfinished objects + var unfinishedObjects uint64 + err = s.db. + Model(&dbMultipartUpload{}). + Select("COUNT(*)"). + Scan(&unfinishedObjects). + Error + if err != nil { + return api.ObjectsStatsResponse{}, err + } + + // size of unfinished objects + var totalUnfinishedObjectsSize uint64 + err = s.db. + Model(&dbMultipartPart{}). + Select("COALESCE(SUM(size), 0)"). + Scan(&totalUnfinishedObjectsSize). + Error + if err != nil { + return api.ObjectsStatsResponse{}, err + } + var totalSectors uint64 batchSize := 500000 @@ -683,11 +705,13 @@ func (s *SQLStore) ObjectsStats(ctx context.Context) (api.ObjectsStatsResponse, } return api.ObjectsStatsResponse{ - MinHealth: objInfo.MinHealth, - NumObjects: objInfo.NumObjects, - TotalObjectsSize: objInfo.TotalObjectsSize, - TotalSectorsSize: totalSectors * rhpv2.SectorSize, - TotalUploadedSize: uint64(totalUploaded) * rhpv2.SectorSize, + MinHealth: objInfo.MinHealth, + NumObjects: objInfo.NumObjects, + NumUnfinishedObjects: unfinishedObjects, + TotalUnfinishedObjectsSize: totalUnfinishedObjectsSize, + TotalObjectsSize: objInfo.TotalObjectsSize, + TotalSectorsSize: totalSectors * rhpv2.SectorSize, + TotalUploadedSize: uint64(totalUploaded) * rhpv2.SectorSize, }, nil }