mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #13787 from grafana/davkal/explore-transactions
Explore: query transactions for faster result display
This commit is contained in:
@@ -176,7 +176,6 @@ export class PrometheusDatasource {
|
||||
|
||||
return this.$q.all(allQueryPromise).then(responseList => {
|
||||
let result = [];
|
||||
let hints = [];
|
||||
|
||||
_.each(responseList, (response, index) => {
|
||||
if (response.status === 'error') {
|
||||
@@ -196,19 +195,14 @@ export class PrometheusDatasource {
|
||||
end: queries[index].end,
|
||||
query: queries[index].expr,
|
||||
responseListLength: responseList.length,
|
||||
responseIndex: index,
|
||||
refId: activeTargets[index].refId,
|
||||
valueWithRefId: activeTargets[index].valueWithRefId,
|
||||
};
|
||||
const series = this.resultTransformer.transform(response, transformerOptions);
|
||||
result = [...result, ...series];
|
||||
|
||||
if (queries[index].hinting) {
|
||||
const queryHints = getQueryHints(series, this);
|
||||
hints = [...hints, ...queryHints];
|
||||
}
|
||||
});
|
||||
|
||||
return { data: result, hints };
|
||||
return { data: result };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -437,6 +431,10 @@ export class PrometheusDatasource {
|
||||
return state;
|
||||
}
|
||||
|
||||
getQueryHints(query: string, result: any[]) {
|
||||
return getQueryHints(query, result, this);
|
||||
}
|
||||
|
||||
loadRules() {
|
||||
this.metadataRequest('/api/v1/rules')
|
||||
.then(res => res.data || res.json())
|
||||
|
||||
@@ -1,100 +1,92 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export function getQueryHints(series: any[], datasource?: any): any[] {
|
||||
const hints = series.map((s, i) => {
|
||||
const query: string = s.query;
|
||||
const index: number = s.responseIndex;
|
||||
if (query === undefined || index === undefined) {
|
||||
return null;
|
||||
}
|
||||
export function getQueryHints(query: string, series?: any[], datasource?: any): any[] {
|
||||
const hints = [];
|
||||
|
||||
// ..._bucket metric needs a histogram_quantile()
|
||||
const histogramMetric = query.trim().match(/^\w+_bucket$/);
|
||||
if (histogramMetric) {
|
||||
const label = 'Time series has buckets, you probably wanted a histogram.';
|
||||
return {
|
||||
index,
|
||||
// ..._bucket metric needs a histogram_quantile()
|
||||
const histogramMetric = query.trim().match(/^\w+_bucket$/);
|
||||
if (histogramMetric) {
|
||||
const label = 'Time series has buckets, you probably wanted a histogram.';
|
||||
hints.push({
|
||||
type: 'HISTOGRAM_QUANTILE',
|
||||
label,
|
||||
fix: {
|
||||
label: 'Fix by adding histogram_quantile().',
|
||||
action: {
|
||||
type: 'ADD_HISTOGRAM_QUANTILE',
|
||||
query,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Check for monotony on series (table results are being ignored here)
|
||||
if (series && series.length > 0) {
|
||||
series.forEach(s => {
|
||||
const datapoints: number[][] = s.datapoints;
|
||||
if (query.indexOf('rate(') === -1 && datapoints.length > 1) {
|
||||
let increasing = false;
|
||||
const nonNullData = datapoints.filter(dp => dp[0] !== null);
|
||||
const monotonic = nonNullData.every((dp, index) => {
|
||||
if (index === 0) {
|
||||
return true;
|
||||
}
|
||||
increasing = increasing || dp[0] > nonNullData[index - 1][0];
|
||||
// monotonic?
|
||||
return dp[0] >= nonNullData[index - 1][0];
|
||||
});
|
||||
if (increasing && monotonic) {
|
||||
const simpleMetric = query.trim().match(/^\w+$/);
|
||||
let label = 'Time series is monotonously increasing.';
|
||||
let fix;
|
||||
if (simpleMetric) {
|
||||
fix = {
|
||||
label: 'Fix by adding rate().',
|
||||
action: {
|
||||
type: 'ADD_RATE',
|
||||
query,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
label = `${label} Try applying a rate() function.`;
|
||||
}
|
||||
hints.push({
|
||||
type: 'APPLY_RATE',
|
||||
label,
|
||||
fix,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Check for recording rules expansion
|
||||
if (datasource && datasource.ruleMappings) {
|
||||
const mapping = datasource.ruleMappings;
|
||||
const mappingForQuery = Object.keys(mapping).reduce((acc, ruleName) => {
|
||||
if (query.search(ruleName) > -1) {
|
||||
return {
|
||||
...acc,
|
||||
[ruleName]: mapping[ruleName],
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
if (_.size(mappingForQuery) > 0) {
|
||||
const label = 'Query contains recording rules.';
|
||||
hints.push({
|
||||
type: 'EXPAND_RULES',
|
||||
label,
|
||||
fix: {
|
||||
label: 'Fix by adding histogram_quantile().',
|
||||
label: 'Expand rules',
|
||||
action: {
|
||||
type: 'ADD_HISTOGRAM_QUANTILE',
|
||||
type: 'EXPAND_RULES',
|
||||
query,
|
||||
index,
|
||||
mapping: mappingForQuery,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Check for monotony
|
||||
const datapoints: number[][] = s.datapoints;
|
||||
if (query.indexOf('rate(') === -1 && datapoints.length > 1) {
|
||||
let increasing = false;
|
||||
const nonNullData = datapoints.filter(dp => dp[0] !== null);
|
||||
const monotonic = nonNullData.every((dp, index) => {
|
||||
if (index === 0) {
|
||||
return true;
|
||||
}
|
||||
increasing = increasing || dp[0] > nonNullData[index - 1][0];
|
||||
// monotonic?
|
||||
return dp[0] >= nonNullData[index - 1][0];
|
||||
});
|
||||
if (increasing && monotonic) {
|
||||
const simpleMetric = query.trim().match(/^\w+$/);
|
||||
let label = 'Time series is monotonously increasing.';
|
||||
let fix;
|
||||
if (simpleMetric) {
|
||||
fix = {
|
||||
label: 'Fix by adding rate().',
|
||||
action: {
|
||||
type: 'ADD_RATE',
|
||||
query,
|
||||
index,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
label = `${label} Try applying a rate() function.`;
|
||||
}
|
||||
return {
|
||||
label,
|
||||
index,
|
||||
fix,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Check for recording rules expansion
|
||||
if (datasource && datasource.ruleMappings) {
|
||||
const mapping = datasource.ruleMappings;
|
||||
const mappingForQuery = Object.keys(mapping).reduce((acc, ruleName) => {
|
||||
if (query.search(ruleName) > -1) {
|
||||
return {
|
||||
...acc,
|
||||
[ruleName]: mapping[ruleName],
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
if (_.size(mappingForQuery) > 0) {
|
||||
const label = 'Query contains recording rules.';
|
||||
return {
|
||||
label,
|
||||
index,
|
||||
fix: {
|
||||
label: 'Expand rules',
|
||||
action: {
|
||||
type: 'EXPAND_RULES',
|
||||
query,
|
||||
index,
|
||||
mapping: mappingForQuery,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// No hint found
|
||||
return null;
|
||||
});
|
||||
return hints;
|
||||
}
|
||||
return hints.length > 0 ? hints : null;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,14 @@ export class ResultTransformer {
|
||||
const prometheusResult = response.data.data.result;
|
||||
|
||||
if (options.format === 'table') {
|
||||
return [this.transformMetricDataToTable(prometheusResult, options.responseListLength, options.refId)];
|
||||
return [
|
||||
this.transformMetricDataToTable(
|
||||
prometheusResult,
|
||||
options.responseListLength,
|
||||
options.refId,
|
||||
options.valueWithRefId
|
||||
),
|
||||
];
|
||||
} else if (options.format === 'heatmap') {
|
||||
let seriesList = [];
|
||||
prometheusResult.sort(sortSeriesByLabel);
|
||||
@@ -66,12 +73,11 @@ export class ResultTransformer {
|
||||
return {
|
||||
datapoints: dps,
|
||||
query: options.query,
|
||||
responseIndex: options.responseIndex,
|
||||
target: metricLabel,
|
||||
};
|
||||
}
|
||||
|
||||
transformMetricDataToTable(md, resultCount: number, refId: string) {
|
||||
transformMetricDataToTable(md, resultCount: number, refId: string, valueWithRefId?: boolean) {
|
||||
const table = new TableModel();
|
||||
let i, j;
|
||||
const metricLabels = {};
|
||||
@@ -96,7 +102,7 @@ export class ResultTransformer {
|
||||
metricLabels[label] = labelIndex + 1;
|
||||
table.columns.push({ text: label, filterable: !label.startsWith('__') });
|
||||
});
|
||||
const valueText = resultCount > 1 ? `Value #${refId}` : 'Value';
|
||||
const valueText = resultCount > 1 || valueWithRefId ? `Value #${refId}` : 'Value';
|
||||
table.columns.push({ text: valueText });
|
||||
|
||||
// Populate rows, set value to empty string when label not present.
|
||||
|
||||
@@ -2,34 +2,31 @@ import { getQueryHints } from '../query_hints';
|
||||
|
||||
describe('getQueryHints()', () => {
|
||||
it('returns no hints for no series', () => {
|
||||
expect(getQueryHints([])).toEqual([]);
|
||||
expect(getQueryHints('', [])).toEqual(null);
|
||||
});
|
||||
|
||||
it('returns no hints for empty series', () => {
|
||||
expect(getQueryHints([{ datapoints: [], query: '' }])).toEqual([null]);
|
||||
expect(getQueryHints('', [{ datapoints: [] }])).toEqual(null);
|
||||
});
|
||||
|
||||
it('returns no hint for a monotonously decreasing series', () => {
|
||||
const series = [{ datapoints: [[23, 1000], [22, 1001]], query: 'metric', responseIndex: 0 }];
|
||||
const hints = getQueryHints(series);
|
||||
expect(hints).toEqual([null]);
|
||||
const series = [{ datapoints: [[23, 1000], [22, 1001]] }];
|
||||
const hints = getQueryHints('metric', series);
|
||||
expect(hints).toEqual(null);
|
||||
});
|
||||
|
||||
it('returns no hint for a flat series', () => {
|
||||
const series = [
|
||||
{ datapoints: [[null, 1000], [23, 1001], [null, 1002], [23, 1003]], query: 'metric', responseIndex: 0 },
|
||||
];
|
||||
const hints = getQueryHints(series);
|
||||
expect(hints).toEqual([null]);
|
||||
const series = [{ datapoints: [[null, 1000], [23, 1001], [null, 1002], [23, 1003]] }];
|
||||
const hints = getQueryHints('metric', series);
|
||||
expect(hints).toEqual(null);
|
||||
});
|
||||
|
||||
it('returns a rate hint for a monotonously increasing series', () => {
|
||||
const series = [{ datapoints: [[23, 1000], [24, 1001]], query: 'metric', responseIndex: 0 }];
|
||||
const hints = getQueryHints(series);
|
||||
const series = [{ datapoints: [[23, 1000], [24, 1001]] }];
|
||||
const hints = getQueryHints('metric', series);
|
||||
expect(hints.length).toBe(1);
|
||||
expect(hints[0]).toMatchObject({
|
||||
label: 'Time series is monotonously increasing.',
|
||||
index: 0,
|
||||
fix: {
|
||||
action: {
|
||||
type: 'ADD_RATE',
|
||||
@@ -40,26 +37,25 @@ describe('getQueryHints()', () => {
|
||||
});
|
||||
|
||||
it('returns no rate hint for a monotonously increasing series that already has a rate', () => {
|
||||
const series = [{ datapoints: [[23, 1000], [24, 1001]], query: 'rate(metric[1m])', responseIndex: 0 }];
|
||||
const hints = getQueryHints(series);
|
||||
expect(hints).toEqual([null]);
|
||||
const series = [{ datapoints: [[23, 1000], [24, 1001]] }];
|
||||
const hints = getQueryHints('rate(metric[1m])', series);
|
||||
expect(hints).toEqual(null);
|
||||
});
|
||||
|
||||
it('returns a rate hint w/o action for a complex monotonously increasing series', () => {
|
||||
const series = [{ datapoints: [[23, 1000], [24, 1001]], query: 'sum(metric)', responseIndex: 0 }];
|
||||
const hints = getQueryHints(series);
|
||||
const series = [{ datapoints: [[23, 1000], [24, 1001]] }];
|
||||
const hints = getQueryHints('sum(metric)', series);
|
||||
expect(hints.length).toBe(1);
|
||||
expect(hints[0].label).toContain('rate()');
|
||||
expect(hints[0].fix).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns a rate hint for a monotonously increasing series with missing data', () => {
|
||||
const series = [{ datapoints: [[23, 1000], [null, 1001], [24, 1002]], query: 'metric', responseIndex: 0 }];
|
||||
const hints = getQueryHints(series);
|
||||
const series = [{ datapoints: [[23, 1000], [null, 1001], [24, 1002]] }];
|
||||
const hints = getQueryHints('metric', series);
|
||||
expect(hints.length).toBe(1);
|
||||
expect(hints[0]).toMatchObject({
|
||||
label: 'Time series is monotonously increasing.',
|
||||
index: 0,
|
||||
fix: {
|
||||
action: {
|
||||
type: 'ADD_RATE',
|
||||
@@ -70,12 +66,11 @@ describe('getQueryHints()', () => {
|
||||
});
|
||||
|
||||
it('returns a histogram hint for a bucket series', () => {
|
||||
const series = [{ datapoints: [[23, 1000]], query: 'metric_bucket', responseIndex: 0 }];
|
||||
const hints = getQueryHints(series);
|
||||
const series = [{ datapoints: [[23, 1000]] }];
|
||||
const hints = getQueryHints('metric_bucket', series);
|
||||
expect(hints.length).toBe(1);
|
||||
expect(hints[0]).toMatchObject({
|
||||
label: 'Time series has buckets, you probably wanted a histogram.',
|
||||
index: 0,
|
||||
fix: {
|
||||
action: {
|
||||
type: 'ADD_HISTOGRAM_QUANTILE',
|
||||
|
||||
Reference in New Issue
Block a user