nodes-heatmap.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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. var currentToolTip = this.tooltip;
  34. element.on("mouseover", function() {
  35. currentToolTip
  36. .style("left", (d3.event.pageX) + "px")
  37. .style("top", (d3.event.pageY - 28) + "px");
  38. cell.style("opacity", 1.0);
  39. }.bind(this))
  40. .on("mousemove", function() {
  41. // Handle pie chart case
  42. var text = cell.attr("tooltiptext");
  43. currentToolTip
  44. .style("background", "black")
  45. .style("opacity", 0.7);
  46. currentToolTip
  47. .html(text)
  48. .style('font-size', '12px')
  49. .style('color', 'white')
  50. .style('font-weight', '400');
  51. currentToolTip
  52. .style("left", (d3.event.pageX) + "px")
  53. .style("top", (d3.event.pageY - 28) + "px");
  54. }.bind(this))
  55. .on("mouseout", function() {
  56. currentToolTip.style("opacity", 0);
  57. cell.style("opacity", 0.8);
  58. }.bind(this));
  59. },
  60. bindSelectCategory: function(element, i) {
  61. element.on("click", function() {
  62. if (this.selectedCategory === i) {
  63. // Remove selection for second click
  64. this.selectedCategory = 0;
  65. } else {
  66. this.selectedCategory = i;
  67. }
  68. this.didInsertElement();
  69. }.bind(this));
  70. },
  71. isNodeSelected: function(node) {
  72. if (this.filter) {
  73. var rack = node.get("rack");
  74. var host = node.get("nodeHostName");
  75. if (!rack.includes(this.filter) && !host.includes(this.filter)) {
  76. return false;
  77. }
  78. }
  79. if (this.selectedCategory === 0) {
  80. return true;
  81. }
  82. var usage = this.calcUsage(node);
  83. var lowerLimit = (this.selectedCategory - 1) * 0.2;
  84. var upperLimit = this.selectedCategory * 0.2;
  85. if (lowerLimit <= usage && usage <= upperLimit) {
  86. return true;
  87. }
  88. return false;
  89. },
  90. // data:
  91. // [{label=label1, value=value1}, ...]
  92. // ...
  93. renderCells: function (model, title) {
  94. var selectedOption = d3.select("select").property("value");
  95. var data = [];
  96. model.forEach(function (o) {
  97. data.push(o);
  98. });
  99. this.chart.g.remove();
  100. this.chart.g = this.chart.svg.append("g");
  101. var g = this.chart.g;
  102. var layout = this.getLayout();
  103. layout.margin = 50;
  104. let racks = new Set();
  105. for (var i = 0; i < data.length; i++) {
  106. racks.add(data[i].get("rack"));
  107. }
  108. let racksArray = [];
  109. racks.forEach(v => racksArray.push(v));
  110. var xOffset = layout.margin;
  111. var yOffset = layout.margin * 3;
  112. var gradientStartColor = "#2ca02c";
  113. var gradientEndColor = "#ffb014";
  114. var colorFunc = d3.interpolateRgb(d3.rgb(gradientStartColor), d3.rgb(gradientEndColor));
  115. var sampleXOffset = (layout.x2 - layout.x1) / 2 - 2.5 * this.SAMPLE_CELL_WIDTH -
  116. 2 * this.CELL_MARGIN;
  117. var sampleYOffset = layout.margin * 2;
  118. var text;
  119. for (i = 1; i <= 5; i++) {
  120. var ratio = i * 0.2 - 0.1;
  121. var rect = g.append("rect")
  122. .attr("x", sampleXOffset)
  123. .attr("y", sampleYOffset)
  124. .attr("fill", this.selectedCategory === i ? "#2c7bb6" : colorFunc(ratio))
  125. .attr("width", this.SAMPLE_CELL_WIDTH)
  126. .attr("height", this.SAMPLE_HEIGHT)
  127. .attr("class", "hyperlink");
  128. this.bindSelectCategory(rect, i);
  129. text = g.append("text")
  130. .text("" + (ratio * 100).toFixed(1) + "% Used")
  131. .attr("y", sampleYOffset + this.SAMPLE_HEIGHT / 2 + 5)
  132. .attr("x", sampleXOffset + this.SAMPLE_CELL_WIDTH / 2)
  133. .attr("class", "heatmap-cell hyperlink");
  134. this.bindSelectCategory(text, i);
  135. sampleXOffset += this.CELL_MARGIN + this.SAMPLE_CELL_WIDTH;
  136. }
  137. if (this.selectedCategory !== 0) {
  138. text = g.append("text")
  139. .text("Clear")
  140. .attr("y", sampleYOffset + this.SAMPLE_HEIGHT / 2 + 5)
  141. .attr("x", sampleXOffset + 20)
  142. .attr("class", "heatmap-clear hyperlink");
  143. this.bindSelectCategory(text, 0);
  144. }
  145. var chartXOffset = -1;
  146. this.totalContainers = 0;
  147. for (i = 0; i < racksArray.length; i++) {
  148. text = g.append("text")
  149. .text(racksArray[i])
  150. .attr("y", yOffset + this.CELL_HEIGHT / 2 + 5)
  151. .attr("x", layout.margin)
  152. .attr("class", "heatmap-rack");
  153. if (-1 === chartXOffset) {
  154. chartXOffset = layout.margin + text.node().getComputedTextLength() + 30;
  155. }
  156. xOffset = chartXOffset;
  157. for (var j = 0; j < data.length; j++) {
  158. var rack = data[j].get("rack");
  159. if (rack === racksArray[i]) {
  160. this.totalContainers += data[j].get("numContainers");
  161. this.addNode(g, xOffset, yOffset, colorFunc, data[j]);
  162. xOffset += this.CELL_MARGIN + this.CELL_WIDTH;
  163. if (xOffset + this.CELL_MARGIN + this.CELL_WIDTH >= layout.x2 -
  164. layout.margin) {
  165. xOffset = chartXOffset;
  166. yOffset = yOffset + this.CELL_MARGIN + this.CELL_HEIGHT;
  167. }
  168. }
  169. }
  170. while (xOffset > chartXOffset && xOffset + this.CELL_MARGIN +
  171. this.CELL_WIDTH < layout.x2 - layout.margin) {
  172. this.addPlaceholderNode(g, xOffset, yOffset);
  173. xOffset += this.CELL_MARGIN + this.CELL_WIDTH;
  174. }
  175. if (xOffset !== chartXOffset) {
  176. xOffset = chartXOffset;
  177. yOffset += this.CELL_MARGIN + this.CELL_HEIGHT;
  178. }
  179. yOffset += this.RACK_MARGIN;
  180. }
  181. layout.y2 = yOffset + layout.margin;
  182. this.adjustMaxHeight(layout.y2);
  183. this.renderTitleAndBG(g, title + selectedOption + ")" , layout, false);
  184. },
  185. addNode: function (g, xOffset, yOffset, colorFunc, data) {
  186. var rect = g.append("rect")
  187. .attr("y", yOffset)
  188. .attr("x", xOffset)
  189. .attr("height", this.CELL_HEIGHT)
  190. .attr("fill", colorFunc(this.calcUsage(data)))
  191. .attr("width", this.CELL_WIDTH)
  192. .attr("tooltiptext", data.get("toolTipText") + this.getToolTipText(data));
  193. if (this.isNodeSelected(data)) {
  194. rect.style("opacity", 0.8);
  195. this.bindTP(rect, rect);
  196. } else {
  197. rect.style("opacity", 0.8);
  198. rect.attr("fill", "DimGray");
  199. }
  200. var node_id = data.get("id"),
  201. node_addr = data.get("nodeHTTPAddress"),
  202. href = `#/yarn-node/${node_id}/${node_addr}`;
  203. var a = g.append("a")
  204. .attr("href", href);
  205. a.append("text")
  206. .text(data.get("nodeHostName"))
  207. .attr("y", yOffset + this.CELL_HEIGHT / 2 + 5)
  208. .attr("x", xOffset + this.CELL_WIDTH / 2)
  209. .attr("class", this.isNodeSelected(data) ? "heatmap-cell" : "heatmap-cell-notselected");
  210. if (this.isNodeSelected(data)) {
  211. this.bindTP(a, rect);
  212. }
  213. },
  214. addPlaceholderNode: function(g, xOffset, yOffset) {
  215. g.append("rect")
  216. .attr("y", yOffset)
  217. .attr("x", xOffset)
  218. .attr("height", this.CELL_HEIGHT)
  219. .attr("fill", "grey")
  220. .attr("width", this.CELL_WIDTH)
  221. .style("opacity", 0.20);
  222. },
  223. draw: function() {
  224. this.initChart(true);
  225. this.renderCells(this.get("model"), this.get("title"), this.get("textWidth"));
  226. },
  227. didInsertElement: function () {
  228. var self = this;
  229. var optionsData = [this.memoryLabel, this.cpuLabel, this.containersLabel];
  230. d3.select("#heatmap-select")
  231. .on('change', function() {
  232. self.renderCells(self.get("model"), self.get("title"), self.get("textWidth"));
  233. })
  234. .selectAll('option')
  235. .data(optionsData).enter()
  236. .append('option')
  237. .text(function (d) { return d; });
  238. this.draw();
  239. },
  240. actions: {
  241. applyFilter: function(event) {
  242. this.filter = event.srcElement.value;
  243. this.selectedCategory = 0;
  244. this.didInsertElement();
  245. }
  246. },
  247. calcUsage: function(data) {
  248. var selectedOption = d3.select('select').property("value");
  249. if (selectedOption === this.memoryLabel) {
  250. return data.get("usedMemoryMB") /
  251. (data.get("usedMemoryMB") + data.get("availMemoryMB"));
  252. }
  253. else if (selectedOption === this.cpuLabel) {
  254. return data.get("usedVirtualCores") /
  255. (data.get("usedVirtualCores") + data.get("availableVirtualCores"));
  256. }
  257. else if (selectedOption === this.containersLabel) {
  258. var totalContainers = this.totalContainers;
  259. if (totalContainers === 0) { return 0; }
  260. return data.get("numContainers") / totalContainers;
  261. }
  262. },
  263. getToolTipText: function(data) {
  264. var selectedOption = d3.select('select').property("value");
  265. if (selectedOption === this.memoryLabel) {
  266. return "<p>Used Memory: " + Math.round(data.get("usedMemoryMB")) + " MB</p>" +
  267. "<p>Available Memory: " + Math.round(data.get("availMemoryMB")) + " MB</p>";
  268. }
  269. else if (selectedOption === this.cpuLabel) {
  270. return "<p>Used VCores: " + Math.round(data.get("usedVirtualCores")) + " VCores</p>" +
  271. "<p>Available VCores: " + Math.round(data.get("availableVirtualCores")) + " VCores</p>";
  272. }
  273. else if (selectedOption === this.containersLabel) {
  274. return "<p>Containers: " + Math.round(data.get("numContainers")) + " Containers</p>" +
  275. "<p>Total Containers: " + this.totalContainers + " Containers</p>";
  276. }
  277. }
  278. });