diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/components/base-chart-component.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/components/base-chart-component.js index b85b6ab..a3eb8aa 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/components/base-chart-component.js +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/components/base-chart-component.js @@ -19,21 +19,19 @@ import Ember from 'ember'; export default Ember.Component.extend({ - chart: undefined, tooltip : undefined, colors: d3.scale.category10().range(), + chart: { + svg: undefined, + g: undefined, + h: 0, + w: 0, + tooltip: undefined + }, - initChart: function() { - this.chart = { - svg: undefined, - g: undefined, - h: 0, - w: 0, - tooltip: undefined - }; - + initChart: function(removeLast = false) { // Init tooltip if it is not initialized - this.tooltip = d3.select("#chart-tooltip"); + // this.tooltip = d3.select("#chart-tooltip"); if (!this.tooltip) { this.tooltip = d3.select("body") .append("div") @@ -42,10 +40,12 @@ export default Ember.Component.extend({ .style("opacity", 0); } - // Init svg - var svg = this.chart.svg; - if (svg) { - svg.remove(); + if (removeLast) { + // Init svg + var svg = this.chart.svg; + if (svg) { + svg.remove(); + } } var parentId = this.get("parentId"); @@ -70,7 +70,7 @@ export default Ember.Component.extend({ this.chart.g = this.chart.svg.append("g"); }, - renderTitleAndBG: function(g, title, layout) { + renderTitleAndBG: function(g, title, layout, background=true) { var bg = g.append("g"); bg.append("text") .text(title) @@ -78,12 +78,14 @@ export default Ember.Component.extend({ .attr("y", layout.y1 + layout.margin + 20) .attr("class", "chart-title"); - bg.append("rect") - .attr("x", layout.x1) - .attr("y", layout.y1) - .attr("width", layout.x2 - layout.x1) - .attr("height", layout.y2 - layout.y1) - .attr("class", "chart-frame"); + if (background) { + bg.append("rect") + .attr("x", layout.x1) + .attr("y", layout.y1) + .attr("width", layout.x2 - layout.x1) + .attr("height", layout.y2 - layout.y1) + .attr("class", "chart-frame"); + } }, bindTooltip: function(d) { @@ -109,6 +111,10 @@ export default Ember.Component.extend({ }.bind(this)); }, + adjustMaxHeight: function(h) { + this.chart.svg.attr("height", h); + }, + getLayout: function() { var x1 = 0; var y1 = 0; @@ -120,7 +126,7 @@ export default Ember.Component.extend({ y1: y1, x2: x2 - 10, y2: y2 - 10, - margin: 10 + margin: 50 }; return layout; }, diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/components/nodes-heatmap.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/components/nodes-heatmap.js new file mode 100644 index 0000000..91e81fc --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/components/nodes-heatmap.js @@ -0,0 +1,182 @@ +/** + * 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 BaseChartComponent from 'yarn-ui/components/base-chart-component'; +import Mock from 'yarn-ui/utils/mock'; + +export default BaseChartComponent.extend({ + CELL_WIDTH: 150, + CELL_HEIGHT: 20, + CELL_MARGIN: 2, + RACK_MARGIN: 20, + filter: "", + + bindTP: function(element) { + element.on("mouseover", function() { + this.tooltip + .style("left", (d3.event.pageX) + "px") + .style("top", (d3.event.pageY - 28) + "px"); + }.bind(this)) + .on("mousemove", function() { + // Handle pie chart case + var text = element.attr("tooltiptext"); + + this.tooltip.style("opacity", .9); + this.tooltip.html(text) + .style("left", (d3.event.pageX) + "px") + .style("top", (d3.event.pageY - 28) + "px"); + }.bind(this)) + .on("mouseout", function() { + this.tooltip.style("opacity", 0); + }.bind(this)); + }, + + // data: + // [{label=label1, value=value1}, ...] + // ... + renderCells: function (model, title) { + var data = []; + model.forEach(function (o) { + data.push(o); + }); + + this.chart.g.remove(); + this.chart.g = this.chart.svg.append("g"); + var g = this.chart.g; + var layout = this.getLayout(); + + let racks = new Set(); + for (var i = 0; i < data.length; i++) { + racks.add(data[i].get("rack")); + } + + let racksArray = []; + racks.forEach(v => racksArray.push(v)); + + var xOffset = layout.margin; + var yOffset = layout.margin * 3; + + var colorFunc = d3.interpolate(d3.rgb("LightCyan"), d3.rgb("SteelBlue")); + + var sampleXOffset = (layout.x2 - layout.x1) / 2 - 2.5 * this.CELL_WIDTH - + 2 * this.CELL_MARGIN; + for (var i = 1; i <= 5; i++) { + var rect = g.append("rect") + .attr("x", sampleXOffset) + .attr("y", layout.margin * 2) + .attr("fill", colorFunc(i * 0.2 - 0.1)) + .attr("width", this.CELL_WIDTH) + .attr("height", this.CELL_HEIGHT); + g.append("text") + .text("" + ((i * 0.2 - 0.1) * 100).toFixed(1) + "% Used") + .attr("y", layout.margin * 2 + 15) + .attr("x", sampleXOffset) + .attr("fill", "Black"); + sampleXOffset += this.CELL_MARGIN + this.CELL_WIDTH; + } + + for (var i = 0; i < racksArray.length; i++) { + var hasEntry = false; + + for (var j = 0; j < data.length; j++) { + var rack = data[j].get("rack"); + var host = data[j].get("nodeHostName"); + + if (rack == racksArray[i]) { + if (rack.includes(this.filter) || host.includes(this.filter)) { + hasEntry = true; + break; + } + } + } + + if (!hasEntry) { + continue; + } + + g.append("text") + .text(racksArray[i]) + .attr("y", yOffset + this.CELL_HEIGHT / 2) + .attr("x", xOffset) + .attr("fill", "Chocolate"); + + yOffset = yOffset + this.CELL_HEIGHT / 2 + this.CELL_MARGIN; + + for (var j = 0; j < data.length; j++) { + var rack = data[j].get("rack"); + var host = data[j].get("nodeHostName"); + + if (rack == racksArray[i]) { + if (!rack.includes(this.filter) && !host.includes(this.filter)) { + continue; + } + + var rect = g.append("rect") + .attr("y", yOffset) + .attr("x", xOffset) + .attr("height", this.CELL_HEIGHT) + .attr("fill", colorFunc(data[j].get("usedMemoryMB") / + (data[j].get("usedMemoryMB") + data[j].get("availMemoryMB")))) + .attr("width", this.CELL_WIDTH) + .attr("tooltiptext", data[j].get("toolTipText")); + + this.bindTP(rect); + + g.append("text") + .text(host) + .attr("y", yOffset + 12) + .attr("x", xOffset); + + xOffset += this.CELL_MARGIN + this.CELL_WIDTH; + if (xOffset + this.CELL_MARGIN + this.CELL_WIDTH >= layout.x2 - + layout.margin) { + xOffset = layout.margin; + yOffset = yOffset + this.CELL_MARGIN + this.CELL_HEIGHT; + } + } + } + + if (xOffset != layout.margin) { + xOffset = layout.margin; + yOffset += this.CELL_MARGIN + this.CELL_HEIGHT; + } + yOffset += this.RACK_MARGIN; + } + + layout.y2 = yOffset + layout.margin; + this.adjustMaxHeight(layout.y2); + this.renderTitleAndBG(g, title, layout, false); + }, + + draw: function() { + this.initChart(true); + // Mock.initMockNodesData(this); + this.renderCells(this.get("model"), this.get("title"), this.get("textWidth")); + }, + + didInsertElement: function () { + this.draw(); + }, + + actions: { + applyFilter: function(event) { + this.filter = event.srcElement.value; + this.didInsertElement(); + } + } +}) \ No newline at end of file diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/components/queue-view.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/components/queue-view.js index 2a90771..8d0932b 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/components/queue-view.js +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/components/queue-view.js @@ -206,7 +206,7 @@ export default Ember.Component.extend(ChartUtilsMixin, { renderQueueCapacities: function (queue, layout) { // Render bar chart - this.renderBarChart(this.charts.g, [{ + this.renderCells(this.charts.g, [{ label: "Cap", value: queue.get("capacity") }, { diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/models/yarn-rm-node.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/models/yarn-rm-node.js index 9a1082c..a15a20f 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/models/yarn-rm-node.js +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/models/yarn-rm-node.js @@ -89,4 +89,11 @@ export default DS.Model.extend({ }); return arr; }.property("availableVirtualCores", "usedVirtualCores"), + + toolTipText: function() { + return "

Rack: " + this.get("rack") + '

' + + "

Host: " + this.get("nodeHostName") + '

' + + "

Used Memory: " + Math.round(this.get("usedMemoryMB")) + ' MB

' + + "

Available Memory: " + Math.round(this.get("availMemoryMB")) + ' MB

'; + }.property(), }); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/components/nodes-heatmap.hbs b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/components/nodes-heatmap.hbs new file mode 100644 index 0000000..61a7d08 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/components/nodes-heatmap.hbs @@ -0,0 +1,22 @@ +{{! + * 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. +}} + +
+ +
+

\ No newline at end of file diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-nodes.hbs b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-nodes.hbs index 3c78498..384edaf 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-nodes.hbs +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-nodes.hbs @@ -17,47 +17,54 @@ --}} {{#if model}} - - - - - - - - - - - - - - - - - - - - {{#each model as |node|}} - - - - - - {{node-link nodeId=node.id nodeHTTPAddress=node.nodeHTTPAddress nodeState=node.state}} - - - - - - - - - - {{/each}} - -
Node LabelsRackNode StateNode AddressNode HTTP AddressLast Health UpdateHealth-ReportContainersMem UsedMem AvailVCores UsedVCores AvailVersion
{{node.nodeLabelsAsString}}{{node.rack}}{{node.state}}{{node.id}}{{node.lastHealthUpdate}}{{node.healthReport}}{{node.numContainers}}{{divide num=node.usedMemoryMB den=1024}} GB{{divide num=node.availMemoryMB den=1024}} GB{{node.usedVirtualCores}}{{node.availableVirtualCores}}{{node.version}}
+

+
+ {{nodes-heatmap model=model parentId="nodes-heatmap-chart" title="Node Heatmap Chart"}} +
+
-{{simple-table table-id="nodes-table" bFilter=true}} + + + + + + + + + + + + + + + + + + + + {{#each model as |node|}} + + + + + + {{node-link nodeId=node.id nodeHTTPAddress=node.nodeHTTPAddress nodeState=node.state}} + + + + + + + + + + {{/each}} + +
Node LabelsRackNode StateNode AddressNode HTTP AddressLast Health UpdateHealth-ReportContainersMem UsedMem AvailVCores UsedVCores AvailVersion
{{node.nodeLabelsAsString}}{{node.rack}}{{node.state}}{{node.id}}{{node.lastHealthUpdate}}{{node.healthReport}}{{node.numContainers}}{{divide num=node.usedMemoryMB den=1024}} GB{{divide num=node.availMemoryMB den=1024}} GB{{node.usedVirtualCores}}{{node.availableVirtualCores}}{{node.version}}
+ + {{simple-table table-id="nodes-table" bFilter=true}} {{else}}

No nodes found on this cluster

{{/if}} + {{outlet}} diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/utils/mock.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/utils/mock.js new file mode 100644 index 0000000..5bc380a --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/utils/mock.js @@ -0,0 +1,36 @@ +/** + * 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. + */ + +export default { + initMockNodesData: function(ref) { + var data = []; + for (var i = 0; i < 20; i++) { + for (var j = 0; j < Math.floor(Math.random() * (40 - 5)) + 5; j++) { + var node = ref.get('targetObject.store').createRecord('YarnRmNode', { + rack: "/rack-" + i, + nodeHostName: "host-" + i + "-" + j, + usedMemoryMB: Math.abs(Math.random() * 10000), + availMemoryMB: Math.abs(Math.random() * 10000) + }); + data.push(node); + } + } + + ref.set("data", data); + }, +} \ No newline at end of file