result_transformer.ts 6.8 KB

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