datasource.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. define([
  2. 'angular',
  3. 'lodash',
  4. 'kbn',
  5. './queryCtrl',
  6. ],
  7. function (angular, _, kbn) {
  8. 'use strict';
  9. var module = angular.module('grafana.services');
  10. var tagList = null;
  11. module.factory('KairosDBDatasource', function($q, $http) {
  12. function KairosDBDatasource(datasource) {
  13. this.type = datasource.type;
  14. this.editorSrc = 'plugins/datasources/kairosdb/kairosdb.editor.html';
  15. this.url = datasource.url;
  16. this.name = datasource.name;
  17. this.supportMetrics = true;
  18. this.grafanaDB = datasource.grafanaDB;
  19. }
  20. // Called once per panel (graph)
  21. KairosDBDatasource.prototype.query = function(options) {
  22. var start = options.range.from;
  23. var end = options.range.to;
  24. var queries = _.compact(_.map(options.targets, _.partial(convertTargetToQuery, options)));
  25. var plotParams = _.compact(_.map(options.targets, function(target) {
  26. var alias = target.alias;
  27. if (typeof target.alias == 'undefined' || target.alias == "")
  28. alias = target.metric;
  29. return !target.hide
  30. ? {alias: alias,
  31. exouter: target.exOuter}
  32. : null;
  33. }));
  34. var handleKairosDBQueryResponseAlias = _.partial(handleKairosDBQueryResponse, plotParams);
  35. // No valid targets, return the empty result to save a round trip.
  36. if (_.isEmpty(queries)) {
  37. var d = $q.defer();
  38. d.resolve({ data: [] });
  39. return d.promise;
  40. }
  41. return this.performTimeSeriesQuery(queries, start, end).then(handleKairosDBQueryResponseAlias,handleQueryError);
  42. };
  43. ///////////////////////////////////////////////////////////////////////
  44. /// Query methods
  45. ///////////////////////////////////////////////////////////////////////
  46. KairosDBDatasource.prototype.performTimeSeriesQuery = function(queries, start, end) {
  47. var reqBody = {
  48. metrics: queries
  49. };
  50. reqBody.cache_time=0;
  51. convertToKairosTime(start,reqBody,'start');
  52. convertToKairosTime(end,reqBody,'end');
  53. var options = {
  54. method: 'POST',
  55. url: '/api/v1/datapoints/query',
  56. data: reqBody
  57. };
  58. options.url = this.url + options.url;
  59. return $http(options);
  60. };
  61. /**
  62. * Gets the list of metrics
  63. * @returns {*|Promise}
  64. */
  65. KairosDBDatasource.prototype.performMetricSuggestQuery = function() {
  66. var options = {
  67. url : this.url + '/api/v1/metricnames',
  68. method : 'GET'
  69. };
  70. return $http(options).then(function(results) {
  71. if (!results.data) {
  72. return [];
  73. }
  74. return results.data.results;
  75. });
  76. };
  77. KairosDBDatasource.prototype.performTagSuggestQuery = function(metricname,range,type,keyValue) {
  78. if (tagList && (metricname === tagList.metricName) && (range.from === tagList.range.from) &&
  79. (range.to === tagList.range.to)) {
  80. return getTagListFromResponse(tagList.results,type,keyValue);
  81. }
  82. tagList = {
  83. metricName:metricname,
  84. range:range
  85. };
  86. var body = {
  87. metrics : [{name : metricname}]
  88. };
  89. convertToKairosTime(range.from,body,'start');
  90. convertToKairosTime(range.to,body,'end');
  91. var options = {
  92. url : this.url + '/api/v1/datapoints/query/tags',
  93. method : 'POST',
  94. data : body
  95. };
  96. return $http(options).then(function(results) {
  97. tagList.results = results;
  98. return getTagListFromResponse(results,type,keyValue);
  99. });
  100. };
  101. /////////////////////////////////////////////////////////////////////////
  102. /// Formatting methods
  103. ////////////////////////////////////////////////////////////////////////
  104. function getTagListFromResponse(results,type,keyValue) {
  105. if (!results.data) {
  106. return [];
  107. }
  108. if (type==="key") {
  109. return _.keys(results.data.queries[0].results[0].tags);
  110. }
  111. else if (type==="value" && _.has(results.data.queries[0].results[0].tags,keyValue)) {
  112. return results.data.queries[0].results[0].tags[keyValue];
  113. }
  114. return [];
  115. }
  116. /**
  117. * Requires a verion of KairosDB with every CORS defects fixed
  118. * @param results
  119. * @returns {*}
  120. */
  121. function handleQueryError(results) {
  122. if (results.data.errors && !_.isEmpty(results.data.errors)) {
  123. var errors = {
  124. message: results.data.errors[0]
  125. };
  126. return $q.reject(errors);
  127. }
  128. else {
  129. return $q.reject(results);
  130. }
  131. }
  132. function handleKairosDBQueryResponse(plotParams, results) {
  133. var output = [];
  134. var index = 0;
  135. _.each(results.data.queries, function (series) {
  136. var sample_size = series.sample_size;
  137. console.log("sample_size:" + sample_size + " samples");
  138. _.each(series.results, function (result) {
  139. //var target = result.name;
  140. var target = plotParams[index].alias;
  141. var details = " ( ";
  142. _.each(result.group_by,function(element) {
  143. if (element.name==="tag") {
  144. _.each(element.group,function(value, key) {
  145. details+= key+"="+value+" ";
  146. });
  147. }
  148. else if (element.name==="value") {
  149. details+= 'value_group='+element.group.group_number+" ";
  150. }
  151. else if (element.name==="time") {
  152. details+= 'time_group='+element.group.group_number+" ";
  153. }
  154. });
  155. details+= ") ";
  156. if (details != " ( ) ")
  157. target += details;
  158. var datapoints = [];
  159. for (var i = 0; i < result.values.length; i++) {
  160. var t = Math.floor(result.values[i][0]);
  161. var v = result.values[i][1];
  162. datapoints[i] = [v, t];
  163. }
  164. if (plotParams[index].exouter)
  165. datapoints = PeakFilter(datapoints, 10);
  166. output.push({ target: target, datapoints: datapoints });
  167. });
  168. index ++;
  169. });
  170. var output2 = { data: _.flatten(output) };
  171. return output2;
  172. }
  173. function convertTargetToQuery(options,target) {
  174. if (!target.metric || target.hide) {
  175. return null;
  176. }
  177. var query = {
  178. name: target.metric
  179. };
  180. query.aggregators = [];
  181. if (target.downsampling!=='(NONE)') {
  182. query.aggregators.push({
  183. name: target.downsampling,
  184. align_sampling: true,
  185. align_start_time: true,
  186. sampling: KairosDBDatasource.prototype.convertToKairosInterval(target.sampling || options.interval)
  187. });
  188. }
  189. if (target.horizontalAggregators) {
  190. _.each(target.horizontalAggregators,function(chosenAggregator) {
  191. var returnedAggregator = {
  192. name:chosenAggregator.name
  193. };
  194. if (chosenAggregator.sampling_rate) {
  195. returnedAggregator.sampling = KairosDBDatasource.prototype.convertToKairosInterval(chosenAggregator.sampling_rate);
  196. returnedAggregator.align_sampling = true;
  197. returnedAggregator.align_start_time =true;
  198. }
  199. if (chosenAggregator.unit) {
  200. returnedAggregator.unit = chosenAggregator.unit+'s';
  201. }
  202. if (chosenAggregator.factor && chosenAggregator.name==='div') {
  203. returnedAggregator.divisor = chosenAggregator.factor;
  204. }
  205. else if (chosenAggregator.factor && chosenAggregator.name==='scale') {
  206. returnedAggregator.factor = chosenAggregator.factor;
  207. }
  208. if (chosenAggregator.percentile) {
  209. returnedAggregator.percentile = chosenAggregator.percentile;
  210. }
  211. query.aggregators.push(returnedAggregator);
  212. });
  213. }
  214. if (_.isEmpty(query.aggregators)) {
  215. delete query.aggregators;
  216. }
  217. if (target.tags) {
  218. query.tags = angular.copy(target.tags);
  219. }
  220. if (target.groupByTags || target.nonTagGroupBys) {
  221. query.group_by = [];
  222. if (target.groupByTags) {query.group_by.push({name: "tag", tags: angular.copy(target.groupByTags)});}
  223. if (target.nonTagGroupBys) {
  224. _.each(target.nonTagGroupBys,function(rawGroupBy) {
  225. var formattedGroupBy = angular.copy(rawGroupBy);
  226. if (formattedGroupBy.name==='time') {
  227. formattedGroupBy.range_size=KairosDBDatasource.prototype.convertToKairosInterval(formattedGroupBy.range_size);
  228. }
  229. query.group_by.push(formattedGroupBy);
  230. });
  231. }
  232. }
  233. return query;
  234. }
  235. ///////////////////////////////////////////////////////////////////////
  236. /// Time conversion functions specifics to KairosDB
  237. //////////////////////////////////////////////////////////////////////
  238. KairosDBDatasource.prototype.convertToKairosInterval = function(intervalString) {
  239. var interval_regex = /(\d+(?:\.\d+)?)([Mwdhmsy])/;
  240. var interval_regex_ms = /(\d+(?:\.\d+)?)(ms)/;
  241. var matches = intervalString.match(interval_regex_ms);
  242. if (!matches) {
  243. matches = intervalString.match(interval_regex);
  244. }
  245. if (!matches) {
  246. throw new Error('Invalid interval string, expecting a number followed by one of "y M w d h m s ms"');
  247. }
  248. var value = matches[1];
  249. var unit = matches[2];
  250. if (value%1!==0) {
  251. if (unit==='ms') {throw new Error('Invalid interval value, cannot be smaller than the millisecond');}
  252. value = Math.round(kbn.intervals_in_seconds[unit]*value*1000);
  253. unit = 'ms';
  254. }
  255. switch (unit) {
  256. case 'ms':
  257. unit = 'milliseconds';
  258. break;
  259. case 's':
  260. unit = 'seconds';
  261. break;
  262. case 'm':
  263. unit = 'minutes';
  264. break;
  265. case 'h':
  266. unit = 'hours';
  267. break;
  268. case 'd':
  269. unit = 'days';
  270. break;
  271. case 'w':
  272. unit = 'weeks';
  273. break;
  274. case 'M':
  275. unit = 'months';
  276. break;
  277. case 'y':
  278. unit = 'years';
  279. break;
  280. default:
  281. console.log("Unknown interval ", intervalString);
  282. break;
  283. }
  284. return {
  285. "value": value,
  286. "unit": unit
  287. };
  288. };
  289. function convertToKairosTime(date, response_obj, start_stop_name) {
  290. var name;
  291. if (_.isString(date)) {
  292. if (date === 'now') {
  293. return;
  294. }
  295. else if (date.indexOf('now-') >= 0) {
  296. name = start_stop_name + "_relative";
  297. date = date.substring(4);
  298. var re_date = /(\d+)\s*(\D+)/;
  299. var result = re_date.exec(date);
  300. if (result) {
  301. var value = result[1];
  302. var unit = result[2];
  303. switch (unit) {
  304. case 'ms':
  305. unit = 'milliseconds';
  306. break;
  307. case 's':
  308. unit = 'seconds';
  309. break;
  310. case 'm':
  311. unit = 'minutes';
  312. break;
  313. case 'h':
  314. unit = 'hours';
  315. break;
  316. case 'd':
  317. unit = 'days';
  318. break;
  319. case 'w':
  320. unit = 'weeks';
  321. break;
  322. case 'M':
  323. unit = 'months';
  324. break;
  325. case 'y':
  326. unit = 'years';
  327. break;
  328. default:
  329. console.log("Unknown date ", date);
  330. break;
  331. }
  332. response_obj[name] = {
  333. "value": value,
  334. "unit": unit
  335. };
  336. return;
  337. }
  338. console.log("Unparseable date", date);
  339. return;
  340. }
  341. date = kbn.parseDate(date);
  342. }
  343. if (_.isDate(date)) {
  344. name = start_stop_name + "_absolute";
  345. response_obj[name] = date.getTime();
  346. return;
  347. }
  348. console.log("Date is neither string nor date");
  349. }
  350. function PeakFilter(dataIn, limit) {
  351. var datapoints = dataIn;
  352. var arrLength = datapoints.length;
  353. if (arrLength <= 3)
  354. return datapoints;
  355. var LastIndx = arrLength - 1;
  356. // Check first point
  357. var prvDelta = Math.abs((datapoints[1][0] - datapoints[0][0]) / datapoints[0][0]);
  358. var nxtDelta = Math.abs((datapoints[1][0] - datapoints[2][0]) / datapoints[2][0]);
  359. if (prvDelta >= limit && nxtDelta < limit)
  360. datapoints[0][0] = datapoints[1][0];
  361. // Check last point
  362. prvDelta = Math.abs((datapoints[LastIndx - 1][0] - datapoints[LastIndx - 2][0]) / datapoints[LastIndx - 2][0]);
  363. nxtDelta = Math.abs((datapoints[LastIndx - 1][0] - datapoints[LastIndx][0]) / datapoints[LastIndx][0]);
  364. if (prvDelta >= limit && nxtDelta < limit)
  365. datapoints[LastIndx][0] = datapoints[LastIndx - 1][0];
  366. for (var i = 1; i < arrLength - 1; i++) {
  367. prvDelta = Math.abs((datapoints[i][0] - datapoints[i - 1][0]) / datapoints[i - 1][0]);
  368. nxtDelta = Math.abs((datapoints[i][0] - datapoints[i + 1][0]) / datapoints[i + 1][0]);
  369. if (prvDelta >= limit && nxtDelta >= limit)
  370. datapoints[i][0] = (datapoints[i-1][0] + datapoints[i+1][0]) / 2;
  371. }
  372. return datapoints;
  373. }
  374. ////////////////////////////////////////////////////////////////////////
  375. return KairosDBDatasource;
  376. });
  377. });