]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | import { Component, ElementRef, Input, OnChanges, OnInit, ViewChild } from '@angular/core'; |
2 | ||
eafe8130 | 3 | import { ChartDataSets, ChartOptions, ChartPoint, ChartType } from 'chart.js'; |
11fdf7f2 TL |
4 | import * as _ from 'lodash'; |
5 | import * as moment from 'moment'; | |
6 | ||
7 | import { ChartTooltip } from '../../../shared/models/chart-tooltip'; | |
8 | ||
9 | @Component({ | |
10 | selector: 'cd-cephfs-chart', | |
11 | templateUrl: './cephfs-chart.component.html', | |
12 | styleUrls: ['./cephfs-chart.component.scss'] | |
13 | }) | |
14 | export class CephfsChartComponent implements OnChanges, OnInit { | |
15 | @ViewChild('chartCanvas') | |
16 | chartCanvas: ElementRef; | |
17 | @ViewChild('chartTooltip') | |
18 | chartTooltip: ElementRef; | |
19 | ||
20 | @Input() | |
21 | mdsCounter: any; | |
22 | ||
494da23a | 23 | lhsCounter = 'mds_mem.ino'; |
11fdf7f2 TL |
24 | rhsCounter = 'mds_server.handle_client_request'; |
25 | ||
eafe8130 TL |
26 | chart: { |
27 | datasets: ChartDataSets[]; | |
28 | options: ChartOptions; | |
29 | chartType: ChartType; | |
30 | } = { | |
31 | datasets: [ | |
32 | { | |
33 | label: this.lhsCounter, | |
34 | yAxisID: 'LHS', | |
35 | data: [], | |
36 | lineTension: 0.1 | |
37 | }, | |
38 | { | |
39 | label: this.rhsCounter, | |
40 | yAxisID: 'RHS', | |
41 | data: [], | |
42 | lineTension: 0.1 | |
43 | } | |
44 | ], | |
45 | options: { | |
46 | title: { | |
47 | text: '', | |
48 | display: true | |
49 | }, | |
50 | responsive: true, | |
51 | maintainAspectRatio: false, | |
52 | legend: { | |
53 | position: 'top' | |
54 | }, | |
55 | scales: { | |
56 | xAxes: [ | |
57 | { | |
58 | position: 'top', | |
59 | type: 'time', | |
60 | time: { | |
61 | displayFormats: { | |
62 | quarter: 'MMM YYYY' | |
63 | } | |
64 | }, | |
65 | ticks: { | |
66 | maxRotation: 0 | |
67 | } | |
68 | } | |
69 | ], | |
70 | yAxes: [ | |
71 | { | |
72 | id: 'LHS', | |
73 | type: 'linear', | |
74 | position: 'left' | |
75 | }, | |
76 | { | |
77 | id: 'RHS', | |
78 | type: 'linear', | |
79 | position: 'right' | |
80 | } | |
81 | ] | |
82 | }, | |
83 | tooltips: { | |
84 | enabled: false, | |
85 | mode: 'index', | |
86 | intersect: false, | |
87 | position: 'nearest', | |
88 | callbacks: { | |
89 | // Pick the Unix timestamp of the first tooltip item. | |
90 | title: (tooltipItems, data): string => { | |
91 | let ts = 0; | |
92 | if (tooltipItems.length > 0) { | |
93 | const item = tooltipItems[0]; | |
94 | const point = data.datasets[item.datasetIndex].data[item.index] as ChartPoint; | |
95 | ts = point.x as number; | |
96 | } | |
97 | return ts.toString(); | |
98 | } | |
99 | } | |
100 | } | |
101 | }, | |
102 | chartType: 'line' | |
103 | }; | |
11fdf7f2 TL |
104 | |
105 | constructor() {} | |
106 | ||
107 | ngOnInit() { | |
108 | if (_.isUndefined(this.mdsCounter)) { | |
109 | return; | |
110 | } | |
eafe8130 TL |
111 | this.setChartTooltip(); |
112 | this.updateChart(); | |
113 | } | |
11fdf7f2 | 114 | |
eafe8130 TL |
115 | ngOnChanges() { |
116 | if (_.isUndefined(this.mdsCounter)) { | |
117 | return; | |
118 | } | |
119 | this.updateChart(); | |
120 | } | |
11fdf7f2 | 121 | |
eafe8130 | 122 | private setChartTooltip() { |
11fdf7f2 TL |
123 | const chartTooltip = new ChartTooltip( |
124 | this.chartCanvas, | |
125 | this.chartTooltip, | |
eafe8130 TL |
126 | (tooltip) => tooltip.caretX + 'px', |
127 | (tooltip) => tooltip.caretY - tooltip.height - 23 + 'px' | |
11fdf7f2 | 128 | ); |
eafe8130 | 129 | chartTooltip.getTitle = (ts) => moment(ts, 'x').format('LTS'); |
11fdf7f2 | 130 | chartTooltip.checkOffset = true; |
eafe8130 TL |
131 | const chartOptions: ChartOptions = { |
132 | title: { | |
133 | text: this.mdsCounter.name | |
11fdf7f2 | 134 | }, |
eafe8130 TL |
135 | tooltips: { |
136 | custom: (tooltip) => chartTooltip.customTooltips(tooltip) | |
137 | } | |
11fdf7f2 | 138 | }; |
eafe8130 | 139 | _.merge(this.chart, { options: chartOptions }); |
11fdf7f2 TL |
140 | } |
141 | ||
eafe8130 TL |
142 | private updateChart() { |
143 | const chartDataSets: ChartDataSets[] = [ | |
144 | { | |
145 | data: this.convertTimeSeries(this.mdsCounter[this.lhsCounter]) | |
146 | }, | |
147 | { | |
148 | data: this.deltaTimeSeries(this.mdsCounter[this.rhsCounter]) | |
149 | } | |
150 | ]; | |
151 | _.merge(this.chart, { | |
152 | datasets: chartDataSets | |
153 | }); | |
154 | this.chart.datasets = [...this.chart.datasets]; // Force angular to update | |
11fdf7f2 TL |
155 | } |
156 | ||
eafe8130 TL |
157 | /** |
158 | * Convert ceph-mgr's time series format (list of 2-tuples | |
159 | * with seconds-since-epoch timestamps) into what chart.js | |
160 | * can handle (list of objects with millisecs-since-epoch | |
161 | * timestamps) | |
162 | */ | |
163 | private convertTimeSeries(sourceSeries) { | |
11fdf7f2 TL |
164 | const data = []; |
165 | _.each(sourceSeries, (dp) => { | |
166 | data.push({ | |
167 | x: dp[0] * 1000, | |
168 | y: dp[1] | |
169 | }); | |
170 | }); | |
171 | ||
eafe8130 TL |
172 | /** |
173 | * MDS performance counters chart is expecting the same number of items | |
174 | * from each data series. Since in deltaTimeSeries we are ignoring the first | |
175 | * element, we will do the same here. | |
176 | */ | |
177 | data.shift(); | |
178 | ||
11fdf7f2 TL |
179 | return data; |
180 | } | |
181 | ||
eafe8130 | 182 | private deltaTimeSeries(sourceSeries) { |
11fdf7f2 TL |
183 | let i; |
184 | let prev = sourceSeries[0]; | |
185 | const result = []; | |
186 | for (i = 1; i < sourceSeries.length; i++) { | |
187 | const cur = sourceSeries[i]; | |
188 | const tdelta = cur[0] - prev[0]; | |
189 | const vdelta = cur[1] - prev[1]; | |
190 | const rate = vdelta / tdelta; | |
191 | ||
192 | result.push({ | |
193 | x: cur[0] * 1000, | |
194 | y: rate | |
195 | }); | |
196 | ||
197 | prev = cur; | |
198 | } | |
199 | return result; | |
200 | } | |
201 | } |