diff --git a/.changelog/27846.txt b/.changelog/27846.txt new file mode 100644 index 00000000000..8ac30642047 --- /dev/null +++ b/.changelog/27846.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: The topology page now also shows reserved resources (CPU and memory) of every node using an orange bar at the end +``` diff --git a/ui/app/components/svg-patterns.hbs b/ui/app/components/svg-patterns.hbs index a87f153ea67..8f878406bda 100644 --- a/ui/app/components/svg-patterns.hbs +++ b/ui/app/components/svg-patterns.hbs @@ -11,6 +11,12 @@ + {{! Evenly sized diagonal stripes in orange}} + + + + + diff --git a/ui/app/components/topo-viz.hbs b/ui/app/components/topo-viz.hbs index ac57a3638b0..e84b49ee7a7 100644 --- a/ui/app/components/topo-viz.hbs +++ b/ui/app/components/topo-viz.hbs @@ -22,6 +22,7 @@ @onAllocationSelect={{this.associateAllocations}} @onAllocationFocus={{this.showTooltip}} @onAllocationBlur={{this.hideTooltip}} + @onReservedFocus={{this.showReservedTooltip}} @onNodeSelect={{this.showNodeDetails}} /> @@ -50,6 +51,21 @@ {{/let}} +
+ {{#let this.highlightReserved as |reserved|}} +
    +
  1. + Memory + {{format-scheduled-bytes reserved.memory start="MiB"}} reserved +
  2. +
  3. + CPU + {{format-scheduled-hertz reserved.cpu}} reserved +
  4. +
+ {{/let}} +
+ {{#if this.activeAllocation}} diff --git a/ui/app/components/topo-viz.js b/ui/app/components/topo-viz.js index 9cd3687ea8e..50fc019c84d 100644 --- a/ui/app/components/topo-viz.js +++ b/ui/app/components/topo-viz.js @@ -26,6 +26,7 @@ export default class TopoViz extends Component { @tracked viewportColumns = 2; @tracked highlightAllocation = null; + @tracked highlightReserved = null; @tracked tooltipProps = {}; @styleStringProperty('tooltipProps') tooltipStyle; @@ -192,6 +193,17 @@ export default class TopoViz extends Component { @action showTooltip(allocation, element) { const bbox = element.getBoundingClientRect(); this.highlightAllocation = allocation; + this.highlightReserved = null; + this.tooltipProps = { + left: window.scrollX + bbox.left + bbox.width / 2, + top: window.scrollY + bbox.top, + }; + } + + @action showReservedTooltip(reserved, element) { + const bbox = element.getBoundingClientRect(); + this.highlightReserved = reserved; + this.highlightAllocation = null; this.tooltipProps = { left: window.scrollX + bbox.left + bbox.width / 2, top: window.scrollY + bbox.top, @@ -200,6 +212,7 @@ export default class TopoViz extends Component { @action hideTooltip() { this.highlightAllocation = null; + this.highlightReserved = null; } @action diff --git a/ui/app/components/topo-viz/datacenter.hbs b/ui/app/components/topo-viz/datacenter.hbs index a133e71a11f..491957a433d 100644 --- a/ui/app/components/topo-viz/datacenter.hbs +++ b/ui/app/components/topo-viz/datacenter.hbs @@ -24,6 +24,7 @@ @onAllocationSelect={{@onAllocationSelect}} @onAllocationFocus={{@onAllocationFocus}} @onAllocationBlur={{@onAllocationBlur}} + @onReservedFocus={{@onReservedFocus}} @onNodeSelect={{@onNodeSelect}} /> diff --git a/ui/app/components/topo-viz/node.hbs b/ui/app/components/topo-viz/node.hbs index ff2801c9329..3cdec47f5a5 100644 --- a/ui/app/components/topo-viz/node.hbs +++ b/ui/app/components/topo-viz/node.hbs @@ -83,6 +83,25 @@ width="{{this.data.memoryRemainder.width}}px" height="{{this.height}}px" /> {{/if}} + {{#if this.data.memoryReserved}} + + + + + {{/if}} {{#each this.data.memory key="allocation.id" as |memory|}} {{/if}} + {{#if this.data.cpuReserved}} + + + + + {{/if}} {{#each this.data.cpu key="allocation.id" as |cpu|}} 0 + ? { + x: (1 - cpuReservedPercent) * width + 0.5, + width: Math.max(cpuReservedPercent * width - 0.5, 0), + } + : null; + const memoryReserved = + memoryReservedPercent > 0 + ? { + x: (1 - memoryReservedPercent) * width + 0.5, + width: Math.max(memoryReservedPercent * width - 0.5, 0), + } + : null; + return { cpu, memory, cpuRemainder, memoryRemainder, + cpuReserved, + memoryReserved, cpuLabel: { x: -this.paddingLeft / 2, y: this.height / 2 + this.yOffset }, memoryLabel: { x: -this.paddingLeft / 2, y: this.height / 2 }, }; diff --git a/ui/app/styles/charts/colors.scss b/ui/app/styles/charts/colors.scss index 925fc58577f..922ee5d9711 100644 --- a/ui/app/styles/charts/colors.scss +++ b/ui/app/styles/charts/colors.scss @@ -107,6 +107,17 @@ $canceled: $dark; ); } + &.reserved { + $reserved-base: lighten($orange, 20%); + background: repeating-linear-gradient( + -45deg, + $reserved-base, + $reserved-base 3px, + darken($reserved-base, 20%) 3px, + darken($reserved-base, 20%) 6px + ); + } + &.running { background: $running; } diff --git a/ui/app/styles/charts/topo-viz-node.scss b/ui/app/styles/charts/topo-viz-node.scss index 81a21d95e50..67509207bbe 100644 --- a/ui/app/styles/charts/topo-viz-node.scss +++ b/ui/app/styles/charts/topo-viz-node.scss @@ -37,6 +37,16 @@ fill: lighten($grey-lighter, 5%); } + .reserved-segment { + rect.layer-0 { + fill: #fcc187; + } + + rect.layer-1 { + fill: url(#diagonal-stripe-orange); + } + } + .dimensions.is-active { .bar { opacity: 0.2; diff --git a/ui/app/templates/topology.hbs b/ui/app/templates/topology.hbs index 6fe5db9410b..beb62ea49ff 100644 --- a/ui/app/templates/topology.hbs +++ b/ui/app/templates/topology.hbs @@ -117,6 +117,14 @@ Starting +
+
+ +
+
+ Reserved +
+
diff --git a/ui/mirage/scenarios/topo.js b/ui/mirage/scenarios/topo.js index c6a0ae397e6..4e44cea3459 100644 --- a/ui/mirage/scenarios/topo.js +++ b/ui/mirage/scenarios/topo.js @@ -23,6 +23,13 @@ export function topoSmall(server) { nodeResources: genResources(3000, 5192), }); + // Add one node with reserved resources to show reserved node in topology view + server.createList('node', 1, 'reserved', { + datacenter: 'dc1', + status: 'ready', + nodeResources: genResources(8000, 12192), + }); + const jobResources = [ ['M: 2560, C: 150'], ['M: 128, C: 400'], @@ -80,6 +87,13 @@ export function topoMedium(server) { nodeResources: genResources(8000, 12192), }); + // Add some nodes with reserved resources to show reserved node in topology view + server.createList('node', 3, 'reserved', { + datacenter: 'us-east-1', + status: 'ready', + nodeResources: genResources(8000, 12192), + }); + const jobResources = [ ['M: 2560, C: 150'], ['M: 128, C: 400'], diff --git a/ui/tests/integration/components/topo-viz/node-test.js b/ui/tests/integration/components/topo-viz/node-test.js index bb58fff80c5..0bd5d0f119a 100644 --- a/ui/tests/integration/components/topo-viz/node-test.js +++ b/ui/tests/integration/components/topo-viz/node-test.js @@ -443,4 +443,44 @@ module('Integration | Component | TopoViz::Node', function (hooks) { await render(commonTemplate); assert.equal(TopoVizNode.emptyMessage, 'Empty Client'); }); + + test('when the node has reserved resources, reserved segments are shown in the CPU and memory bars', async function (assert) { + const node = nodeGen('Node One', 'dc1', 1000, 1000); + node.node.reserved = { cpu: 100, memory: 200 }; + + this.setProperties( + props({ + node: { + ...node, + allocations: [allocGen(node, 100, 100)], + }, + }) + ); + + await render(commonTemplate); + + assert.ok( + TopoVizNode.reservedMemoryRect, + 'reserved memory segment is rendered' + ); + assert.ok(TopoVizNode.reservedCpuRect, 'reserved cpu segment is rendered'); + }); + + test('when the node has no reserved resources, no reserved segments are shown', async function (assert) { + const node = nodeGen('Node One', 'dc1', 1000, 1000); + + this.setProperties( + props({ + node: { + ...node, + allocations: [allocGen(node, 100, 100)], + }, + }) + ); + + await render(commonTemplate); + + assert.notOk(TopoVizNode.reservedMemoryRect, 'no reserved memory segment'); + assert.notOk(TopoVizNode.reservedCpuRect, 'no reserved cpu segment'); + }); }); diff --git a/ui/tests/pages/components/topo-viz/node.js b/ui/tests/pages/components/topo-viz/node.js index eb146fb6d51..4727a43885e 100644 --- a/ui/tests/pages/components/topo-viz/node.js +++ b/ui/tests/pages/components/topo-viz/node.js @@ -47,6 +47,9 @@ export default (scope) => ({ id: attribute('data-test-cpu-rect'), }), + reservedMemoryRect: isPresent('[data-test-reserved-memory]'), + reservedCpuRect: isPresent('[data-test-reserved-cpu]'), + mouseout: triggerable('mouseout', '[data-test-topo-node-svg]'), emptyMessage: text('[data-test-empty-message]'),