]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts
7b6e5f8a0fbc92210348902c78f9f75bc20be9f5
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / dashboard / health / health.component.ts
1 import { Component, OnDestroy, OnInit } from '@angular/core';
2
3 import { I18n } from '@ngx-translate/i18n-polyfill';
4 import * as _ from 'lodash';
5 import { Subscription } from 'rxjs';
6
7 import { HealthService } from '../../../shared/api/health.service';
8 import { Icons } from '../../../shared/enum/icons.enum';
9 import { Permissions } from '../../../shared/models/permissions';
10 import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe';
11 import { DimlessPipe } from '../../../shared/pipes/dimless.pipe';
12 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
13 import {
14 FeatureTogglesMap$,
15 FeatureTogglesService
16 } from '../../../shared/services/feature-toggles.service';
17 import { RefreshIntervalService } from '../../../shared/services/refresh-interval.service';
18 import { PgCategoryService } from '../../shared/pg-category.service';
19 import { HealthPieColor } from '../health-pie/health-pie-color.enum';
20
21 @Component({
22 selector: 'cd-health',
23 templateUrl: './health.component.html',
24 styleUrls: ['./health.component.scss']
25 })
26 export class HealthComponent implements OnInit, OnDestroy {
27 healthData: any;
28 interval = new Subscription();
29 permissions: Permissions;
30 enabledFeature$: FeatureTogglesMap$;
31 icons = Icons;
32
33 rawCapacityChartConfig = {
34 options: {
35 title: { display: true, position: 'bottom' }
36 }
37 };
38 objectsChartConfig = {
39 options: {
40 title: { display: true, position: 'bottom' }
41 },
42 colors: [
43 {
44 backgroundColor: [
45 HealthPieColor.DEFAULT_GREEN,
46 HealthPieColor.DEFAULT_MAGENTA,
47 HealthPieColor.DEFAULT_ORANGE,
48 HealthPieColor.DEFAULT_RED
49 ]
50 }
51 ]
52 };
53 pgStatusChartConfig = {
54 colors: [
55 {
56 backgroundColor: [
57 HealthPieColor.DEFAULT_GREEN,
58 HealthPieColor.DEFAULT_BLUE,
59 HealthPieColor.DEFAULT_ORANGE,
60 HealthPieColor.DEFAULT_RED
61 ]
62 }
63 ]
64 };
65
66 constructor(
67 private healthService: HealthService,
68 private i18n: I18n,
69 private authStorageService: AuthStorageService,
70 private pgCategoryService: PgCategoryService,
71 private featureToggles: FeatureTogglesService,
72 private refreshIntervalService: RefreshIntervalService,
73 private dimlessBinary: DimlessBinaryPipe,
74 private dimless: DimlessPipe
75 ) {
76 this.permissions = this.authStorageService.getPermissions();
77 this.enabledFeature$ = this.featureToggles.get();
78 }
79
80 ngOnInit() {
81 this.getHealth();
82 this.interval = this.refreshIntervalService.intervalData$.subscribe(() => {
83 this.getHealth();
84 });
85 }
86
87 ngOnDestroy() {
88 this.interval.unsubscribe();
89 }
90
91 getHealth() {
92 this.healthService.getMinimalHealth().subscribe((data: any) => {
93 this.healthData = data;
94 });
95 }
96
97 prepareReadWriteRatio(chart: Record<string, any>) {
98 const ratioLabels = [];
99 const ratioData = [];
100
101 const total =
102 this.healthData.client_perf.write_op_per_sec + this.healthData.client_perf.read_op_per_sec;
103
104 ratioLabels.push(
105 `${this.i18n('Writes')} (${this.calcPercentage(
106 this.healthData.client_perf.write_op_per_sec,
107 total
108 )}%)`
109 );
110 ratioData.push(this.healthData.client_perf.write_op_per_sec);
111 ratioLabels.push(
112 `${this.i18n('Reads')} (${this.calcPercentage(
113 this.healthData.client_perf.read_op_per_sec,
114 total
115 )}%)`
116 );
117 ratioData.push(this.healthData.client_perf.read_op_per_sec);
118
119 chart.dataset[0].data = ratioData;
120 chart.labels = ratioLabels;
121 }
122
123 prepareRawUsage(chart: Record<string, any>, data: Record<string, any>) {
124 const percentAvailable = this.calcPercentage(
125 data.df.stats.total_bytes - data.df.stats.total_used_raw_bytes,
126 data.df.stats.total_bytes
127 );
128 const percentUsed = this.calcPercentage(
129 data.df.stats.total_used_raw_bytes,
130 data.df.stats.total_bytes
131 );
132
133 chart.dataset[0].data = [data.df.stats.total_used_raw_bytes, data.df.stats.total_avail_bytes];
134
135 chart.labels = [
136 `${this.dimlessBinary.transform(data.df.stats.total_used_raw_bytes)} ${this.i18n(
137 'Used'
138 )} (${percentUsed}%)`,
139 `${this.dimlessBinary.transform(
140 data.df.stats.total_bytes - data.df.stats.total_used_raw_bytes
141 )} ${this.i18n('Avail.')} (${percentAvailable}%)`
142 ];
143
144 chart.options.title.text = `${this.dimlessBinary.transform(
145 data.df.stats.total_bytes
146 )} ${this.i18n('total')}`;
147 }
148
149 preparePgStatus(chart: Record<string, any>, data: Record<string, any>) {
150 const categoryPgAmount: Record<string, number> = {};
151 let totalPgs = 0;
152
153 _.forEach(data.pg_info.statuses, (pgAmount, pgStatesText) => {
154 const categoryType = this.pgCategoryService.getTypeByStates(pgStatesText);
155
156 if (_.isUndefined(categoryPgAmount[categoryType])) {
157 categoryPgAmount[categoryType] = 0;
158 }
159 categoryPgAmount[categoryType] += pgAmount;
160 totalPgs += pgAmount;
161 });
162
163 chart.dataset[0].data = this.pgCategoryService
164 .getAllTypes()
165 .map((categoryType) => categoryPgAmount[categoryType]);
166
167 chart.labels = [
168 `${this.i18n('Clean')} (${this.calcPercentage(categoryPgAmount['clean'], totalPgs)}%)`,
169 `${this.i18n('Working')} (${this.calcPercentage(categoryPgAmount['working'], totalPgs)}%)`,
170 `${this.i18n('Warning')} (${this.calcPercentage(categoryPgAmount['warning'], totalPgs)}%)`,
171 `${this.i18n('Unknown')} (${this.calcPercentage(categoryPgAmount['unknown'], totalPgs)}%)`
172 ];
173 }
174
175 prepareObjects(chart: Record<string, any>, data: Record<string, any>) {
176 const totalReplicas = data.pg_info.object_stats.num_object_copies;
177 const healthy =
178 totalReplicas -
179 data.pg_info.object_stats.num_objects_misplaced -
180 data.pg_info.object_stats.num_objects_degraded -
181 data.pg_info.object_stats.num_objects_unfound;
182
183 chart.labels = [
184 `${this.i18n('Healthy')} (${this.calcPercentage(healthy, totalReplicas)}%)`,
185 `${this.i18n('Misplaced')} (${this.calcPercentage(
186 data.pg_info.object_stats.num_objects_misplaced,
187 totalReplicas
188 )}%)`,
189 `${this.i18n('Degraded')} (${this.calcPercentage(
190 data.pg_info.object_stats.num_objects_degraded,
191 totalReplicas
192 )}%)`,
193 `${this.i18n('Unfound')} (${this.calcPercentage(
194 data.pg_info.object_stats.num_objects_unfound,
195 totalReplicas
196 )}%)`
197 ];
198
199 chart.dataset[0].data = [
200 healthy,
201 data.pg_info.object_stats.num_objects_misplaced,
202 data.pg_info.object_stats.num_objects_degraded,
203 data.pg_info.object_stats.num_objects_unfound
204 ];
205
206 chart.options.title.text = `${this.dimless.transform(
207 data.pg_info.object_stats.num_objects
208 )} ${this.i18n('total')} (${this.dimless.transform(totalReplicas)} ${this.i18n('replicas')})`;
209
210 chart.options.maintainAspectRatio = window.innerWidth >= 375;
211 }
212
213 isClientReadWriteChartShowable() {
214 const readOps = this.healthData.client_perf.read_op_per_sec || 0;
215 const writeOps = this.healthData.client_perf.write_op_per_sec || 0;
216
217 return readOps + writeOps > 0;
218 }
219
220 private calcPercentage(dividend: number, divisor: number) {
221 if (!_.isNumber(dividend) || !_.isNumber(divisor) || divisor === 0) {
222 return 0;
223 }
224
225 return Math.round((dividend / divisor) * 100);
226 }
227 }