]> git.proxmox.com Git - ceph.git/blob - 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
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
20 @Component({
21 selector: 'cd-health',
22 templateUrl: './health.component.html',
23 styleUrls: ['./health.component.scss']
24 })
25 export class HealthComponent implements OnInit, OnDestroy {
26 healthData: any;
27 interval = new Subscription();
28 permissions: Permissions;
29 enabledFeature$: FeatureTogglesMap$;
30 icons = Icons;
31
32 clientStatsConfig = {
33 colors: [
34 {
35 backgroundColor: ['--color-cyan', '--color-purple']
36 }
37 ]
38 };
39
40 rawCapacityChartConfig = {
41 colors: [
42 {
43 backgroundColor: ['--color-blue', '--color-gray']
44 }
45 ]
46 };
47
48 pgStatusChartConfig = {
49 options: {
50 events: ['']
51 }
52 };
53
54 constructor(
55 private healthService: HealthService,
56 private i18n: I18n,
57 private authStorageService: AuthStorageService,
58 private pgCategoryService: PgCategoryService,
59 private featureToggles: FeatureTogglesService,
60 private refreshIntervalService: RefreshIntervalService,
61 private dimlessBinary: DimlessBinaryPipe,
62 private dimless: DimlessPipe
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
85 prepareReadWriteRatio(chart: Record<string, any>) {
86 const ratioLabels = [];
87 const ratioData = [];
88
89 const total =
90 this.healthData.client_perf.write_op_per_sec + this.healthData.client_perf.read_op_per_sec;
91
92 ratioLabels.push(
93 `${this.i18n(`Reads`)}: ${this.dimless.transform(
94 this.healthData.client_perf.read_op_per_sec
95 )} ${this.i18n(`/s`)}`
96 );
97 ratioData.push(this.calcPercentage(this.healthData.client_perf.read_op_per_sec, total));
98 ratioLabels.push(
99 `${this.i18n(`Writes`)}: ${this.dimless.transform(
100 this.healthData.client_perf.write_op_per_sec
101 )} ${this.i18n(`/s`)}`
102 );
103 ratioData.push(this.calcPercentage(this.healthData.client_perf.write_op_per_sec, total));
104
105 chart.labels = ratioLabels;
106 chart.dataset[0].data = ratioData;
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
130 chart.labels = ratioLabels;
131 chart.dataset[0].data = ratioData;
132 chart.dataset[0].label = `${this.dimlessBinary.transform(total).replace(' ', '\n')}${this.i18n(
133 `/s`
134 )}`;
135 }
136
137 prepareRawUsage(chart: Record<string, any>, data: Record<string, any>) {
138 const percentAvailable = this.calcPercentage(
139 data.df.stats.total_bytes - data.df.stats.total_used_raw_bytes,
140 data.df.stats.total_bytes
141 );
142 const percentUsed = this.calcPercentage(
143 data.df.stats.total_used_raw_bytes,
144 data.df.stats.total_bytes
145 );
146
147 chart.dataset[0].data = [percentUsed, percentAvailable];
148
149 chart.labels = [
150 `${this.i18n(`Used`)}: ${this.dimlessBinary.transform(data.df.stats.total_used_raw_bytes)}`,
151 `${this.i18n(`Avail.`)}: ${this.dimlessBinary.transform(
152 data.df.stats.total_bytes - data.df.stats.total_used_raw_bytes
153 )}`
154 ];
155
156 chart.dataset[0].label = `${percentUsed}%\nof ${this.dimlessBinary.transform(
157 data.df.stats.total_bytes
158 )}`;
159 }
160
161 preparePgStatus(chart: Record<string, any>, data: Record<string, any>) {
162 const categoryPgAmount: Record<string, number> = {};
163 let totalPgs = 0;
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;
172 totalPgs += pgAmount;
173 });
174
175 for (const categoryType of this.pgCategoryService.getAllTypes()) {
176 if (_.isUndefined(categoryPgAmount[categoryType])) {
177 categoryPgAmount[categoryType] = 0;
178 }
179 }
180
181 chart.dataset[0].data = this.pgCategoryService
182 .getAllTypes()
183 .map((categoryType) => this.calcPercentage(categoryPgAmount[categoryType], totalPgs));
184
185 chart.labels = [
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'])}`
190 ];
191
192 chart.dataset[0].label = `${totalPgs}\n${this.i18n(`PGs`)}`;
193 }
194
195 prepareObjects(chart: Record<string, any>, data: Record<string, any>) {
196 const objectCopies = data.pg_info.object_stats.num_object_copies;
197 const healthy =
198 objectCopies -
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;
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 );
215
216 chart.labels = [
217 `${this.i18n(`Healthy`)}: ${healthyPercentage}%`,
218 `${this.i18n(`Misplaced`)}: ${misplacedPercentage}%`,
219 `${this.i18n(`Degraded`)}: ${degradedPercentage}%`,
220 `${this.i18n(`Unfound`)}: ${unfoundPercentage}%`
221 ];
222
223 chart.dataset[0].data = [
224 healthyPercentage,
225 misplacedPercentage,
226 degradedPercentage,
227 unfoundPercentage
228 ];
229
230 chart.dataset[0].label = `${this.dimless.transform(
231 data.pg_info.object_stats.num_objects
232 )}\n${this.i18n(`objects`)}`;
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 }
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 }
249 }