]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: cluster-utilization card 48173/head
authorPedro Gonzalez Gomez <pegonzal@redhat.com>
Tue, 11 Oct 2022 17:43:34 +0000 (19:43 +0200)
committerPedro Gonzalez Gomez <pegonzal@redhat.com>
Mon, 16 Jan 2023 11:15:39 +0000 (12:15 +0100)
mgr/dashboard: some improvements

mgr/dashboard: updated promqls and improved tooltips

mgr/dashboard: some fixes and improvements

mgr/dashboard: added square box plugin and improved/fixed code

mgr/dashboard: added maxValue option for graphs and added tooltips to text

mgr/dashboard: removed unnecessary variables

mgr/dashboard: fix openapi

Signed-off-by: Pedro Gonzalez Gomez <pegonzal@redhat.com>
16 files changed:
src/pybind/mgr/dashboard/controllers/prometheus.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-area-chart/dashboard-area-chart.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-area-chart/dashboard-area-chart.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-area-chart/dashboard-area-chart.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-area-chart/dashboard-area-chart.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-time-selector/dashboard-time-selector.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-time-selector/dashboard-time-selector.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-time-selector/dashboard-time-selector.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-time-selector/dashboard-time-selector.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard/dashboard.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard/dashboard.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/enum/dashboard-promqls.enum.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/styles/defaults/_bootstrap-defaults.scss
src/pybind/mgr/dashboard/openapi.yaml

index ae4abfc1668859529111e9b4cc526981b39ca077..1c50cec45b52cb65bbcd3eb895b237098f3d254f 100644 (file)
@@ -86,6 +86,11 @@ class Prometheus(PrometheusRESTController):
     def rules(self, **params):
         return self.prometheus_proxy('GET', '/rules', params)
 
