display: false
},
tooltips: {
+ custom: function (tooltipModel: { x: number; y: number }) {
+ tooltipModel.x = 10;
+ tooltipModel.y = 0;
+ }.bind(this),
intersect: false,
displayColors: true,
backgroundColor: this.cssHelper.propertyValue('chart-color-tooltip-background'),
borderArea: true,
chartAreaBorder: {
borderColor: this.cssHelper.propertyValue('chart-color-slight-dark-gray'),
- borderWidth: 2
+ borderWidth: 1
}
}
};
private convertToChartDataUnits(data: any): any {
let dataWithUnits: string = '';
- if (this.chartDataUnits) {
+ if (this.chartDataUnits !== null) {
if (this.dataUnits === 'B') {
dataWithUnits = this.numberFormatter.formatBytesFromTo(
data,
DashboardTimeSelectorComponent
],
- exports: [DashboardV3Component, CardComponent, CardRowComponent]
+ exports: [
+ DashboardV3Component,
+ CardComponent,
+ CardRowComponent,
+ DashboardAreaChartComponent,
+ DashboardTimeSelectorComponent
+ ]
})
export class DashboardV3Module {}
import { Component, OnDestroy, OnInit } from '@angular/core';
import _ from 'lodash';
-import { Observable, Subscription, timer } from 'rxjs';
+import { Observable, Subscription } from 'rxjs';
import { take } from 'rxjs/operators';
import moment from 'moment';
});
}
- getPrometheusData(selectedTime: any) {
- this.prometheusService.ifPrometheusConfigured(() => {
- if (this.timerGetPrometheusDataSub) {
- this.timerGetPrometheusDataSub.unsubscribe();
- }
- this.timerGetPrometheusDataSub = timer(0, this.timerTime).subscribe(() => {
- selectedTime = this.updateTimeStamp(selectedTime);
-
- for (const queryName in queries) {
- if (queries.hasOwnProperty(queryName)) {
- const query = queries[queryName];
- let interval = selectedTime.step;
-
- if (query.includes('rate') && selectedTime.step < 20) {
- interval = 20;
- } else if (query.includes('rate')) {
- interval = selectedTime.step * 2;
- }
-
- const intervalAdjustedQuery = query.replace(/\[(.*?)\]/g, `[${interval}s]`);
-
- this.prometheusService
- .getPrometheusData({
- params: intervalAdjustedQuery,
- start: selectedTime['start'],
- end: selectedTime['end'],
- step: selectedTime['step']
- })
- .subscribe((data: any) => {
- if (data.result.length) {
- this.queriesResults[queryName] = data.result[0].values;
- }
- });
- }
- }
- });
- });
- }
-
- private updateTimeStamp(selectedTime: any): any {
- let formattedDate = {};
- const date: number = selectedTime['start'] + this.timerTime / 1000;
- const dateNow: number = selectedTime['end'] + this.timerTime / 1000;
- formattedDate = {
- start: date,
- end: dateNow,
- step: selectedTime['step']
- };
- return formattedDate;
+ public getPrometheusData(selectedTime: any) {
+ this.queriesResults = this.prometheusService.getPrometheusQueriesData(
+ selectedTime,
+ queries,
+ this.queriesResults
+ );
}
}
<div class="container-fluid">
<div class="row">
- <div class="col-lg-3">
- <cd-card cardTitle="Inventory"
- i18n-title
- class="col-sm-3 px-3"
- aria-label="Inventory card">
- <hr>
- <li class="list-group-item">
- <cd-card-row [data]="rgwDaemonCount"
- link="/rgw/daemon"
- title="Gateway"
- summaryType="simplified"
- *ngIf="rgwDaemonCount != null"></cd-card-row>
- </li>
- <hr>
- <li class="list-group-item">
- <cd-card-row [data]="rgwRealmCount"
- link="/rgw/multisite"
- title="Realm"
- summaryType="simplified"
- *ngIf="rgwRealmCount != null"></cd-card-row>
- </li>
- <hr>
- <li class="list-group-item">
- <cd-card-row [data]="rgwZonegroupCount"
- link="/rgw/multisite"
- title="Zonegroup"
- summaryType="simplified"
- *ngIf="rgwZonegroupCount != null"></cd-card-row>
- </li>
- <hr>
- <li class="list-group-item">
- <cd-card-row [data]="rgwZoneCount"
- link="/rgw/multisite"
- title="Zone"
- summaryType="simplified"
- *ngIf="rgwZoneCount != null"></cd-card-row>
- </li>
- <hr>
- <li class="list-group-item">
- <cd-card-row [data]="rgwBucketCount"
- link="/rgw/bucket"
- title="Bucket"
- summaryType="simplified"
- *ngIf="rgwBucketCount != null"></cd-card-row>
- </li>
- <hr>
- <li class="list-group-item">
- <cd-card-row [data]="UserCount"
- link="/rgw/user"
- title="User"
- summaryType="simplified"
- *ngIf="UserCount != null"></cd-card-row>
- </li>
- <hr>
- <li class="list-group-item">
- <cd-card-row [data]="objectCount"
- title="Object"
- summaryType="simplified"
- *ngIf="objectCount != null"></cd-card-row>
- </li>
- </cd-card>
- </div>
+ <cd-card cardTitle="Inventory"
+ i18n-title
+ class="col-sm-3 px-3 d-flex"
+ aria-label="Inventory card">
+ <hr>
+ <li class="list-group-item">
+ <cd-card-row [data]="rgwDaemonCount"
+ link="/rgw/daemon"
+ title="Daemon"
+ summaryType="simplified"
+ *ngIf="rgwDaemonCount != null"></cd-card-row>
+ </li>
+ <hr>
+ <li class="list-group-item">
+ <cd-card-row [data]="rgwRealmCount"
+ link="/rgw/multisite"
+ title="Realm"
+ summaryType="simplified"
+ *ngIf="rgwRealmCount != null"></cd-card-row>
+ </li>
+ <hr>
+ <li class="list-group-item">
+ <cd-card-row [data]="rgwZonegroupCount"
+ link="/rgw/multisite"
+ title="Zonegroup"
+ summaryType="simplified"
+ *ngIf="rgwZonegroupCount != null"></cd-card-row>
+ </li>
+ <hr>
+ <li class="list-group-item">
+ <cd-card-row [data]="rgwZoneCount"
+ link="/rgw/multisite"
+ title="Zone"
+ summaryType="simplified"
+ *ngIf="rgwZoneCount != null"></cd-card-row>
+ </li>
+ <hr>
+ <li class="list-group-item">
+ <cd-card-row [data]="rgwBucketCount"
+ link="/rgw/bucket"
+ title="Bucket"
+ summaryType="simplified"
+ *ngIf="rgwBucketCount != null"></cd-card-row>
+ </li>
+ <hr>
+ <li class="list-group-item">
+ <cd-card-row [data]="UserCount"
+ link="/rgw/user"
+ title="User"
+ summaryType="simplified"
+ *ngIf="UserCount != null"></cd-card-row>
+ </li>
+ <hr>
+ <li class="list-group-item">
+ <cd-card-row [data]="objectCount"
+ title="Object"
+ summaryType="simplified"
+ *ngIf="objectCount != null"></cd-card-row>
+ </li>
+ </cd-card>
+ <cd-card cardTitle="Performance Statistics"
+ i18n-title
+ class="col-sm-6 d-flex"
+ ria-label="Performance Statistics card">
+ <div class="ms-4 me-4 mt-0">
+ <cd-dashboard-time-selector (selectedTime)="getPrometheusData($event)">
+ </cd-dashboard-time-selector>
+ <cd-dashboard-area-chart chartTitle="Requests/sec"
+ dataUnits=""
+ label="Requests/sec"
+ [data]="queriesResults.RGW_REQUEST_PER_SECOND">
+ </cd-dashboard-area-chart>
+ <cd-dashboard-area-chart chartTitle="Latency"
+ dataUnits="ms"
+ label="GET"
+ label2="PUT"
+ [data]="queriesResults.AVG_GET_LATENCY"
+ [data2]="queriesResults.AVG_PUT_LATENCY">
+ </cd-dashboard-area-chart>
+ <cd-dashboard-area-chart chartTitle="Bandwidth"
+ dataUnits="B"
+ label="GET"
+ label2="PUT"
+ [data]="queriesResults.GET_BANDWIDTH"
+ [data2]="queriesResults.PUT_BANDWIDTH">
+ </cd-dashboard-area-chart>
+ </div>
+ </cd-card>
<div class="col-lg-3">
<cd-card cardTitle="Used Capacity"
i18n-title
- class="col-sm-2 px-3 d-flex w-100 h-50 mt-4 pb-5"
+ class="col-sm-2 d-flex w-100 h-50 pb-3"
aria-label="Details card">
<span class="ms-4 me-4 text-center">
<h1>{{ totalPoolUsedBytes | dimlessBinary}}</h1>
</cd-card>
<cd-card cardTitle="Avg Object Size"
i18n-title
- class="col-sm-2 px-3 d-flex w-100 h-50 mt-2 pb-5"
+ class="col-sm-2 d-flex w-100 h-50 pt-3"
aria-label="Details card">
<span class="ms-4 me-4 text-center">
<h1>{{ averageObjectSize | dimlessBinary}}</h1>
</div>
</div>
</div>
+
.list-group-item {
border: 0;
}
-
-.pb-5 {
- padding-bottom: 3.5rem !important;
-}
it('should render all cards', () => {
fixture.detectChanges();
const dashboardCards = fixture.debugElement.nativeElement.querySelectorAll('cd-card');
- expect(dashboardCards.length).toBe(3);
+ expect(dashboardCards.length).toBe(4);
});
it('should get corresponding data into Daemons', () => {
import _ from 'lodash';
import { Subscription } from 'rxjs';
-import { HealthService } from '~/app/shared/api/health.service';
import { Permissions } from '~/app/shared/models/permissions';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.service';
import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service';
import { RgwUserService } from '~/app/shared/api/rgw-user.service';
-import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
-import {
- FeatureTogglesMap$,
- FeatureTogglesService
-} from '~/app/shared/services/feature-toggles.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';
@Component({
selector: 'cd-rgw-overview-dashboard',
export class RgwOverviewDashboardComponent implements OnInit, OnDestroy {
interval = new Subscription();
permissions: Permissions;
- enabledFeature$: FeatureTogglesMap$;
rgwDaemonCount = 0;
rgwRealmCount = 0;
rgwZonegroupCount = 0;
UserSub: Subscription;
HealthSub: Subscription;
BucketSub: Subscription;
+ queriesResults: any = {
+ RGW_REQUEST_PER_SECOND: '',
+ BANDWIDTH: '',
+ AVG_GET_LATENCY: '',
+ AVG_PUT_LATENCY: ''
+ };
+ timerGetPrometheusDataSub: Subscription;
constructor(
private authStorageService: AuthStorageService,
- private featureToggles: FeatureTogglesService,
private healthService: HealthService,
private refreshIntervalService: RefreshIntervalService,
private rgwDaemonService: RgwDaemonService,
private rgwZonegroupService: RgwZonegroupService,
private rgwZoneService: RgwZoneService,
private rgwBucketService: RgwBucketService,
- private rgwUserService: RgwUserService
+ private rgwUserService: RgwUserService,
+ private prometheusService: PrometheusService
) {
this.permissions = this.authStorageService.getPermissions();
- this.enabledFeature$ = this.featureToggles.get();
}
ngOnInit() {
this.ZoneSUb = this.rgwZoneService.list().subscribe((data: any) => {
this.rgwZoneCount = data['zones'].length;
});
+ this.getPrometheusData(this.prometheusService.lastHourDateObject);
}
ngOnDestroy() {
this.BucketSub.unsubscribe();
this.UserSub.unsubscribe();
this.HealthSub.unsubscribe();
+ if (this.timerGetPrometheusDataSub) {
+ this.timerGetPrometheusDataSub.unsubscribe();
+ }
+ }
+
+ getPrometheusData(selectedTime: any) {
+ this.queriesResults = this.prometheusService.getPrometheusQueriesData(
+ selectedTime,
+ queries,
+ this.queriesResults,
+ true
+ );
}
}
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
-import { Observable } from 'rxjs';
+import { Observable, Subscription, timer } from 'rxjs';
import { map } from 'rxjs/operators';
import { AlertmanagerSilence } from '../models/alertmanager-silence';
AlertmanagerNotification,
PrometheusRuleGroup
} from '../models/prometheus-alerts';
+import moment from 'moment';
@Injectable({
providedIn: 'root'
})
export class PrometheusService {
+ timerGetPrometheusDataSub: Subscription;
+ timerTime = 30000;
+ readonly lastHourDateObject = {
+ start: moment().unix() - 3600,
+ end: moment().unix(),
+ step: 14
+ };
private baseURL = 'api/prometheus';
private settingsKey = {
alertmanager: 'ui-api/prometheus/alertmanager-api-host',
private getSettingsValue(data: any): string {
return data.value || data.instance || '';
}
+
+ getPrometheusQueriesData(
+ selectedTime: any,
+ queries: any,
+ queriesResults: any,
+ checkNan?: boolean
+ ) {
+ this.ifPrometheusConfigured(() => {
+ if (this.timerGetPrometheusDataSub) {
+ this.timerGetPrometheusDataSub.unsubscribe();
+ }
+ this.timerGetPrometheusDataSub = timer(0, this.timerTime).subscribe(() => {
+ selectedTime = this.updateTimeStamp(selectedTime);
+
+ for (const queryName in queries) {
+ if (queries.hasOwnProperty(queryName)) {
+ const query = queries[queryName];
+ this.getPrometheusData({
+ params: encodeURIComponent(query),
+ start: selectedTime['start'],
+ end: selectedTime['end'],
+ step: selectedTime['step']
+ }).subscribe((data: any) => {
+ if (data.result.length) {
+ queriesResults[queryName] = data.result[0].values;
+ }
+ if (
+ queriesResults[queryName] !== undefined &&
+ queriesResults[queryName] !== '' &&
+ checkNan
+ ) {
+ queriesResults[queryName].forEach((valueArray: string[]) => {
+ if (valueArray.includes('NaN')) {
+ const index = valueArray.indexOf('NaN');
+ if (index !== -1) {
+ valueArray[index] = '0';
+ }
+ }
+ });
+ }
+ });
+ }
+ }
+ });
+ });
+ return queriesResults;
+ }
+
+ private updateTimeStamp(selectedTime: any): any {
+ let formattedDate = {};
+ let secondsAgo = selectedTime['end'] - selectedTime['start'];
+ const date: number = moment().unix() - secondsAgo;
+ const dateNow: number = moment().unix();
+ formattedDate = {
+ start: date,
+ end: dateNow,
+ step: selectedTime['step']
+ };
+ return formattedDate;
+ }
}
export enum Promqls {
USEDCAPACITY = 'ceph_cluster_total_used_bytes',
- IPS = 'sum(rate(ceph_osd_op_w_in_bytes[$interval]))',
- OPS = 'sum(rate(ceph_osd_op_r_out_bytes[$interval]))',
- READLATENCY = 'avg_over_time(ceph_osd_apply_latency_ms[$interval])',
- WRITELATENCY = 'avg_over_time(ceph_osd_commit_latency_ms[$interval])',
- READCLIENTTHROUGHPUT = 'sum(rate(ceph_pool_rd_bytes[$interval]))',
- WRITECLIENTTHROUGHPUT = 'sum(rate(ceph_pool_wr_bytes[$interval]))',
- RECOVERYBYTES = 'sum(rate(ceph_osd_recovery_bytes[$interval]))'
+ IPS = 'sum(rate(ceph_osd_op_w_in_bytes[1m]))',
+ OPS = 'sum(rate(ceph_osd_op_r_out_bytes[1m]))',
+ READLATENCY = 'avg_over_time(ceph_osd_apply_latency_ms[1m])',
+ WRITELATENCY = 'avg_over_time(ceph_osd_commit_latency_ms[1m])',
+ READCLIENTTHROUGHPUT = 'sum(rate(ceph_pool_rd_bytes[1m]))',
+ WRITECLIENTTHROUGHPUT = 'sum(rate(ceph_pool_wr_bytes[1m]))',
+ RECOVERYBYTES = 'sum(rate(ceph_osd_recovery_bytes[1m]))'
+}
+
+export enum RgwPromqls {
+ RGW_REQUEST_PER_SECOND = 'sum(rate(ceph_rgw_req[1m]))',
+ AVG_GET_LATENCY = 'sum(rate(ceph_rgw_get_initial_lat_sum[1m])) / sum(rate(ceph_rgw_get_initial_lat_count[1m]))',
+ AVG_PUT_LATENCY = 'sum(rate(ceph_rgw_put_initial_lat_sum[1m])) / sum(rate(ceph_rgw_put_initial_lat_count[1m]))',
+ GET_BANDWIDTH = 'sum(rate(ceph_rgw_get_b[1m]))',
+ PUT_BANDWIDTH = 'sum(rate(ceph_rgw_put_b[1m]))'
}
formatNumberFromTo(
n: any,
units: any,
- targetedUnits: string,
+ targetedUnits: string = '',
conversionFactor: number,
unitsArray: string[],
decimals: number = 1
formatFromTo(
value: any,
units: string,
- targetedUnits: string,
+ targetedUnits: string = '',
factor: number,
labels: string[],
decimals: number = 1
formatUnitlessFromTo(
value: any,
units: string,
- targetedUnits: string,
+ targetedUnits: string = '',
decimals: number = 1
): any {
return this.formatFromTo(value, units, targetedUnits, 1000, this.unitlessLabels, decimals);