dfshealth.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  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. (function () {
  19. "use strict";
  20. dust.loadSource(dust.compile($('#tmpl-dfshealth').html(), 'dfshealth'));
  21. dust.loadSource(dust.compile($('#tmpl-startup-progress').html(), 'startup-progress'));
  22. dust.loadSource(dust.compile($('#tmpl-datanode').html(), 'datanode-info'));
  23. dust.loadSource(dust.compile($('#tmpl-datanode-volume-failures').html(), 'datanode-volume-failures'));
  24. dust.loadSource(dust.compile($('#tmpl-snapshot').html(), 'snapshot-info'));
  25. $.fn.dataTable.ext.order['ng-value'] = function (settings, col)
  26. {
  27. return this.api().column(col, {order:'index'} ).nodes().map(function (td, i) {
  28. return $(td).attr('ng-value');
  29. });
  30. };
  31. function load_overview() {
  32. var BEANS = [
  33. {"name": "nn", "url": "/jmx?qry=Hadoop:service=NameNode,name=NameNodeInfo"},
  34. {"name": "nnstat", "url": "/jmx?qry=Hadoop:service=NameNode,name=NameNodeStatus"},
  35. {"name": "fs", "url": "/jmx?qry=Hadoop:service=NameNode,name=FSNamesystemState"},
  36. {"name": "fsn", "url": "/jmx?qry=Hadoop:service=NameNode,name=FSNamesystem"},
  37. {"name": "blockstats", "url": "/jmx?qry=Hadoop:service=NameNode,name=BlockStats"},
  38. {"name": "mem", "url": "/jmx?qry=java.lang:type=Memory"}
  39. ];
  40. var HELPERS = {
  41. 'helper_fs_max_objects': function (chunk, ctx, bodies, params) {
  42. var o = ctx.current();
  43. if (o.MaxObjects > 0) {
  44. chunk.write('(' + Math.round((o.FilesTotal + o.BlockTotal) / o.MaxObjects * 100) * 100 + ')%');
  45. }
  46. },
  47. 'helper_dir_status': function (chunk, ctx, bodies, params) {
  48. var j = ctx.current();
  49. for (var i in j) {
  50. chunk.write('<tr><td>' + i + '</td><td>' + j[i] + '</td><td>' + params.type + '</td></tr>');
  51. }
  52. },
  53. 'helper_date_tostring' : function (chunk, ctx, bodies, params) {
  54. var value = dust.helpers.tap(params.value, chunk, ctx);
  55. return chunk.write('' + moment(Number(value)).format('ddd MMM DD HH:mm:ss ZZ YYYY'));
  56. }
  57. };
  58. var data = {};
  59. $.ajax({'url': '/conf', 'dataType': 'xml', 'async': false}).done(
  60. function(d) {
  61. var $xml = $(d);
  62. var namespace, nnId;
  63. $xml.find('property').each(function(idx,v) {
  64. if ($(v).find('name').text() === 'dfs.nameservice.id') {
  65. namespace = $(v).find('value').text();
  66. }
  67. if ($(v).find('name').text() === 'dfs.ha.namenode.id') {
  68. nnId = $(v).find('value').text();
  69. }
  70. });
  71. if (namespace && nnId) {
  72. data['HAInfo'] = {"Namespace": namespace, "NamenodeID": nnId};
  73. }
  74. });
  75. // Workarounds for the fact that JMXJsonServlet returns non-standard JSON strings
  76. function workaround(nn) {
  77. nn.JournalTransactionInfo = JSON.parse(nn.JournalTransactionInfo);
  78. nn.NameJournalStatus = JSON.parse(nn.NameJournalStatus);
  79. nn.NameDirStatuses = JSON.parse(nn.NameDirStatuses);
  80. nn.NodeUsage = JSON.parse(nn.NodeUsage);
  81. nn.CorruptFiles = JSON.parse(nn.CorruptFiles);
  82. return nn;
  83. }
  84. load_json(
  85. BEANS,
  86. guard_with_startup_progress(function(d) {
  87. for (var k in d) {
  88. data[k] = k === 'nn' ? workaround(d[k].beans[0]) : d[k].beans[0];
  89. }
  90. var blockstats = data['blockstats'];
  91. for (var k in blockstats.StorageTypeStats) {
  92. var b = blockstats.StorageTypeStats[k].value;
  93. b.capacityUsedPercentage = b.capacityUsed * 100.0 / b.capacityTotal;
  94. b.capacityRemainingPercentage = b.capacityRemaining * 100.0 / b.capacityTotal;
  95. }
  96. data.fs.ObjectsTotal = data.fs.FilesTotal + data.fs.BlocksTotal;
  97. render();
  98. }),
  99. function (url, jqxhr, text, err) {
  100. show_err_msg('<p>Failed to retrieve data from ' + url + ', cause: ' + err + '</p>');
  101. });
  102. function render() {
  103. var base = dust.makeBase(HELPERS);
  104. dust.render('dfshealth', base.push(data), function(err, out) {
  105. $('#tab-overview').html(out);
  106. $('#ui-tabs a[href="#tab-overview"]').tab('show');
  107. });
  108. }
  109. }
  110. function show_err_msg(msg) {
  111. $('#alert-panel-body').html(msg);
  112. $('#alert-panel').show();
  113. }
  114. function ajax_error_handler(url, jqxhr, text, err) {
  115. show_err_msg('<p>Failed to retrieve data from ' + url + ', cause: ' + err + '</p>');
  116. }
  117. function guard_with_startup_progress(fn) {
  118. return function() {
  119. try {
  120. fn.apply(this, arguments);
  121. } catch (err) {
  122. if (err instanceof TypeError) {
  123. show_err_msg('NameNode is still loading. Redirecting to the Startup Progress page.');
  124. load_startup_progress();
  125. }
  126. }
  127. };
  128. }
  129. function load_startup_progress() {
  130. function workaround(r) {
  131. function rename_property(o, s, d) {
  132. if (o[s] !== undefined) {
  133. o[d] = o[s];
  134. delete o[s];
  135. }
  136. }
  137. r.percentComplete *= 100;
  138. $.each(r.phases, function (idx, p) {
  139. p.percentComplete *= 100;
  140. $.each(p.steps, function (idx2, s) {
  141. s.percentComplete *= 100;
  142. // dust.js is confused by these optional keys in nested
  143. // structure, rename them
  144. rename_property(s, "desc", "stepDesc");
  145. rename_property(s, "file", "stepFile");
  146. rename_property(s, "size", "stepSize");
  147. });
  148. });
  149. return r;
  150. }
  151. $.get('/startupProgress', function (resp) {
  152. var data = workaround(resp);
  153. dust.render('startup-progress', data, function(err, out) {
  154. $('#tab-startup-progress').html(out);
  155. $('#ui-tabs a[href="#tab-startup-progress"]').tab('show');
  156. });
  157. }).error(ajax_error_handler);
  158. }
  159. function load_datanode_info() {
  160. var HELPERS = {
  161. 'helper_relative_time' : function (chunk, ctx, bodies, params) {
  162. var value = dust.helpers.tap(params.value, chunk, ctx);
  163. return chunk.write(moment().subtract(Number(value), 'seconds').format('ddd MMM DD HH:mm:ss ZZ YYYY'));
  164. },
  165. 'helper_usage_bar' : function (chunk, ctx, bodies, params) {
  166. var value = dust.helpers.tap(params.value, chunk, ctx);
  167. var v = Number(value);
  168. var r = null;
  169. if (v < 70) {
  170. r = 'progress-bar-success';
  171. } else if (v < 85) {
  172. r = 'progress-bar-warning';
  173. } else {
  174. r = "progress-bar-danger";
  175. }
  176. return chunk.write(r);
  177. },
  178. };
  179. function workaround(r) {
  180. function node_map_to_array(nodes) {
  181. var res = [];
  182. for (var n in nodes) {
  183. var p = nodes[n];
  184. p.name = n;
  185. res.push(p);
  186. }
  187. return res;
  188. }
  189. function augment_live_nodes(nodes) {
  190. for (var i = 0, e = nodes.length; i < e; ++i) {
  191. var n = nodes[i];
  192. n.usedPercentage = Math.round((n.used + n.nonDfsUsedSpace) * 1.0 / n.capacity * 100);
  193. var port = n.infoAddr.split(":")[1];
  194. var securePort = n.infoSecureAddr.split(":")[1];
  195. var dnHost = n.name.split(":")[0];
  196. n.dnWebAddress = dnHost + ":" + port;
  197. if (securePort != 0) {
  198. n.dnWebAddress = dnHost + ":" + securePort;
  199. }
  200. if (n.adminState === "In Service") {
  201. n.state = "alive";
  202. } else if (nodes[i].adminState === "Decommission In Progress") {
  203. n.state = "decommissioning";
  204. } else if (nodes[i].adminState === "Decommissioned") {
  205. n.state = "decommissioned";
  206. } else if (nodes[i].adminState === "Entering Maintenance") {
  207. n.state = "entering-maintenance";
  208. } else if (nodes[i].adminState === "In Maintenance") {
  209. n.state = "in-maintenance";
  210. }
  211. }
  212. }
  213. function augment_dead_nodes(nodes) {
  214. for (var i = 0, e = nodes.length; i < e; ++i) {
  215. if (nodes[i].adminState === "Decommissioned") {
  216. nodes[i].state = "down-decommissioned";
  217. } else if (nodes[i].adminState === "In Maintenance") {
  218. nodes[i].state = "down-maintenance";
  219. } else {
  220. nodes[i].state = "down";
  221. }
  222. }
  223. }
  224. r.LiveNodes = node_map_to_array(JSON.parse(r.LiveNodes));
  225. augment_live_nodes(r.LiveNodes);
  226. r.DeadNodes = node_map_to_array(JSON.parse(r.DeadNodes));
  227. augment_dead_nodes(r.DeadNodes);
  228. r.DecomNodes = node_map_to_array(JSON.parse(r.DecomNodes));
  229. r.EnteringMaintenanceNodes = node_map_to_array(JSON.parse(r.EnteringMaintenanceNodes));
  230. return r;
  231. }
  232. function renderHistogram(dnData) {
  233. var data = dnData.LiveNodes.map(function(dn) {
  234. return (dn.usedSpace / dn.capacity) * 100.0;
  235. });
  236. var formatCount = d3.format(",.0f");
  237. var widthCap = $("div.container").width();
  238. var heightCap = 150;
  239. var margin = {top: 10, right: 60, bottom: 30, left: 30},
  240. width = widthCap * 0.9,
  241. height = heightCap - margin.top - margin.bottom;
  242. var x = d3.scaleLinear()
  243. .domain([0.0, 100.0])
  244. .range([0, width]);
  245. var bins = d3.histogram()
  246. .domain(x.domain())
  247. .thresholds(x.ticks(20))
  248. (data);
  249. var y = d3.scaleLinear()
  250. .domain([0, d3.max(bins, function(d) { return d.length; })])
  251. .range([height, 0]);
  252. var svg = d3.select("#datanode-usage-histogram").append("svg")
  253. .attr("width", width + 50.0)
  254. .attr("height", height + margin.top + margin.bottom)
  255. .append("g")
  256. .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
  257. svg.append("text")
  258. .attr("x", (width / 2))
  259. .attr("y", heightCap - 6 - (margin.top / 2))
  260. .attr("text-anchor", "middle")
  261. .style("font-size", "15px")
  262. .text("Disk usage of each DataNode (%)");
  263. var bar = svg.selectAll(".bar")
  264. .data(bins)
  265. .enter().append("g")
  266. .attr("class", "bar")
  267. .attr("transform", function(d) { return "translate(" + x(d.x0) + "," + y(d.length) + ")"; });
  268. bar.append("rect")
  269. .attr("x", 1)
  270. .attr("width", x(bins[0].x1) - x(bins[0].x0) - 1)
  271. .attr("height", function(d) { return height - y(d.length); });
  272. bar.append("text")
  273. .attr("dy", ".75em")
  274. .attr("y", 6)
  275. .attr("x", (x(bins[0].x1) - x(bins[0].x0)) / 2)
  276. .attr("text-anchor", "middle")
  277. .text(function(d) { return formatCount(d.length); });
  278. svg.append("g")
  279. .attr("class", "axis axis--x")
  280. .attr("transform", "translate(0," + height + ")")
  281. .call(d3.axisBottom(x));
  282. }
  283. $.get(
  284. '/jmx?qry=Hadoop:service=NameNode,name=NameNodeInfo',
  285. guard_with_startup_progress(function (resp) {
  286. var data = workaround(resp.beans[0]);
  287. var base = dust.makeBase(HELPERS);
  288. dust.render('datanode-info', base.push(data), function(err, out) {
  289. $('#tab-datanode').html(out);
  290. $('#table-datanodes').dataTable( {
  291. 'lengthMenu': [ [25, 50, 100, -1], [25, 50, 100, "All"] ],
  292. 'columns': [
  293. { 'orderDataType': 'ng-value', 'searchable': true },
  294. { 'orderDataType': 'ng-value', 'searchable': true },
  295. { 'orderDataType': 'ng-value', 'type': 'numeric' },
  296. { 'orderDataType': 'ng-value', 'type': 'numeric' },
  297. { 'orderData': 3, 'type': 'numeric' },
  298. { 'orderDataType': 'ng-value', 'type': 'numeric'},
  299. { 'orderData': 5 }
  300. ]});
  301. renderHistogram(data);
  302. $('#ui-tabs a[href="#tab-datanode"]').tab('show');
  303. });
  304. })).error(ajax_error_handler);
  305. }
  306. function load_datanode_volume_failures() {
  307. var HELPERS = {
  308. 'helper_date_tostring' : function (chunk, ctx, bodies, params) {
  309. var value = dust.helpers.tap(params.value, chunk, ctx);
  310. return chunk.write('' + moment(Number(value)).format('ddd MMM DD HH:mm:ss ZZ YYYY'));
  311. }
  312. };
  313. function workaround(r) {
  314. function node_map_to_array(nodes) {
  315. var res = [];
  316. for (var n in nodes) {
  317. var p = nodes[n];
  318. // Filter the display to only datanodes with volume failures.
  319. if (p.volfails > 0) {
  320. p.name = n;
  321. res.push(p);
  322. }
  323. }
  324. return res;
  325. }
  326. r.LiveNodes = node_map_to_array(JSON.parse(r.LiveNodes));
  327. return r;
  328. }
  329. $.get(
  330. '/jmx?qry=Hadoop:service=NameNode,name=NameNodeInfo',
  331. guard_with_startup_progress(function (resp) {
  332. var data = workaround(resp.beans[0]);
  333. var base = dust.makeBase(HELPERS);
  334. dust.render('datanode-volume-failures', base.push(data), function(err, out) {
  335. $('#tab-datanode-volume-failures').html(out);
  336. $('#ui-tabs a[href="#tab-datanode-volume-failures"]').tab('show');
  337. });
  338. })).error(ajax_error_handler);
  339. }
  340. function load_snapshot_info() {
  341. $.get(
  342. '/jmx?qry=Hadoop:service=NameNode,name=SnapshotInfo',
  343. guard_with_startup_progress(function (resp) {
  344. dust.render('snapshot-info', resp.beans[0], function(err, out) {
  345. $('#tab-snapshot').html(out);
  346. $('#ui-tabs a[href="#tab-snapshot"]').tab('show');
  347. });
  348. })).error(ajax_error_handler);
  349. }
  350. function load_page() {
  351. var hash = window.location.hash;
  352. switch(hash) {
  353. case "#tab-datanode":
  354. load_datanode_info();
  355. break;
  356. case "#tab-datanode-volume-failures":
  357. load_datanode_volume_failures();
  358. break;
  359. case "#tab-snapshot":
  360. load_snapshot_info();
  361. break;
  362. case "#tab-startup-progress":
  363. load_startup_progress();
  364. break;
  365. case "#tab-overview":
  366. load_overview();
  367. break;
  368. default:
  369. window.location.hash = "tab-overview";
  370. break;
  371. }
  372. }
  373. load_page();
  374. $(window).bind('hashchange', function () {
  375. load_page();
  376. });
  377. })();