2020-09-03 02:37:13 +00:00
|
|
|
import Component from '@glimmer/component';
|
2020-09-04 07:43:27 +00:00
|
|
|
import { tracked } from '@glimmer/tracking';
|
|
|
|
import { action } from '@ember/object';
|
|
|
|
import { guidFor } from '@ember/object/internals';
|
|
|
|
|
|
|
|
export default class TopoVizNode extends Component {
|
|
|
|
@tracked data = { cpu: [], memory: [] };
|
|
|
|
@tracked dimensionsWidth = 0;
|
|
|
|
@tracked padding = 5;
|
|
|
|
@tracked activeAllocation = null;
|
|
|
|
|
|
|
|
get height() {
|
|
|
|
return this.args.heightScale ? this.args.heightScale(this.args.node.resources.memory) : 15;
|
|
|
|
}
|
|
|
|
|
2020-09-11 02:29:25 +00:00
|
|
|
get labelHeight() {
|
|
|
|
return this.height / 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
get paddingLeft() {
|
|
|
|
const labelWidth = 20;
|
|
|
|
return this.padding + labelWidth;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Since strokes are placed centered on the perimeter of fills, The width of the stroke needs to be removed from
|
|
|
|
// the height of the fill to match unstroked height and avoid clipping.
|
|
|
|
get selectedHeight() {
|
|
|
|
return this.height - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Since strokes are placed centered on the perimeter of fills, half the width of the stroke needs to be added to
|
|
|
|
// the yOffset to match heights with unstroked shapes.
|
|
|
|
get selectedYOffset() {
|
|
|
|
return this.height + 2.5;
|
|
|
|
}
|
|
|
|
|
2020-09-04 07:43:27 +00:00
|
|
|
get yOffset() {
|
|
|
|
return this.height + 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
get maskHeight() {
|
|
|
|
return this.height + this.yOffset;
|
|
|
|
}
|
|
|
|
|
|
|
|
get totalHeight() {
|
|
|
|
return this.maskHeight + this.padding * 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
get maskId() {
|
|
|
|
return `topo-viz-node-mask-${guidFor(this)}`;
|
|
|
|
}
|
2020-09-03 02:37:13 +00:00
|
|
|
|
|
|
|
get count() {
|
|
|
|
return this.args.node.get('allocations.length');
|
|
|
|
}
|
2020-09-04 07:43:27 +00:00
|
|
|
|
|
|
|
get allocations() {
|
2020-09-11 02:29:25 +00:00
|
|
|
// return this.args.node.allocations.filterBy('isScheduled').sortBy('resources.memory');
|
|
|
|
const totalCPU = this.args.node.resources.cpu;
|
|
|
|
const totalMemory = this.args.node.resources.memory;
|
|
|
|
|
|
|
|
// Sort by the delta between memory and cpu percent. This creates the least amount of
|
|
|
|
// drift between the positional alignment of an alloc's cpu and memory representations.
|
|
|
|
return this.args.node.allocations.filterBy('isScheduled').sort((a, b) => {
|
|
|
|
const deltaA = Math.abs(a.resources.memory / totalMemory - a.resources.cpu / totalCPU);
|
|
|
|
const deltaB = Math.abs(b.resources.memory / totalMemory - b.resources.cpu / totalCPU);
|
|
|
|
return deltaA - deltaB;
|
|
|
|
});
|
2020-09-04 07:43:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
async reloadNode() {
|
|
|
|
if (this.args.node.isPartial) {
|
|
|
|
await this.args.node.reload();
|
|
|
|
this.data = this.computeData(this.dimensionsWidth);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
render(svg) {
|
2020-09-11 02:29:25 +00:00
|
|
|
this.dimensionsWidth = svg.clientWidth - this.padding - this.paddingLeft;
|
2020-09-04 07:43:27 +00:00
|
|
|
this.data = this.computeData(this.dimensionsWidth);
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
highlightAllocation(allocation) {
|
|
|
|
this.activeAllocation = allocation;
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
clearHighlight() {
|
|
|
|
this.activeAllocation = null;
|
|
|
|
}
|
|
|
|
|
2020-09-04 19:53:18 +00:00
|
|
|
@action
|
|
|
|
selectAllocation(allocation) {
|
|
|
|
if (this.args.onAllocationSelect) this.args.onAllocationSelect(allocation);
|
|
|
|
}
|
|
|
|
|
2020-09-04 07:43:27 +00:00
|
|
|
computeData(width) {
|
|
|
|
// TODO: differentiate reserved and resources
|
|
|
|
if (!this.args.node.resources) return;
|
|
|
|
|
|
|
|
const totalCPU = this.args.node.resources.cpu;
|
|
|
|
const totalMemory = this.args.node.resources.memory;
|
|
|
|
let cpuOffset = 0;
|
|
|
|
let memoryOffset = 0;
|
|
|
|
|
|
|
|
const cpu = [];
|
|
|
|
const memory = [];
|
|
|
|
for (const allocation of this.allocations) {
|
|
|
|
const cpuPercent = allocation.resources.cpu / totalCPU;
|
|
|
|
const memoryPercent = allocation.resources.memory / totalMemory;
|
|
|
|
const isFirst = allocation === this.allocations[0];
|
2020-09-11 02:29:25 +00:00
|
|
|
const isSelected =
|
|
|
|
allocation.taskGroupName === this.args.activeTaskGroup &&
|
|
|
|
allocation.belongsTo('job').id() === this.args.activeJobId;
|
2020-09-04 07:43:27 +00:00
|
|
|
|
|
|
|
let cpuWidth = cpuPercent * width - 1;
|
|
|
|
let memoryWidth = memoryPercent * width - 1;
|
|
|
|
if (isFirst) {
|
|
|
|
cpuWidth += 0.5;
|
|
|
|
memoryWidth += 0.5;
|
|
|
|
}
|
2020-09-11 02:29:25 +00:00
|
|
|
if (isSelected) {
|
|
|
|
cpuWidth--;
|
|
|
|
memoryWidth--;
|
|
|
|
}
|
2020-09-04 07:43:27 +00:00
|
|
|
|
|
|
|
cpu.push({
|
|
|
|
allocation,
|
2020-09-11 02:29:25 +00:00
|
|
|
isSelected,
|
2020-09-04 07:43:27 +00:00
|
|
|
offset: cpuOffset * 100,
|
|
|
|
percent: cpuPercent * 100,
|
|
|
|
width: cpuWidth,
|
2020-09-11 02:29:25 +00:00
|
|
|
x: cpuOffset * width + (isFirst ? 0 : 0.5) + (isSelected ? 0.5 : 0),
|
2020-09-04 07:43:27 +00:00
|
|
|
className: allocation.clientStatus,
|
|
|
|
});
|
|
|
|
memory.push({
|
|
|
|
allocation,
|
2020-09-11 02:29:25 +00:00
|
|
|
isSelected,
|
2020-09-04 07:43:27 +00:00
|
|
|
offset: memoryOffset * 100,
|
|
|
|
percent: memoryPercent * 100,
|
|
|
|
width: memoryWidth,
|
2020-09-11 02:29:25 +00:00
|
|
|
x: memoryOffset * width + (isFirst ? 0 : 0.5) + (isSelected ? 0.5 : 0),
|
2020-09-04 07:43:27 +00:00
|
|
|
className: allocation.clientStatus,
|
|
|
|
});
|
|
|
|
|
|
|
|
cpuOffset += cpuPercent;
|
|
|
|
memoryOffset += memoryPercent;
|
|
|
|
}
|
|
|
|
|
|
|
|
const cpuRemainder = {
|
|
|
|
x: cpuOffset * width + 0.5,
|
|
|
|
width: width - cpuOffset * width,
|
|
|
|
};
|
|
|
|
const memoryRemainder = {
|
|
|
|
x: memoryOffset * width + 0.5,
|
|
|
|
width: width - memoryOffset * width,
|
|
|
|
};
|
|
|
|
|
2020-09-11 02:29:25 +00:00
|
|
|
return {
|
|
|
|
cpu,
|
|
|
|
memory,
|
|
|
|
cpuRemainder,
|
|
|
|
memoryRemainder,
|
|
|
|
cpuLabel: { x: -this.paddingLeft / 2, y: this.height / 2 + this.yOffset },
|
|
|
|
memoryLabel: { x: -this.paddingLeft / 2, y: this.height / 2 },
|
|
|
|
};
|
2020-09-04 07:43:27 +00:00
|
|
|
}
|
2020-09-03 02:37:13 +00:00
|
|
|
}
|
2020-09-04 07:43:27 +00:00
|
|
|
|
|
|
|
// capture width on did insert element
|
|
|
|
// update width on window resize
|
|
|
|
// recompute data when width changes
|