completer.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. import { PrometheusDatasource } from './datasource';
  2. import _ from 'lodash';
  3. export class PromCompleter {
  4. labelQueryCache: any;
  5. labelNameCache: any;
  6. labelValueCache: any;
  7. templateVariableCompletions: any;
  8. identifierRegexps = [/\[/, /[a-zA-Z0-9_:]/];
  9. constructor(private datasource: PrometheusDatasource, private templateSrv) {
  10. this.labelQueryCache = {};
  11. this.labelNameCache = {};
  12. this.labelValueCache = {};
  13. this.templateVariableCompletions = this.templateSrv.variables.map(variable => {
  14. return {
  15. caption: '$' + variable.name,
  16. value: '$' + variable.name,
  17. meta: 'variable',
  18. score: Number.MAX_VALUE,
  19. };
  20. });
  21. }
  22. getCompletions(editor, session, pos, prefix, callback) {
  23. const wrappedCallback = (err, completions) => {
  24. completions = completions.concat(this.templateVariableCompletions);
  25. return callback(err, completions);
  26. };
  27. const token = session.getTokenAt(pos.row, pos.column);
  28. switch (token.type) {
  29. case 'entity.name.tag.label-matcher':
  30. this.getCompletionsForLabelMatcherName(session, pos).then(completions => {
  31. wrappedCallback(null, completions);
  32. });
  33. return;
  34. case 'string.quoted.label-matcher':
  35. this.getCompletionsForLabelMatcherValue(session, pos).then(completions => {
  36. wrappedCallback(null, completions);
  37. });
  38. return;
  39. case 'entity.name.tag.label-list-matcher':
  40. this.getCompletionsForBinaryOperator(session, pos).then(completions => {
  41. wrappedCallback(null, completions);
  42. });
  43. return;
  44. }
  45. if (token.type === 'paren.lparen' && token.value === '[') {
  46. const vectors = [];
  47. for (const unit of ['s', 'm', 'h']) {
  48. for (const value of [1, 5, 10, 30]) {
  49. vectors.push({
  50. caption: value + unit,
  51. value: '[' + value + unit,
  52. meta: 'range vector',
  53. });
  54. }
  55. }
  56. vectors.unshift({
  57. caption: '$__interval_ms',
  58. value: '[$__interval_ms',
  59. meta: 'range vector',
  60. });
  61. vectors.unshift({
  62. caption: '$__interval',
  63. value: '[$__interval',
  64. meta: 'range vector',
  65. });
  66. wrappedCallback(null, vectors);
  67. return;
  68. }
  69. const query = prefix;
  70. return this.datasource.performSuggestQuery(query, true).then(metricNames => {
  71. wrappedCallback(
  72. null,
  73. metricNames.map(name => {
  74. let value = name;
  75. if (prefix === '(') {
  76. value = '(' + name;
  77. }
  78. return {
  79. caption: name,
  80. value: value,
  81. meta: 'metric',
  82. };
  83. })
  84. );
  85. });
  86. }
  87. getCompletionsForLabelMatcherName(session, pos) {
  88. const metricName = this.findMetricName(session, pos.row, pos.column);
  89. if (!metricName) {
  90. return Promise.resolve(this.transformToCompletions(['__name__', 'instance', 'job'], 'label name'));
  91. }
  92. if (this.labelNameCache[metricName]) {
  93. return Promise.resolve(this.labelNameCache[metricName]);
  94. }
  95. return this.getLabelNameAndValueForExpression(metricName, 'metricName').then(result => {
  96. const labelNames = this.transformToCompletions(
  97. _.uniq(
  98. _.flatten(
  99. result.map(r => {
  100. return Object.keys(r);
  101. })
  102. )
  103. ),
  104. 'label name'
  105. );
  106. this.labelNameCache[metricName] = labelNames;
  107. return Promise.resolve(labelNames);
  108. });
  109. }
  110. getCompletionsForLabelMatcherValue(session, pos) {
  111. const metricName = this.findMetricName(session, pos.row, pos.column);
  112. if (!metricName) {
  113. return Promise.resolve([]);
  114. }
  115. const labelNameToken = this.findToken(
  116. session,
  117. pos.row,
  118. pos.column,
  119. 'entity.name.tag.label-matcher',
  120. null,
  121. 'paren.lparen.label-matcher'
  122. );
  123. if (!labelNameToken) {
  124. return Promise.resolve([]);
  125. }
  126. const labelName = labelNameToken.value;
  127. if (this.labelValueCache[metricName] && this.labelValueCache[metricName][labelName]) {
  128. return Promise.resolve(this.labelValueCache[metricName][labelName]);
  129. }
  130. return this.getLabelNameAndValueForExpression(metricName, 'metricName').then(result => {
  131. const labelValues = this.transformToCompletions(
  132. _.uniq(
  133. result.map(r => {
  134. return r[labelName];
  135. })
  136. ),
  137. 'label value'
  138. );
  139. this.labelValueCache[metricName] = this.labelValueCache[metricName] || {};
  140. this.labelValueCache[metricName][labelName] = labelValues;
  141. return Promise.resolve(labelValues);
  142. });
  143. }
  144. getCompletionsForBinaryOperator(session, pos) {
  145. const keywordOperatorToken = this.findToken(session, pos.row, pos.column, 'keyword.control', null, 'identifier');
  146. if (!keywordOperatorToken) {
  147. return Promise.resolve([]);
  148. }
  149. let rparenToken, expr;
  150. switch (keywordOperatorToken.value) {
  151. case 'by':
  152. case 'without':
  153. rparenToken = this.findToken(
  154. session,
  155. keywordOperatorToken.row,
  156. keywordOperatorToken.column,
  157. 'paren.rparen',
  158. null,
  159. 'identifier'
  160. );
  161. if (!rparenToken) {
  162. return Promise.resolve([]);
  163. }
  164. expr = this.findExpressionMatchedParen(session, rparenToken.row, rparenToken.column);
  165. if (expr === '') {
  166. return Promise.resolve([]);
  167. }
  168. return this.getLabelNameAndValueForExpression(expr, 'expression').then(result => {
  169. const labelNames = this.transformToCompletions(
  170. _.uniq(
  171. _.flatten(
  172. result.map(r => {
  173. return Object.keys(r);
  174. })
  175. )
  176. ),
  177. 'label name'
  178. );
  179. this.labelNameCache[expr] = labelNames;
  180. return labelNames;
  181. });
  182. case 'on':
  183. case 'ignoring':
  184. case 'group_left':
  185. case 'group_right':
  186. const binaryOperatorToken = this.findToken(
  187. session,
  188. keywordOperatorToken.row,
  189. keywordOperatorToken.column,
  190. 'keyword.operator.binary',
  191. null,
  192. 'identifier'
  193. );
  194. if (!binaryOperatorToken) {
  195. return Promise.resolve([]);
  196. }
  197. rparenToken = this.findToken(
  198. session,
  199. binaryOperatorToken.row,
  200. binaryOperatorToken.column,
  201. 'paren.rparen',
  202. null,
  203. 'identifier'
  204. );
  205. if (rparenToken) {
  206. expr = this.findExpressionMatchedParen(session, rparenToken.row, rparenToken.column);
  207. if (expr === '') {
  208. return Promise.resolve([]);
  209. }
  210. return this.getLabelNameAndValueForExpression(expr, 'expression').then(result => {
  211. const labelNames = this.transformToCompletions(
  212. _.uniq(
  213. _.flatten(
  214. result.map(r => {
  215. return Object.keys(r);
  216. })
  217. )
  218. ),
  219. 'label name'
  220. );
  221. this.labelNameCache[expr] = labelNames;
  222. return labelNames;
  223. });
  224. } else {
  225. const metricName = this.findMetricName(session, binaryOperatorToken.row, binaryOperatorToken.column);
  226. return this.getLabelNameAndValueForExpression(metricName, 'metricName').then(result => {
  227. const labelNames = this.transformToCompletions(
  228. _.uniq(
  229. _.flatten(
  230. result.map(r => {
  231. return Object.keys(r);
  232. })
  233. )
  234. ),
  235. 'label name'
  236. );
  237. this.labelNameCache[metricName] = labelNames;
  238. return Promise.resolve(labelNames);
  239. });
  240. }
  241. }
  242. return Promise.resolve([]);
  243. }
  244. getLabelNameAndValueForExpression(expr: string, type: string): Promise<any> {
  245. if (this.labelQueryCache[expr]) {
  246. return Promise.resolve(this.labelQueryCache[expr]);
  247. }
  248. let query = expr;
  249. if (type === 'metricName') {
  250. let op = '=~';
  251. if (/[a-zA-Z_:][a-zA-Z0-9_:]*/.test(expr)) {
  252. op = '=';
  253. }
  254. query = '{__name__' + op + '"' + expr + '"}';
  255. }
  256. const { start, end } = this.datasource.getTimeRange();
  257. const url = '/api/v1/series?match[]=' + encodeURIComponent(query) + '&start=' + start + '&end=' + end;
  258. return this.datasource.metadataRequest(url).then(response => {
  259. this.labelQueryCache[expr] = response.data.data;
  260. return response.data.data;
  261. });
  262. }
  263. transformToCompletions(words, meta) {
  264. return words.map(name => {
  265. return {
  266. caption: name,
  267. value: name,
  268. meta: meta,
  269. score: Number.MAX_VALUE,
  270. };
  271. });
  272. }
  273. findMetricName(session, row, column) {
  274. let metricName = '';
  275. let tokens;
  276. const nameLabelNameToken = this.findToken(
  277. session,
  278. row,
  279. column,
  280. 'entity.name.tag.label-matcher',
  281. '__name__',
  282. 'paren.lparen.label-matcher'
  283. );
  284. if (nameLabelNameToken) {
  285. tokens = session.getTokens(nameLabelNameToken.row);
  286. const nameLabelValueToken = tokens[nameLabelNameToken.index + 2];
  287. if (nameLabelValueToken && nameLabelValueToken.type === 'string.quoted.label-matcher') {
  288. metricName = nameLabelValueToken.value.slice(1, -1); // cut begin/end quotation
  289. }
  290. } else {
  291. const metricNameToken = this.findToken(session, row, column, 'identifier', null, null);
  292. if (metricNameToken) {
  293. tokens = session.getTokens(metricNameToken.row);
  294. metricName = metricNameToken.value;
  295. }
  296. }
  297. return metricName;
  298. }
  299. findToken(session, row, column, target, value, guard) {
  300. let tokens, idx;
  301. // find index and get column of previous token
  302. for (let r = row; r >= 0; r--) {
  303. let c;
  304. tokens = session.getTokens(r);
  305. if (r === row) {
  306. // current row
  307. c = 0;
  308. for (idx = 0; idx < tokens.length; idx++) {
  309. const nc = c + tokens[idx].value.length;
  310. if (nc >= column) {
  311. break;
  312. }
  313. c = nc;
  314. }
  315. } else {
  316. idx = tokens.length - 1;
  317. c =
  318. _.sum(
  319. tokens.map(t => {
  320. return t.value.length;
  321. })
  322. ) - tokens[tokens.length - 1].value.length;
  323. }
  324. for (; idx >= 0; idx--) {
  325. if (tokens[idx].type === guard) {
  326. return null;
  327. }
  328. if (tokens[idx].type === target && (!value || tokens[idx].value === value)) {
  329. tokens[idx].row = r;
  330. tokens[idx].column = c;
  331. tokens[idx].index = idx;
  332. return tokens[idx];
  333. }
  334. c -= tokens[idx].value.length;
  335. }
  336. }
  337. return null;
  338. }
  339. findExpressionMatchedParen(session, row, column) {
  340. let tokens, idx;
  341. let deep = 1;
  342. let expression = ')';
  343. for (let r = row; r >= 0; r--) {
  344. tokens = session.getTokens(r);
  345. if (r === row) {
  346. // current row
  347. let c = 0;
  348. for (idx = 0; idx < tokens.length; idx++) {
  349. c += tokens[idx].value.length;
  350. if (c >= column) {
  351. break;
  352. }
  353. }
  354. } else {
  355. idx = tokens.length - 1;
  356. }
  357. for (; idx >= 0; idx--) {
  358. expression = tokens[idx].value + expression;
  359. if (tokens[idx].type === 'paren.rparen') {
  360. deep++;
  361. } else if (tokens[idx].type === 'paren.lparen') {
  362. deep--;
  363. if (deep === 0) {
  364. return expression;
  365. }
  366. }
  367. }
  368. }
  369. return expression;
  370. }
  371. }