From bb0a85e58f8b4c57c5e95774942df46a7ede879c Mon Sep 17 00:00:00 2001 From: Aashish Sharma Date: Mon, 28 Oct 2024 15:39:52 +0530 Subject: [PATCH] mgr/dashboard: fix total objects/Avg object size in RGW Overview Page Till now we are calculating the total number of objects and the average object size in the RGW Overview Page using `ceph df` command's output. As per the discussion with RGW team, this data is not correct as S3 objects in rgw can occupy more than one rados object. This PR tends to make the overview page's info in sync with the RGW bucket page's info. Fixes: https://tracker.ceph.com/issues/68733 Signed-off-by: Aashish Sharma (cherry picked from commit 74b0749b7e63f2690a70d0226a1d730b23ddaea1) --- .../src/app/ceph/rgw/models/rgw-bucket.ts | 37 +++++ .../rgw-bucket-list.component.ts | 50 +++--- .../rgw-overview-dashboard.component.spec.ts | 153 ++++++++++-------- .../rgw-overview-dashboard.component.ts | 51 +++--- .../src/app/shared/api/rgw-bucket.service.ts | 59 ++++++- src/pybind/mgr/dashboard/services/cluster.py | 33 +--- 6 files changed, 226 insertions(+), 157 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-bucket.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-bucket.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-bucket.ts new file mode 100644 index 0000000000000..96553c20e9110 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-bucket.ts @@ -0,0 +1,37 @@ +export interface Bucket { + bucket: string; + tenant: string; + versioning: string; + zonegroup: string; + placement_rule: string; + explicit_placement: { + data_pool: string; + data_extra_pool: string; + index_pool: string; + }; + id: string; + marker: string; + index_type: string; + index_generation: number; + num_shards: number; + reshard_status: string; + judge_reshard_lock_time: string; + object_lock_enabled: boolean; + mfa_enabled: boolean; + owner: string; + ver: string; + master_ver: string; + mtime: string; + creation_time: string; + max_marker: string; + usage: Record; + bucket_quota: { + enabled: boolean; + check_on_raw: boolean; + max_size: number; + max_size_kb: number; + max_objects: number; + }; + read_tracker: number; + bid: string; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts index 58adf6ab08fa4..fdb6217fd4469 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts @@ -1,7 +1,8 @@ -import { Component, NgZone, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { Component, NgZone, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; import _ from 'lodash'; -import { forkJoin as observableForkJoin, Observable, Subscriber } from 'rxjs'; +import { forkJoin as observableForkJoin, Observable, Subscriber, Subscription } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service'; import { ListWithDetails } from '~/app/shared/classes/list-with-details.class'; @@ -19,6 +20,7 @@ import { DimlessPipe } from '~/app/shared/pipes/dimless.pipe'; import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; import { ModalService } from '~/app/shared/services/modal.service'; import { URLBuilderService } from '~/app/shared/services/url-builder.service'; +import { Bucket } from '../models/rgw-bucket'; const BASE_URL = 'rgw/bucket'; @@ -28,7 +30,7 @@ const BASE_URL = 'rgw/bucket'; styleUrls: ['./rgw-bucket-list.component.scss'], providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }] }) -export class RgwBucketListComponent extends ListWithDetails implements OnInit { +export class RgwBucketListComponent extends ListWithDetails implements OnInit, OnDestroy { @ViewChild(TableComponent, { static: true }) table: TableComponent; @ViewChild('bucketSizeTpl', { static: true }) @@ -39,9 +41,10 @@ export class RgwBucketListComponent extends ListWithDetails implements OnInit { permission: Permission; tableActions: CdTableAction[]; columns: CdTableColumn[] = []; - buckets: object[] = []; + buckets: Bucket[] = []; selection: CdTableSelection = new CdTableSelection(); declare staleTimeout: number; + private subs: Subscription = new Subscription(); constructor( private authStorageService: AuthStorageService, @@ -121,33 +124,18 @@ export class RgwBucketListComponent extends ListWithDetails implements OnInit { this.setTableRefreshTimeout(); } - transformBucketData() { - _.forEach(this.buckets, (bucketKey) => { - const maxBucketSize = bucketKey['bucket_quota']['max_size']; - const maxBucketObjects = bucketKey['bucket_quota']['max_objects']; - bucketKey['bucket_size'] = 0; - bucketKey['num_objects'] = 0; - if (!_.isEmpty(bucketKey['usage'])) { - bucketKey['bucket_size'] = bucketKey['usage']['rgw.main']['size_actual']; - bucketKey['num_objects'] = bucketKey['usage']['rgw.main']['num_objects']; - } - bucketKey['size_usage'] = - maxBucketSize > 0 ? bucketKey['bucket_size'] / maxBucketSize : undefined; - bucketKey['object_usage'] = - maxBucketObjects > 0 ? bucketKey['num_objects'] / maxBucketObjects : undefined; - }); - } - getBucketList(context: CdTableFetchDataContext) { this.setTableRefreshTimeout(); - this.rgwBucketService.list(true).subscribe( - (resp: object[]) => { - this.buckets = resp; - this.transformBucketData(); - }, - () => { - context.error(); - } + this.subs.add( + this.rgwBucketService + .fetchAndTransformBuckets() + .pipe(switchMap(() => this.rgwBucketService.buckets$)) + .subscribe({ + next: (buckets) => { + this.buckets = buckets; + }, + error: () => context.error() + }) ); } @@ -185,4 +173,8 @@ export class RgwBucketListComponent extends ListWithDetails implements OnInit { } }); } + + ngOnDestroy() { + this.subs.unsubscribe(); + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.spec.ts index 36cafa855a3f2..c7aaddcd08fdb 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.spec.ts @@ -1,24 +1,33 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - +import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { of, BehaviorSubject, combineLatest } from 'rxjs'; import { RgwOverviewDashboardComponent } from './rgw-overview-dashboard.component'; -import { of } from 'rxjs'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service'; import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service'; import { RgwDaemon } from '../models/rgw-daemon'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { CardComponent } from '~/app/shared/components/card/card.component'; +import { CardRowComponent } from '~/app/shared/components/card-row/card-row.component'; import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { RgwRealmService } from '~/app/shared/api/rgw-realm.service'; -import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service'; import { RgwZoneService } from '~/app/shared/api/rgw-zone.service'; -import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service'; -import { HealthService } from '~/app/shared/api/health.service'; -import { CardRowComponent } from '~/app/shared/components/card-row/card-row.component'; -import { CardComponent } from '~/app/shared/components/card/card.component'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { configureTestBed } from '~/testing/unit-test-helper'; +import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service'; describe('RgwOverviewDashboardComponent', () => { let component: RgwOverviewDashboardComponent; let fixture: ComponentFixture; + let listDaemonsSpy: jest.SpyInstance; + let listRealmsSpy: jest.SpyInstance; + let listZonegroupsSpy: jest.SpyInstance; + let listZonesSpy: jest.SpyInstance; + let fetchAndTransformBucketsSpy: jest.SpyInstance; + let totalBucketsAndUsersSpy: jest.SpyInstance; + + const totalNumObjectsSubject = new BehaviorSubject(290); + const totalUsedCapacitySubject = new BehaviorSubject(9338880); + const averageObjectSizeSubject = new BehaviorSubject(1280); + const bucketsCount = 2; + const usersCount = 5; const daemon: RgwDaemon = { id: '8000', service_map_id: '4803', @@ -47,38 +56,44 @@ describe('RgwOverviewDashboardComponent', () => { zones: ['zone4', 'zone5', 'zone6', 'zone7'] }; - const bucketAndUserList = { - buckets_count: 2, - users_count: 2 - }; - - const healthData = { - total_objects: '290', - total_pool_bytes_used: 9338880 - }; - - let listDaemonsSpy: jest.SpyInstance; - let listZonesSpy: jest.SpyInstance; - let listZonegroupsSpy: jest.SpyInstance; - let listRealmsSpy: jest.SpyInstance; - let listBucketsSpy: jest.SpyInstance; - let healthDataSpy: jest.SpyInstance; - - configureTestBed({ - declarations: [ - RgwOverviewDashboardComponent, - CardComponent, - CardRowComponent, - DimlessBinaryPipe - ], - schemas: [NO_ERRORS_SCHEMA], - imports: [HttpClientTestingModule] - }); - beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ + RgwOverviewDashboardComponent, + CardComponent, + CardRowComponent, + DimlessBinaryPipe + ], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: RgwDaemonService, useValue: { list: jest.fn() } }, + { provide: RgwRealmService, useValue: { list: jest.fn() } }, + { provide: RgwZonegroupService, useValue: { list: jest.fn() } }, + { provide: RgwZoneService, useValue: { list: jest.fn() } }, + { + provide: RgwBucketService, + useValue: { + fetchAndTransformBuckets: jest.fn(), + totalNumObjects$: totalNumObjectsSubject.asObservable(), + totalUsedCapacity$: totalUsedCapacitySubject.asObservable(), + averageObjectSize$: averageObjectSizeSubject.asObservable(), + getTotalBucketsAndUsersLength: jest.fn() + } + } + ], + imports: [HttpClientTestingModule] + }).compileComponents(); + fixture = TestBed.createComponent(RgwOverviewDashboardComponent); + component = fixture.componentInstance; listDaemonsSpy = jest .spyOn(TestBed.inject(RgwDaemonService), 'list') .mockReturnValue(of([daemon])); + fetchAndTransformBucketsSpy = jest + .spyOn(TestBed.inject(RgwBucketService), 'fetchAndTransformBuckets') + .mockReturnValue(of(null)); + totalBucketsAndUsersSpy = jest + .spyOn(TestBed.inject(RgwBucketService), 'getTotalBucketsAndUsersLength') + .mockReturnValue(of({ buckets_count: bucketsCount, users_count: usersCount })); listRealmsSpy = jest .spyOn(TestBed.inject(RgwRealmService), 'list') .mockReturnValue(of(realmList)); @@ -86,56 +101,60 @@ describe('RgwOverviewDashboardComponent', () => { .spyOn(TestBed.inject(RgwZonegroupService), 'list') .mockReturnValue(of(zonegroupList)); listZonesSpy = jest.spyOn(TestBed.inject(RgwZoneService), 'list').mockReturnValue(of(zoneList)); - listBucketsSpy = jest - .spyOn(TestBed.inject(RgwBucketService), 'getTotalBucketsAndUsersLength') - .mockReturnValue(of(bucketAndUserList)); - healthDataSpy = jest - .spyOn(TestBed.inject(HealthService), 'getClusterCapacity') - .mockReturnValue(of(healthData)); - fixture = TestBed.createComponent(RgwOverviewDashboardComponent); - component = fixture.componentInstance; fixture.detectChanges(); }); - it('should create', () => { + it('should create the component', () => { expect(component).toBeTruthy(); }); it('should render all cards', () => { - fixture.detectChanges(); const dashboardCards = fixture.debugElement.nativeElement.querySelectorAll('cd-card'); expect(dashboardCards.length).toBe(5); }); - it('should get corresponding data into Daemons', () => { - expect(listDaemonsSpy).toHaveBeenCalled(); - expect(component.rgwDaemonCount).toEqual(1); - }); - - it('should get corresponding data into Realms', () => { + it('should get data for Realms', () => { expect(listRealmsSpy).toHaveBeenCalled(); expect(component.rgwRealmCount).toEqual(2); }); - it('should get corresponding data into Zonegroups', () => { + it('should get data for Zonegroups', () => { expect(listZonegroupsSpy).toHaveBeenCalled(); expect(component.rgwZonegroupCount).toEqual(3); }); - it('should get corresponding data into Zones', () => { + it('should get data for Zones', () => { expect(listZonesSpy).toHaveBeenCalled(); expect(component.rgwZoneCount).toEqual(4); }); - it('should get corresponding data into Buckets', () => { - expect(listBucketsSpy).toHaveBeenCalled(); - expect(component.rgwBucketCount).toEqual(2); - expect(component.UserCount).toEqual(2); - }); - - it('should get corresponding data into Objects and capacity', () => { - expect(healthDataSpy).toHaveBeenCalled(); - expect(component.objectCount).toEqual('290'); + it('should set component properties from services using combineLatest', fakeAsync(() => { + component.interval = of(null).subscribe(() => { + component.fetchDataSub = combineLatest([ + TestBed.inject(RgwDaemonService).list(), + TestBed.inject(RgwBucketService).fetchAndTransformBuckets(), + totalNumObjectsSubject.asObservable(), + totalUsedCapacitySubject.asObservable(), + averageObjectSizeSubject.asObservable(), + TestBed.inject(RgwBucketService).getTotalBucketsAndUsersLength() + ]).subscribe(([daemonData, _, objectCount, usedCapacity, averageSize, bucketData]) => { + component.rgwDaemonCount = daemonData.length; + component.objectCount = objectCount; + component.totalPoolUsedBytes = usedCapacity; + component.averageObjectSize = averageSize; + component.rgwBucketCount = bucketData.buckets_count; + component.UserCount = bucketData.users_count; + }); + }); + tick(); + expect(listDaemonsSpy).toHaveBeenCalled(); + expect(fetchAndTransformBucketsSpy).toHaveBeenCalled(); + expect(totalBucketsAndUsersSpy).toHaveBeenCalled(); + expect(component.rgwDaemonCount).toEqual(1); + expect(component.objectCount).toEqual(290); expect(component.totalPoolUsedBytes).toEqual(9338880); - }); + expect(component.averageObjectSize).toEqual(1280); + expect(component.rgwBucketCount).toEqual(bucketsCount); + expect(component.UserCount).toEqual(usersCount); + })); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.ts index 00037a7235b8e..f3a99505e2cee 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.ts @@ -1,7 +1,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import _ from 'lodash'; -import { Observable, ReplaySubject, Subscription, of } from 'rxjs'; +import { Observable, ReplaySubject, Subscription, combineLatest, of } from 'rxjs'; import { Permissions } from '~/app/shared/models/permissions'; import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; @@ -14,7 +14,6 @@ import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service'; import { PrometheusService } from '~/app/shared/api/prometheus.service'; import { RgwPromqls as queries } from '~/app/shared/enum/dashboard-promqls.enum'; -import { HealthService } from '~/app/shared/api/health.service'; import { Icons } from '~/app/shared/enum/icons.enum'; import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service'; import { catchError, shareReplay, switchMap, tap } from 'rxjs/operators'; @@ -39,13 +38,10 @@ export class RgwOverviewDashboardComponent implements OnInit, OnDestroy { totalPoolUsedBytes = 0; averageObjectSize = 0; realmData: any; - daemonSub: Subscription; realmSub: Subscription; multisiteInfo: object[] = []; ZonegroupSub: Subscription; ZoneSUb: Subscription; - HealthSub: Subscription; - BucketSub: Subscription; queriesResults: { [key: string]: [] } = { RGW_REQUEST_PER_SECOND: [], BANDWIDTH: [], @@ -65,10 +61,10 @@ export class RgwOverviewDashboardComponent implements OnInit, OnDestroy { multisiteSyncStatus$: Observable; subject = new ReplaySubject(); syncCardLoading = true; + fetchDataSub: Subscription; constructor( private authStorageService: AuthStorageService, - private healthService: HealthService, private refreshIntervalService: RefreshIntervalService, private rgwDaemonService: RgwDaemonService, private rgwRealmService: RgwRealmService, @@ -83,24 +79,23 @@ export class RgwOverviewDashboardComponent implements OnInit, OnDestroy { ngOnInit() { this.interval = this.refreshIntervalService.intervalData$.subscribe(() => { - this.daemonSub = this.rgwDaemonService.list().subscribe((data: any) => { - this.rgwDaemonCount = data.length; - }); - this.HealthSub = this.healthService.getClusterCapacity().subscribe((data: any) => { - this.objectCount = data['total_objects']; - this.totalPoolUsedBytes = data['total_pool_bytes_used']; - this.averageObjectSize = data['average_object_size']; - }); - setTimeout(() => { + this.fetchDataSub = combineLatest([ + this.rgwDaemonService.list(), + this.rgwBucketService.fetchAndTransformBuckets(), + this.rgwBucketService.totalNumObjects$, + this.rgwBucketService.totalUsedCapacity$, + this.rgwBucketService.averageObjectSize$, + this.rgwBucketService.getTotalBucketsAndUsersLength() + ]).subscribe(([daemonData, _, objectCount, usedCapacity, averageSize, bucketData]) => { + this.rgwDaemonCount = daemonData.length; + this.objectCount = objectCount; + this.totalPoolUsedBytes = usedCapacity; + this.averageObjectSize = averageSize; + this.rgwBucketCount = bucketData.buckets_count; + this.UserCount = bucketData.users_count; this.getSyncStatus(); }); }); - this.BucketSub = this.rgwBucketService - .getTotalBucketsAndUsersLength() - .subscribe((data: any) => { - this.rgwBucketCount = data['buckets_count']; - this.UserCount = data['users_count']; - }); this.realmSub = this.rgwRealmService.list().subscribe((data: any) => { this.rgwRealmCount = data['realms'].length; }); @@ -139,14 +134,12 @@ export class RgwOverviewDashboardComponent implements OnInit, OnDestroy { } ngOnDestroy() { - this.interval.unsubscribe(); - this.daemonSub.unsubscribe(); - this.realmSub.unsubscribe(); - this.ZonegroupSub.unsubscribe(); - this.ZoneSUb.unsubscribe(); - this.BucketSub.unsubscribe(); - this.HealthSub.unsubscribe(); - this.prometheusService.unsubscribe(); + this.interval?.unsubscribe(); + this.realmSub?.unsubscribe(); + this.ZonegroupSub?.unsubscribe(); + this.ZoneSUb?.unsubscribe(); + this.fetchDataSub?.unsubscribe(); + this.prometheusService?.unsubscribe(); } getPrometheusData(selectedTime: any) { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts index 595b02ec276d7..ed3134f5cae6b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts @@ -2,8 +2,9 @@ import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import _ from 'lodash'; -import { of as observableOf } from 'rxjs'; -import { catchError, mapTo } from 'rxjs/operators'; +import { BehaviorSubject, of as observableOf } from 'rxjs'; +import { catchError, map, mapTo } from 'rxjs/operators'; +import { Bucket } from '~/app/ceph/rgw/models/rgw-bucket'; import { ApiClient } from '~/app/shared/api/api-client'; import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service'; @@ -15,11 +16,65 @@ import { cdEncode } from '~/app/shared/decorators/cd-encode'; }) export class RgwBucketService extends ApiClient { private url = 'api/rgw/bucket'; + private bucketsSubject = new BehaviorSubject([]); + private totalNumObjectsSubject = new BehaviorSubject(0); + private totalUsedCapacitySubject = new BehaviorSubject(0); + private averageObjectSizeSubject = new BehaviorSubject(0); + buckets$ = this.bucketsSubject.asObservable(); + totalNumObjects$ = this.totalNumObjectsSubject.asObservable(); + totalUsedCapacity$ = this.totalUsedCapacitySubject.asObservable(); + averageObjectSize$ = this.averageObjectSizeSubject.asObservable(); constructor(private http: HttpClient, private rgwDaemonService: RgwDaemonService) { super(); } + fetchAndTransformBuckets() { + return this.list(true).pipe( + map((buckets: Bucket[]) => { + let totalNumObjects = 0; + let totalUsedCapacity = 0; + let averageObjectSize = 0; + const transformedBuckets = buckets.map((bucket) => this.transformBucket(bucket)); + transformedBuckets.forEach((bucket) => { + totalNumObjects += bucket?.num_objects || 0; + totalUsedCapacity += bucket?.bucket_size || 0; + }); + averageObjectSize = this.calculateAverageObjectSize(totalNumObjects, totalUsedCapacity); + this.bucketsSubject.next(transformedBuckets); + this.totalNumObjectsSubject.next(totalNumObjects); + this.totalUsedCapacitySubject.next(totalUsedCapacity); + this.averageObjectSizeSubject.next(averageObjectSize); + }) + ); + } + + transformBucket(bucket: Bucket) { + const maxBucketSize = bucket?.bucket_quota?.max_size ?? 0; + const maxBucketObjects = bucket?.bucket_quota?.max_objects ?? 0; + const bucket_size = bucket['usage']?.['rgw.main']?.['size_actual'] || 0; + const num_objects = bucket['usage']?.['rgw.main']?.['num_objects'] || 0; + return { + ...bucket, + bucket_size, + num_objects, + size_usage: this.calculateSizeUsage(bucket_size, maxBucketSize), + object_usage: this.calculateObjectUsage(num_objects, maxBucketObjects) + }; + } + + calculateSizeUsage(bucket_size: number, maxBucketSize: number) { + return maxBucketSize > 0 ? bucket_size / maxBucketSize : undefined; + } + + calculateObjectUsage(num_objects: number, maxBucketObjects: number) { + return maxBucketObjects > 0 ? num_objects / maxBucketObjects : undefined; + } + + calculateAverageObjectSize(totalNumObjects: number, totalUsedCapacity: number) { + return totalNumObjects > 0 ? totalUsedCapacity / totalNumObjects : 0; + } + /** * Get the list of buckets. * @return Observable diff --git a/src/pybind/mgr/dashboard/services/cluster.py b/src/pybind/mgr/dashboard/services/cluster.py index 9caaf1963366b..3d7c21ac9ae25 100644 --- a/src/pybind/mgr/dashboard/services/cluster.py +++ b/src/pybind/mgr/dashboard/services/cluster.py @@ -9,9 +9,6 @@ class ClusterCapacity(NamedTuple): total_avail_bytes: int total_bytes: int total_used_raw_bytes: int - total_objects: int - total_pool_bytes_used: int - average_object_size: int class ClusterModel: @@ -47,33 +44,9 @@ class ClusterModel: @classmethod def get_capacity(cls) -> ClusterCapacity: df = mgr.get('df') - total_pool_bytes_used = 0 - average_object_size = 0 - total_data_pool_objects = 0 - total_data_pool_bytes_used = 0 - rgw_pools_data = cls.get_rgw_pools() - - for pool in df['pools']: - pool_name = str(pool['name']) - if pool_name in rgw_pools_data: - if pool_name.endswith('.data'): - objects = pool['stats']['objects'] - pool_bytes_used = pool['stats']['bytes_used'] - total_pool_bytes_used += pool_bytes_used - total_data_pool_objects += objects - replica = rgw_pools_data[pool_name] - total_data_pool_bytes_used += pool_bytes_used / replica - - average_object_size = total_data_pool_bytes_used / total_data_pool_objects if total_data_pool_objects != 0 else 0 # noqa E501 #pylint: disable=line-too-long - - return ClusterCapacity( - total_avail_bytes=df['stats']['total_avail_bytes'], - total_bytes=df['stats']['total_bytes'], - total_used_raw_bytes=df['stats']['total_used_raw_bytes'], - total_objects=total_data_pool_objects, - total_pool_bytes_used=total_pool_bytes_used, - average_object_size=average_object_size - )._asdict() + return ClusterCapacity(total_avail_bytes=df['stats']['total_avail_bytes'], + total_bytes=df['stats']['total_bytes'], + total_used_raw_bytes=df['stats']['total_used_raw_bytes'])._asdict() @classmethod def get_rgw_pools(cls): -- 2.39.5