-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[YUNIKORN-2325] Add a chart to display multi-type resource utilization (
- Loading branch information
1 parent
7051f34
commit 6cac541
Showing
21 changed files
with
1,142 additions
and
32 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
src/app/components/app-node-utilizations/app-node-utilizations.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<!-- | ||
* 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 | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* 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. | ||
--> | ||
|
||
<mat-card appearance="outlined" class="box-card"> | ||
<mat-card-header> | ||
<mat-card-title>Node Resource Utilization</mat-card-title> | ||
</mat-card-header> | ||
<mat-card-content> | ||
<div class="status-wrapper flex-grid"> | ||
<div class="chart-wrapper flex-primary"> | ||
<app-vertical-bar-chart [bucketList]="bucketList" [barChartDataSets]="barChartDataSets" /> | ||
</div> | ||
</div> | ||
</mat-card-content> | ||
</mat-card> |
17 changes: 17 additions & 0 deletions
17
src/app/components/app-node-utilizations/app-node-utilizations.component.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/** | ||
* 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 | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* 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. | ||
*/ |
114 changes: 114 additions & 0 deletions
114
src/app/components/app-node-utilizations/app-node-utilizations.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/** | ||
* 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 | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* 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. | ||
*/ | ||
|
||
import { HttpClientTestingModule } from '@angular/common/http/testing'; | ||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||
|
||
import { MatCardModule } from '@angular/material/card'; | ||
import { AppNodeUtilizationsComponent } from '@app/components/app-node-utilizations/app-node-utilizations.component'; | ||
import { VerticalBarChartComponent } from '@app/components/vertical-bar-chart/vertical-bar-chart.component'; | ||
import { CHART_COLORS } from '@app/utils/constants'; | ||
|
||
describe('AppNodeUtilizationsComponent', () => { | ||
let component: AppNodeUtilizationsComponent; | ||
let fixture: ComponentFixture<AppNodeUtilizationsComponent>; | ||
|
||
beforeEach(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [HttpClientTestingModule, MatCardModule], | ||
declarations: [AppNodeUtilizationsComponent, VerticalBarChartComponent] | ||
}); | ||
|
||
fixture = TestBed.createComponent(AppNodeUtilizationsComponent); | ||
component = fixture.componentInstance; | ||
}); | ||
|
||
it('test AppNodeUtilizationsComponent.calculateAvgUtilization()', () => { | ||
type TestCase = { | ||
description: string; | ||
nodeNumInBuckets: number[]; | ||
expected: number; | ||
}; | ||
const testCases: TestCase[] = [ | ||
{ | ||
description: 'Test 2 nodes, 1 node in 0~10%, 1 node in 10~20%', | ||
nodeNumInBuckets: [1, 1, 0, 0, 0, 0, 0, 0, 0, 0], | ||
expected: 0.1 | ||
}, | ||
{ | ||
description: 'Test 10 nodes, 1 node in each bucket', | ||
nodeNumInBuckets: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], | ||
expected: 0.5 | ||
}, | ||
{ | ||
description: 'Test zero node in buckets', | ||
nodeNumInBuckets: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], | ||
expected: 0 | ||
}, | ||
] | ||
|
||
testCases.forEach((testCase: TestCase) => { | ||
const result = component.calculateAvgUtilization(testCase.nodeNumInBuckets); | ||
expect(result).toEqual(testCase.expected); | ||
}); | ||
}); | ||
|
||
it('test AppNodeUtilizationsComponent.generateColorMapping()', () => { | ||
const types = [ | ||
'type03', 'type01', 'type02', 'type04', 'type05', | ||
'type06', 'type07', 'type08', 'type09', 'type10', 'type11' | ||
]; | ||
const colorMapping = component.generateColorMapping(types); | ||
|
||
expect(colorMapping.size).toBe(11); | ||
expect(colorMapping.get('type01')).toBe(CHART_COLORS[0]); | ||
expect(colorMapping.get('type02')).toBe(CHART_COLORS[1]); | ||
expect(colorMapping.get('type03')).toBe(CHART_COLORS[2]); | ||
expect(colorMapping.get('type11')).toBe(CHART_COLORS[0]); | ||
}); | ||
|
||
it('test AppNodeUtilizationsComponent.getBarDescription()', () => { | ||
type TestCase = { | ||
description: string; | ||
nodeNames: string[]; | ||
expected: string; | ||
}; | ||
const testCases: TestCase[] = [ | ||
{ | ||
description: 'Test single node', | ||
nodeNames: [""], | ||
expected: "" | ||
}, | ||
{ | ||
description: 'Test unordered multi-nodes', | ||
nodeNames: ["node02", "node01"], | ||
expected: "node01\nnode02" | ||
}, | ||
{ | ||
description: 'Test over than MAX_NODES_IN_DESCRIPTION nodes', | ||
nodeNames: ["node01", "node02", "node03", "node04", "node05", "node06", "node07", "node08", "node09", "node10", "node11", "node12", "node13", "node14", "node15", "node16"], | ||
expected: "node01\nnode02\nnode03\nnode04\nnode05\nnode06\nnode07\nnode08\nnode09\nnode10\nnode11\nnode12\nnode13\nnode14\nnode15\n...1 more" | ||
}, | ||
] | ||
|
||
testCases.forEach((testCase: TestCase) => { | ||
const result = component.getBarDescription(testCase.nodeNames); | ||
expect(result).toEqual(testCase.expected); | ||
}); | ||
}); | ||
}); |
150 changes: 150 additions & 0 deletions
150
src/app/components/app-node-utilizations/app-node-utilizations.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
/** | ||
* 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 | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* 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. | ||
*/ | ||
|
||
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; | ||
import { BarChartDataSet } from '@app/models/chart-data.model'; | ||
import { CHART_COLORS, DEFAULT_BAR_COLOR } from '@app/utils/constants'; | ||
import { CommonUtil } from '@app/utils/common.util'; | ||
import { NodeUtilization, NodeUtilizationsInfo } from '@app/models/node-utilization.model'; | ||
import { SchedulerService } from '@app/services/scheduler/scheduler.service'; | ||
|
||
|
||
@Component({ | ||
selector: 'app-node-utilizations', | ||
templateUrl: './app-node-utilizations.component.html', | ||
styleUrls: ['./app-node-utilizations.component.scss'] | ||
}) | ||
export class AppNodeUtilizationsComponent implements OnInit, OnChanges { | ||
nodeUtilizations: NodeUtilization[] = []; | ||
|
||
// input data for vertical bar chart, key is resource type | ||
bucketList: string[] = []; // one bucket list for all resource types, length should be exactly 10 | ||
barChartDataSets: BarChartDataSet[] = new Array<BarChartDataSet>(); // one dataset for each type | ||
|
||
@Input() partitionSelected: string = ""; | ||
|
||
constructor( | ||
private scheduler: SchedulerService | ||
) { } | ||
|
||
ngOnInit() { | ||
this.reloadBarChartData() | ||
} | ||
|
||
ngOnChanges(changes: SimpleChanges) { | ||
if ( | ||
changes['partitionSelected'] | ||
) { | ||
this.reloadBarChartData() | ||
} | ||
} | ||
|
||
reloadBarChartData() { | ||
this.scheduler.fetchNodeUtilizationsInfo().subscribe((data) => { | ||
let nodeUtilizationsInfo: NodeUtilizationsInfo[] = data | ||
for (let i = 0; i < nodeUtilizationsInfo.length; i++) { | ||
if (nodeUtilizationsInfo[i].partition === this.partitionSelected) { | ||
let nodeUtilizations = nodeUtilizationsInfo[i].utilizations | ||
this.fetchBarChartData(nodeUtilizations) | ||
break; | ||
} | ||
} | ||
}); | ||
} | ||
|
||
fetchBarChartData(nodeUtilizations: NodeUtilization[]) { | ||
let barChartDataSets = new Array<BarChartDataSet>(); | ||
if (nodeUtilizations.length === 0) { | ||
// clean bar chart data | ||
this.barChartDataSets = barChartDataSets; | ||
return; | ||
} | ||
|
||
let colorMapping = this.generateColorMapping( | ||
nodeUtilizations.map((nodeUtilization) => (nodeUtilization.type)) | ||
); | ||
|
||
for (let i = 0; i < nodeUtilizations.length; i++) { | ||
let type = nodeUtilizations[i].type; | ||
let utilization = nodeUtilizations[i].utilization | ||
let borderWidth = 1 | ||
|
||
if (i === 0) { | ||
// get bucketList only from the first type of node utilization | ||
// should always be 10 buckets. (ranging from 0% to 100%). | ||
this.bucketList = utilization.map((item) => item.bucketName); | ||
} | ||
let bucketValues = utilization.map((item) => item.numOfNodes); | ||
barChartDataSets.push(new BarChartDataSet( | ||
type, | ||
bucketValues, | ||
this.calculateAvgUtilization(bucketValues), | ||
colorMapping.get(type) ?? DEFAULT_BAR_COLOR, | ||
borderWidth, | ||
utilization.map((item) => this.getBarDescription(item.nodeNames)) | ||
)) | ||
} | ||
|
||
// sort by resource type first, then sort by avg utilization rate | ||
barChartDataSets.sort((a, b) => CommonUtil.resourcesCompareFn(a.label, b.label)); | ||
barChartDataSets.sort((a, b) => b.avgUtilizationRate - a.avgUtilizationRate); | ||
barChartDataSets = barChartDataSets.slice(0, 10); // only show top 10 resources | ||
|
||
// refresh bar chart data | ||
this.barChartDataSets = barChartDataSets; | ||
} | ||
|
||
calculateAvgUtilization(nodeNumInBuckets: number[]): number { | ||
// Calculates the average utilization of nodes based on a distribution of node utilizations. | ||
// Note: It not a precise average. | ||
// value of nodeCounts[0] means node count of 0%~10%, take 5% as the utilization of node in bucket | ||
// value of nodeCounts[1] means node count of 10%~20, take 15% as the utilization of node in bucket | ||
// value of nodeCounts[9] means node count of 90%~100%, take 95% as the utilization of node in bucket | ||
let totalNodes = 0; | ||
let weightedSum = 0; | ||
for (let i = 0; i < 10; i++) { //buckets have fixed length 10 | ||
if (nodeNumInBuckets[i] != undefined) { | ||
totalNodes += nodeNumInBuckets[i]; | ||
weightedSum += nodeNumInBuckets[i] * (5 + 10 * i); | ||
} | ||
} | ||
return totalNodes ? weightedSum / totalNodes / 100 : 0; | ||
} | ||
|
||
generateColorMapping(types: string[]): Map<string, string> { | ||
// give each resource type a color based on its index after lexicographically sorting | ||
types.sort(); | ||
let colorMapping = new Map<string, string>(); | ||
for (let i = 0; i < types.length; i++) { | ||
colorMapping.set(types[i], CHART_COLORS[i % 10]) | ||
} | ||
return colorMapping | ||
} | ||
|
||
getBarDescription(nodeNames: string[] | null): string { | ||
let MAX_NODES_IN_DESCRIPTION = 15; | ||
let description: string | undefined; | ||
if (nodeNames && nodeNames.length > MAX_NODES_IN_DESCRIPTION) { | ||
// only put MAX_NODES_IN_DESCRIPTION nodes in description, others will be replaced by '...N more' | ||
description = nodeNames.slice(0, MAX_NODES_IN_DESCRIPTION).sort().join("\n") + "\n..." + (nodeNames.length - MAX_NODES_IN_DESCRIPTION) + " more"; | ||
} else { | ||
description = nodeNames ? nodeNames.sort().join("\n") : undefined; | ||
} | ||
return description || "" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.