result_transformer.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import _ from 'lodash';
  2. import TableModel from 'app/core/table_model';
  3. import { TimeSeries, FieldType } from '@grafana/data';
  4. import { TemplateSrv } from 'app/features/templating/template_srv';
  5. export class ResultTransformer {
  6. constructor(private templateSrv: TemplateSrv) {}
  7. transform(response: any, options: any): Array<TableModel | TimeSeries> {
  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. tags: metricData.metric,
  69. };
  70. }
  71. transformMetricDataToTable(md: any, resultCount: number, refId: string, valueWithRefId?: boolean): TableModel {
  72. const table = new TableModel();
  73. let i: number, j: number;
  74. const metricLabels: { [key: string]: number } = {};
  75. if (!md || md.length === 0) {
  76. return table;
  77. }
  78. // Collect all labels across all metrics
  79. _.each(md, series => {
  80. for (const label in series.metric) {
  81. if (!metricLabels.hasOwnProperty(label)) {
  82. metricLabels[label] = 1;
  83. }
  84. }
  85. });
  86. // Sort metric labels, create columns for them and record their index
  87. const sortedLabels = _.keys(metricLabels).sort();
  88. table.columns.push({ text: 'Time', type: FieldType.time });
  89. _.each(sortedLabels, (label, labelIndex) => {
  90. metricLabels[label] = labelIndex + 1;
  91. table.columns.push({ text: label, filterable: true });
  92. });
  93. const valueText = resultCount > 1 || valueWithRefId ? `Value #${refId}` : 'Value';
  94. table.columns.push({ text: valueText });
  95. // Populate rows, set value to empty string when label not present.
  96. _.each(md, series => {
  97. if (series.value) {
  98. series.values = [series.value];
  99. }
  100. if (series.values) {
  101. for (i = 0; i < series.values.length; i++) {
  102. const values = series.values[i];
  103. const reordered: any = [values[0] * 1000];
  104. if (series.metric) {
  105. for (j = 0; j < sortedLabels.length; j++) {
  106. const label = sortedLabels[j];
  107. if (series.metric.hasOwnProperty(label)) {
  108. reordered.push(series.metric[label]);
  109. } else {
  110. reordered.push('');
  111. }
  112. }
  113. }
  114. reordered.push(parseFloat(values[1]));
  115. table.rows.push(reordered);
  116. }
  117. }
  118. });
  119. return table;
  120. }
  121. transformInstantMetricData(md: any, options: any) {
  122. const dps = [];
  123. let metricLabel = null;
  124. metricLabel = this.createMetricLabel(md.metric, options);
  125. dps.push([parseFloat(md.value[1]), md.value[0] * 1000]);
  126. return { target: metricLabel, datapoints: dps, labels: md.metric };
  127. }
  128. createMetricLabel(labelData: { [key: string]: string }, options: any) {
  129. let label = '';
  130. if (_.isUndefined(options) || _.isEmpty(options.legendFormat)) {
  131. label = this.getOriginalMetricName(labelData);
  132. } else {
  133. label = this.renderTemplate(this.templateSrv.replace(options.legendFormat), labelData);
  134. }
  135. if (!label || label === '{}') {
  136. label = options.query;
  137. }
  138. return label;
  139. }
  140. renderTemplate(aliasPattern: string, aliasData: { [key: string]: string }) {
  141. const aliasRegex = /\{\{\s*(.+?)\s*\}\}/g;
  142. return aliasPattern.replace(aliasRegex, (match, g1) => {
  143. if (aliasData[g1]) {
  144. return aliasData[g1];
  145. }
  146. return g1;
  147. });
  148. }
  149. getOriginalMetricName(labelData: { [key: string]: string }) {
  150. const metricName = labelData.__name__ || '';
  151. delete labelData.__name__;
  152. const labelPart = _.map(_.toPairs(labelData), label => {
  153. return label[0] + '="' + label[1] + '"';
  154. }).join(',');
  155. return metricName + '{' + labelPart + '}';
  156. }
  157. transformToHistogramOverTime(seriesList: TimeSeries[]) {
  158. /* t1 = timestamp1, t2 = timestamp2 etc.
  159. t1 t2 t3 t1 t2 t3
  160. le10 10 10 0 => 10 10 0
  161. le20 20 10 30 => 10 0 30
  162. le30 30 10 35 => 10 0 5
  163. */
  164. for (let i = seriesList.length - 1; i > 0; i--) {
  165. const topSeries = seriesList[i].datapoints;
  166. const bottomSeries = seriesList[i - 1].datapoints;
  167. if (!topSeries || !bottomSeries) {
  168. throw new Error('Prometheus heatmap transform error: data should be a time series');
  169. }
  170. for (let j = 0; j < topSeries.length; j++) {
  171. const bottomPoint = bottomSeries[j] || [0];
  172. topSeries[j][0] -= bottomPoint[0];
  173. }
  174. }
  175. return seriesList;
  176. }
  177. }
  178. function sortSeriesByLabel(s1: TimeSeries, s2: TimeSeries): number {
  179. let le1, le2;
  180. try {
  181. // fail if not integer. might happen with bad queries
  182. le1 = parseHistogramLabel(s1.target);
  183. le2 = parseHistogramLabel(s2.target);
  184. } catch (err) {
  185. console.log(err);
  186. return 0;
  187. }
  188. if (le1 > le2) {
  189. return 1;
  190. }
  191. if (le1 < le2) {
  192. return -1;
  193. }
  194. return 0;
  195. }
  196. function parseHistogramLabel(le: string): number {
  197. if (le === '+Inf') {
  198. return +Infinity;
  199. }
  200. return Number(le);
  201. }