diff options
Diffstat (limited to 'src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts')
-rw-r--r-- | src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts new file mode 100644 index 000000000..4d1dac769 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts @@ -0,0 +1,278 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; + +import _ from 'lodash'; +import { Subscription } from 'rxjs'; +import { take } from 'rxjs/operators'; + +import { PgCategoryService } from '~/app/ceph/shared/pg-category.service'; +import { HealthService } from '~/app/shared/api/health.service'; +import { OsdService } from '~/app/shared/api/osd.service'; +import { CssHelper } from '~/app/shared/classes/css-helper'; +import { Icons } from '~/app/shared/enum/icons.enum'; +import { OsdSettings } from '~/app/shared/models/osd-settings'; +import { Permissions } from '~/app/shared/models/permissions'; +import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe'; +import { DimlessPipe } from '~/app/shared/pipes/dimless.pipe'; +import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; +import { + FeatureTogglesMap$, + FeatureTogglesService +} from '~/app/shared/services/feature-toggles.service'; +import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.service'; + +@Component({ + selector: 'cd-health', + templateUrl: './health.component.html', + styleUrls: ['./health.component.scss'] +}) +export class HealthComponent implements OnInit, OnDestroy { + healthData: any; + osdSettings = new OsdSettings(); + interval = new Subscription(); + permissions: Permissions; + enabledFeature$: FeatureTogglesMap$; + icons = Icons; + color: string; + + clientStatsConfig = { + colors: [ + { + backgroundColor: [ + this.cssHelper.propertyValue('chart-color-cyan'), + this.cssHelper.propertyValue('chart-color-purple') + ] + } + ] + }; + + rawCapacityChartConfig = { + colors: [ + { + backgroundColor: [ + this.cssHelper.propertyValue('chart-color-blue'), + this.cssHelper.propertyValue('chart-color-gray') + ] + } + ] + }; + + pgStatusChartConfig = { + options: { + events: [''] + } + }; + + constructor( + private healthService: HealthService, + private osdService: OsdService, + private authStorageService: AuthStorageService, + private pgCategoryService: PgCategoryService, + private featureToggles: FeatureTogglesService, + private refreshIntervalService: RefreshIntervalService, + private dimlessBinary: DimlessBinaryPipe, + private dimless: DimlessPipe, + private cssHelper: CssHelper + ) { + this.permissions = this.authStorageService.getPermissions(); + this.enabledFeature$ = this.featureToggles.get(); + } + + ngOnInit() { + this.interval = this.refreshIntervalService.intervalData$.subscribe(() => { + this.getHealth(); + }); + + this.osdService + .getOsdSettings() + .pipe(take(1)) + .subscribe((data: any) => { + this.osdSettings = data; + }); + } + + ngOnDestroy() { + this.interval.unsubscribe(); + } + + getHealth() { + this.healthService.getMinimalHealth().subscribe((data: any) => { + this.healthData = data; + }); + } + + prepareReadWriteRatio(chart: Record<string, any>) { + const ratioLabels = []; + const ratioData = []; + + const total = + this.healthData.client_perf.write_op_per_sec + this.healthData.client_perf.read_op_per_sec; + + ratioLabels.push( + `${$localize`Reads`}: ${this.dimless.transform( + this.healthData.client_perf.read_op_per_sec + )} ${$localize`/s`}` + ); + ratioData.push(this.calcPercentage(this.healthData.client_perf.read_op_per_sec, total)); + ratioLabels.push( + `${$localize`Writes`}: ${this.dimless.transform( + this.healthData.client_perf.write_op_per_sec + )} ${$localize`/s`}` + ); + ratioData.push(this.calcPercentage(this.healthData.client_perf.write_op_per_sec, total)); + + chart.labels = ratioLabels; + chart.dataset[0].data = ratioData; + chart.dataset[0].label = `${this.dimless.transform(total)}\n${$localize`IOPS`}`; + } + + prepareClientThroughput(chart: Record<string, any>) { + const ratioLabels = []; + const ratioData = []; + + const total = + this.healthData.client_perf.read_bytes_sec + this.healthData.client_perf.write_bytes_sec; + + ratioLabels.push( + `${$localize`Reads`}: ${this.dimlessBinary.transform( + this.healthData.client_perf.read_bytes_sec + )}${$localize`/s`}` + ); + ratioData.push(this.calcPercentage(this.healthData.client_perf.read_bytes_sec, total)); + ratioLabels.push( + `${$localize`Writes`}: ${this.dimlessBinary.transform( + this.healthData.client_perf.write_bytes_sec + )}${$localize`/s`}` + ); + ratioData.push(this.calcPercentage(this.healthData.client_perf.write_bytes_sec, total)); + + chart.labels = ratioLabels; + chart.dataset[0].data = ratioData; + chart.dataset[0].label = `${this.dimlessBinary + .transform(total) + .replace(' ', '\n')}${$localize`/s`}`; + } + + prepareRawUsage(chart: Record<string, any>, data: Record<string, any>) { + const percentAvailable = this.calcPercentage( + data.df.stats.total_bytes - data.df.stats.total_used_raw_bytes, + data.df.stats.total_bytes + ); + const percentUsed = this.calcPercentage( + data.df.stats.total_used_raw_bytes, + data.df.stats.total_bytes + ); + + if (percentUsed / 100 >= this.osdSettings.nearfull_ratio) { + this.color = 'chart-color-red'; + } else if (percentUsed / 100 >= this.osdSettings.full_ratio) { + this.color = 'chart-color-yellow'; + } else { + this.color = 'chart-color-blue'; + } + this.rawCapacityChartConfig.colors[0].backgroundColor[0] = this.cssHelper.propertyValue( + this.color + ); + + chart.dataset[0].data = [percentUsed, percentAvailable]; + + chart.labels = [ + `${$localize`Used`}: ${this.dimlessBinary.transform(data.df.stats.total_used_raw_bytes)}`, + `${$localize`Avail.`}: ${this.dimlessBinary.transform( + data.df.stats.total_bytes - data.df.stats.total_used_raw_bytes + )}` + ]; + + chart.dataset[0].label = `${percentUsed}%\nof ${this.dimlessBinary.transform( + data.df.stats.total_bytes + )}`; + } + + preparePgStatus(chart: Record<string, any>, data: Record<string, any>) { + const categoryPgAmount: Record<string, number> = {}; + let totalPgs = 0; + + _.forEach(data.pg_info.statuses, (pgAmount, pgStatesText) => { + const categoryType = this.pgCategoryService.getTypeByStates(pgStatesText); + + if (_.isUndefined(categoryPgAmount[categoryType])) { + categoryPgAmount[categoryType] = 0; + } + categoryPgAmount[categoryType] += pgAmount; + totalPgs += pgAmount; + }); + + for (const categoryType of this.pgCategoryService.getAllTypes()) { + if (_.isUndefined(categoryPgAmount[categoryType])) { + categoryPgAmount[categoryType] = 0; + } + } + + chart.dataset[0].data = this.pgCategoryService + .getAllTypes() + .map((categoryType) => this.calcPercentage(categoryPgAmount[categoryType], totalPgs)); + + chart.labels = [ + `${$localize`Clean`}: ${this.dimless.transform(categoryPgAmount['clean'])}`, + `${$localize`Working`}: ${this.dimless.transform(categoryPgAmount['working'])}`, + `${$localize`Warning`}: ${this.dimless.transform(categoryPgAmount['warning'])}`, + `${$localize`Unknown`}: ${this.dimless.transform(categoryPgAmount['unknown'])}` + ]; + + chart.dataset[0].label = `${totalPgs}\n${$localize`PGs`}`; + } + + prepareObjects(chart: Record<string, any>, data: Record<string, any>) { + const objectCopies = data.pg_info.object_stats.num_object_copies; + const healthy = + objectCopies - + data.pg_info.object_stats.num_objects_misplaced - + data.pg_info.object_stats.num_objects_degraded - + data.pg_info.object_stats.num_objects_unfound; + const healthyPercentage = this.calcPercentage(healthy, objectCopies); + const misplacedPercentage = this.calcPercentage( + data.pg_info.object_stats.num_objects_misplaced, + objectCopies + ); + const degradedPercentage = this.calcPercentage( + data.pg_info.object_stats.num_objects_degraded, + objectCopies + ); + const unfoundPercentage = this.calcPercentage( + data.pg_info.object_stats.num_objects_unfound, + objectCopies + ); + + chart.labels = [ + `${$localize`Healthy`}: ${healthyPercentage}%`, + `${$localize`Misplaced`}: ${misplacedPercentage}%`, + `${$localize`Degraded`}: ${degradedPercentage}%`, + `${$localize`Unfound`}: ${unfoundPercentage}%` + ]; + + chart.dataset[0].data = [ + healthyPercentage, + misplacedPercentage, + degradedPercentage, + unfoundPercentage + ]; + + chart.dataset[0].label = `${this.dimless.transform( + data.pg_info.object_stats.num_objects + )}\n${$localize`objects`}`; + } + + isClientReadWriteChartShowable() { + const readOps = this.healthData.client_perf.read_op_per_sec || 0; + const writeOps = this.healthData.client_perf.write_op_per_sec || 0; + + return readOps + writeOps > 0; + } + + private calcPercentage(dividend: number, divisor: number) { + if (!_.isNumber(dividend) || !_.isNumber(divisor) || divisor === 0) { + return 0; + } + + return Math.ceil((dividend / divisor) * 100 * 100) / 100; + } +} |