result_transformer.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import _ from 'lodash';
  2. import TableModel from 'app/core/table_model';
  3. export class ResultTransformer {
  4. constructor(private templateSrv) {}
  5. transform(response: any, options: any): any[] {
  6. const prometheusResult = response.data.data.result;
  7. if (options.format === 'table') {
  8. return [
  9. this.transformMetricDataToTable(
  10. prometheusResult,
  11. options.responseListLength,
  12. options.refId,
  13. options.valueWithRefId
  14. ),
  15. ];
  16. } else if (prometheusResult && options.format === 'heatmap') {
  17. let seriesList = [];
  18. prometheusResult.sort(sortSeriesByLabel);
  19. for (const metricData of prometheusResult) {
  20. seriesList.push(this.transformMetricData(metricData, options, options.start, options.end));
  21. }
  22. seriesList = this.transformToHistogramOverTime(seriesList);
  23. return seriesList;
  24. } else if (prometheusResult) {
  25. const seriesList = [];
  26. for (const metricData of prometheusResult) {
  27. if (response.data.data.resultType === 'matrix') {
  28. seriesList.push(this.transformMetricData(metricData, options, options.start, options.end));
  29. } else if (response.data.data.resultType === 'vector') {
  30. seriesList.push(this.transformInstantMetricData(metricData, options));
  31. }
  32. }
  33. return seriesList;
  34. }
  35. return [];
  36. }
  37. transformMetricData(metricData, options, start, end) {
  38. const dps = [];
  39. let metricLabel = null;
  40. metricLabel = this.createMetricLabel(metricData.metric, options);
  41. const stepMs = parseInt(options.step, 10) * 1000;
  42. let baseTimestamp = start * 1000;
  43. if (metricData.values === undefined) {
  44. throw new Error('Prometheus heatmap error: data should be a time series');
  45. }
  46. for (const value of metricData.values) {
  47. let dpValue = parseFloat(value[1]);
  48. if (_.isNaN(dpValue)) {
  49. dpValue = null;
  50. }
  51. const timestamp = parseFloat(value[0]) * 1000;
  52. for (let t = baseTimestamp; t < timestamp; t += stepMs) {
  53. dps.push([null, t]);
  54. }
  55. baseTimestamp = timestamp + stepMs;
  56. dps.push([dpValue, timestamp]);
  57. }
  58. const endTimestamp = end * 1000;
  59. for (let t = baseTimestamp; t <= endTimestamp; t += stepMs) {
  60. dps.push([null, t]);
  61. }
  62. return {
  63. datapoints: dps,
  64. query: options.query,
  65. target: metricLabel,
  66. };
  67. }
  68. transformMetricDataToTable(md, resultCount: number, refId: string, valueWithRefId?: boolean) {
  69. const table = new TableModel();
  70. let i, j;
  71. const metricLabels = {};
  72. if (!md || md.length === 0) {
  73. return table;
  74. }
  75. // Collect all labels across all metrics
  76. _.each(md, series => {
  77. for (const label in series.metric) {
  78. if (!metricLabels.hasOwnProperty(label)) {
  79. metricLabels[label] = 1;
  80. }
  81. }
  82. });
  83. // Sort metric labels, create columns for them and record their index
  84. const sortedLabels = _.keys(metricLabels).sort();
  85. table.columns.push({ text: 'Time', type: 'time' });
  86. _.each(sortedLabels, (label, labelIndex) => {
  87. metricLabels[label] = labelIndex + 1;
  88. table.columns.push({ text: label, filterable: !label.startsWith('__') });
  89. });
  90. const valueText = resultCount > 1 || valueWithRefId ? `Value #${refId}` : 'Value';
  91. table.columns.push({ text: valueText });
  92. // Populate rows, set value to empty string when label not present.
  93. _.each(md, series => {
  94. if (series.value) {
  95. series.values = [series.value];
  96. }
  97. if (series.values) {
  98. for (i = 0; i < series.values.length; i++) {
  99. const values = series.values[i];
  100. const reordered: any = [values[0] * 1000];
  101. if (series.metric) {
  102. for (j = 0; j < sortedLabels.length; j++) {
  103. const label = sortedLabels[j];
  104. if (series.metric.hasOwnProperty(label)) {
  105. reordered.push(series.metric[label]);
  106. } else {
  107. reordered.push('');
  108. }
  109. }
  110. }
  111. reordered.push(parseFloat(values[1]));
  112. table.rows.push(reordered);
  113. }
  114. }
  115. });
  116. return table;
  117. }
  118. transformInstantMetricData(md, options) {
  119. const dps = [];
  120. let metricLabel = null;
  121. metricLabel = this.createMetricLabel(md.metric, options);
  122. dps.push([parseFloat(md.value[1]), md.value[0] * 1000]);
  123. return { target: metricLabel, datapoints: dps, labels: md.metric };
  124. }
  125. createMetricLabel(labelData, options) {
  126. let label = '';
  127. if (_.isUndefined(options) || _.isEmpty(options.legendFormat)) {
  128. label = this.getOriginalMetricName(labelData);
  129. } else {
  130. label = this.renderTemplate(this.templateSrv.replace(options.legendFormat), labelData);
  131. }
  132. if (!label || label === '{}') {
  133. label = options.query;
  134. }
  135. return label;
  136. }
  137. renderTemplate(aliasPattern, aliasData) {
  138. const aliasRegex = /\{\{\s*(.+?)\s*\}\}/g;
  139. return aliasPattern.replace(aliasRegex, (match, g1) => {
  140. if (aliasData[g1]) {
  141. return aliasData[g1];
  142. }
  143. return g1;
  144. });
  145. }
  146. getOriginalMetricName(labelData) {
  147. const metricName = labelData.__name__ || '';
  148. delete labelData.__name__;
  149. const labelPart = _.map(_.toPairs(labelData), label => {
  150. return label[0] + '="' + label[1] + '"';
  151. }).join(',');
  152. return metricName + '{' + labelPart + '}';
  153. }
  154. transformToHistogramOverTime(seriesList) {
  155. /* t1 = timestamp1, t2 = timestamp2 etc.
  156. t1 t2 t3 t1 t2 t3
  157. le10 10 10 0 => 10 10 0
  158. le20 20 10 30 => 10 0 30
  159. le30 30 10 35 => 10 0 5
  160. */
  161. for (let i = seriesList.length - 1; i > 0; i--) {
  162. const topSeries = seriesList[i].datapoints;
  163. const bottomSeries = seriesList[i - 1].datapoints;
  164. if (!topSeries || !bottomSeries) {
  165. throw new Error('Prometheus heatmap transform error: data should be a time series');
  166. }
  167. for (let j = 0; j < topSeries.length; j++) {
  168. const bottomPoint = bottomSeries[j] || [0];
  169. topSeries[j][0] -= bottomPoint[0];
  170. }
  171. }
  172. return seriesList;
  173. }
  174. }
  175. function sortSeriesByLabel(s1, s2): number {
  176. let le1, le2;
  177. try {
  178. // fail if not integer. might happen with bad queries
  179. le1 = parseHistogramLabel(s1.metric.le);
  180. le2 = parseHistogramLabel(s2.metric.le);
  181. } catch (err) {
  182. console.log(err);
  183. return 0;
  184. }
  185. if (le1 > le2) {
  186. return 1;
  187. }
  188. if (le1 < le2) {
  189. return -1;
  190. }
  191. return 0;
  192. }
  193. function parseHistogramLabel(le: string): number {
  194. if (le === '+Inf') {
  195. return +Infinity;
  196. }
  197. return Number(le);
  198. }