datasource.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. import _ from 'lodash';
  2. import * as dateMath from 'app/core/utils/datemath';
  3. import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version';
  4. import gfunc from './gfunc';
  5. /** @ngInject */
  6. export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv) {
  7. this.basicAuth = instanceSettings.basicAuth;
  8. this.url = instanceSettings.url;
  9. this.name = instanceSettings.name;
  10. this.graphiteVersion = instanceSettings.jsonData.graphiteVersion || '0.9';
  11. this.supportsTags = supportsTags(this.graphiteVersion);
  12. this.cacheTimeout = instanceSettings.cacheTimeout;
  13. this.withCredentials = instanceSettings.withCredentials;
  14. this.render_method = instanceSettings.render_method || 'POST';
  15. this.funcDefs = null;
  16. this.funcDefsPromise = null;
  17. this.getQueryOptionsInfo = function() {
  18. return {
  19. maxDataPoints: true,
  20. cacheTimeout: true,
  21. links: [
  22. {
  23. text: 'Help',
  24. url: 'http://docs.grafana.org/features/datasources/graphite/#using-graphite-in-grafana',
  25. },
  26. ],
  27. };
  28. };
  29. this.query = function(options) {
  30. var graphOptions = {
  31. from: this.translateTime(options.rangeRaw.from, false),
  32. until: this.translateTime(options.rangeRaw.to, true),
  33. targets: options.targets,
  34. format: options.format,
  35. cacheTimeout: options.cacheTimeout || this.cacheTimeout,
  36. maxDataPoints: options.maxDataPoints,
  37. };
  38. var params = this.buildGraphiteParams(graphOptions, options.scopedVars);
  39. if (params.length === 0) {
  40. return $q.when({ data: [] });
  41. }
  42. var httpOptions: any = {
  43. method: 'POST',
  44. url: '/render',
  45. data: params.join('&'),
  46. headers: {
  47. 'Content-Type': 'application/x-www-form-urlencoded',
  48. },
  49. };
  50. if (options.panelId) {
  51. httpOptions.requestId = this.name + '.panelId.' + options.panelId;
  52. }
  53. return this.doGraphiteRequest(httpOptions).then(this.convertDataPointsToMs);
  54. };
  55. this.convertDataPointsToMs = function(result) {
  56. if (!result || !result.data) {
  57. return [];
  58. }
  59. for (var i = 0; i < result.data.length; i++) {
  60. var series = result.data[i];
  61. for (var y = 0; y < series.datapoints.length; y++) {
  62. series.datapoints[y][1] *= 1000;
  63. }
  64. }
  65. return result;
  66. };
  67. this.parseTags = function(tagString) {
  68. let tags = [];
  69. tags = tagString.split(',');
  70. if (tags.length === 1) {
  71. tags = tagString.split(' ');
  72. if (tags[0] === '') {
  73. tags = [];
  74. }
  75. }
  76. return tags;
  77. };
  78. this.annotationQuery = function(options) {
  79. // Graphite metric as annotation
  80. if (options.annotation.target) {
  81. var target = templateSrv.replace(options.annotation.target, {}, 'glob');
  82. var graphiteQuery = {
  83. rangeRaw: options.rangeRaw,
  84. targets: [{ target: target }],
  85. format: 'json',
  86. maxDataPoints: 100,
  87. };
  88. return this.query(graphiteQuery).then(function(result) {
  89. var list = [];
  90. for (var i = 0; i < result.data.length; i++) {
  91. var target = result.data[i];
  92. for (var y = 0; y < target.datapoints.length; y++) {
  93. var datapoint = target.datapoints[y];
  94. if (!datapoint[0]) {
  95. continue;
  96. }
  97. list.push({
  98. annotation: options.annotation,
  99. time: datapoint[1],
  100. title: target.target,
  101. });
  102. }
  103. }
  104. return list;
  105. });
  106. } else {
  107. // Graphite event as annotation
  108. var tags = templateSrv.replace(options.annotation.tags);
  109. return this.events({ range: options.rangeRaw, tags: tags }).then(results => {
  110. var list = [];
  111. for (var i = 0; i < results.data.length; i++) {
  112. var e = results.data[i];
  113. var tags = e.tags;
  114. if (_.isString(e.tags)) {
  115. tags = this.parseTags(e.tags);
  116. }
  117. list.push({
  118. annotation: options.annotation,
  119. time: e.when * 1000,
  120. title: e.what,
  121. tags: tags,
  122. text: e.data,
  123. });
  124. }
  125. return list;
  126. });
  127. }
  128. };
  129. this.events = function(options) {
  130. try {
  131. var tags = '';
  132. if (options.tags) {
  133. tags = '&tags=' + options.tags;
  134. }
  135. return this.doGraphiteRequest({
  136. method: 'GET',
  137. url:
  138. '/events/get_data?from=' +
  139. this.translateTime(options.range.from, false) +
  140. '&until=' +
  141. this.translateTime(options.range.to, true) +
  142. tags,
  143. });
  144. } catch (err) {
  145. return $q.reject(err);
  146. }
  147. };
  148. this.targetContainsTemplate = function(target) {
  149. return templateSrv.variableExists(target.target);
  150. };
  151. this.translateTime = function(date, roundUp) {
  152. if (_.isString(date)) {
  153. if (date === 'now') {
  154. return 'now';
  155. } else if (date.indexOf('now-') >= 0 && date.indexOf('/') === -1) {
  156. date = date.substring(3);
  157. date = date.replace('m', 'min');
  158. date = date.replace('M', 'mon');
  159. return date;
  160. }
  161. date = dateMath.parse(date, roundUp);
  162. }
  163. // graphite' s from filter is exclusive
  164. // here we step back one minute in order
  165. // to guarantee that we get all the data that
  166. // exists for the specified range
  167. if (roundUp) {
  168. if (date.get('s')) {
  169. date.add(1, 'm');
  170. }
  171. } else if (roundUp === false) {
  172. if (date.get('s')) {
  173. date.subtract(1, 'm');
  174. }
  175. }
  176. return date.unix();
  177. };
  178. this.metricFindQuery = function(query, optionalOptions) {
  179. let options = optionalOptions || {};
  180. let interpolatedQuery = templateSrv.replace(query);
  181. // special handling for tag_values(<tag>[,<expression>]*), this is used for template variables
  182. let matches = interpolatedQuery.match(/^tag_values\(([^,]+)((, *[^,]+)*)\)$/);
  183. if (matches) {
  184. const expressions = [];
  185. const exprRegex = /, *([^,]+)/g;
  186. let match;
  187. while ((match = exprRegex.exec(matches[2])) !== null) {
  188. expressions.push(match[1]);
  189. }
  190. options.limit = 10000;
  191. return this.getTagValuesAutoComplete(expressions, matches[1], undefined, options);
  192. }
  193. // special handling for tags(<expression>[,<expression>]*), this is used for template variables
  194. matches = interpolatedQuery.match(/^tags\(([^,]*)((, *[^,]+)*)\)$/);
  195. if (matches) {
  196. const expressions = [];
  197. if (matches[1]) {
  198. expressions.push(matches[1]);
  199. const exprRegex = /, *([^,]+)/g;
  200. let match;
  201. while ((match = exprRegex.exec(matches[2])) !== null) {
  202. expressions.push(match[1]);
  203. }
  204. }
  205. options.limit = 10000;
  206. return this.getTagsAutoComplete(expressions, undefined, options);
  207. }
  208. let httpOptions: any = {
  209. method: 'GET',
  210. url: '/metrics/find',
  211. params: {
  212. query: interpolatedQuery,
  213. },
  214. // for cancellations
  215. requestId: options.requestId,
  216. };
  217. if (options.range) {
  218. httpOptions.params.from = this.translateTime(options.range.from, false);
  219. httpOptions.params.until = this.translateTime(options.range.to, true);
  220. }
  221. return this.doGraphiteRequest(httpOptions).then(results => {
  222. return _.map(results.data, metric => {
  223. return {
  224. text: metric.text,
  225. expandable: metric.expandable ? true : false,
  226. };
  227. });
  228. });
  229. };
  230. this.getTags = function(optionalOptions) {
  231. let options = optionalOptions || {};
  232. let httpOptions: any = {
  233. method: 'GET',
  234. url: '/tags',
  235. // for cancellations
  236. requestId: options.requestId,
  237. };
  238. if (options.range) {
  239. httpOptions.params.from = this.translateTime(options.range.from, false);
  240. httpOptions.params.until = this.translateTime(options.range.to, true);
  241. }
  242. return this.doGraphiteRequest(httpOptions).then(results => {
  243. return _.map(results.data, tag => {
  244. return {
  245. text: tag.tag,
  246. id: tag.id,
  247. };
  248. });
  249. });
  250. };
  251. this.getTagValues = function(tag, optionalOptions) {
  252. let options = optionalOptions || {};
  253. let httpOptions: any = {
  254. method: 'GET',
  255. url: '/tags/' + templateSrv.replace(tag),
  256. // for cancellations
  257. requestId: options.requestId,
  258. };
  259. if (options.range) {
  260. httpOptions.params.from = this.translateTime(options.range.from, false);
  261. httpOptions.params.until = this.translateTime(options.range.to, true);
  262. }
  263. return this.doGraphiteRequest(httpOptions).then(results => {
  264. if (results.data && results.data.values) {
  265. return _.map(results.data.values, value => {
  266. return {
  267. text: value.value,
  268. id: value.id,
  269. };
  270. });
  271. } else {
  272. return [];
  273. }
  274. });
  275. };
  276. this.getTagsAutoComplete = (expressions, tagPrefix, optionalOptions) => {
  277. let options = optionalOptions || {};
  278. let httpOptions: any = {
  279. method: 'GET',
  280. url: '/tags/autoComplete/tags',
  281. params: {
  282. expr: _.map(expressions, expression => templateSrv.replace(expression)),
  283. },
  284. // for cancellations
  285. requestId: options.requestId,
  286. };
  287. if (tagPrefix) {
  288. httpOptions.params.tagPrefix = tagPrefix;
  289. }
  290. if (options.limit) {
  291. httpOptions.params.limit = options.limit;
  292. }
  293. if (options.range) {
  294. httpOptions.params.from = this.translateTime(options.range.from, false);
  295. httpOptions.params.until = this.translateTime(options.range.to, true);
  296. }
  297. return this.doGraphiteRequest(httpOptions).then(results => {
  298. if (results.data) {
  299. return _.map(results.data, tag => {
  300. return { text: tag };
  301. });
  302. } else {
  303. return [];
  304. }
  305. });
  306. };
  307. this.getTagValuesAutoComplete = (expressions, tag, valuePrefix, optionalOptions) => {
  308. let options = optionalOptions || {};
  309. let httpOptions: any = {
  310. method: 'GET',
  311. url: '/tags/autoComplete/values',
  312. params: {
  313. expr: _.map(expressions, expression => templateSrv.replace(expression)),
  314. tag: templateSrv.replace(tag),
  315. },
  316. // for cancellations
  317. requestId: options.requestId,
  318. };
  319. if (valuePrefix) {
  320. httpOptions.params.valuePrefix = valuePrefix;
  321. }
  322. if (options.limit) {
  323. httpOptions.params.limit = options.limit;
  324. }
  325. if (options.range) {
  326. httpOptions.params.from = this.translateTime(options.range.from, false);
  327. httpOptions.params.until = this.translateTime(options.range.to, true);
  328. }
  329. return this.doGraphiteRequest(httpOptions).then(results => {
  330. if (results.data) {
  331. return _.map(results.data, value => {
  332. return { text: value };
  333. });
  334. } else {
  335. return [];
  336. }
  337. });
  338. };
  339. this.getVersion = function(optionalOptions) {
  340. let options = optionalOptions || {};
  341. let httpOptions = {
  342. method: 'GET',
  343. url: '/version',
  344. requestId: options.requestId,
  345. };
  346. return this.doGraphiteRequest(httpOptions)
  347. .then(results => {
  348. if (results.data) {
  349. let semver = new SemVersion(results.data);
  350. return semver.isValid() ? results.data : '';
  351. }
  352. return '';
  353. })
  354. .catch(() => {
  355. return '';
  356. });
  357. };
  358. this.createFuncInstance = function(funcDef, options?) {
  359. return gfunc.createFuncInstance(funcDef, options, this.funcDefs);
  360. };
  361. this.getFuncDef = function(name) {
  362. return gfunc.getFuncDef(name, this.funcDefs);
  363. };
  364. this.waitForFuncDefsLoaded = function() {
  365. return this.getFuncDefs();
  366. };
  367. this.getFuncDefs = function() {
  368. if (this.funcDefsPromise !== null) {
  369. return this.funcDefsPromise;
  370. }
  371. if (!supportsFunctionIndex(this.graphiteVersion)) {
  372. this.funcDefs = gfunc.getFuncDefs(this.graphiteVersion);
  373. this.funcDefsPromise = Promise.resolve(this.funcDefs);
  374. return this.funcDefsPromise;
  375. }
  376. let httpOptions = {
  377. method: 'GET',
  378. url: '/functions',
  379. };
  380. this.funcDefsPromise = this.doGraphiteRequest(httpOptions)
  381. .then(results => {
  382. if (results.status !== 200 || typeof results.data !== 'object') {
  383. this.funcDefs = gfunc.getFuncDefs(this.graphiteVersion);
  384. } else {
  385. this.funcDefs = gfunc.parseFuncDefs(results.data);
  386. }
  387. return this.funcDefs;
  388. })
  389. .catch(err => {
  390. console.log('Fetching graphite functions error', err);
  391. this.funcDefs = gfunc.getFuncDefs(this.graphiteVersion);
  392. return this.funcDefs;
  393. });
  394. return this.funcDefsPromise;
  395. };
  396. this.testDatasource = function() {
  397. return this.metricFindQuery('*').then(function() {
  398. return { status: 'success', message: 'Data source is working' };
  399. });
  400. };
  401. this.doGraphiteRequest = function(options) {
  402. if (this.basicAuth || this.withCredentials) {
  403. options.withCredentials = true;
  404. }
  405. if (this.basicAuth) {
  406. options.headers = options.headers || {};
  407. options.headers.Authorization = this.basicAuth;
  408. }
  409. options.url = this.url + options.url;
  410. options.inspect = { type: 'graphite' };
  411. return backendSrv.datasourceRequest(options);
  412. };
  413. this._seriesRefLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  414. this.buildGraphiteParams = function(options, scopedVars) {
  415. var graphite_options = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout'];
  416. var clean_options = [],
  417. targets = {};
  418. var target, targetValue, i;
  419. var regex = /\#([A-Z])/g;
  420. var intervalFormatFixRegex = /'(\d+)m'/gi;
  421. var hasTargets = false;
  422. options['format'] = 'json';
  423. function fixIntervalFormat(match) {
  424. return match.replace('m', 'min').replace('M', 'mon');
  425. }
  426. for (i = 0; i < options.targets.length; i++) {
  427. target = options.targets[i];
  428. if (!target.target) {
  429. continue;
  430. }
  431. if (!target.refId) {
  432. target.refId = this._seriesRefLetters[i];
  433. }
  434. targetValue = templateSrv.replace(target.target, scopedVars);
  435. targetValue = targetValue.replace(intervalFormatFixRegex, fixIntervalFormat);
  436. targets[target.refId] = targetValue;
  437. }
  438. function nestedSeriesRegexReplacer(match, g1) {
  439. return targets[g1] || match;
  440. }
  441. for (i = 0; i < options.targets.length; i++) {
  442. target = options.targets[i];
  443. if (!target.target) {
  444. continue;
  445. }
  446. targetValue = targets[target.refId];
  447. targetValue = targetValue.replace(regex, nestedSeriesRegexReplacer);
  448. targets[target.refId] = targetValue;
  449. if (!target.hide) {
  450. hasTargets = true;
  451. clean_options.push('target=' + encodeURIComponent(targetValue));
  452. }
  453. }
  454. _.each(options, function(value, key) {
  455. if (_.indexOf(graphite_options, key) === -1) {
  456. return;
  457. }
  458. if (value) {
  459. clean_options.push(key + '=' + encodeURIComponent(value));
  460. }
  461. });
  462. if (!hasTargets) {
  463. return [];
  464. }
  465. return clean_options;
  466. };
  467. }
  468. function supportsTags(version: string): boolean {
  469. return isVersionGtOrEq(version, '1.1');
  470. }
  471. function supportsFunctionIndex(version: string): boolean {
  472. return isVersionGtOrEq(version, '1.1');
  473. }