completer.ts 11 KB

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