widget_mixin.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  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. var App = require('app');
  19. App.WidgetMixin = Ember.Mixin.create({
  20. /**
  21. * @type {RegExp}
  22. * @const
  23. */
  24. EXPRESSION_REGEX: /\$\{([\w\s\.\,\+\-\*\/\(\)\:\=\[\]]*)\}/g,
  25. /**
  26. * @type {RegExp}
  27. * @const
  28. */
  29. MATH_EXPRESSION_REGEX: /^[\d\s\+\-\*\/\(\)\.]+$/,
  30. /**
  31. * @type {RegExp}
  32. * @const
  33. */
  34. VALUE_NAME_REGEX: /[\w\.\,\:\=\[\]]+/g,
  35. /**
  36. * @type {string}
  37. * @const
  38. */
  39. CLONE_SUFFIX: '(Copy)',
  40. /**
  41. * common metrics container
  42. * @type {Array}
  43. */
  44. metrics: [],
  45. /**
  46. *
  47. */
  48. aggregatorFunc: ['._sum', '._avg', '._min', '._max'],
  49. /**
  50. * @type {boolean}
  51. */
  52. isLoaded: false,
  53. /**
  54. * @type {App.Widget}
  55. * @default null
  56. */
  57. content: null,
  58. beforeRender: function () {
  59. this.get('metrics').clear();
  60. this.loadMetrics();
  61. },
  62. /**
  63. * load metrics
  64. */
  65. loadMetrics: function () {
  66. var requestData = this.getRequestData(this.get('content.metrics')),
  67. request,
  68. requestCounter = 0,
  69. self = this;
  70. for (var i in requestData) {
  71. request = requestData[i];
  72. requestCounter++;
  73. if (this.get('content.widgetType') === 'HEATMAP') {
  74. if (request.service_name === 'STACK') {
  75. this.getHostsMetrics(request).complete(function () {
  76. requestCounter--;
  77. if (requestCounter === 0) self.onMetricsLoaded();
  78. });
  79. } else {
  80. this.getHostComponentsMetrics(request).complete(function () {
  81. requestCounter--;
  82. if (requestCounter === 0) self.onMetricsLoaded();
  83. });
  84. }
  85. } else if (request.host_component_criteria) {
  86. this.getHostComponentMetrics(request).always(function () {
  87. requestCounter--;
  88. if (requestCounter === 0) self.onMetricsLoaded();
  89. });
  90. } else {
  91. this.getServiceComponentMetrics(request).complete(function () {
  92. requestCounter--;
  93. if (requestCounter === 0) self.onMetricsLoaded();
  94. });
  95. }
  96. }
  97. },
  98. /**
  99. * get data formatted for request
  100. * @param {Array} metrics
  101. */
  102. getRequestData: function (metrics) {
  103. var requestsData = {};
  104. if (metrics) {
  105. metrics.forEach(function (metric, index) {
  106. var key;
  107. if (metric.host_component_criteria) {
  108. this.tweakHostComponentCriteria(metric);
  109. key = metric.service_name + '_' + metric.component_name + '_' + metric.host_component_criteria;
  110. } else {
  111. key = metric.service_name + '_' + metric.component_name;
  112. }
  113. var requestMetric = $.extend({}, metric);
  114. if (requestsData[key]) {
  115. requestsData[key]["metric_paths"].push(requestMetric["metric_path"]);
  116. } else {
  117. requestMetric["metric_paths"] = [requestMetric["metric_path"]];
  118. delete requestMetric["metric_path"];
  119. requestsData[key] = requestMetric;
  120. }
  121. }, this);
  122. }
  123. return requestsData;
  124. },
  125. /**
  126. * Tweak necessary host component criteria
  127. * NameNode HA host component criteria is applicable only in HA mode
  128. */
  129. tweakHostComponentCriteria: function (metric) {
  130. switch (metric.component_name) {
  131. case 'NAMENODE':
  132. if (metric.host_component_criteria === 'host_components/metrics/dfs/FSNamesystem/HAState=active') {
  133. //if (metric.host_component_criteria)
  134. var hdfs = App.HDFSService.find().objectAt(0);
  135. var activeNNHostName = !hdfs.get('snameNode') && hdfs.get('activeNameNode');
  136. if (!activeNNHostName) {
  137. metric.host_component_criteria = 'host_components/HostRoles/component_name=NAMENODE';
  138. }
  139. }
  140. break;
  141. }
  142. },
  143. /**
  144. * make GET call to server in order to fetch service-component metrics
  145. * @param {object} request
  146. * @returns {$.ajax}
  147. */
  148. getServiceComponentMetrics: function (request) {
  149. return App.ajax.send({
  150. name: 'widgets.serviceComponent.metrics.get',
  151. sender: this,
  152. data: {
  153. serviceName: request.service_name,
  154. componentName: request.component_name,
  155. metricPaths: request.metric_paths.join(',')
  156. },
  157. success: 'getMetricsSuccessCallback'
  158. });
  159. },
  160. /**
  161. * make GET call to server in order to fetch specifc host-component metrics
  162. * @param {object} request
  163. * @returns {$.Deferred}
  164. */
  165. getHostComponentMetrics: function (request) {
  166. var dfd;
  167. var self = this;
  168. dfd = $.Deferred();
  169. this.getHostComponentName(request).done(function (data) {
  170. if (data) {
  171. request.host_name = data.host_components[0].HostRoles.host_name;
  172. App.ajax.send({
  173. name: 'widgets.hostComponent.metrics.get',
  174. sender: self,
  175. data: {
  176. componentName: request.component_name,
  177. hostName: request.host_name,
  178. metricPaths: request.metric_paths.join(',')
  179. }
  180. }).done(function (metricData) {
  181. self.getMetricsSuccessCallback(metricData);
  182. dfd.resolve();
  183. }).fail(function (data) {
  184. dfd.reject();
  185. });
  186. }
  187. }).fail(function (data) {
  188. dfd.reject();
  189. });
  190. return dfd.promise();
  191. },
  192. /**
  193. * make GET call to server in order to fetch host-component names
  194. * @param {object} request
  195. * @returns {$.ajax}
  196. */
  197. getHostComponentName: function (request) {
  198. return App.ajax.send({
  199. name: 'widgets.hostComponent.get.hostName',
  200. sender: this,
  201. data: {
  202. serviceName: request.service_name,
  203. componentName: request.component_name,
  204. metricPaths: request.metric_paths.join(','),
  205. hostComponentCriteria: request.host_component_criteria
  206. }
  207. });
  208. },
  209. /**
  210. * callback on getting aggregated metrics and host component metrics
  211. * @param data
  212. */
  213. getMetricsSuccessCallback: function (data) {
  214. var metrics = [];
  215. if (this.get('content.metrics')) {
  216. this.get('content.metrics').forEach(function (_metric) {
  217. var metric_path;
  218. this.aggregatorFunc.forEach(function (_func) {
  219. if (_metric.metric_path.endsWith(_func)) {
  220. // truncate aggregator function suffixed at the end of the metric
  221. metric_path = _metric.metric_path.substring(0, _metric.metric_path.indexOf(_func));
  222. }
  223. }, this);
  224. if (!metric_path) {
  225. metric_path = _metric.metric_path;
  226. }
  227. if (!Em.isNone(Em.get(data, metric_path.replace(/\//g, '.')))) {
  228. _metric.data = Em.get(data, metric_path.replace(/\//g, '.'));
  229. this.get('metrics').pushObject(_metric);
  230. }
  231. }, this);
  232. }
  233. },
  234. /**
  235. * make GET call to get host component metrics accross
  236. * @param {object} request
  237. * @return {$.ajax}
  238. */
  239. getHostComponentsMetrics: function (request) {
  240. request.metric_paths.forEach(function (_metric, index) {
  241. request.metric_paths[index] = "host_components/" + _metric;
  242. });
  243. return App.ajax.send({
  244. name: 'widgets.serviceComponent.metrics.get',
  245. sender: this,
  246. data: {
  247. serviceName: request.service_name,
  248. componentName: request.component_name,
  249. metricPaths: request.metric_paths.join(',')
  250. },
  251. success: 'getHostComponentsMetricsSuccessCallback'
  252. });
  253. },
  254. getHostComponentsMetricsSuccessCallback: function (data) {
  255. var metrics = this.get('content.metrics');
  256. data.host_components.forEach(function (item) {
  257. metrics.forEach(function (_metric) {
  258. if (!Em.isNone(Em.get(item, _metric.metric_path.replace(/\//g, '.')))) {
  259. var metric = $.extend({}, _metric, true);
  260. metric.data = Em.get(item, _metric.metric_path.replace(/\//g, '.'));
  261. metric.hostName = item.HostRoles.host_name;
  262. this.get('metrics').pushObject(metric);
  263. }
  264. }, this);
  265. }, this);
  266. },
  267. getHostsMetrics: function (request) {
  268. return App.ajax.send({
  269. name: 'widgets.hosts.metrics.get',
  270. sender: this,
  271. data: {
  272. metricPaths: request.metric_paths.join(',')
  273. },
  274. success: 'getHostsMetricsSuccessCallback'
  275. });
  276. },
  277. getHostsMetricsSuccessCallback: function (data) {
  278. var metrics = this.get('content.metrics');
  279. data.items.forEach(function (item) {
  280. metrics.forEach(function (_metric, index) {
  281. if (!Em.isNone(Em.get(item, _metric.metric_path.replace(/\//g, '.')))) {
  282. var metric = $.extend({}, _metric, true);
  283. metric.data = Em.get(item, _metric.metric_path.replace(/\//g, '.'));
  284. metric.hostName = item.Hosts.host_name;
  285. this.get('metrics').pushObject(metric);
  286. }
  287. }, this);
  288. }, this);
  289. },
  290. /**
  291. * callback on metrics loaded
  292. */
  293. onMetricsLoaded: function () {
  294. var self = this;
  295. this.set('isLoaded', true);
  296. this.drawWidget();
  297. setTimeout(function () {
  298. self.loadMetrics();
  299. }, App.contentUpdateInterval);
  300. },
  301. /**
  302. * draw widget
  303. */
  304. drawWidget: function () {
  305. if (this.get('isLoaded')) {
  306. this.calculateValues();
  307. this.set('value', this.get('content.values')[0] && this.get('content.values')[0].computedValue);
  308. }
  309. },
  310. /**
  311. * calculate series datasets for graph widgets
  312. */
  313. calculateValues: function () {
  314. var metrics = this.get('metrics');
  315. var displayUnit = this.get('content.properties.display_unit');
  316. this.get('content.values').forEach(function (value) {
  317. var computeExpression = this.computeExpression(this.extractExpressions(value), metrics);
  318. value.computedValue = value.value.replace(this.get('EXPRESSION_REGEX'), function (match) {
  319. return (!Em.isNone(computeExpression[match])) ? computeExpression[match] + (displayUnit || "") : Em.I18n.t('common.na');
  320. });
  321. }, this);
  322. },
  323. /**
  324. * extract expressions
  325. * Example:
  326. * input: "${a/b} equal ${b+a}"
  327. * expressions: ['a/b', 'b+a']
  328. *
  329. * @param {object} input
  330. * @returns {Array}
  331. */
  332. extractExpressions: function (input) {
  333. var pattern = this.get('EXPRESSION_REGEX'),
  334. expressions = [],
  335. match;
  336. while (match = pattern.exec(input.value)) {
  337. expressions.push(match[1]);
  338. }
  339. return expressions;
  340. },
  341. /**
  342. * compute expression
  343. * @param expressions
  344. * @param metrics
  345. * @returns {object}
  346. */
  347. computeExpression: function (expressions, metrics) {
  348. var result = {};
  349. expressions.forEach(function (_expression) {
  350. var validExpression = true;
  351. var value = "";
  352. //replace values with metrics data
  353. var beforeCompute = _expression.replace(this.get('VALUE_NAME_REGEX'), function (match) {
  354. if (window.isNaN(match)) {
  355. if (metrics.someProperty('name', match)) {
  356. return metrics.findProperty('name', match).data;
  357. } else {
  358. validExpression = false;
  359. console.error('Metrics with name "' + match + '" not found to compute expression');
  360. }
  361. } else {
  362. return match;
  363. }
  364. });
  365. //check for correct math expression
  366. if (!(validExpression && this.get('MATH_EXPRESSION_REGEX').test(beforeCompute))) {
  367. validExpression = false;
  368. console.error('Value for metric is not correct mathematical expression: ' + beforeCompute);
  369. }
  370. result['${' + _expression + '}'] = (validExpression) ? Number(window.eval(beforeCompute)).toString() : value;
  371. }, this);
  372. return result;
  373. },
  374. /*
  375. * make call when clicking on "remove icon" on widget
  376. */
  377. hideWidget: function (event) {
  378. this.get('controller').hideWidget(
  379. {
  380. context: Em.Object.create({
  381. id: event.context
  382. })
  383. }
  384. );
  385. },
  386. /*
  387. * make call when clicking on "clone icon" on widget
  388. */
  389. cloneWidget: function (event) {
  390. var self = this;
  391. return App.showConfirmationPopup(
  392. function () {
  393. self.postWidgetDefinition(true);
  394. },
  395. Em.I18n.t('widget.clone.body').format(self.get('content.widgetName')),
  396. null,
  397. null,
  398. Em.I18n.t('common.clone')
  399. );
  400. },
  401. /**
  402. * collect all needed data to create new widget
  403. * @returns {{WidgetInfo: {widget_name: *, display_name: *, widget_type: *, description: *, scope: *, metrics: *, values: *, properties: *}}}
  404. */
  405. collectWidgetData: function () {
  406. return {
  407. WidgetInfo: {
  408. widget_name: this.get('content.widgetName'),
  409. widget_type: this.get('content.widgetType'),
  410. description: this.get('content.widgetDescription'),
  411. scope: this.get('content.scope'),
  412. "metrics": this.get('content.metrics').map(function (metric) {
  413. return {
  414. "name": metric.name,
  415. "service_name": metric.serviceName,
  416. "component_name": metric.componentName,
  417. "metric_path": metric.metric_path,
  418. "category": metric.category
  419. }
  420. }),
  421. values: this.get('content.values'),
  422. properties: this.get('content.properties')
  423. }
  424. };
  425. },
  426. /**
  427. * post widget definition to server
  428. * @param {boolean} isClone
  429. * @returns {$.ajax}
  430. */
  431. postWidgetDefinition: function (isClone) {
  432. var data = this.collectWidgetData();
  433. if (isClone) {
  434. data.WidgetInfo.widget_name += this.get('CLONE_SUFFIX');
  435. }
  436. return App.ajax.send({
  437. name: 'widgets.wizard.add',
  438. sender: this,
  439. data: {
  440. data: data
  441. },
  442. success: 'postWidgetDefinitionSuccessCallback'
  443. });
  444. },
  445. postWidgetDefinitionSuccessCallback: function (data) {
  446. var widgets = this.get('content.layout.widgets').toArray();
  447. widgets.pushObject(Em.Object.create({
  448. id: data.resources[0].WidgetInfo.id
  449. }));
  450. App.router.get('mainServiceInfoSummaryController').saveWidgetLayout(widgets);
  451. },
  452. /*
  453. * make call when clicking on "edit icon" on widget
  454. */
  455. editWidget: function (event) {
  456. this.get('controller').editWidget(this.get('content'));
  457. }
  458. });
  459. App.WidgetPreviewMixin = Ember.Mixin.create({
  460. beforeRender: Em.K,
  461. isLoaded: true,
  462. metrics: [],
  463. content: Em.Object.create({
  464. id: 1,
  465. values: []
  466. }),
  467. drawWidget: function () {
  468. this.get('content').setProperties({
  469. 'values': this.get('controller.widgetValues'),
  470. 'properties': this.get('controller.widgetProperties'),
  471. 'widgetName': this.get('controller.widgetName'),
  472. 'metrics': this.get('controller.widgetMetrics').map(function (metric) {
  473. return {
  474. "name": metric.name,
  475. "service_name": metric.serviceName,
  476. "component_name": metric.componentName,
  477. "metric_path": metric.metricPath,
  478. "host_component_criteria": metric.hostComponentCriteria,
  479. "category": metric.category
  480. }
  481. })
  482. });
  483. this.loadMetrics();
  484. this._super();
  485. }.observes('controller.widgetProperties', 'controller.widgetValues', 'controller.widgetMetrics', 'controller.widgetName'),
  486. onMetricsLoaded: Em.K
  487. });