tree-selector.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  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 Ember from "ember";
  19. import {PARTITION_LABEL} from '../constants';
  20. const INBETWEEN_HEIGHT = 130;
  21. export default Ember.Component.extend({
  22. // Map: <queue-name, queue>
  23. map: undefined,
  24. // Normalized data for d3
  25. treeData: undefined,
  26. // folded queues, folded[<queue-name>] == true means <queue-name> is folded
  27. foldedQueues: {},
  28. // maxDepth
  29. maxDepth: 0,
  30. // num of leaf queue, folded queue is treated as leaf queue
  31. numOfLeafQueue: 0,
  32. // mainSvg
  33. mainSvg: undefined,
  34. used: undefined,
  35. max: undefined,
  36. didUpdateAttrs: function({ oldAttrs, newAttrs }) {
  37. if (oldAttrs.filteredPartition.value !== newAttrs.filteredPartition.value) {
  38. this.reDraw();
  39. }
  40. },
  41. // Init data
  42. initData: function() {
  43. this.map = {};
  44. this.treeData = {};
  45. this.maxDepth = 0;
  46. this.numOfLeafQueue = 0;
  47. this.get("model").forEach(
  48. function(o) {
  49. this.map[o.id] = o;
  50. }.bind(this)
  51. );
  52. // var selected = this.get("selected");
  53. this.used = this.get("used");
  54. this.max = this.get("max");
  55. this.initQueue("root", 1, this.treeData);
  56. },
  57. // get Children array of given queue
  58. getChildrenNamesArray: function(q) {
  59. var namesArr = [];
  60. // Folded queue's children is empty
  61. if (this.foldedQueues[q.get("name")]) {
  62. return namesArr;
  63. }
  64. var names = q.get("children");
  65. if (names) {
  66. names.forEach(function(name) {
  67. namesArr.push(name);
  68. });
  69. }
  70. return namesArr;
  71. },
  72. // Init queues
  73. initQueue: function(queueName, depth, node) {
  74. if (!queueName || !this.map[queueName]) {
  75. // Queue is not existed
  76. return false;
  77. }
  78. if (depth > this.maxDepth) {
  79. this.maxDepth = this.maxDepth + 1;
  80. }
  81. var queue = this.map[queueName];
  82. if (
  83. this.filteredPartition &&
  84. !queue.get("partitions").contains(this.filteredPartition)
  85. ) {
  86. return false;
  87. }
  88. var names = this.getChildrenNamesArray(queue);
  89. node.name = queueName;
  90. node.parent = queue.get("parent");
  91. node.queueData = queue;
  92. if (names.length > 0) {
  93. node.children = [];
  94. names.forEach(
  95. function(name) {
  96. var childQueueData = {};
  97. node.children.push(childQueueData);
  98. const status = this.initQueue(name, depth + 1, childQueueData);
  99. if (!status) {
  100. node.children.pop();
  101. }
  102. }.bind(this)
  103. );
  104. } else {
  105. this.numOfLeafQueue = this.numOfLeafQueue + 1;
  106. }
  107. return true;
  108. },
  109. update: function(source, root, tree, diagonal) {
  110. var duration = 300;
  111. var i = 0;
  112. // Compute the new tree layout.
  113. var nodes = tree.nodes(root).reverse();
  114. var links = tree.links(nodes);
  115. // Normalize for fixed-depth.
  116. nodes.forEach(function(d) {
  117. d.y = d.depth * 200;
  118. });
  119. // Update the nodes…
  120. var node = this.mainSvg.selectAll("g.node").data(nodes, function(d) {
  121. return d.id || (d.id = ++i);
  122. });
  123. // Enter any new nodes at the parent's previous position.
  124. var nodeEnter = node
  125. .enter()
  126. .append("g")
  127. .attr("class", "node")
  128. .attr("transform", function() {
  129. return `translate(${source.y0 + 50}, ${source.x0})`;
  130. })
  131. .on(
  132. "click",
  133. function(d) {
  134. if (d.queueData.get("name") !== this.get("selected")) {
  135. document.location.href =
  136. "#/yarn-queues/" + d.queueData.get("name") + "!";
  137. }
  138. Ember.run.later(
  139. this,
  140. function() {
  141. var treeWidth = this.maxDepth * 200;
  142. var treeHeight = this.numOfLeafQueue * INBETWEEN_HEIGHT;
  143. var tree = d3.layout.tree().size([treeHeight, treeWidth]);
  144. var diagonal = d3.svg.diagonal().projection(function(d) {
  145. return [d.y + 50, d.x];
  146. });
  147. this.update(this.treeData, this.treeData, tree, diagonal);
  148. },
  149. 100
  150. );
  151. }.bind(this)
  152. )
  153. .on("dblclick", function(d) {
  154. document.location.href =
  155. "#/yarn-queue/" + d.queueData.get("name") + "/apps";
  156. });
  157. nodeEnter
  158. .append("circle")
  159. .attr("r", 1e-6)
  160. .style(
  161. "fill",
  162. function(d) {
  163. const usedCapacity = getUsedCapacity(d.queueData, this.filteredPartition);
  164. if (usedCapacity <= 60.0) {
  165. return "#60cea5";
  166. } else if (usedCapacity <= 100.0) {
  167. return "#ffbc0b";
  168. } else {
  169. return "#ef6162";
  170. }
  171. }.bind(this)
  172. );
  173. // append percentage
  174. nodeEnter
  175. .append("text")
  176. .attr("x", function() {
  177. return 0;
  178. })
  179. .attr("dy", ".35em")
  180. .attr("fill", "white")
  181. .attr("text-anchor", function() {
  182. return "middle";
  183. })
  184. .text(
  185. function(d) {
  186. const usedCapacity = getUsedCapacity(d.queueData, this.filteredPartition);
  187. if (usedCapacity >= 100.0) {
  188. return usedCapacity.toFixed(0) + "%";
  189. } else {
  190. return usedCapacity.toFixed(1) + "%";
  191. }
  192. }.bind(this)
  193. )
  194. .style("fill-opacity", 1e-6);
  195. // append queue name
  196. nodeEnter
  197. .append("text")
  198. .attr("x", "0px")
  199. .attr("dy", "45px")
  200. .attr("text-anchor", "middle")
  201. .text(function(d) {
  202. return d.name;
  203. })
  204. .style("fill-opacity", 1e-6);
  205. // Transition nodes to their new position.
  206. var nodeUpdate = node
  207. .transition()
  208. .duration(duration)
  209. .attr("transform", function(d) {
  210. return `translate(${d.y + 50}, ${d.x})`;
  211. });
  212. nodeUpdate
  213. .select("circle")
  214. .attr("r", 30)
  215. .attr("href", function(d) {
  216. return "#/yarn-queues/" + d.queueData.get("name");
  217. })
  218. .style(
  219. "stroke-width",
  220. function(d) {
  221. if (d.queueData.get("name") === this.get("selected")) {
  222. return 7;
  223. } else {
  224. return 2;
  225. }
  226. }.bind(this)
  227. )
  228. .style(
  229. "stroke",
  230. function(d) {
  231. if (d.queueData.get("name") === this.get("selected")) {
  232. return "gray";
  233. } else {
  234. return "gray";
  235. }
  236. }.bind(this)
  237. );
  238. nodeUpdate.selectAll("text").style("fill-opacity", 1);
  239. // Transition exiting nodes to the parent's new position.
  240. var nodeExit = node
  241. .exit()
  242. .transition()
  243. .duration(duration)
  244. .attr("transform", function() {
  245. return `translate(${source.y}, ${source.x})`;
  246. })
  247. .remove();
  248. nodeExit.select("circle").attr("r", 1e-6);
  249. nodeExit.select("text").style("fill-opacity", 1e-6);
  250. // Update the links…
  251. var link = this.mainSvg.selectAll("path.link").data(links, function(d) {
  252. return d.target.id;
  253. });
  254. // Enter any new links at the parent's previous position.
  255. link
  256. .enter()
  257. .insert("path", "g")
  258. .attr("class", "link")
  259. .attr("d", function() {
  260. var o = { x: source.x0, y: source.y0 + 50 };
  261. return diagonal({ source: o, target: o });
  262. });
  263. // Transition links to their new position.
  264. link
  265. .transition()
  266. .duration(duration)
  267. .attr("d", diagonal);
  268. // Transition exiting nodes to the parent's new position.
  269. link
  270. .exit()
  271. .transition()
  272. .duration(duration)
  273. .attr("d", function() {
  274. var o = { x: source.x, y: source.y };
  275. return diagonal({ source: o, target: o });
  276. })
  277. .remove();
  278. // Stash the old positions for transition.
  279. nodes.forEach(function(d) {
  280. d.x0 = d.x;
  281. d.y0 = d.y;
  282. });
  283. },
  284. reDraw: function() {
  285. this.initData();
  286. var margin = { top: 20, right: 120, bottom: 20, left: 120 };
  287. var treeWidth = this.maxDepth * 200;
  288. var treeHeight = this.numOfLeafQueue * INBETWEEN_HEIGHT;
  289. var width = treeWidth + margin.left + margin.right;
  290. var height = treeHeight + margin.top + margin.bottom;
  291. if (this.mainSvg) {
  292. this.mainSvg.selectAll("*").remove();
  293. } else {
  294. this.mainSvg = d3
  295. .select("#" + this.get("parentId"))
  296. .append("svg")
  297. .attr("width", width)
  298. .attr("height", height)
  299. .attr("class", "tree-selector");
  300. }
  301. this.mainSvg
  302. .append("g")
  303. .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
  304. var tree = d3.layout.tree().size([treeHeight, treeWidth]);
  305. var diagonal = d3.svg.diagonal().projection(function(d) {
  306. return [d.y + 50, d.x];
  307. });
  308. var root = this.treeData;
  309. root.x0 = height / 2;
  310. root.y0 = 0;
  311. d3.select(window.frameElement).style("height", height);
  312. this.update(root, root, tree, diagonal);
  313. },
  314. didInsertElement: function() {
  315. this.reDraw();
  316. }
  317. });
  318. const getUsedCapacity = (queueData, filter=PARTITION_LABEL) => {
  319. const type = queueData.get("type");
  320. var result;
  321. switch (type) {
  322. case "capacity":
  323. const partitionMap = queueData.get("partitionMap");
  324. if (null == partitionMap || null == partitionMap[filter] || null == partitionMap[filter].absoluteUsedCapacity) {
  325. result = 0.0;
  326. } else {
  327. result = partitionMap[filter].absoluteUsedCapacity;
  328. }
  329. break;
  330. case "fair":
  331. if (null == queueData.get("fairResources") || null == queueData.get("fairResources").memory || null == queueData.get("usedResources") || null == queueData.get("usedResources").memory || 0 == queueData.get("fairResources").memory) {
  332. result = 0.0;
  333. } else {
  334. result = queueData.get("usedResources").memory / queueData.get("fairResources").memory * 100;
  335. }
  336. break;
  337. case "fifo":
  338. if (null == queueData.get("usedCapacity") || (null == queueData.get("capacity")) || (queueData.get("capacity") == 0)) {
  339. result = 0.0;
  340. } else {
  341. result = queueData.get("usedCapacity") / queueData.get("capacity") * 100;
  342. }
  343. break;
  344. default:
  345. result = 0.0;
  346. }
  347. return result;
  348. };