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