summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts
diff options
context:
space:
mode:
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.ts278
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;
+ }
+}