1
0

nodes-heatmap.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. import BaseChartComponent from 'yarn-ui/components/base-chart-component';
  19. export default BaseChartComponent.extend({
  20. CELL_WIDTH: 250,
  21. SAMPLE_CELL_WIDTH: 100,
  22. SAMPLE_HEIGHT: 30,
  23. CELL_HEIGHT: 30,
  24. CELL_MARGIN: 2,
  25. RACK_MARGIN: 20,
  26. filter: "",
  27. selectedCategory: 0,
  28. memoryLabel: "Memory",
  29. cpuLabel: "VCores",
  30. containersLabel: "Containers",
  31. totalContainers: 0,
  32. bindTP: function(element, cell) {
  33. element.on("mouseover", function() {
  34. this.tooltip
  35. .style("left", (d3.event.pageX) + "px")
  36. .style("top", (d3.event.pageY - 28) + "px");
  37. cell.style("opacity", 1.0);
  38. }.bind(this))
  39. .on("mousemove", function() {
  40. // Handle pie chart case
  41. var text = cell.attr("tooltiptext");
  42. this.tooltip.style("opacity", 0.9);
  43. this.tooltip.html(text)
  44. .style("left", (d3.event.pageX) + "px")
  45. .style("top", (d3.event.pageY - 28) + "px");
  46. }.bind(this))
  47. .on("mouseout", function() {
  48. this.tooltip.style("opacity", 0);
  49. cell.style("opacity", 0.8);
  50. }.bind(this));
  51. },
  52. bindSelectCategory: function(element, i) {
  53. element.on("click", function() {
  54. if (this.selectedCategory === i) {
  55. // Remove selection for second click
  56. this.selectedCategory = 0;
  57. } else {
  58. this.selectedCategory = i;
  59. }
  60. this.didInsertElement();
  61. }.bind(this));
  62. },
  63. isNodeSelected: function(node) {
  64. if (this.filter) {
  65. var rack = node.get("rack");
  66. var host = node.get("nodeHostName");
  67. if (!rack.includes(this.filter) && !host.includes(this.filter)) {
  68. return false;
  69. }
  70. }
  71. if (this.selectedCategory === 0) {
  72. return true;
  73. }
  74. var usage = this.calcUsage(node);
  75. var lowerLimit = (this.selectedCategory - 1) * 0.2;
  76. var upperLimit = this.selectedCategory * 0.2;
  77. if (lowerLimit <= usage && usage <= upperLimit) {
  78. return true;
  79. }
  80. return false;
  81. },
  82. // data:
  83. // [{label=label1, value=value1}, ...]
  84. // ...
  85. renderCells: function (model, title) {
  86. var selectedOption = d3.select("select").property("value");
  87. var data = [];
  88. model.forEach(function (o) {
  89. data.push(o);
  90. });
  91. this.chart.g.remove();
  92. this.chart.g = this.chart.svg.append("g");
  93. var g = this.chart.g;
  94. var layout = this.getLayout();
  95. layout.margin = 50;
  96. let racks = new Set();
  97. for (var i = 0; i < data.length; i++) {
  98. racks.add(data[i].get("rack"));
  99. }
  100. let racksArray = [];
  101. racks.forEach(v => racksArray.push(v));
  102. var xOffset = layout.margin;
  103. var yOffset = layout.margin * 3;
  104. var colorFunc = d3.interpolate(d3.rgb("#bdddf5"), d3.rgb("#0f3957"));
  105. var sampleXOffset = (layout.x2 - layout.x1) / 2 - 2.5 * this.SAMPLE_CELL_WIDTH -
  106. 2 * this.CELL_MARGIN;
  107. var sampleYOffset = layout.margin * 2;
  108. var text;
  109. for (i = 1; i <= 5; i++) {
  110. var ratio = i * 0.2 - 0.1;
  111. var rect = g.append("rect")
  112. .attr("x", sampleXOffset)
  113. .attr("y", sampleYOffset)
  114. .attr("fill", this.selectedCategory === i ? "#2ca02c" : colorFunc(ratio))
  115. .attr("width", this.SAMPLE_CELL_WIDTH)
  116. .attr("height", this.SAMPLE_HEIGHT)
  117. .attr("class", "hyperlink");
  118. this.bindSelectCategory(rect, i);
  119. text = g.append("text")
  120. .text("" + (ratio * 100).toFixed(1) + "% Used")
  121. .attr("y", sampleYOffset + this.SAMPLE_HEIGHT / 2 + 5)
  122. .attr("x", sampleXOffset + this.SAMPLE_CELL_WIDTH / 2)
  123. .attr("class", "heatmap-cell hyperlink");
  124. this.bindSelectCategory(text, i);
  125. sampleXOffset += this.CELL_MARGIN + this.SAMPLE_CELL_WIDTH;
  126. }
  127. if (this.selectedCategory !== 0) {
  128. text = g.append("text")
  129. .text("Clear")
  130. .attr("y", sampleYOffset + this.SAMPLE_HEIGHT / 2 + 5)
  131. .attr("x", sampleXOffset + 20)
  132. .attr("class", "heatmap-clear hyperlink");
  133. this.bindSelectCategory(text, 0);
  134. }
  135. var chartXOffset = -1;
  136. this.totalContainers = 0;
  137. for (i = 0; i < racksArray.length; i++) {
  138. text = g.append("text")
  139. .text(racksArray[i])
  140. .attr("y", yOffset + this.CELL_HEIGHT / 2 + 5)
  141. .attr("x", layout.margin)
  142. .attr("class", "heatmap-rack");
  143. if (-1 === chartXOffset) {
  144. chartXOffset = layout.margin + text.node().getComputedTextLength() + 30;
  145. }
  146. xOffset = chartXOffset;
  147. for (var j = 0; j < data.length; j++) {
  148. var rack = data[j].get("rack");
  149. if (rack === racksArray[i]) {
  150. this.totalContainers += data[j].get("numContainers");
  151. this.addNode(g, xOffset, yOffset, colorFunc, data[j]);
  152. xOffset += this.CELL_MARGIN + this.CELL_WIDTH;
  153. if (xOffset + this.CELL_MARGIN + this.CELL_WIDTH >= layout.x2 -
  154. layout.margin) {
  155. xOffset = chartXOffset;
  156. yOffset = yOffset + this.CELL_MARGIN + this.CELL_HEIGHT;
  157. }
  158. }
  159. }
  160. while (xOffset > chartXOffset && xOffset + this.CELL_MARGIN +
  161. this.CELL_WIDTH < layout.x2 - layout.margin) {
  162. this.addPlaceholderNode(g, xOffset, yOffset);
  163. xOffset += this.CELL_MARGIN + this.CELL_WIDTH;
  164. }
  165. if (xOffset !== chartXOffset) {
  166. xOffset = chartXOffset;
  167. yOffset += this.CELL_MARGIN + this.CELL_HEIGHT;
  168. }
  169. yOffset += this.RACK_MARGIN;
  170. }
  171. layout.y2 = yOffset + layout.margin;
  172. this.adjustMaxHeight(layout.y2);
  173. this.renderTitleAndBG(g, title + selectedOption + ")" , layout, false);
  174. },
  175. addNode: function (g, xOffset, yOffset, colorFunc, data) {
  176. var rect = g.append("rect")
  177. .attr("y", yOffset)
  178. .attr("x", xOffset)
  179. .attr("height", this.CELL_HEIGHT)
  180. .attr("fill", colorFunc(this.calcUsage(data)))
  181. .attr("width", this.CELL_WIDTH)
  182. .attr("tooltiptext", data.get("toolTipText") + this.getToolTipText(data));
  183. if (this.isNodeSelected(data)) {
  184. rect.style("opacity", 0.8);
  185. this.bindTP(rect, rect);
  186. } else {
  187. rect.style("opacity", 0.8);
  188. rect.attr("fill", "DimGray");
  189. }
  190. var node_id = data.get("id"),
  191. node_addr = data.get("nodeHTTPAddress"),
  192. href = `#/yarn-node/${node_id}/${node_addr}`;
  193. var a = g.append("a")
  194. .attr("href", href);
  195. a.append("text")
  196. .text(data.get("nodeHostName"))
  197. .attr("y", yOffset + this.CELL_HEIGHT / 2 + 5)
  198. .attr("x", xOffset + this.CELL_WIDTH / 2)
  199. .attr("class", this.isNodeSelected(data) ? "heatmap-cell" : "heatmap-cell-notselected");
  200. if (this.isNodeSelected(data)) {
  201. this.bindTP(a, rect);
  202. }
  203. },
  204. addPlaceholderNode: function(g, xOffset, yOffset) {
  205. g.append("rect")
  206. .attr("y", yOffset)
  207. .attr("x", xOffset)
  208. .attr("height", this.CELL_HEIGHT)
  209. .attr("fill", "grey")
  210. .attr("width", this.CELL_WIDTH)
  211. .style("opacity", 0.20);
  212. },
  213. draw: function() {
  214. this.initChart(true);
  215. this.renderCells(this.get("model"), this.get("title"), this.get("textWidth"));
  216. },
  217. didInsertElement: function () {
  218. var parentId = this.get("parentId");
  219. var self = this;
  220. var optionsData = [this.memoryLabel, this.cpuLabel, this.containersLabel];
  221. d3.select("#heatmap-select")
  222. .on('change', function() {
  223. self.renderCells(self.get("model"), self.get("title"), self.get("textWidth"));
  224. })
  225. .selectAll('option')
  226. .data(optionsData).enter()
  227. .append('option')
  228. .text(function (d) { return d; });
  229. this.draw();
  230. },
  231. actions: {
  232. applyFilter: function(event) {
  233. this.filter = event.srcElement.value;
  234. this.selectedCategory = 0;
  235. this.didInsertElement();
  236. }
  237. },
  238. calcUsage: function(data) {
  239. var selectedOption = d3.select('select').property("value");
  240. if (selectedOption === this.memoryLabel) {
  241. return data.get("usedMemoryMB") /
  242. (data.get("usedMemoryMB") + data.get("availMemoryMB"));
  243. }
  244. else if (selectedOption === this.cpuLabel) {
  245. return data.get("usedVirtualCores") /
  246. (data.get("usedVirtualCores") + data.get("availableVirtualCores"));
  247. }
  248. else if (selectedOption === this.containersLabel) {
  249. var totalContainers = this.totalContainers;
  250. if (totalContainers === 0) { return 0; }
  251. return data.get("numContainers") / totalContainers;
  252. }
  253. },
  254. getToolTipText: function(data) {
  255. var selectedOption = d3.select('select').property("value");
  256. if (selectedOption === this.memoryLabel) {
  257. return "<p>Used Memory: " + Math.round(data.get("usedMemoryMB")) + " MB</p>" +
  258. "<p>Available Memory: " + Math.round(data.get("availMemoryMB")) + " MB</p>";
  259. }
  260. else if (selectedOption === this.cpuLabel) {
  261. return "<p>Used VCores: " + Math.round(data.get("usedVirtualCores")) + " VCores</p>" +
  262. "<p>Available VCores: " + Math.round(data.get("availableVirtualCores")) + " VCores</p>";
  263. }
  264. else if (selectedOption === this.containersLabel) {
  265. return "<p>Containers: " + Math.round(data.get("numContainers")) + " Containers</p>" +
  266. "<p>Total Containers: " + this.totalContainers + " Containers</p>";
  267. }
  268. }
  269. });