timeline-view.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  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 Converter from 'yarn-ui/utils/converter';
  20. import ColumnDef from 'em-table/utils/column-definition';
  21. import TableDefinition from 'em-table/utils/table-definition';
  22. export default Ember.Component.extend({
  23. tableDefinition: TableDefinition.create({
  24. searchType: 'manual',
  25. }),
  26. graphDrawn: false,
  27. actions: {
  28. changeViewType(param) {
  29. this.sendAction("changeViewType", param);
  30. }
  31. },
  32. canvas: {
  33. svg: undefined,
  34. h: 0,
  35. w: 0,
  36. tooltip: undefined
  37. },
  38. clusterMetrics: undefined,
  39. modelArr: [],
  40. containerIdArr: [],
  41. colors: d3.scale.category10().range(),
  42. _selected: undefined,
  43. gridColumns: [],
  44. gridRows: [],
  45. serviceName: undefined,
  46. selected: function() {
  47. return this._selected;
  48. }.property(),
  49. tableComponentName: function() {
  50. return "app-attempt-table";
  51. }.property(),
  52. setSelected: function(d) {
  53. var dom;
  54. if (this._selected === d) {
  55. return;
  56. }
  57. // restore color
  58. if (this._selected) {
  59. dom = d3.select("#timeline-bar-" + this._selected.get("id"));
  60. dom.attr("fill", this.colors[0]);
  61. }
  62. this._selected = d;
  63. this.set("selected", d);
  64. dom = d3.select("#timeline-bar-" + d.get("id"));
  65. dom.attr("fill", this.colors[1]);
  66. },
  67. getPerItemHeight: function() {
  68. var arrSize = this.modelArr.length;
  69. if (arrSize < 20) {
  70. return 30;
  71. } else if (arrSize < 100) {
  72. return 10;
  73. } else {
  74. return 2;
  75. }
  76. },
  77. getPerItemGap: function() {
  78. var arrSize = this.modelArr.length;
  79. if (arrSize < 20) {
  80. return 5;
  81. } else if (arrSize < 100) {
  82. return 1;
  83. } else {
  84. return 1;
  85. }
  86. },
  87. getCanvasHeight: function() {
  88. return (this.getPerItemHeight() + this.getPerItemGap()) * this.modelArr.length + 200;
  89. },
  90. draw: function(start, end) {
  91. // get w/h of the svg
  92. var bbox = d3.select("#" + this.get("parent-id"))
  93. .node()
  94. .getBoundingClientRect();
  95. this.canvas.w = bbox.width;
  96. this.canvas.h = this.getCanvasHeight();
  97. this.canvas.svg = d3.select("#" + this.get("parent-id"))
  98. .append("svg")
  99. .attr("width", this.canvas.w)
  100. .attr("height", this.canvas.h)
  101. .attr("id", this.get("my-id"));
  102. this.renderTimeline(start, end);
  103. },
  104. renderTimeline: function(start, end) {
  105. var border = 30;
  106. var singleBarHeight = this.getPerItemHeight();
  107. var gap = this.getPerItemGap();
  108. var textWidth = 200;
  109. /*
  110. start-time end-time
  111. |--------------------------------------|
  112. ==============
  113. ==============
  114. ==============
  115. ===============
  116. */
  117. var xScaler = d3.scale.linear()
  118. .domain([start, end])
  119. .range([0, this.canvas.w - 2 * border - textWidth]);
  120. /*
  121. * Render frame of timeline view
  122. */
  123. this.canvas.svg.append("line")
  124. .attr("x1", border + textWidth)
  125. .attr("y1", border - 5)
  126. .attr("x2", this.canvas.w - border)
  127. .attr("y2", border - 5)
  128. .attr("class", "chart");
  129. this.canvas.svg.append("line")
  130. .attr("x1", border + textWidth)
  131. .attr("y1", border - 10)
  132. .attr("x2", border + textWidth)
  133. .attr("y2", border - 5)
  134. .attr("class", "chart");
  135. this.canvas.svg.append("line")
  136. .attr("x1", this.canvas.w - border)
  137. .attr("y1", border - 10)
  138. .attr("x2", this.canvas.w - border)
  139. .attr("y2", border - 5)
  140. .attr("class", "chart");
  141. this.canvas.svg.append("text")
  142. .text(Converter.timeStampToDate(start))
  143. .attr("y", border - 15)
  144. .attr("x", border + textWidth)
  145. .attr("class", "bar-chart-text")
  146. .attr("text-anchor", "left");
  147. this.canvas.svg.append("text")
  148. .text(Converter.timeStampToDate(end))
  149. .attr("y", border - 15)
  150. .attr("x", this.canvas.w - border)
  151. .attr("class", "bar-chart-text")
  152. .attr("text-anchor", "end");
  153. // show bar
  154. var bar = this.canvas.svg.selectAll("bars")
  155. .data(this.modelArr)
  156. .enter()
  157. .append("rect")
  158. .attr("y", function(d, i) {
  159. return border + (gap + singleBarHeight) * i;
  160. })
  161. .attr("x", function(d) {
  162. return border + textWidth + xScaler(d.get("startTs"));
  163. })
  164. .attr("height", singleBarHeight)
  165. .attr("fill", function() {
  166. return this.colors[0];
  167. }.bind(this))
  168. .attr("width", function(d) {
  169. var finishedTs = xScaler(d.get("finishedTs"));
  170. finishedTs = finishedTs > 0 ? finishedTs : xScaler(end);
  171. return finishedTs - xScaler(d.get("startTs"));
  172. })
  173. .attr("id", function(d) {
  174. return "timeline-bar-" + d.get("id");
  175. });
  176. bar.on("click", function(d) {
  177. this.setSelected(d);
  178. }.bind(this));
  179. this.bindTooltip(bar);
  180. if (this.modelArr.length <= 20) {
  181. // show bar texts
  182. for (var i = 0; i < this.modelArr.length; i++) {
  183. this.canvas.svg.append("text")
  184. .text(this.modelArr[i].get(this.get("label")))
  185. .attr("y", border + (gap + singleBarHeight) * i + singleBarHeight / 2)
  186. .attr("x", border)
  187. .attr("class", "bar-chart-text");
  188. }
  189. }
  190. },
  191. bindTooltip: function(d) {
  192. d.on("mouseover", function() {
  193. this.tooltip
  194. .style("left", (d3.event.pageX) + "px")
  195. .style("top", (d3.event.pageY - 28) + "px");
  196. }.bind(this))
  197. .on("mousemove", function(d) {
  198. this.tooltip.style("opacity", 0.9);
  199. this.tooltip.html(d.get("tooltipLabel"))
  200. .style("left", (d3.event.pageX) + "px")
  201. .style("top", (d3.event.pageY - 28) + "px");
  202. }.bind(this))
  203. .on("mouseout", function() {
  204. this.tooltip.style("opacity", 0);
  205. }.bind(this));
  206. },
  207. initTooltip: function() {
  208. this.tooltip = d3.select("body")
  209. .append("div")
  210. .attr("class", "tooltip")
  211. .attr("id", "chart-tooltip")
  212. .style("opacity", 0);
  213. },
  214. didInsertElement: function() {
  215. // init model
  216. this.modelArr = [];
  217. this.containerIdArr = [];
  218. if (this.get("rmModel")) {
  219. this.get("rmModel").forEach(function(o) {
  220. if(!this.modelArr.contains(o)) {
  221. this.modelArr.push(o);
  222. this.containerIdArr.push(o.id);
  223. }
  224. }.bind(this));
  225. }
  226. if (this.get("tsModel")) {
  227. this.get("tsModel").forEach(function(o) {
  228. if(!this.containerIdArr.contains(o.id)) {
  229. this.modelArr.push(o);
  230. }
  231. }.bind(this));
  232. }
  233. if (this.modelArr.length === 0) {
  234. return;
  235. }
  236. this.modelArr.sort(function(a, b) {
  237. var tsA = a.get("startTs");
  238. var tsB = b.get("startTs");
  239. return tsA - tsB;
  240. });
  241. if (this.get('attemptModel')) {
  242. this.setAttemptsGridColumnsAndRows();
  243. } else {
  244. this.setContainersGridColumnsAndRows();
  245. }
  246. },
  247. didUpdate: function() {
  248. if (this.get("viewType") === "grid" || this.graphDrawn) {
  249. return;
  250. }
  251. this.initTooltip();
  252. var begin = 0;
  253. if (this.modelArr.length > 0) {
  254. begin = this.modelArr[0].get("startTs");
  255. }
  256. var end = 0;
  257. for (var i = 0; i < this.modelArr.length; i++) {
  258. var ts = this.modelArr[i].get("finishedTs");
  259. if (ts > end) {
  260. end = ts;
  261. }
  262. }
  263. if (end < begin) {
  264. end = Date.now();
  265. }
  266. this.draw(begin, end);
  267. if (this.modelArr.length > 0) {
  268. this.setSelected(this.modelArr[0]);
  269. }
  270. this.graphDrawn = true;
  271. },
  272. setAttemptsGridColumnsAndRows: function() {
  273. var self = this;
  274. var columns = [];
  275. var serviceName = this.get('serviceName');
  276. columns.push({
  277. id: 'id',
  278. headerTitle: 'Attempt ID',
  279. contentPath: 'id',
  280. cellComponentName: 'em-table-linked-cell',
  281. minWidth: '300px',
  282. getCellContent: function(row) {
  283. var attemptId = row.get('id');
  284. var query = serviceName? '?service='+serviceName : '';
  285. return {
  286. displayText: attemptId,
  287. href: `#/yarn-app-attempt/${attemptId}${query}`
  288. };
  289. }
  290. }, {
  291. id: 'attemptStartedTime',
  292. headerTitle: 'Started Time',
  293. contentPath: 'attemptStartedTime'
  294. }, {
  295. id: 'finishedTime',
  296. headerTitle: 'Finished Time',
  297. contentPath: 'finishedTime',
  298. getCellContent: function(row) {
  299. if (row.get('finishedTs')) {
  300. return row.get('finishedTime');
  301. }
  302. return 'N/A';
  303. }
  304. }, {
  305. id: 'elapsedTime',
  306. headerTitle: 'Elapsed Time',
  307. contentPath: 'elapsedTime'
  308. }, {
  309. id: 'appMasterContainerId',
  310. headerTitle: 'AM Container ID',
  311. contentPath: 'appMasterContainerId',
  312. minWidth: '350px'
  313. }, {
  314. id: 'amNodeId',
  315. headerTitle: 'AM Node ID',
  316. contentPath: 'amNodeId'
  317. }, {
  318. id: 'attemptState',
  319. headerTitle: 'State',
  320. contentPath: 'attemptState',
  321. getCellContent: function(row) {
  322. var state = row.get('attemptState');
  323. if (state) {
  324. return state;
  325. } else {
  326. return 'N/A';
  327. }
  328. }
  329. }, {
  330. id: 'nodeHttpAddress',
  331. headerTitle: 'NodeManager Web UI',
  332. contentPath: 'nodeHttpAddress',
  333. cellComponentName: 'em-table-html-cell',
  334. getCellContent: function(row) {
  335. var address = self.checkHttpProtocol(row.get('nodeHttpAddress'));
  336. if (address) {
  337. return `<a href="${address}" target="_blank">${address}</a>`;
  338. } else {
  339. return 'N/A';
  340. }
  341. }
  342. }, {
  343. id: 'logsLink',
  344. headerTitle: 'Logs',
  345. contentPath: 'logsLink',
  346. cellComponentName: 'em-table-html-cell',
  347. getCellContent: function(row) {
  348. var logUrl = self.checkHttpProtocol(row.get('logsLink'));
  349. if (logUrl) {
  350. return `<a href="${logUrl}" target="_blank">Link</a>`;
  351. } else {
  352. return 'N/A';
  353. }
  354. }
  355. });
  356. var gridCols = ColumnDef.make(columns);
  357. this.set('gridColumns', gridCols);
  358. this.set('gridRows', this.modelArr);
  359. },
  360. setContainersGridColumnsAndRows: function() {
  361. var self = this;
  362. var columns = [];
  363. columns.push({
  364. id: 'id',
  365. headerTitle: 'Container ID',
  366. contentPath: 'id',
  367. minWidth: '350px'
  368. }, {
  369. id: 'startedTime',
  370. headerTitle: 'Started Time',
  371. contentPath: 'startedTime'
  372. }, {
  373. id: 'finishedTime',
  374. headerTitle: 'Finished Time',
  375. contentPath: 'finishedTime',
  376. getCellContent: function(row) {
  377. if (row.get('finishedTs')) {
  378. return row.get('finishedTime');
  379. }
  380. return 'N/A';
  381. }
  382. }, {
  383. id: 'elapsedTime',
  384. headerTitle: 'Elapsed Time',
  385. contentPath: 'elapsedTime'
  386. }, {
  387. id: 'priority',
  388. headerTitle: 'Priority',
  389. contentPath: 'priority'
  390. }, {
  391. id: 'containerExitStatus',
  392. headerTitle: 'Exit Status',
  393. contentPath: 'containerExitStatus',
  394. getCellContent: function(row) {
  395. var status = row.get('containerExitStatus');
  396. if (status) {
  397. return status;
  398. } else {
  399. return 'N/A';
  400. }
  401. }
  402. }, {
  403. id: 'containerState',
  404. headerTitle: 'State',
  405. contentPath: 'containerState',
  406. getCellContent: function(row) {
  407. var state = row.get('containerState');
  408. if (state) {
  409. return state;
  410. } else {
  411. return 'N/A';
  412. }
  413. }
  414. }, {
  415. id: 'logUrl',
  416. headerTitle: 'Logs',
  417. contentPath: 'logUrl',
  418. cellComponentName: 'em-table-html-cell',
  419. getCellContent: function(row) {
  420. var url = self.checkHttpProtocol(row.get('logUrl'));
  421. if (url) {
  422. return `<a href="${url}" target="_blank">${url}</a>`;
  423. } else {
  424. return 'N/A';
  425. }
  426. }
  427. }, {
  428. id: 'nodeHttpAddress',
  429. headerTitle: 'Node Manager UI',
  430. contentPath: 'nodeHttpAddress',
  431. cellComponentName: 'em-table-html-cell',
  432. getCellContent: function(row) {
  433. var address = self.checkHttpProtocol(row.get('nodeHttpAddress'));
  434. if (address) {
  435. return `<a href="${address}" target="_blank">${address}</a>`;
  436. } else {
  437. return 'N/A';
  438. }
  439. }
  440. });
  441. var gridCols = ColumnDef.make(columns);
  442. this.set('gridColumns', gridCols);
  443. this.set('gridRows', this.modelArr);
  444. },
  445. checkHttpProtocol: function(prop) {
  446. if (prop && prop.indexOf('://') < 0) {
  447. prop = 'http://' + prop;
  448. }
  449. return prop;
  450. }
  451. });