mirror of https://github.com/grafana/grafana
parent
425d2cfd3a
commit
479209f483
@ -0,0 +1,199 @@ |
||||
import _ from 'lodash'; |
||||
import TableModel from 'app/core/table_model'; |
||||
|
||||
export class ResultTransformer { |
||||
constructor(private templateSrv) {} |
||||
|
||||
transform(result: any, response: any, options: any) { |
||||
let prometheusResult = response.data.data.result; |
||||
|
||||
if (options.format === 'table') { |
||||
result.push(this.transformMetricDataToTable(prometheusResult, options.responseListLength, options.responseIndex)); |
||||
} else if (options.format === 'heatmap') { |
||||
let seriesList = []; |
||||
prometheusResult.sort(sortSeriesByLabel); |
||||
for (let metricData of prometheusResult) { |
||||
seriesList.push(this.transformMetricData(metricData, options, options.start, options.end)); |
||||
} |
||||
seriesList = this.transformToHistogramOverTime(seriesList); |
||||
result.push(...seriesList); |
||||
} else { |
||||
for (let metricData of prometheusResult) { |
||||
if (response.data.data.resultType === 'matrix') { |
||||
result.push(this.transformMetricData(metricData, options, options.start, options.end)); |
||||
} else if (response.data.data.resultType === 'vector') { |
||||
result.push(this.transformInstantMetricData(metricData, options)); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
transformMetricData(md, options, start, end) { |
||||
let dps = [], |
||||
metricLabel = null; |
||||
|
||||
metricLabel = this.createMetricLabel(md.metric, options); |
||||
|
||||
const stepMs = parseInt(options.step) * 1000; |
||||
let baseTimestamp = start * 1000; |
||||
for (let value of md.values) { |
||||
let dp_value = parseFloat(value[1]); |
||||
if (_.isNaN(dp_value)) { |
||||
dp_value = null; |
||||
} |
||||
|
||||
const timestamp = parseFloat(value[0]) * 1000; |
||||
for (let t = baseTimestamp; t < timestamp; t += stepMs) { |
||||
dps.push([null, t]); |
||||
} |
||||
baseTimestamp = timestamp + stepMs; |
||||
dps.push([dp_value, timestamp]); |
||||
} |
||||
|
||||
const endTimestamp = end * 1000; |
||||
for (let t = baseTimestamp; t <= endTimestamp; t += stepMs) { |
||||
dps.push([null, t]); |
||||
} |
||||
|
||||
return { target: metricLabel, datapoints: dps }; |
||||
} |
||||
|
||||
transformMetricDataToTable(md, resultCount: number, resultIndex: number) { |
||||
var table = new TableModel(); |
||||
var i, j; |
||||
var metricLabels = {}; |
||||
|
||||
if (md.length === 0) { |
||||
return table; |
||||
} |
||||
|
||||
// Collect all labels across all metrics
|
||||
_.each(md, function(series) { |
||||
for (var label in series.metric) { |
||||
if (!metricLabels.hasOwnProperty(label)) { |
||||
metricLabels[label] = 1; |
||||
} |
||||
} |
||||
}); |
||||
|
||||
// Sort metric labels, create columns for them and record their index
|
||||
var sortedLabels = _.keys(metricLabels).sort(); |
||||
table.columns.push({ text: 'Time', type: 'time' }); |
||||
_.each(sortedLabels, function(label, labelIndex) { |
||||
metricLabels[label] = labelIndex + 1; |
||||
table.columns.push({ text: label }); |
||||
}); |
||||
let valueText = resultCount > 1 ? `Value #${String.fromCharCode(65 + resultIndex)}` : 'Value'; |
||||
table.columns.push({ text: valueText }); |
||||
|
||||
// Populate rows, set value to empty string when label not present.
|
||||
_.each(md, function(series) { |
||||
if (series.value) { |
||||
series.values = [series.value]; |
||||
} |
||||
if (series.values) { |
||||
for (i = 0; i < series.values.length; i++) { |
||||
var values = series.values[i]; |
||||
var reordered: any = [values[0] * 1000]; |
||||
if (series.metric) { |
||||
for (j = 0; j < sortedLabels.length; j++) { |
||||
var label = sortedLabels[j]; |
||||
if (series.metric.hasOwnProperty(label)) { |
||||
reordered.push(series.metric[label]); |
||||
} else { |
||||
reordered.push(''); |
||||
} |
||||
} |
||||
} |
||||
reordered.push(parseFloat(values[1])); |
||||
table.rows.push(reordered); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
return table; |
||||
} |
||||
|
||||
transformInstantMetricData(md, options) { |
||||
var dps = [], |
||||
metricLabel = null; |
||||
metricLabel = this.createMetricLabel(md.metric, options); |
||||
dps.push([parseFloat(md.value[1]), md.value[0] * 1000]); |
||||
return { target: metricLabel, datapoints: dps }; |
||||
} |
||||
|
||||
createMetricLabel(labelData, options) { |
||||
if (_.isUndefined(options) || _.isEmpty(options.legendFormat)) { |
||||
return this.getOriginalMetricName(labelData); |
||||
} |
||||
|
||||
return this.renderTemplate(this.templateSrv.replace(options.legendFormat), labelData) || '{}'; |
||||
} |
||||
|
||||
renderTemplate(aliasPattern, aliasData) { |
||||
var aliasRegex = /\{\{\s*(.+?)\s*\}\}/g; |
||||
return aliasPattern.replace(aliasRegex, function(match, g1) { |
||||
if (aliasData[g1]) { |
||||
return aliasData[g1]; |
||||
} |
||||
return g1; |
||||
}); |
||||
} |
||||
|
||||
getOriginalMetricName(labelData) { |
||||
var metricName = labelData.__name__ || ''; |
||||
delete labelData.__name__; |
||||
var labelPart = _.map(_.toPairs(labelData), function(label) { |
||||
return label[0] + '="' + label[1] + '"'; |
||||
}).join(','); |
||||
return metricName + '{' + labelPart + '}'; |
||||
} |
||||
|
||||
transformToHistogramOverTime(seriesList) { |
||||
/* t1 = timestamp1, t2 = timestamp2 etc. |
||||
t1 t2 t3 t1 t2 t3 |
||||
le10 10 10 0 => 10 10 0 |
||||
le20 20 10 30 => 10 0 30 |
||||
le30 30 10 35 => 10 0 5 |
||||
*/ |
||||
for (let i = seriesList.length - 1; i > 0; i--) { |
||||
let topSeries = seriesList[i].datapoints; |
||||
let bottomSeries = seriesList[i - 1].datapoints; |
||||
for (let j = 0; j < topSeries.length; j++) { |
||||
topSeries[j][0] -= bottomSeries[j][0]; |
||||
} |
||||
} |
||||
|
||||
return seriesList; |
||||
} |
||||
} |
||||
|
||||
function sortSeriesByLabel(s1, s2): number { |
||||
let le1, le2; |
||||
|
||||
try { |
||||
// fail if not integer. might happen with bad queries
|
||||
le1 = parseHistogramLabel(s1.metric.le); |
||||
le2 = parseHistogramLabel(s2.metric.le); |
||||
} catch (err) { |
||||
console.log(err); |
||||
return 0; |
||||
} |
||||
|
||||
if (le1 > le2) { |
||||
return 1; |
||||
} |
||||
|
||||
if (le1 < le2) { |
||||
return -1; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
function parseHistogramLabel(le: string): number { |
||||
if (le === '+Inf') { |
||||
return +Infinity; |
||||
} |
||||
return Number(le); |
||||
} |
@ -0,0 +1,78 @@ |
||||
import { ResultTransformer } from '../result_transformer'; |
||||
|
||||
describe('Prometheus Result Transformer', () => { |
||||
let ctx: any = {}; |
||||
|
||||
beforeEach(() => { |
||||
ctx.templateSrv = { |
||||
replace: str => str, |
||||
}; |
||||
ctx.resultTransformer = new ResultTransformer(ctx.templateSrv); |
||||
}); |
||||
|
||||
describe('When resultFormat is table', () => { |
||||
var response = { |
||||
status: 'success', |
||||
data: { |
||||
resultType: 'matrix', |
||||
result: [ |
||||
{ |
||||
metric: { __name__: 'test', job: 'testjob' }, |
||||
values: [[1443454528, '3846']], |
||||
}, |
||||
{ |
||||
metric: { |
||||
__name__: 'test', |
||||
instance: 'localhost:8080', |
||||
job: 'otherjob', |
||||
}, |
||||
values: [[1443454529, '3847']], |
||||
}, |
||||
], |
||||
}, |
||||
}; |
||||
|
||||
it('should return table model', () => { |
||||
var table = ctx.resultTransformer.transformMetricDataToTable(response.data.result); |
||||
expect(table.type).toBe('table'); |
||||
expect(table.rows).toEqual([ |
||||
[1443454528000, 'test', '', 'testjob', 3846], |
||||
[1443454529000, 'test', 'localhost:8080', 'otherjob', 3847], |
||||
]); |
||||
expect(table.columns).toEqual([ |
||||
{ text: 'Time', type: 'time' }, |
||||
{ text: '__name__' }, |
||||
{ text: 'instance' }, |
||||
{ text: 'job' }, |
||||
{ text: 'Value' }, |
||||
]); |
||||
}); |
||||
}); |
||||
|
||||
describe('When resultFormat is table and instant = true', () => { |
||||
var response = { |
||||
status: 'success', |
||||
data: { |
||||
resultType: 'vector', |
||||
result: [ |
||||
{ |
||||
metric: { __name__: 'test', job: 'testjob' }, |
||||
value: [1443454528, '3846'], |
||||
}, |
||||
], |
||||
}, |
||||
}; |
||||
|
||||
it('should return table model', () => { |
||||
var table = ctx.resultTransformer.transformMetricDataToTable(response.data.result); |
||||
expect(table.type).toBe('table'); |
||||
expect(table.rows).toEqual([[1443454528000, 'test', 'testjob', 3846]]); |
||||
expect(table.columns).toEqual([ |
||||
{ text: 'Time', type: 'time' }, |
||||
{ text: '__name__' }, |
||||
{ text: 'job' }, |
||||
{ text: 'Value' }, |
||||
]); |
||||
}); |
||||
}); |
||||
}); |
Loading…
Reference in new issue