completer.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  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. let wrappedCallback = (err, completions) => {
  24. completions = completions.concat(this.templateVariableCompletions);
  25. return callback(err, completions);
  26. };
  27. let 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. var vectors = [];
  47. for (let unit of ['s', 'm', 'h']) {
  48. for (let 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. var 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. let 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. var labelNames = this.transformToCompletions(
  97. _.uniq(
  98. _.flatten(
  99. result.map(r => {
  100. return Object.keys(r.metric);
  101. })
  102. )
  103. ),
  104. 'label name'
  105. );
  106. this.labelNameCache[metricName] = labelNames;
  107. return Promise.resolve(labelNames);
  108. });
  109. }
  110. getCompletionsForLabelMatcherValue(session, pos) {
  111. let metricName = this.findMetricName(session, pos.row, pos.column);
  112. if (!metricName) {
  113. return Promise.resolve([]);
  114. }
  115. var 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. var 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. var labelValues = this.transformToCompletions(
  132. _.uniq(
  133. result.map(r => {
  134. return r.metric[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. let 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. var labelNames = this.transformToCompletions(
  170. _.uniq(
  171. _.flatten(
  172. result.map(r => {
  173. return Object.keys(r.metric);
  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. let 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. var labelNames = this.transformToCompletions(
  212. _.uniq(
  213. _.flatten(
  214. result.map(r => {
  215. return Object.keys(r.metric);
  216. })
  217. )
  218. ),
  219. 'label name'
  220. );
  221. this.labelNameCache[expr] = labelNames;
  222. return labelNames;
  223. });
  224. } else {
  225. let metricName = this.findMetricName(session, binaryOperatorToken.row, binaryOperatorToken.column);
  226. return this.getLabelNameAndValueForExpression(metricName, 'metricName').then(result => {
  227. var labelNames = this.transformToCompletions(
  228. _.uniq(
  229. _.flatten(
  230. result.map(r => {
  231. return Object.keys(r.metric);
  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, type) {
  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. return this.datasource.performInstantQuery({ expr: query }, new Date().getTime() / 1000).then(response => {
  257. this.labelQueryCache[expr] = response.data.data.result;
  258. return response.data.data.result;
  259. });
  260. }
  261. transformToCompletions(words, meta) {
  262. return words.map(name => {
  263. return {
  264. caption: name,
  265. value: name,
  266. meta: meta,
  267. score: Number.MAX_VALUE,
  268. };
  269. });
  270. }
  271. findMetricName(session, row, column) {
  272. var metricName = '';
  273. var tokens;
  274. var nameLabelNameToken = this.findToken(
  275. session,
  276. row,
  277. column,
  278. 'entity.name.tag.label-matcher',
  279. '__name__',
  280. 'paren.lparen.label-matcher'
  281. );
  282. if (nameLabelNameToken) {
  283. tokens = session.getTokens(nameLabelNameToken.row);
  284. var nameLabelValueToken = tokens[nameLabelNameToken.index + 2];
  285. if (nameLabelValueToken && nameLabelValueToken.type === 'string.quoted.label-matcher') {
  286. metricName = nameLabelValueToken.value.slice(1, -1); // cut begin/end quotation
  287. }
  288. } else {
  289. var metricNameToken = this.findToken(session, row, column, 'identifier', null, null);
  290. if (metricNameToken) {
  291. tokens = session.getTokens(metricNameToken.row);
  292. metricName = metricNameToken.value;
  293. }
  294. }
  295. return metricName;
  296. }
  297. findToken(session, row, column, target, value, guard) {
  298. var tokens, idx;
  299. // find index and get column of previous token
  300. for (var r = row; r >= 0; r--) {
  301. let c;
  302. tokens = session.getTokens(r);
  303. if (r === row) {
  304. // current row
  305. c = 0;
  306. for (idx = 0; idx < tokens.length; idx++) {
  307. let nc = c + tokens[idx].value.length;
  308. if (nc >= column) {
  309. break;
  310. }
  311. c = nc;
  312. }
  313. } else {
  314. idx = tokens.length - 1;
  315. c =
  316. _.sum(
  317. tokens.map(t => {
  318. return t.value.length;
  319. })
  320. ) - tokens[tokens.length - 1].value.length;
  321. }
  322. for (; idx >= 0; idx--) {
  323. if (tokens[idx].type === guard) {
  324. return null;
  325. }
  326. if (tokens[idx].type === target && (!value || tokens[idx].value === value)) {
  327. tokens[idx].row = r;
  328. tokens[idx].column = c;
  329. tokens[idx].index = idx;
  330. return tokens[idx];
  331. }
  332. c -= tokens[idx].value.length;
  333. }
  334. }
  335. return null;
  336. }
  337. findExpressionMatchedParen(session, row, column) {
  338. let tokens, idx;
  339. let deep = 1;
  340. let expression = ')';
  341. for (let r = row; r >= 0; r--) {
  342. tokens = session.getTokens(r);
  343. if (r === row) {
  344. // current row
  345. let c = 0;
  346. for (idx = 0; idx < tokens.length; idx++) {
  347. c += tokens[idx].value.length;
  348. if (c >= column) {
  349. break;
  350. }
  351. }
  352. } else {
  353. idx = tokens.length - 1;
  354. }
  355. for (; idx >= 0; idx--) {
  356. expression = tokens[idx].value + expression;
  357. if (tokens[idx].type === 'paren.rparen') {
  358. deep++;
  359. } else if (tokens[idx].type === 'paren.lparen') {
  360. deep--;
  361. if (deep === 0) {
  362. return expression;
  363. }
  364. }
  365. }
  366. }
  367. return expression;
  368. }
  369. }