graph_widget_view.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  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.GraphWidgetView = Em.View.extend(App.WidgetMixin, {
  20. templateName: require('templates/common/widget/graph_widget'),
  21. /**
  22. * common metrics container
  23. * @type {Array}
  24. */
  25. metrics: [],
  26. /**
  27. * 3600 sec in 1 hour
  28. * @const
  29. */
  30. TIME_FACTOR: 3600,
  31. /**
  32. * custom time range, set when graph opened in popup
  33. * @type {number|null}
  34. */
  35. customTimeRange: null,
  36. /**
  37. * value in seconds
  38. * @type {number}
  39. */
  40. timeRange: function () {
  41. var timeRange = parseInt(this.get('content.properties.time_range'));
  42. if (isNaN(timeRange)) {
  43. //1h - default time range
  44. timeRange = 1;
  45. }
  46. return this.get('customTimeRange') || timeRange * this.get('TIME_FACTOR');
  47. }.property('content.properties.time_range', 'customTimeRange'),
  48. /**
  49. * value in ms
  50. * @type {number}
  51. */
  52. timeStep: 15,
  53. /**
  54. * @type {Array}
  55. */
  56. data: [],
  57. drawWidget: function () {
  58. if (this.get('isLoaded')) {
  59. this.set('data', this.calculateValues());
  60. }
  61. },
  62. /**
  63. * calculate series datasets for graph widgets
  64. */
  65. calculateValues: function () {
  66. var metrics = this.get('metrics');
  67. var seriesData = [];
  68. if (this.get('content.values')) {
  69. this.get('content.values').forEach(function (value) {
  70. var expression = this.extractExpressions(value)[0];
  71. var computedData;
  72. var datasetKey = value.value.match(this.get('EXPRESSION_REGEX'))[0];
  73. if (expression) {
  74. computedData = this.computeExpression(expression, metrics)[datasetKey];
  75. //exclude empty datasets
  76. if (computedData.length > 0) {
  77. seriesData.push({
  78. name: value.name,
  79. data: computedData
  80. });
  81. }
  82. }
  83. }, this);
  84. }
  85. return seriesData;
  86. },
  87. /**
  88. * compute expression
  89. *
  90. * @param {string} expression
  91. * @param {object} metrics
  92. * @returns {object}
  93. */
  94. computeExpression: function (expression, metrics) {
  95. var validExpression = true,
  96. value = [],
  97. dataLinks = {},
  98. dataLength = -1,
  99. beforeCompute,
  100. result = {},
  101. isDataCorrupted = false,
  102. isPointNull = false;
  103. //replace values with metrics data
  104. expression.match(this.get('VALUE_NAME_REGEX')).forEach(function (match) {
  105. if (isNaN(match)) {
  106. if (metrics.someProperty('name', match)) {
  107. dataLinks[match] = metrics.findProperty('name', match).data;
  108. if (!isDataCorrupted) {
  109. isDataCorrupted = (dataLength !== -1 && dataLength !== dataLinks[match].length);
  110. }
  111. dataLength = (dataLinks[match].length > dataLength) ? dataLinks[match].length : dataLength;
  112. } else {
  113. validExpression = false;
  114. console.error('Metrics with name "' + match + '" not found to compute expression');
  115. }
  116. }
  117. });
  118. if (validExpression) {
  119. if (isDataCorrupted) {
  120. this.adjustData(dataLinks, dataLength);
  121. }
  122. for (var i = 0, timestamp; i < dataLength; i++) {
  123. isPointNull = false;
  124. beforeCompute = expression.replace(this.get('VALUE_NAME_REGEX'), function (match) {
  125. if (isNaN(match)) {
  126. timestamp = dataLinks[match][i][1];
  127. isPointNull = (isPointNull) ? true : (Em.isNone(dataLinks[match][i][0]));
  128. return dataLinks[match][i][0];
  129. } else {
  130. return match;
  131. }
  132. });
  133. var dataLinkPointValue = isPointNull ? null : Number(window.eval(beforeCompute));
  134. // expression resulting into `0/0` will produce NaN Object which is not a valid series data value for RickShaw graphs
  135. if (isNaN(dataLinkPointValue)) {
  136. dataLinkPointValue = 0;
  137. }
  138. value.push([dataLinkPointValue, timestamp]);
  139. }
  140. }
  141. result['${' + expression + '}'] = value;
  142. return result;
  143. },
  144. /**
  145. * add missing points, with zero value, to series
  146. *
  147. * @param {object} dataLinks
  148. * @param {number} length
  149. */
  150. adjustData: function(dataLinks, length) {
  151. //series with full data taken as original
  152. var original = [];
  153. var substituteValue = null;
  154. for (var i in dataLinks) {
  155. if (dataLinks[i].length === length) {
  156. original = dataLinks[i];
  157. break;
  158. }
  159. }
  160. original.forEach(function(point, index) {
  161. for (var i in dataLinks) {
  162. if (!dataLinks[i][index] || dataLinks[i][index][1] !== point[1]) {
  163. dataLinks[i].splice(index, 0, [substituteValue, point[1]]);
  164. }
  165. }
  166. }, this);
  167. },
  168. /**
  169. * make GET call to server in order to fetch service-component metrics
  170. * @param {object} request
  171. * @returns {$.ajax}
  172. */
  173. getServiceComponentMetrics: function (request) {
  174. return App.ajax.send({
  175. name: 'widgets.serviceComponent.metrics.get',
  176. sender: this,
  177. data: {
  178. serviceName: request.service_name,
  179. componentName: request.component_name,
  180. metricPaths: this.addTimeProperties(request.metric_paths).join(',')
  181. },
  182. success: 'getMetricsSuccessCallback'
  183. });
  184. },
  185. /**
  186. * make GET call to server in order to fetch host-component metrics
  187. * @param {object} request
  188. * @returns {$.ajax}
  189. */
  190. getHostComponentMetrics: function (request) {
  191. var dfd;
  192. var self = this;
  193. dfd = $.Deferred();
  194. this.getHostComponentName(request).done(function (data) {
  195. if (data) {
  196. request.host_name = data.host_components[0].HostRoles.host_name;
  197. App.ajax.send({
  198. name: 'widgets.hostComponent.metrics.get',
  199. sender: self,
  200. data: {
  201. componentName: request.component_name,
  202. hostName: request.host_name,
  203. metricPaths: self.addTimeProperties(request.metric_paths).join(',')
  204. }
  205. }).done(function(metricData) {
  206. self.getMetricsSuccessCallback(metricData);
  207. dfd.resolve();
  208. }).fail(function(data){
  209. dfd.reject();
  210. });
  211. }
  212. }).fail(function(data){
  213. dfd.reject();
  214. });
  215. return dfd.promise();
  216. },
  217. /**
  218. * add time properties
  219. * @param {Array} metricPaths
  220. * @returns {Array} result
  221. */
  222. addTimeProperties: function (metricPaths) {
  223. var toSeconds = Math.round(App.dateTime() / 1000);
  224. var fromSeconds = toSeconds - this.get('timeRange');
  225. var step = this.get('timeStep');
  226. var result = [];
  227. metricPaths.forEach(function (metricPath) {
  228. result.push(metricPath + '[' + fromSeconds + ',' + toSeconds + ',' + step + ']');
  229. }, this);
  230. return result;
  231. },
  232. /**
  233. * @type {Em.View}
  234. * @class
  235. */
  236. graphView: App.ChartLinearTimeView.extend({
  237. noTitleUnderGraph: true,
  238. inWidget: true,
  239. description: function () {
  240. return this.get('parentView.content.description');
  241. }.property('parentView.content.description'),
  242. /**
  243. * set custom time range for graph widget
  244. */
  245. setTimeRange: function () {
  246. if (this.get('isPopup')) {
  247. this.set('parentView.customTimeRange', this.get('timeUnitSeconds'));
  248. } else {
  249. this.set('parentView.customTimeRange', null);
  250. }
  251. }.observes('isPopup', 'timeUnitSeconds'),
  252. /**
  253. * graph height
  254. * @type {number}
  255. */
  256. height: 95,
  257. /**
  258. * @type {string}
  259. */
  260. id: function () {
  261. return 'widget_'+ this.get('parentView.content.id') + '_graph';
  262. }.property('parentView.content.id'),
  263. /**
  264. * @type {string}
  265. */
  266. renderer: function () {
  267. return this.get('parentView.content.properties.graph_type') === 'STACK' ? 'area' : 'line';
  268. }.property('parentView.content.properties.graph_type'),
  269. title: function () {
  270. return this.get('parentView.content.widgetName');
  271. }.property('parentView.content.widgetName'),
  272. transformToSeries: function (seriesData) {
  273. var seriesArray = [];
  274. seriesData.forEach(function (_series) {
  275. seriesArray.push(this.transformData(_series.data, _series.name));
  276. }, this);
  277. return seriesArray;
  278. },
  279. loadData: function () {
  280. var self = this;
  281. Em.run.next(function () {
  282. self._refreshGraph(self.get('parentView.data'))
  283. });
  284. },
  285. didInsertElement: function () {
  286. this.loadData();
  287. }.observes('parentView.data')
  288. })
  289. });