--- /dev/null
+<div class="chart-container">
+ <canvas baseChart
+ #chartCanvas
+ [datasets]="chart?.datasets"
+ [options]="chart?.options"
+ [chartType]="chart?.chartType">
+ </canvas>
+ <div class="chartjs-tooltip"
+ #chartTooltip>
+ <table></table>
+ </div>
+</div>
--- /dev/null
+.chart-container {
+ position: relative;
+ margin: auto;
+ height: 500px;
+ width: 100%;
+}
--- /dev/null
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ChartsModule } from 'ng2-charts/ng2-charts';
+
+import { CephfsChartComponent } from './cephfs-chart.component';
+
+describe('CephfsChartComponent', () => {
+ let component: CephfsChartComponent;
+ let fixture: ComponentFixture<CephfsChartComponent>;
+
+ beforeEach(
+ async(() => {
+ TestBed.configureTestingModule({
+ imports: [ChartsModule],
+ declarations: [CephfsChartComponent]
+ }).compileComponents();
+ })
+ );
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(CephfsChartComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
--- /dev/null
+import { Component, Input, OnChanges, OnInit } from '@angular/core';
+
+import * as _ from 'lodash';
+
+@Component({
+ selector: 'cd-cephfs-chart',
+ templateUrl: './cephfs-chart.component.html',
+ styleUrls: ['./cephfs-chart.component.scss']
+})
+export class CephfsChartComponent implements OnChanges, OnInit {
+ @Input() mdsCounter: any;
+
+ lhsCounter = 'mds.inodes';
+ rhsCounter = 'mds_server.handle_client_request';
+
+ chart: any;
+
+ constructor() {}
+
+ ngOnInit() {
+ if (_.isUndefined(this.mdsCounter)) {
+ return;
+ }
+
+ const lhsData = this.convert_timeseries(this.mdsCounter[this.lhsCounter]);
+ const rhsData = this.delta_timeseries(this.mdsCounter[this.rhsCounter]);
+
+ this.chart = {
+ datasets: [
+ {
+ label: this.lhsCounter,
+ yAxisID: 'LHS',
+ data: lhsData,
+ tension: 0.1
+ },
+ {
+ label: this.rhsCounter,
+ yAxisID: 'RHS',
+ data: rhsData,
+ tension: 0.1
+ }
+ ],
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ legend: {
+ position: 'top'
+ },
+ scales: {
+ xAxes: [
+ {
+ position: 'top',
+ type: 'time',
+ time: {
+ displayFormats: {
+ quarter: 'MMM YYYY'
+ }
+ }
+ }
+ ],
+ yAxes: [
+ {
+ id: 'LHS',
+ type: 'linear',
+ position: 'left',
+ min: 0
+ },
+ {
+ id: 'RHS',
+ type: 'linear',
+ position: 'right',
+ min: 0
+ }
+ ]
+ }
+ },
+ chartType: 'line'
+ };
+ }
+
+ ngOnChanges() {
+ if (!this.chart) {
+ return;
+ }
+
+ const lhsData = this.convert_timeseries(this.mdsCounter[this.lhsCounter]);
+ const rhsData = this.delta_timeseries(this.mdsCounter[this.rhsCounter]);
+
+ this.chart.datasets[0].data = lhsData;
+ this.chart.datasets[1].data = rhsData;
+ }
+
+ // Convert ceph-mgr's time series format (list of 2-tuples
+ // with seconds-since-epoch timestamps) into what chart.js
+ // can handle (list of objects with millisecs-since-epoch
+ // timestamps)
+ convert_timeseries(sourceSeries) {
+ const data = [];
+ _.each(sourceSeries, dp => {
+ data.push({
+ x: dp[0] * 1000,
+ y: dp[1]
+ });
+ });
+
+ return data;
+ }
+
+ delta_timeseries(sourceSeries) {
+ let i;
+ let prev = sourceSeries[0];
+ const result = [];
+ for (i = 1; i < sourceSeries.length; i++) {
+ const cur = sourceSeries[i];
+ const tdelta = cur[0] - prev[0];
+ const vdelta = cur[1] - prev[1];
+ const rate = vdelta / tdelta;
+
+ result.push({
+ x: cur[0] * 1000,
+ y: rate
+ });
+
+ prev = cur;
+ }
+ return result;
+ }
+}
import { AppRoutingModule } from '../../app-routing.module';
import { SharedModule } from '../../shared/shared.module';
+import { CephfsChartComponent } from './cephfs-chart/cephfs-chart.component';
import { CephfsService } from './cephfs.service';
import { CephfsComponent } from './cephfs/cephfs.component';
import { ClientsComponent } from './clients/clients.component';
ChartsModule,
ProgressbarModule.forRoot()
],
- declarations: [CephfsComponent, ClientsComponent],
+ declarations: [CephfsComponent, ClientsComponent, CephfsChartComponent],
providers: [CephfsService]
})
export class CephfsModule {}
</div>
<div class="row"
- *ngFor="let mdsCounter of objectValues(mdsCounters)">
+ *ngFor="let mdsCounter of objectValues(mdsCounters); trackBy: trackByFn">
<div class="cold-md-12">
- <div class="chart-container">
- <canvas baseChart
- [datasets]="mdsCounter.datasets"
- [options]="mdsCounter.options"
- [chartType]="mdsCounter.chartType">
- </canvas>
- </div>
+ <cd-cephfs-chart [mdsCounter]="mdsCounter"></cd-cephfs-chart>
</div>
</div>
-.chart-container {
- position: relative;
- margin: auto;
- height: 500px;
- width: 100%;
-}
-
.progress {
margin-bottom: 0px;
}
import { Observable } from 'rxjs/Observable';
import { SharedModule } from '../../../shared/shared.module';
+import { CephfsChartComponent } from '../cephfs-chart/cephfs-chart.component';
import { CephfsService } from '../cephfs.service';
import { CephfsComponent } from './cephfs.component';
BsDropdownModule.forRoot(),
ProgressbarModule.forRoot()
],
- declarations: [CephfsComponent],
+ declarations: [CephfsComponent, CephfsChartComponent],
providers: [
{ provide: CephfsService, useValue: fakeFilesystemService }
]
import { ActivatedRoute } from '@angular/router';
import * as _ from 'lodash';
+import { Subscription } from 'rxjs/Subscription';
import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe';
import { DimlessPipe } from '../../../shared/pipes/dimless.pipe';
@ViewChild('poolProgressTmpl') poolProgressTmpl: TemplateRef<any>;
@ViewChild('activityTmpl') activityTmpl: TemplateRef<any>;
- routeParamsSubscribe: any;
+ routeParamsSubscribe: Subscription;
objectValues = Object.values;
- single: any[];
- multi: any[];
-
- view: any[] = [700, 400];
-
id: number;
name: string;
ranks: any;
mdsCounters = {};
- lhsCounter = 'mds.inodes';
- rhsCounter = 'mds_server.handle_client_request';
- charts = {};
- interval: any;
-
constructor(
private route: ActivatedRoute,
private cephfsService: CephfsService,
];
this.name = data.cephfs.name;
this.clientCount = data.cephfs.client_count;
- this.draw_chart();
});
- }
- draw_chart() {
this.cephfsService.getMdsCounters(this.id).subscribe(data => {
- const topChart = true;
-
_.each(this.mdsCounters, (value, key) => {
if (data[key] === undefined) {
delete this.mdsCounters[key];
}
});
- _.each(data, (mdsData, mdsName) => {
- const lhsData = this.convert_timeseries(mdsData[this.lhsCounter]);
- const rhsData = this.delta_timeseries(mdsData[this.rhsCounter]);
-
- if (this.mdsCounters[mdsName] === undefined) {
- this.mdsCounters[mdsName] = {
- datasets: [
- {
- label: this.lhsCounter,
- yAxisID: 'LHS',
- data: lhsData,
- tension: 0.1
- },
- {
- label: this.rhsCounter,
- yAxisID: 'RHS',
- data: rhsData,
- tension: 0.1
- }
- ],
- options: {
- responsive: true,
- maintainAspectRatio: false,
- legend: {
- position: 'top',
- display: topChart
- },
- scales: {
- xAxes: [
- {
- position: 'top',
- type: 'time',
- display: topChart,
- time: {
- displayFormats: {
- quarter: 'MMM YYYY'
- }
- }
- }
- ],
- yAxes: [
- {
- id: 'LHS',
- type: 'linear',
- position: 'left',
- min: 0
- },
- {
- id: 'RHS',
- type: 'linear',
- position: 'right',
- min: 0
- }
- ]
- }
- },
- chartType: 'line'
- };
- } else {
- this.mdsCounters[mdsName].datasets[0].data = lhsData;
- this.mdsCounters[mdsName].datasets[1].data = rhsData;
- }
+ _.each(data, (mdsData: any, mdsName) => {
+ mdsData.name = mdsName;
+ this.mdsCounters[mdsName] = mdsData;
});
});
}
- // Convert ceph-mgr's time series format (list of 2-tuples
- // with seconds-since-epoch timestamps) into what chart.js
- // can handle (list of objects with millisecs-since-epoch
- // timestamps)
- convert_timeseries(sourceSeries) {
- const data = [];
- _.each(sourceSeries, dp => {
- data.push({
- x: dp[0] * 1000,
- y: dp[1]
- });
- });
-
- return data;
- }
-
- delta_timeseries(sourceSeries) {
- let i;
- let prev = sourceSeries[0];
- const result = [];
- for (i = 1; i < sourceSeries.length; i++) {
- const cur = sourceSeries[i];
- const tdelta = cur[0] - prev[0];
- const vdelta = cur[1] - prev[1];
- const rate = vdelta / tdelta;
-
- result.push({
- x: cur[0] * 1000,
- y: rate
- });
-
- prev = cur;
- }
- return result;
+ trackByFn(index, item) {
+ return item.name;
}
}