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