This is an automated email from the ASF dual-hosted git repository.
wilfreds pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/yunikorn-web.git
The following commit(s) were added to refs/heads/master by this push:
new d4e85f3 [YUNIKORN-325] add node resource utilization chart (#136)
d4e85f3 is described below
commit d4e85f35eb924d79b37f0b7331a5f270febe0741
Author: Cliff Su <[email protected]>
AuthorDate: Fri Oct 27 12:18:01 2023 +1100
[YUNIKORN-325] add node resource utilization chart (#136)
Add node utilisation to the dashboard in the form of a bar chart.
New component for a bar chart, besides the donut chart added.
Add nodes/utilization mock data to json db
Closes: #136
Signed-off-by: Wilfred Spiegelenburg <[email protected]>
---
json-db.json | 418 ++++++++++++++++++++-
json-routes.json | 1 +
src/app/app.module.ts | 40 +-
.../app-node-utilization.component.html} | 27 +-
.../app-node-utilization.component.scss} | 14 +-
.../app-node-utilization.component.spec.ts} | 30 +-
.../app-node-utilization.component.ts} | 18 +-
.../components/app-status/app-status.component.ts | 5 +-
.../bar-chart/bar-chart.component.html} | 18 +-
.../bar-chart/bar-chart.component.scss} | 13 +-
.../bar-chart.component.spec.ts} | 29 +-
.../bar-chart.component.ts} | 53 +--
.../container-status/container-status.component.ts | 5 +-
.../components/dashboard/dashboard.component.html | 5 +
.../dashboard/dashboard.component.spec.ts | 2 +
.../components/dashboard/dashboard.component.ts | 58 ++-
.../donut-chart/donut-chart.component.ts | 9 +-
.../{donut-data.model.ts => chart-data.model.ts} | 2 +-
...nut-data.model.ts => node-utilization.model.ts} | 19 +-
src/app/services/scheduler/scheduler.service.ts | 8 +-
src/app/testing/mocks.ts | 1 +
21 files changed, 604 insertions(+), 171 deletions(-)
diff --git a/json-db.json b/json-db.json
index 6144881..9ba1666 100644
--- a/json-db.json
+++ b/json-db.json
@@ -751,6 +751,422 @@
"reservations": []
}
],
+ "utilization": [
+ {
+ "type": "ephemeral-storage",
+ "utilization": [
+ {
+ "bucketName": "0-10%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "10-20%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "20-30%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "30-40%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "40-50%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "50-60%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "60-70%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "70-80%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "80-90%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "90-100%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "hugepages-1Gi",
+ "utilization": [
+ {
+ "bucketName": "0-10%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "10-20%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "20-30%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "30-40%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "40-50%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "50-60%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "60-70%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "70-80%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "80-90%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "90-100%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "hugepages-2Mi",
+ "utilization": [
+ {
+ "bucketName": "0-10%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "10-20%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "20-30%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "30-40%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "40-50%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "50-60%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "60-70%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "70-80%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "80-90%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "90-100%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "memory",
+ "utilization": [
+ {
+ "bucketName": "0-10%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ },
+ {
+ "bucketName": "10-20%",
+ "numOfNodes": 1,
+ "nodeNames": [
+ "aethergpu"
+ ]
+ },
+ {
+ "bucketName": "20-30%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ },
+ {
+ "bucketName": "30-40%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ },
+ {
+ "bucketName": "40-50%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ },
+ {
+ "bucketName": "50-60%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ },
+ {
+ "bucketName": "60-70%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ },
+ {
+ "bucketName": "70-80%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ },
+ {
+ "bucketName": "80-90%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ },
+ {
+ "bucketName": "90-100%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ }
+ ]
+ },
+ {
+ "type": "pods",
+ "utilization": [
+ {
+ "bucketName": "0-10%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "10-20%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "20-30%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "30-40%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "40-50%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "50-60%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "60-70%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "70-80%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "80-90%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ },
+ {
+ "bucketName": "90-100%",
+ "numOfNodes": -1,
+ "nodeNames": [
+ "N/A"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "vcore",
+ "utilization": [
+ {
+ "bucketName": "0-10%",
+ "numOfNodes": 1,
+ "nodeNames": [
+ "aethergpu"
+ ]
+ },
+ {
+ "bucketName": "10-20%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ },
+ {
+ "bucketName": "20-30%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ },
+ {
+ "bucketName": "30-40%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ },
+ {
+ "bucketName": "40-50%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ },
+ {
+ "bucketName": "50-60%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ },
+ {
+ "bucketName": "60-70%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ },
+ {
+ "bucketName": "70-80%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ },
+ {
+ "bucketName": "80-90%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ },
+ {
+ "bucketName": "90-100%",
+ "numOfNodes": 0,
+ "nodeNames": null
+ }
+ ]
+ }
+ ],
"partitions": [
{
"clusterId": "mycluster",
@@ -903,4 +1319,4 @@
}
]
}
-}
+}
\ No newline at end of file
diff --git a/json-routes.json b/json-routes.json
index fe19605..7bb5032 100644
--- a/json-routes.json
+++ b/json-routes.json
@@ -1,4 +1,5 @@
{
+ "/ws/v1/nodes/utilization": "/utilization",
"/ws/v1/*": "/$1",
"/history/apps": "/appHistory",
"/history/containers": "/containerHistory",
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 48ed265..6cc8fc8 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -40,24 +40,26 @@ import { MatButtonModule } from '@angular/material/button';
import { MatExpansionModule } from '@angular/material/expansion';
import {MatIconModule} from '@angular/material/icon';
-import { AppRoutingModule } from './app-routing.module';
-import { envConfigFactory, EnvconfigService } from
'./services/envconfig/envconfig.service';
-import { ApiErrorInterceptor } from
'./interceptors/api-error/api-error.interceptor';
-import { AppComponent } from './app.component';
-import { DashboardComponent } from
'./components/dashboard/dashboard.component';
-import { QueuesViewComponent } from
'./components/queues-view/queues-view.component';
-import { DonutChartComponent } from
'./components/donut-chart/donut-chart.component';
-import { AreaChartComponent } from
'./components/area-chart/area-chart.component';
-import { AppStatusComponent } from
'./components/app-status/app-status.component';
-import { AppHistoryComponent } from
'./components/app-history/app-history.component';
-import { ContainerStatusComponent } from
'./components/container-status/container-status.component';
-import { ContainerHistoryComponent } from
'./components/container-history/container-history.component';
-import { QueueRackComponent } from
'./components/queue-rack/queue-rack.component';
-import { AppsViewComponent } from './components/apps-view/apps-view.component';
-import { NodesViewComponent } from
'./components/nodes-view/nodes-view.component';
-import { ErrorViewComponent } from
'./components/error-view/error-view.component';
-import { StatusViewComponent } from
'./components/status-view/status-view.component';
-import { HealthchecksComponent } from
'./components/healthchecks/healthchecks.component';
+import { AppRoutingModule } from '@app/app-routing.module';
+import { envConfigFactory, EnvconfigService } from
'@app/services/envconfig/envconfig.service';
+import { ApiErrorInterceptor } from
'@app/interceptors/api-error/api-error.interceptor';
+import { AppComponent } from '@app/app.component';
+import { DashboardComponent } from
'@app/components/dashboard/dashboard.component';
+import { QueuesViewComponent } from
'@app/components/queues-view/queues-view.component';
+import { DonutChartComponent } from
'@app/components/donut-chart/donut-chart.component';
+import { AreaChartComponent } from
'@app/components/area-chart/area-chart.component';
+import { AppStatusComponent } from
'@app/components/app-status/app-status.component';
+import { AppHistoryComponent } from
'@app/components/app-history/app-history.component';
+import { ContainerStatusComponent } from
'@app/components/container-status/container-status.component';
+import { ContainerHistoryComponent } from
'@app/components/container-history/container-history.component';
+import { QueueRackComponent } from
'@app/components/queue-rack/queue-rack.component';
+import { AppsViewComponent } from
'@app/components/apps-view/apps-view.component';
+import { NodesViewComponent } from
'@app/components/nodes-view/nodes-view.component';
+import { ErrorViewComponent } from
'@app/components/error-view/error-view.component';
+import { StatusViewComponent } from
'@app/components/status-view/status-view.component';
+import { HealthchecksComponent } from
'@app/components/healthchecks/healthchecks.component';
+import { AppNodeUtilizationComponent } from
'@app/components/app-node-utilization/app-node-utilization.component';
+import { BarChartComponent } from
'@app/components/bar-chart/bar-chart.component';
@NgModule({
declarations: [
@@ -76,6 +78,8 @@ import { HealthchecksComponent } from
'./components/healthchecks/healthchecks.co
ErrorViewComponent,
StatusViewComponent,
HealthchecksComponent,
+ AppNodeUtilizationComponent,
+ BarChartComponent,
],
imports: [
BrowserModule,
diff --git a/src/app/models/donut-data.model.ts
b/src/app/components/app-node-utilization/app-node-utilization.component.html
similarity index 67%
copy from src/app/models/donut-data.model.ts
copy to
src/app/components/app-node-utilization/app-node-utilization.component.html
index 09457ac..3137ce6 100644
--- a/src/app/models/donut-data.model.ts
+++
b/src/app/components/app-node-utilization/app-node-utilization.component.html
@@ -1,4 +1,4 @@
-/**
+<!--
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
@@ -14,16 +14,17 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */
+ -->
-export class DonutDataItem {
- name: string;
- value: number;
- color: string;
-
- constructor(name: string, value: number, color: string) {
- this.name = name;
- this.value = value;
- this.color = color;
- }
-}
+<mat-card appearance="outlined" class="box-card">
+ <mat-card-header>
+ <mat-card-title>Nodes Resource Utilization</mat-card-title>
+ </mat-card-header>
+ <mat-card-content>
+ <div class="status-wrapper flex-grid">
+ <div class="chart-wrapper flex-primary">
+ <app-bar-chart [data]="chartData" />
+ </div>
+ </div>
+ </mat-card-content>
+</mat-card>
diff --git a/src/app/models/donut-data.model.ts
b/src/app/components/app-node-utilization/app-node-utilization.component.scss
similarity index 79%
copy from src/app/models/donut-data.model.ts
copy to
src/app/components/app-node-utilization/app-node-utilization.component.scss
index 09457ac..9dc82d0 100644
--- a/src/app/models/donut-data.model.ts
+++
b/src/app/components/app-node-utilization/app-node-utilization.component.scss
@@ -16,14 +16,8 @@
* limitations under the License.
*/
-export class DonutDataItem {
- name: string;
- value: number;
- color: string;
-
- constructor(name: string, value: number, color: string) {
- this.name = name;
- this.value = value;
- this.color = color;
- }
+.status-wrapper {
+ width: 100%;
+ height: 100%;
+ align-items: center;
}
diff --git a/src/app/components/container-status/container-status.component.ts
b/src/app/components/app-node-utilization/app-node-utilization.component.spec.ts
similarity index 54%
copy from src/app/components/container-status/container-status.component.ts
copy to
src/app/components/app-node-utilization/app-node-utilization.component.spec.ts
index 2390d59..237bf64 100644
--- a/src/app/components/container-status/container-status.component.ts
+++
b/src/app/components/app-node-utilization/app-node-utilization.component.spec.ts
@@ -16,19 +16,23 @@
* limitations under the License.
*/
-import { Component, OnInit, Input } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { AppNodeUtilizationComponent } from
'@app/components/app-node-utilization/app-node-utilization.component';
-import { DonutDataItem } from '@app/models/donut-data.model';
+describe('AppNodeUtilizationComponent', () => {
+ let component: AppNodeUtilizationComponent;
+ let fixture: ComponentFixture<AppNodeUtilizationComponent>;
-@Component({
- selector: 'app-container-status',
- templateUrl: './container-status.component.html',
- styleUrls: ['./container-status.component.scss'],
-})
-export class ContainerStatusComponent implements OnInit {
- @Input() chartData: DonutDataItem[] = [];
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ declarations: [AppNodeUtilizationComponent]
+ });
+ fixture = TestBed.createComponent(AppNodeUtilizationComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
- constructor() {}
-
- ngOnInit() {}
-}
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/models/donut-data.model.ts
b/src/app/components/app-node-utilization/app-node-utilization.component.ts
similarity index 69%
copy from src/app/models/donut-data.model.ts
copy to
src/app/components/app-node-utilization/app-node-utilization.component.ts
index 09457ac..28b5046 100644
--- a/src/app/models/donut-data.model.ts
+++ b/src/app/components/app-node-utilization/app-node-utilization.component.ts
@@ -16,14 +16,14 @@
* limitations under the License.
*/
-export class DonutDataItem {
- name: string;
- value: number;
- color: string;
+import { Component, Input } from '@angular/core';
+import { ChartDataItem } from '@app/models/chart-data.model';
- constructor(name: string, value: number, color: string) {
- this.name = name;
- this.value = value;
- this.color = color;
- }
+@Component({
+ selector: 'app-node-utilization',
+ templateUrl: './app-node-utilization.component.html',
+ styleUrls: ['./app-node-utilization.component.scss']
+})
+export class AppNodeUtilizationComponent {
+ @Input() chartData: ChartDataItem[] = [];
}
diff --git a/src/app/components/app-status/app-status.component.ts
b/src/app/components/app-status/app-status.component.ts
index 91fa178..e936156 100644
--- a/src/app/components/app-status/app-status.component.ts
+++ b/src/app/components/app-status/app-status.component.ts
@@ -17,8 +17,7 @@
*/
import { Component, OnInit, Input } from '@angular/core';
-
-import { DonutDataItem } from '@app/models/donut-data.model';
+import { ChartDataItem } from '@app/models/chart-data.model';
@Component({
selector: 'app-application-status',
@@ -26,7 +25,7 @@ import { DonutDataItem } from '@app/models/donut-data.model';
styleUrls: ['./app-status.component.scss'],
})
export class AppStatusComponent implements OnInit {
- @Input() chartData: DonutDataItem[] = [];
+ @Input() chartData: ChartDataItem[] = [];
constructor() {}
diff --git a/src/app/models/donut-data.model.ts
b/src/app/components/bar-chart/bar-chart.component.html
similarity index 78%
copy from src/app/models/donut-data.model.ts
copy to src/app/components/bar-chart/bar-chart.component.html
index 09457ac..5d08ea8 100644
--- a/src/app/models/donut-data.model.ts
+++ b/src/app/components/bar-chart/bar-chart.component.html
@@ -1,4 +1,4 @@
-/**
+<!--
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
@@ -14,16 +14,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */
+ -->
-export class DonutDataItem {
- name: string;
- value: number;
- color: string;
-
- constructor(name: string, value: number, color: string) {
- this.name = name;
- this.value = value;
- this.color = color;
- }
-}
+<div style="width: 100%; height: 100%">
+ <canvas class="bar-chart" id="{{ chartContainerId }}"></canvas>
+</div>
diff --git a/src/app/models/donut-data.model.ts
b/src/app/components/bar-chart/bar-chart.component.scss
similarity index 79%
copy from src/app/models/donut-data.model.ts
copy to src/app/components/bar-chart/bar-chart.component.scss
index 09457ac..6a5db9a 100644
--- a/src/app/models/donut-data.model.ts
+++ b/src/app/components/bar-chart/bar-chart.component.scss
@@ -16,14 +16,7 @@
* limitations under the License.
*/
-export class DonutDataItem {
- name: string;
- value: number;
- color: string;
-
- constructor(name: string, value: number, color: string) {
- this.name = name;
- this.value = value;
- this.color = color;
- }
+.bar-chart {
+ width: 100%;
+ height: 100%;
}
diff --git a/src/app/components/container-status/container-status.component.ts
b/src/app/components/bar-chart/bar-chart.component.spec.ts
similarity index 58%
copy from src/app/components/container-status/container-status.component.ts
copy to src/app/components/bar-chart/bar-chart.component.spec.ts
index 2390d59..49e06be 100644
--- a/src/app/components/container-status/container-status.component.ts
+++ b/src/app/components/bar-chart/bar-chart.component.spec.ts
@@ -16,19 +16,24 @@
* limitations under the License.
*/
-import { Component, OnInit, Input } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { DonutDataItem } from '@app/models/donut-data.model';
+import { BarChartComponent } from './bar-chart.component';
-@Component({
- selector: 'app-container-status',
- templateUrl: './container-status.component.html',
- styleUrls: ['./container-status.component.scss'],
-})
-export class ContainerStatusComponent implements OnInit {
- @Input() chartData: DonutDataItem[] = [];
+describe('BarChartComponent', () => {
+ let component: BarChartComponent;
+ let fixture: ComponentFixture<BarChartComponent>;
- constructor() {}
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ declarations: [BarChartComponent]
+ });
+ fixture = TestBed.createComponent(BarChartComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
- ngOnInit() {}
-}
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/donut-chart/donut-chart.component.ts
b/src/app/components/bar-chart/bar-chart.component.ts
similarity index 71%
copy from src/app/components/donut-chart/donut-chart.component.ts
copy to src/app/components/bar-chart/bar-chart.component.ts
index e7bb71e..c89b7c5 100644
--- a/src/app/components/donut-chart/donut-chart.component.ts
+++ b/src/app/components/bar-chart/bar-chart.component.ts
@@ -16,42 +16,31 @@
* limitations under the License.
*/
-import {
- Component,
- OnInit,
- AfterViewInit,
- Input,
- OnChanges,
- SimpleChanges,
- OnDestroy,
-} from '@angular/core';
-import { Subject } from 'rxjs';
-import { takeUntil } from 'rxjs/operators';
-import { Chart, ArcElement, DoughnutController } from 'chart.js';
-
-import { CommonUtil } from '@app/utils/common.util';
-import { DonutDataItem } from '@app/models/donut-data.model';
+import { AfterViewInit, Component, Input, OnChanges, OnDestroy, OnInit,
SimpleChanges } from '@angular/core';
+import { ChartDataItem } from '@app/models/chart-data.model';
import { EventBusService, EventMap } from
'@app/services/event-bus/event-bus.service';
+import { CommonUtil } from '@app/utils/common.util';
+import { Chart, BarController, CategoryScale, BarElement } from 'chart.js';
+import { Subject, takeUntil } from 'rxjs';
-Chart.register(ArcElement, DoughnutController);
+Chart.register(BarElement, CategoryScale, BarController);
@Component({
- selector: 'app-donut-chart',
- templateUrl: './donut-chart.component.html',
- styleUrls: ['./donut-chart.component.scss'],
+ selector: 'app-bar-chart',
+ templateUrl: './bar-chart.component.html',
+ styleUrls: ['./bar-chart.component.scss']
})
-export class DonutChartComponent implements OnInit, AfterViewInit, OnChanges,
OnDestroy {
+export class BarChartComponent implements OnInit, AfterViewInit, OnChanges,
OnDestroy {
destroy$ = new Subject<boolean>();
chartContainerId = '';
- donutChartData: DonutDataItem[] = [];
- donutChart: Chart<'doughnut', number[], string> | undefined;
+ donutChartData: ChartDataItem[] = [];
+ donutChart: Chart<'bar', number[], string> | undefined;
- @Input() data: DonutDataItem[] = [];
-
- constructor(private eventBus: EventBusService) {}
+ @Input() data: ChartDataItem[] = [];
+ constructor(private eventBus: EventBusService) { }
ngOnInit() {
- this.chartContainerId = CommonUtil.createUniqId('donut_chart_');
+ this.chartContainerId = CommonUtil.createUniqId('bar_chart_');
this.eventBus
.getEvent(EventMap.WindowResizedEvent)
@@ -81,7 +70,7 @@ export class DonutChartComponent implements OnInit,
AfterViewInit, OnChanges, On
}
}
- renderChart(chartData: DonutDataItem[] = []) {
+ renderChart(chartData: ChartDataItem[] = []) {
if (!this.chartContainerId) {
return;
}
@@ -98,22 +87,20 @@ export class DonutChartComponent implements OnInit,
AfterViewInit, OnChanges, On
}
this.donutChart = new Chart(ctx!, {
- type: 'doughnut',
+ type: 'bar',
data: {
labels: chartLabels,
datasets: [
{
+ label: 'My First Dataset',
data: dataValues,
backgroundColor: colors,
- },
+ borderWidth: 1
+ }
],
},
options: {
responsive: true,
- animation: {
- animateScale: true,
- animateRotate: true,
- },
plugins: {
legend: {
display: false,
diff --git a/src/app/components/container-status/container-status.component.ts
b/src/app/components/container-status/container-status.component.ts
index 2390d59..e8f5f42 100644
--- a/src/app/components/container-status/container-status.component.ts
+++ b/src/app/components/container-status/container-status.component.ts
@@ -17,8 +17,7 @@
*/
import { Component, OnInit, Input } from '@angular/core';
-
-import { DonutDataItem } from '@app/models/donut-data.model';
+import { ChartDataItem } from '@app/models/chart-data.model';
@Component({
selector: 'app-container-status',
@@ -26,7 +25,7 @@ import { DonutDataItem } from '@app/models/donut-data.model';
styleUrls: ['./container-status.component.scss'],
})
export class ContainerStatusComponent implements OnInit {
- @Input() chartData: DonutDataItem[] = [];
+ @Input() chartData: ChartDataItem[] = [];
constructor() {}
diff --git a/src/app/components/dashboard/dashboard.component.html
b/src/app/components/dashboard/dashboard.component.html
index 7cc895f..852ad12 100644
--- a/src/app/components/dashboard/dashboard.component.html
+++ b/src/app/components/dashboard/dashboard.component.html
@@ -93,4 +93,9 @@
<app-container-history
[chartData]="containerHistoryData"></app-container-history>
</div>
</div>
+ <div class="flex-grid grid-row">
+ <div class="left-col flex-primary">
+ <app-node-utilization [chartData]="nodeUtilizationData" />
+ </div>
+ </div>
</div>
diff --git a/src/app/components/dashboard/dashboard.component.spec.ts
b/src/app/components/dashboard/dashboard.component.spec.ts
index e3c3fdb..43efdd2 100644
--- a/src/app/components/dashboard/dashboard.component.spec.ts
+++ b/src/app/components/dashboard/dashboard.component.spec.ts
@@ -31,6 +31,7 @@ import { ContainerStatusComponent } from
'@app/components/container-status/conta
import { ContainerHistoryComponent } from
'@app/components/container-history/container-history.component';
import { SchedulerService } from '@app/services/scheduler/scheduler.service';
import { EventBusService } from '@app/services/event-bus/event-bus.service';
+import { AppNodeUtilizationComponent } from
'@app/components/app-node-utilization/app-node-utilization.component';
import {
MockSchedulerService,
MockNgxSpinnerService,
@@ -49,6 +50,7 @@ describe('DashboardComponent', () => {
MockComponent(AppHistoryComponent),
MockComponent(ContainerStatusComponent),
MockComponent(ContainerHistoryComponent),
+ MockComponent(AppNodeUtilizationComponent),
],
imports: [MatCardModule, MatMenuModule, RouterTestingModule],
providers: [
diff --git a/src/app/components/dashboard/dashboard.component.ts
b/src/app/components/dashboard/dashboard.component.ts
index 2cf141c..e633659 100644
--- a/src/app/components/dashboard/dashboard.component.ts
+++ b/src/app/components/dashboard/dashboard.component.ts
@@ -21,13 +21,15 @@ import { NgxSpinnerService } from 'ngx-spinner';
import { finalize } from 'rxjs/operators';
import { SchedulerService } from '@app/services/scheduler/scheduler.service';
import { BuildInfo, ClusterInfo } from '@app/models/cluster-info.model';
-import { DonutDataItem } from '@app/models/donut-data.model';
+import { ChartDataItem } from '@app/models/chart-data.model';
import { AreaDataItem } from '@app/models/area-data.model';
import { HistoryInfo } from '@app/models/history-info.model';
import { Applications, Partition } from '@app/models/partition-info.model';
import { EventBusService, EventMap } from
'@app/services/event-bus/event-bus.service';
import { NOT_AVAILABLE } from '@app/utils/constants';
+const CHART_COLORS = ['#4285f4', '#db4437', '#f4b400', '#0f9d58', '#ff6d00',
'#3949ab', '#facc54', '#26bbf0', '#cc6164', '#60cea5'];
+
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
@@ -41,8 +43,9 @@ export class DashboardComponent implements OnInit {
totalNodes = '';
totalApplications = '';
totalContainers = '';
- appStatusData: DonutDataItem[] = [];
- containerStatusData: DonutDataItem[] = [];
+ appStatusData: ChartDataItem[] = [];
+ containerStatusData: ChartDataItem[] = [];
+ nodeUtilizationData: ChartDataItem[] = [];
appHistoryData: AreaDataItem[] = [];
containerHistoryData: AreaDataItem[] = [];
clusterInfo: ClusterInfo = this.getEmptyClusterInfo();
@@ -54,7 +57,7 @@ export class DashboardComponent implements OnInit {
private scheduler: SchedulerService,
private spinner: NgxSpinnerService,
private eventBus: EventBusService
- ) {}
+ ) { }
ngOnInit() {
this.spinner.show();
@@ -107,6 +110,29 @@ export class DashboardComponent implements OnInit {
this.appHistoryData = this.getAreaChartData(data);
});
+ this.scheduler.fetchNodeUtilization().subscribe((data) => {
+ const utilizationData: Record<string, number> = {};
+ data.forEach((utilizationInfo) => {
+ utilizationInfo.utilization.forEach(({ bucketName, numOfNodes }) => {
+ const numOfNodesValue = numOfNodes === -1 ? 0 : numOfNodes;
+ if (utilizationData[bucketName]) {
+ utilizationData[bucketName] += numOfNodesValue;
+ } else {
+ utilizationData[bucketName] = numOfNodesValue;
+ }
+ });
+ });
+
+ this.nodeUtilizationData = [];
+ Object.keys(utilizationData).forEach((name, index) => {
+ this.nodeUtilizationData.push(new ChartDataItem(
+ name,
+ utilizationData[name],
+ CHART_COLORS[index],
+ ));
+ });
+ });
+
this.scheduler.fetchContainerHistory().subscribe((data) => {
this.initialContainerHistory = data;
this.containerHistoryData = this.getAreaChartData(data);
@@ -122,22 +148,22 @@ export class DashboardComponent implements OnInit {
updateAppStatusData(applications: Applications) {
this.appStatusData = []
- if (applications.New) this.appStatusData.push(new DonutDataItem('New',
applications.New, '#facc54'))
- if (applications.Accepted) this.appStatusData.push(new
DonutDataItem('Accepted', applications.Accepted, '#facc54'))
- if (applications.Starting) this.appStatusData.push(new
DonutDataItem('Starting', applications.Starting, '#26bbf0'))
- if (applications.Running) this.appStatusData.push(new
DonutDataItem('Running', applications.Running, '#26bbf0'))
- if (applications.Rejected) this.appStatusData.push(new
DonutDataItem('Rejected', applications.Rejected, '#cc6164'))
- if (applications.Completing) this.appStatusData.push(new
DonutDataItem('Completing', applications.Completing, '#60cea5'))
- if (applications.Completed) this.appStatusData.push(new
DonutDataItem('Completed', applications.Completed, '#60cea5'))
- if (applications.Failing) this.appStatusData.push(new
DonutDataItem('Failing', applications.Failing, '#cc6164'))
- if (applications.Failed) this.appStatusData.push(new
DonutDataItem('Failed', applications.Failed, '#cc6164'))
- if (applications.Expired) this.appStatusData.push(new
DonutDataItem('Expired', applications.Expired, '#cc6164'))
- if (applications.Resuming) this.appStatusData.push(new
DonutDataItem('Resuming', applications.Resuming, '#facc54'))
+ if (applications.New) this.appStatusData.push(new ChartDataItem('New',
applications.New, '#facc54'))
+ if (applications.Accepted) this.appStatusData.push(new
ChartDataItem('Accepted', applications.Accepted, '#facc54'))
+ if (applications.Starting) this.appStatusData.push(new
ChartDataItem('Starting', applications.Starting, '#26bbf0'))
+ if (applications.Running) this.appStatusData.push(new
ChartDataItem('Running', applications.Running, '#26bbf0'))
+ if (applications.Rejected) this.appStatusData.push(new
ChartDataItem('Rejected', applications.Rejected, '#cc6164'))
+ if (applications.Completing) this.appStatusData.push(new
ChartDataItem('Completing', applications.Completing, '#60cea5'))
+ if (applications.Completed) this.appStatusData.push(new
ChartDataItem('Completed', applications.Completed, '#60cea5'))
+ if (applications.Failing) this.appStatusData.push(new
ChartDataItem('Failing', applications.Failing, '#cc6164'))
+ if (applications.Failed) this.appStatusData.push(new
ChartDataItem('Failed', applications.Failed, '#cc6164'))
+ if (applications.Expired) this.appStatusData.push(new
ChartDataItem('Expired', applications.Expired, '#cc6164'))
+ if (applications.Resuming) this.appStatusData.push(new
ChartDataItem('Resuming', applications.Resuming, '#facc54'))
}
updateContainerStatusData(info: Partition) {
this.containerStatusData = [
- new DonutDataItem('Running', +info.totalContainers, '#26bbf0'),
+ new ChartDataItem('Running', +info.totalContainers, '#26bbf0'),
];
}
diff --git a/src/app/components/donut-chart/donut-chart.component.ts
b/src/app/components/donut-chart/donut-chart.component.ts
index e7bb71e..94d119f 100644
--- a/src/app/components/donut-chart/donut-chart.component.ts
+++ b/src/app/components/donut-chart/donut-chart.component.ts
@@ -28,9 +28,8 @@ import {
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Chart, ArcElement, DoughnutController } from 'chart.js';
-
import { CommonUtil } from '@app/utils/common.util';
-import { DonutDataItem } from '@app/models/donut-data.model';
+import { ChartDataItem } from '@app/models/chart-data.model';
import { EventBusService, EventMap } from
'@app/services/event-bus/event-bus.service';
Chart.register(ArcElement, DoughnutController);
@@ -43,10 +42,10 @@ Chart.register(ArcElement, DoughnutController);
export class DonutChartComponent implements OnInit, AfterViewInit, OnChanges,
OnDestroy {
destroy$ = new Subject<boolean>();
chartContainerId = '';
- donutChartData: DonutDataItem[] = [];
+ donutChartData: ChartDataItem[] = [];
donutChart: Chart<'doughnut', number[], string> | undefined;
- @Input() data: DonutDataItem[] = [];
+ @Input() data: ChartDataItem[] = [];
constructor(private eventBus: EventBusService) {}
@@ -81,7 +80,7 @@ export class DonutChartComponent implements OnInit,
AfterViewInit, OnChanges, On
}
}
- renderChart(chartData: DonutDataItem[] = []) {
+ renderChart(chartData: ChartDataItem[] = []) {
if (!this.chartContainerId) {
return;
}
diff --git a/src/app/models/donut-data.model.ts
b/src/app/models/chart-data.model.ts
similarity index 97%
copy from src/app/models/donut-data.model.ts
copy to src/app/models/chart-data.model.ts
index 09457ac..4c7a9b0 100644
--- a/src/app/models/donut-data.model.ts
+++ b/src/app/models/chart-data.model.ts
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-export class DonutDataItem {
+export class ChartDataItem {
name: string;
value: number;
color: string;
diff --git a/src/app/models/donut-data.model.ts
b/src/app/models/node-utilization.model.ts
similarity index 79%
rename from src/app/models/donut-data.model.ts
rename to src/app/models/node-utilization.model.ts
index 09457ac..529922f 100644
--- a/src/app/models/donut-data.model.ts
+++ b/src/app/models/node-utilization.model.ts
@@ -16,14 +16,13 @@
* limitations under the License.
*/
-export class DonutDataItem {
- name: string;
- value: number;
- color: string;
-
- constructor(name: string, value: number, color: string) {
- this.name = name;
- this.value = value;
- this.color = color;
- }
+export class NodeUtilization {
+ constructor(
+ public type: string,
+ public utilization: {
+ bucketName: string;
+ numOfNodes: number;
+ nodeNames: null | string[];
+ }[],
+ ) {}
}
diff --git a/src/app/services/scheduler/scheduler.service.ts
b/src/app/services/scheduler/scheduler.service.ts
index 251b5e2..e8803a9 100644
--- a/src/app/services/scheduler/scheduler.service.ts
+++ b/src/app/services/scheduler/scheduler.service.ts
@@ -18,7 +18,7 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
-import { Observable, queueScheduler } from 'rxjs';
+import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { QueueInfo, QueuePropertyItem } from '@app/models/queue-info.model';
@@ -33,6 +33,7 @@ import { NodeInfo } from '@app/models/node-info.model';
import { NOT_AVAILABLE } from '@app/utils/constants';
import { Partition } from '@app/models/partition-info.model';
import { SchedulerHealthInfo } from "@app/models/scheduler-health-info.model";
+import { NodeUtilization } from '@app/models/node-utilization.model';
@Injectable({
providedIn: 'root',
@@ -253,6 +254,11 @@ export class SchedulerService {
);
}
+ fetchNodeUtilization(): Observable<NodeUtilization[]>{
+ const nodeUtilizationUrl =
`${this.envConfig.getSchedulerWebAddress()}/ws/v1/nodes/utilization`;
+ return this.httpClient.get(nodeUtilizationUrl).pipe(map((data: any) =>
data as NodeUtilization[]));
+ }
+
fecthHealthchecks(): Observable<SchedulerHealthInfo> {
const healthCheckUrl =
`${this.envConfig.getSchedulerWebAddress()}/ws/v1/scheduler/healthcheck`;
return this.httpClient.get(healthCheckUrl).pipe(map((data: any) => data as
SchedulerHealthInfo));
diff --git a/src/app/testing/mocks.ts b/src/app/testing/mocks.ts
index 45efbd4..805dadb 100644
--- a/src/app/testing/mocks.ts
+++ b/src/app/testing/mocks.ts
@@ -31,6 +31,7 @@ export const MockSchedulerService = {
fetchAppHistory: () => of([]),
fetchContainerHistory: () => of([]),
fetchNodeList: () => of([]),
+ fetchNodeUtilization: () => of([]),
fecthHealthchecks: () => of([]),
};
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]