+    @RESTController.Collection(method='GET', path='/data')
+    def get_prometeus_data(self, **params):
+        params['query'] = params.pop('params')
+        return self.prometheus_proxy('GET', '/query_range', params)
+
     @RESTController.Collection(method='GET', path='/silences')
     def get_silences(self, **params):
         return self.alert_proxy('GET', '/silences', params)
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-area-chart/dashboard-area-chart.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-area-chart/dashboard-area-chart.component.html
new file mode 100644 (file)
index 0000000..f5b5bc5
--- /dev/null
@@ -0,0 +1,23 @@
+<div class="row">
+  <div class="col-3">
+    <br>
+    <b class="chartTitle"
+       i18n>{{ chartTitle }}</b>
+    <br>
+    <span [ngbTooltip]="label"
+          i18n>{{currentData}} {{ currentDataUnits }}</span>
+    <br>
+    <span [ngbTooltip]="label2"
+          i18n>{{currentData2}} {{ currentDataUnits2 }}</span>
+  </div>
+  <div class="col-9">
+    <div class="chart">
+      <canvas baseChart
+              [datasets]="chartData.dataset"
+              [options]="options"
+              [chartType]="'line'"
+              [plugins]="chartAreaBorderPlugin">
+      </canvas>
+    </div>
+  </div>
+</div>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-area-chart/dashboard-area-chart.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-area-chart/dashboard-area-chart.component.scss
new file mode 100644 (file)
index 0000000..dd3f915
--- /dev/null
@@ -0,0 +1,8 @@
+.chartTitle {
+  margin-top: 5vw;
+}
+
+.chart {
+  height: 7vh;
+  margin-top: 15px;
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-area-chart/dashboard-area-chart.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-area-chart/dashboard-area-chart.component.spec.ts
new file mode 100644 (file)
index 0000000..0501ac7
--- /dev/null
@@ -0,0 +1,36 @@
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CssHelper } from '~/app/shared/classes/css-helper';
+import { DimlessBinaryPerSecondPipe } from '~/app/shared/pipes/dimless-binary-per-second.pipe';
+import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
+import { DimlessPipe } from '~/app/shared/pipes/dimless.pipe';
+import { FormatterService } from '~/app/shared/services/formatter.service';
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { DashboardAreaChartComponent } from './dashboard-area-chart.component';
+
+describe('DashboardAreaChartComponent', () => {
+  let component: DashboardAreaChartComponent;
+  let fixture: ComponentFixture<DashboardAreaChartComponent>;
+
+  configureTestBed({
+    schemas: [NO_ERRORS_SCHEMA],
+    declarations: [DashboardAreaChartComponent],
+    providers: [
+      CssHelper,
+      DimlessBinaryPipe,
+      DimlessBinaryPerSecondPipe,
+      DimlessPipe,
+      FormatterService
+    ]
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(DashboardAreaChartComponent);
+    component = fixture.componentInstance;
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-area-chart/dashboard-area-chart.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-area-chart/dashboard-area-chart.component.ts
new file mode 100644 (file)
index 0000000..d7cc51e
--- /dev/null
@@ -0,0 +1,261 @@
+import { AfterViewInit, Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core';
+
+import { CssHelper } from '~/app/shared/classes/css-helper';
+import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
+import { DimlessBinaryPerSecondPipe } from '~/app/shared/pipes/dimless-binary-per-second.pipe';
+import { FormatterService } from '~/app/shared/services/formatter.service';
+import { BaseChartDirective, PluginServiceGlobalRegistrationAndOptions } from 'ng2-charts';
+import { DimlessPipe } from '~/app/shared/pipes/dimless.pipe';
+
+@Component({
+  selector: 'cd-dashboard-area-chart',
+  templateUrl: './dashboard-area-chart.component.html',
+  styleUrls: ['./dashboard-area-chart.component.scss']
+})
+export class DashboardAreaChartComponent implements OnInit, OnChanges, AfterViewInit {
+  @ViewChild(BaseChartDirective) chart: BaseChartDirective;
+
+  @Input()
+  chartTitle: string;
+  @Input()
+  maxValue?: any;
+  @Input()
+  dataUnits: string;
+  @Input()
+  data: any;
+  @Input()
+  data2?: any;
+  @Input()
+  label: any;
+  @Input()
+  label2?: any;
+
+  currentDataUnits: string;
+  currentData: number;
+  currentDataUnits2?: string;
+  currentData2?: number;
+
+  chartData: any = {
+    dataset: [
+      {
+        label: '',
+        data: [{ x: 0, y: 0 }],
+        tension: 0,
+        pointBackgroundColor: this.cssHelper.propertyValue('chart-color-strong-blue'),
+        backgroundColor: this.cssHelper.propertyValue('chart-color-translucent-blue'),
+        borderColor: this.cssHelper.propertyValue('chart-color-strong-blue')
+      },
+      {
+        label: '',
+        data: [],
+        tension: 0,
+        pointBackgroundColor: this.cssHelper.propertyValue('chart-color-orange'),
+        backgroundColor: this.cssHelper.propertyValue('chart-color-yellow'),
+        borderColor: this.cssHelper.propertyValue('chart-color-orange')
+      }
+    ]
+  };
+
+  options: any = {
+    responsive: true,
+    maintainAspectRatio: false,
+    elements: {
+      point: {
+        radius: 0
+      }
+    },
+    legend: {
+      display: false
+    },
+    tooltips: {
+      intersect: false,
+      displayColors: true,
+      backgroundColor: this.cssHelper.propertyValue('chart-color-tooltip-background'),
+      callbacks: {
+        title: function (tooltipItem: any): any {
+          return tooltipItem[0].xLabel;
+        }
+      }
+    },
+    hover: {
+      intersect: false
+    },
+    scales: {
+      xAxes: [
+        {
+          display: false,
+          type: 'time',
+          gridLines: {
+            display: false
+          },
+          time: {
+            tooltipFormat: 'YYYY/MM/DD hh:mm:ss'
+          }
+        }
+      ],
+      yAxes: [
+        {
+          gridLines: {
+            display: false
+          },
+          ticks: {
+            beginAtZero: true,
+            maxTicksLimit: 3,
+            callback: (value: any) => {
+              if (value === 0) {
+                return null;
+              }
+              return this.fillString(this.convertUnits(value));
+            }
+          }
+        }
+      ]
+    },
+    plugins: {
+      borderArea: true,
+      chartAreaBorder: {
+        borderColor: this.cssHelper.propertyValue('chart-color-slight-dark-gray'),
+        borderWidth: 2
+      }
+    }
+  };
+
+  public chartAreaBorderPlugin: PluginServiceGlobalRegistrationAndOptions[] = [
+    {
+      beforeDraw(chart: Chart) {
+        if (!chart.options.plugins.borderArea) {
+          return;
+        }
+        const {
+          ctx,
+          chartArea: { left, top, right, bottom }
+        } = chart;
+        ctx.save();
+        ctx.strokeStyle = chart.options.plugins.chartAreaBorder.borderColor;
+        ctx.lineWidth = chart.options.plugins.chartAreaBorder.borderWidth;
+        ctx.setLineDash(chart.options.plugins.chartAreaBorder.borderDash || []);
+        ctx.lineDashOffset = chart.options.plugins.chartAreaBorder.borderDashOffset;
+        ctx.strokeRect(left, top, right - left - 1, bottom);
+        ctx.restore();
+      }
+    }
+  ];
+
+  constructor(
+    private cssHelper: CssHelper,
+    private dimlessBinary: DimlessBinaryPipe,
+    private dimlessBinaryPerSecond: DimlessBinaryPerSecondPipe,
+    private dimlessPipe: DimlessPipe,
+    private formatter: FormatterService
+  ) {}
+
+  ngOnInit(): void {
+    this.currentData = Number(
+      this.chartData.dataset[0].data[this.chartData.dataset[0].data.length - 1].y
+    );
+    if (this.data2) {
+      this.currentData2 = Number(
+        this.chartData.dataset[1].data[this.chartData.dataset[1].data.length - 1].y
+      );
+    }
+  }
+
+  ngOnChanges(): void {
+    if (this.data) {
+      this.setChartTicks();
+      this.chartData.dataset[0].data = this.formatData(this.data);
+      this.chartData.dataset[0].label = this.label;
+      [this.currentData, this.currentDataUnits] = this.convertUnits(
+        this.data[this.data.length - 1][1]
+      ).split(' ');
+    }
+    if (this.data2) {
+      this.chartData.dataset[1].data = this.formatData(this.data2);
+      this.chartData.dataset[1].label = this.label2;
+      [this.currentData2, this.currentDataUnits2] = this.convertUnits(
+        this.data2[this.data2.length - 1][1]
+      ).split(' ');
+    }
+  }
+
+  ngAfterViewInit(): void {
+    if (this.data) {
+      this.setChartTicks();
+    }
+  }
+
+  private formatData(array: Array<any>): any {
+    let formattedData = {};
+    formattedData = array.map((data: any) => ({
+      x: data[0] * 1000,
+      y: Number(this.convertUnits(data[1]).replace(/[^\d,.]+/g, ''))
+    }));
+    return formattedData;
+  }
+
+  private convertUnits(data: any): any {
+    let dataWithUnits: string;
+    if (this.dataUnits === 'bytes') {
+      dataWithUnits = this.dimlessBinary.transform(data);
+    } else if (this.dataUnits === 'bytesPerSecond') {
+      dataWithUnits = this.dimlessBinaryPerSecond.transform(data);
+    } else if (this.dataUnits === 'ms') {
+      dataWithUnits = this.formatter.format_number(data, 1000, ['ms', 's']);
+    } else {
+      dataWithUnits = this.dimlessPipe.transform(data);
+    }
+    return dataWithUnits;
+  }
+
+  private fillString(str: string): string {
+    let maxNumberOfChar: number = 8;
+    let numberOfChars: number = str.length;
+    if (str.length < 4) {
+      maxNumberOfChar = 11;
+    }
+    for (; numberOfChars < maxNumberOfChar; numberOfChars++) {
+      str = '\u00A0' + str;
+    }
+    return str + '\u00A0\u00A0';
+  }
+
+  private setChartTicks() {
+    if (this.chart && this.maxValue) {
+      let [maxValue, maxValueDataUnits] = this.convertUnits(this.maxValue).split(' ');
+      this.chart.chart.options.scales.yAxes[0].ticks.suggestedMax = maxValue;
+      this.chart.chart.options.scales.yAxes[0].ticks.suggestedMin = 0;
+      this.chart.chart.options.scales.yAxes[0].ticks.stepSize = Number((maxValue / 2).toFixed(0));
+      this.chart.chart.options.scales.yAxes[0].ticks.callback = (value: any) => {
+        if (value === 0) {
+          return null;
+        }
+        return this.fillString(`${value} ${maxValueDataUnits}`);
+      };
+      this.chart.chart.update();
+    } else if (this.chart && this.data) {
+      let maxValueData = Math.max(...this.data.map((values: any) => values[1]));
+      if (this.data2) {
+        var maxValueData2 = Math.max(...this.data2.map((values: any) => values[1]));
+      }
+      let [maxValue, maxValueDataUnits] = this.convertUnits(
+        Math.max(maxValueData, maxValueData2)
+      ).split(' ');
+
+      this.chart.chart.options.scales.yAxes[0].ticks.suggestedMax = maxValue * 1.2;
+      this.chart.chart.options.scales.yAxes[0].ticks.suggestedMin = 0;
+      this.chart.chart.options.scales.yAxes[0].ticks.stepSize = Number(
+        ((maxValue * 1.2) / 2).toFixed(0)
+      );
+      this.chart.chart.options.scales.yAxes[0].ticks.callback = (value: any) => {
+        if (value === 0) {
+          return null;
+        }
+        if (!maxValueDataUnits) {
+          return this.fillString(`${value}`);
+        }
+        return this.fillString(`${value} ${maxValueDataUnits}`);
+      };
+      this.chart.chart.update();
+    }
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-time-selector/dashboard-time-selector.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-time-selector/dashboard-time-selector.component.html
new file mode 100644 (file)
index 0000000..cd960d0
--- /dev/null
@@ -0,0 +1,11 @@
+<div class="timeSelector">
+  <select id="timepicker"
+          name="timepicker"
+          [(ngModel)]="time"
+          (ngModelChange)="emitTime()"
+          class="form-select">
+    <option *ngFor="let key of times"
+            [ngValue]="key.value">{{ key.name }}
+    </option>
+  </select>
+</div>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-time-selector/dashboard-time-selector.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-time-selector/dashboard-time-selector.component.scss
new file mode 100644 (file)
index 0000000..13572dc
--- /dev/null
@@ -0,0 +1,10 @@
+select#timepicker {
+  border: 0;
+}
+
+.timeSelector {
+  position: absolute;
+  right: 18px;
+  top: 20px;
+  width: 12rem;
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-time-selector/dashboard-time-selector.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-time-selector/dashboard-time-selector.component.spec.ts
new file mode 100644 (file)
index 0000000..9aeec4d
--- /dev/null
@@ -0,0 +1,24 @@
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { DashboardTimeSelectorComponent } from './dashboard-time-selector.component';
+
+describe('DashboardTimeSelectorComponent', () => {
+  let component: DashboardTimeSelectorComponent;
+  let fixture: ComponentFixture<DashboardTimeSelectorComponent>;
+
+  configureTestBed({
+    schemas: [NO_ERRORS_SCHEMA],
+    declarations: [DashboardTimeSelectorComponent]
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(DashboardTimeSelectorComponent);
+    component = fixture.componentInstance;
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-time-selector/dashboard-time-selector.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/new-dashboard/dashboard-time-selector/dashboard-time-selector.component.ts
new file mode 100644 (file)
index 0000000..15c8253
--- /dev/null
@@ -0,0 +1,77 @@
+import { Component, EventEmitter, Output } from '@angular/core';
+
+import moment from 'moment';
+
+@Component({
+  selector: 'cd-dashboard-time-selector',
+  templateUrl: './dashboard-time-selector.component.html',
+  styleUrls: ['./dashboard-time-selector.component.scss']
+})
+export class DashboardTimeSelectorComponent {
+  @Output()
+  selectedTime = new EventEmitter<any>();
+
+  times: any;
+  time: any;
+
+  constructor() {
+    this.times = [
+      {
+        name: $localize`Last 5 minutes`,
+        value: this.timeToDate(5 * 60)
+      },
+      {
+        name: $localize`Last 15 minutes`,
+        value: this.timeToDate(15 * 60)
+      },
+      {
+        name: $localize`Last 30 minutes`,
+        value: this.timeToDate(30 * 60)
+      },
+      {
+        name: $localize`Last 1 hour`,
+        value: this.timeToDate(3600)
+      },
+      {
+        name: $localize`Last 3 hours`,
+        value: this.timeToDate(3 * 3600)
+      },
+      {
+        name: $localize`Last 6 hours`,
+        value: this.timeToDate(6 * 3600, 30)
+      },
+      {
+        name: $localize`Last 12 hours`,
+        value: this.timeToDate(12 * 3600, 60)
+      },
+      {
+        name: $localize`Last 24 hours`,
+        value: this.timeToDate(24 * 3600, 120)
+      },
+      {
+        name: $localize`Last 2 days`,
+        value: this.timeToDate(48 * 3600, 300)
+      },
+      {
+        name: $localize`Last 7 days`,
+        value: this.timeToDate(168 * 3600, 900)
+      }
+    ];
+    this.time = this.times[3].value;
+  }
+
+  emitTime() {
+    this.selectedTime.emit(this.time);
+  }
+
+  private timeToDate(secondsAgo: number, step: number = 30): any {
+    const date: number = moment().unix() - secondsAgo;
+    const dateNow: number = moment().unix();
+    const formattedDate: any = {
+      start: date,
+      end: dateNow,
+      step: step
+    };
+    return formattedDate;
+  }
+}
index c32c2a630a19d9dcfb308aa90fb95b594aa79a0d..466da56256959f231e36045024622ad4c4af73c5 100644 (file)
@@ -3,14 +3,16 @@ import { NgModule } from '@angular/core';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { RouterModule } from '@angular/router';
 
-import { NgbNavModule, NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap';
+import { NgbNavModule, NgbPopoverModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
 import { ChartsModule } from 'ng2-charts';
 import { SimplebarAngularModule } from 'simplebar-angular';
 
 import { SharedModule } from '~/app/shared/shared.module';
 import { CephSharedModule } from '../shared/ceph-shared.module';
 import { CardComponent } from './card/card.component';
+import { DashboardAreaChartComponent } from './dashboard-area-chart/dashboard-area-chart.component';
 import { DashboardPieComponent } from './dashboard-pie/dashboard-pie.component';
+import { DashboardTimeSelectorComponent } from './dashboard-time-selector/dashboard-time-selector.component';
 import { DashboardComponent } from './dashboard/dashboard.component';
 import { CardRowComponent } from './card-row/card-row.component';
 import { PgSummaryPipe } from './pg-summary.pipe';
@@ -24,6 +26,7 @@ import { PgSummaryPipe } from './pg-summary.pipe';
     ChartsModule,
     RouterModule,
     NgbPopoverModule,
+    NgbTooltipModule,
     FormsModule,
     ReactiveFormsModule,
     SimplebarAngularModule
@@ -33,9 +36,10 @@ import { PgSummaryPipe } from './pg-summary.pipe';
     DashboardComponent,
     CardComponent,
     DashboardPieComponent,
-    DashboardPieComponent,
     CardRowComponent,
-    PgSummaryPipe
+    PgSummaryPipe,
+    DashboardAreaChartComponent,
+    DashboardTimeSelectorComponent
   ]
 })
 export class NewDashboardModule {}
index 0d7fa1c0018596c286f09dae8c806a8be65e2ea1..0b534719331ea5f6c1504e1f9cf34b3550276372 100644 (file)
       </li>
     </cd-card>
 
-    <cd-card title="Capacity utilization"
+    <cd-card title="Cluster utilization"
              i18n-title
              class="col-sm-6 px-3">
       <div class="ms-4 me-4">
-        Text
+        <cd-dashboard-time-selector (selectedTime)="getPrometheusData($event)">
+        </cd-dashboard-time-selector>
+        <ng-container *ngIf="capacity">
+          <cd-dashboard-area-chart chartTitle="Used Capacity"
+                                   [maxValue]="capacity.total_bytes"
+                                   dataUnits="bytes"
+                                   label="Used Capacity"
+                                   [data]="queriesResults.USEDCAPACITY">
+          </cd-dashboard-area-chart>
+        </ng-container>
+        <cd-dashboard-area-chart chartTitle="IOPS"
+                                 dataUnits="none"
+                                 label="IPS"
+                                 label2="OPS"
+                                 [data]="queriesResults.IPS"
+                                 [data2]="queriesResults.OPS">
+        </cd-dashboard-area-chart>
+        <cd-dashboard-area-chart chartTitle="Latency"
+                                 dataUnits="ms"
+                                 label="Read"
+                                 label2="Write"
+                                 [data]="queriesResults.READLATENCY"
+                                 [data2]="queriesResults.WRITELATENCY">
+        </cd-dashboard-area-chart>
+        <cd-dashboard-area-chart chartTitle="Client Throughput"
+                                 dataUnits="bytesPerSecond"
+                                 label="Read"
+                                 label2="Write"
+                                 [data]="queriesResults.READCLIENTTHROUGHPUT"
+                                 [data2]="queriesResults.WRITECLIENTTHROUGHPUT">
+        </cd-dashboard-area-chart>
       </div>
     </cd-card>
 
index 094200fc542c43ab22ac3d110b21509e1c756670..e82f1d49286326cc61e141737ced2fe46f7e85a8 100644 (file)
@@ -1,8 +1,9 @@
 import { Component, OnDestroy, OnInit } from '@angular/core';
 
 import _ from 'lodash';
-import { Observable, Subscription } from 'rxjs';
+import { Observable, Subscription, timer } from 'rxjs';
 import { take } from 'rxjs/operators';
+import moment from 'moment';
 
 import { ClusterService } from '~/app/shared/api/cluster.service';
 import { ConfigurationService } from '~/app/shared/api/configuration.service';
@@ -10,6 +11,7 @@ import { HealthService } from '~/app/shared/api/health.service';
 import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
 import { OsdService } from '~/app/shared/api/osd.service';
 import { PrometheusService } from '~/app/shared/api/prometheus.service';
+import { Promqls as queries } from '~/app/shared/enum/dashboard-promqls.enum';
 import { Icons } from '~/app/shared/enum/icons.enum';
 import { DashboardDetails } from '~/app/shared/models/cd-details';
 import { Permissions } from '~/app/shared/models/permissions';
@@ -55,6 +57,22 @@ export class DashboardComponent implements OnInit, OnDestroy {
   healthData: any;
   categoryPgAmount: Record<string, number> = {};
   totalPgs = 0;
+  queriesResults: any = {
+    USEDCAPACITY: '',
+    IPS: '',
+    OPS: '',
+    READLATENCY: '',
+    WRITELATENCY: '',
+    READCLIENTTHROUGHPUT: '',
+    WRITECLIENTTHROUGHPUT: ''
+  };
+  timerGetPrometheusDataSub: Subscription;
+  timerTime = 30000;
+  readonly lastHourDateObject = {
+    start: moment().unix() - 3600,
+    end: moment().unix(),
+    step: 30
+  };
 
   constructor(
     private summaryService: SummaryService,
@@ -78,6 +96,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
       this.triggerPrometheusAlerts();
       this.getCapacityCardData();
     });
+    this.getPrometheusData(this.lastHourDateObject);
     this.getDetailsCardData();
   }
 
@@ -142,4 +161,42 @@ export class DashboardComponent implements OnInit, OnDestroy {
       });
     });
   }
+
+  getPrometheusData(selectedTime: any) {
+    if (this.timerGetPrometheusDataSub) {
+      this.timerGetPrometheusDataSub.unsubscribe();
+    }
+    this.timerGetPrometheusDataSub = timer(0, this.timerTime).subscribe(() => {
+      selectedTime = this.updateTimeStamp(selectedTime);
+
+      for (const queryName in queries) {
+        if (queries.hasOwnProperty(queryName)) {
+          this.prometheusService
+            .getPrometheusData({
+              params: queries[queryName],
+              start: selectedTime['start'],
+              end: selectedTime['end'],
+              step: selectedTime['step']
+            })
+            .subscribe((data: any) => {
+              if (data.result.length) {
+                this.queriesResults[queryName] = data.result[0].values;
+              }
+            });
+        }
+      }
+    });
+  }
+
+  private updateTimeStamp(selectedTime: any): any {
+    let formattedDate = {};
+    const date: number = selectedTime['start'] + this.timerTime / 1000;
+    const dateNow: number = selectedTime['end'] + this.timerTime / 1000;
+    formattedDate = {
+      start: date,
+      end: dateNow,
+      step: selectedTime['step']
+    };
+    return formattedDate;
+  }
 }
index 58191721953d57441f189218d94246f2aceff762..30d7d488649ae21dc7a3e8a5954219e4365b1012 100644 (file)
@@ -24,6 +24,10 @@ export class PrometheusService {
 
   constructor(private http: HttpClient, private settingsService: SettingsService) {}
 
+  getPrometheusData(params: any): any {
+    return this.http.get<any>(`${this.baseURL}/data`, { params });
+  }
+
   ifAlertmanagerConfigured(fn: (value?: string) => void, elseFn?: () => void): void {
     this.settingsService.ifSettingConfigured(this.settingsKey.alertmanager, fn, elseFn);
   }
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/dashboard-promqls.enum.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/dashboard-promqls.enum.ts
new file mode 100644 (file)
index 0000000..870af88
--- /dev/null
@@ -0,0 +1,9 @@
+export enum Promqls {
+  USEDCAPACITY = 'ceph_cluster_total_used_bytes',
+  IPS = 'sum(irate(ceph_osd_op_w_in_bytes[1m]))',
+  OPS = 'sum(irate(ceph_osd_op_r_out_bytes[1m]))',
+  READLATENCY = 'avg(ceph_osd_apply_latency_ms)',
+  WRITELATENCY = 'avg(ceph_osd_commit_latency_ms)',
+  READCLIENTTHROUGHPUT = 'sum(irate(ceph_pool_rd_bytes[1m]))',
+  WRITECLIENTTHROUGHPUT = 'sum(irate(ceph_pool_wr_bytes[1m]))'
+}
index f871b4ff80f8ed6f84e87405925f418536761645..1272c3c77ce62a11d074f43eacb48aca4d0caab5 100644 (file)
@@ -91,6 +91,8 @@ $chart-color-center-text: #151515 !default;
 $chart-color-center-text-description: #72767b !default;
 $chart-color-tooltip-background: $black !default;
 $chart-danger: #c9190b !default;
+$chart-color-strong-blue: #0078c8 !default;
+$chart-color-translucent-blue: #0096dc80 !default;
 
 // Typography
 
index 9a41690d71b12ba640e68cc20503b465c517a9ee..09af946e27053176e40688801f35e414bf74d37a 100644 (file)
@@ -7487,6 +7487,28 @@ paths:
       - jwt: []
       tags:
       - Prometheus
+  /api/prometheus/data:
+    get:
+      parameters: []
+      responses:
+        '200':
+          content:
+            application/vnd.ceph.api.v1.0+json:
+              type: object
+          description: OK
+        '400':
+          description: Operation exception. Please check the response body for details.
+        '401':
+          description: Unauthenticated access. Please login first.
+        '403':
+          description: Unauthorized access. Please check your permissions.
+        '500':
+          description: Unexpected error. Please check the response body for the stack
+            trace.
+      security:
+      - jwt: []
+      tags:
+      - Prometheus
   /api/prometheus/notifications:
     get:
       parameters: []