]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: fix total objects/Avg object size in RGW Overview Page 61458/head
authorAashish Sharma <aasharma@li-e74156cc-2f67-11b2-a85c-e98659a63c5c.ibm.com>
Mon, 28 Oct 2024 10:09:52 +0000 (15:39 +0530)
committerAashish Sharma <aasharma@li-e74156cc-2f67-11b2-a85c-e98659a63c5c.ibm.com>
Tue, 21 Jan 2025 04:55:03 +0000 (10:25 +0530)
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 <aasharma@redhat.com>
(cherry picked from commit 74b0749b7e63f2690a70d0226a1d730b23ddaea1)

Conflicts:
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.ts

src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-bucket.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts
src/pybind/mgr/dashboard/services/cluster.py

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 (file)
index 0000000..96553c2
--- /dev/null
@@ -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<string, any>;
+  bucket_quota: {
+    enabled: boolean;
+    check_on_raw: boolean;
+    max_size: number;
+    max_size_kb: number;
+    max_objects: number;
+  };
+  read_tracker: number;
+  bid: string;
+}
index 58adf6ab08fa48e9665acbd0a612ee44d0da7b1b..fdb6217fd4469cbd587519bb0e695f7eceb76a74 100644 (file)
@@ -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();
+  }
 }
index fca535535c1c846b2253cc5a65f3ec7efe3b8a40..0fa635c22b54cfc44c9c36bd2856b5f73e4a1d6c 100644 (file)
@@ -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 { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
-import { RgwDaemon } from '../models/rgw-daemon';
 import { HttpClientTestingModule } from '@angular/common/http/testing';
-import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
-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 { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
+import { RgwDaemon } from '../models/rgw-daemon';
 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 { configureTestBed } from '~/testing/unit-test-helper';
+import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
+import { RgwZoneService } from '~/app/shared/api/rgw-zone.service';
+import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
 
 describe('RgwOverviewDashboardComponent', () => {
   let component: RgwOverviewDashboardComponent;
   let fixture: ComponentFixture<RgwOverviewDashboardComponent>;
+  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<number>(290);
+  const totalUsedCapacitySubject = new BehaviorSubject<number>(9338880);
+  const averageObjectSizeSubject = new BehaviorSubject<number>(1280);
+  const bucketsCount = 2;
+  const usersCount = 5;
   const daemon: RgwDaemon = {
     id: '8000',
     service_map_id: '4803',
@@ -46,38 +55,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));
@@ -85,56 +100,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);
+  }));
 });
index 8b5901769c35794e6d0ac1c780424b5243fe42d3..f3a99505e2cee920058115b7ca85a252159a7b19 100644 (file)
@@ -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<any>;
   subject = new ReplaySubject<any>();
   syncCardLoading = true;
+  fetchDataSub: Subscription;
 
   constructor(
     private authStorageService: AuthStorageService,
-    private healthService: HealthService,
     private refreshIntervalService: RefreshIntervalService,
     private rgwDaemonService: RgwDaemonService,
     private rgwRealmService: RgwRealmService,
@@ -83,22 +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.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.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'];
-      });
-      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;
     });
@@ -137,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) {
index ddeeadf5e49baee39fb3ca085e76047af4452261..8dbebff1f61addfca94f77ce8517ba49c740dfe9 100644 (file)
@@ -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<Bucket[]>([]);
+  private totalNumObjectsSubject = new BehaviorSubject<number>(0);
+  private totalUsedCapacitySubject = new BehaviorSubject<number>(0);
+  private averageObjectSizeSubject = new BehaviorSubject<number>(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<Object[]>
index 9caaf1963366b978b5ed7e3236500c8363e559d9..3d7c21ac9ae253c078e39e276292b792bddfa178 100644 (file)
@@ -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):