result_transformer.ts 6.6 KB

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