Prometheus: Revert stepMode functionality (#38982)

* Revert "Prometheus: add functionality to specify desired step interval in dashboards panels (#36422)"

This reverts commit ddf5b65c51a36168e803d471f65c9f9e81abbf57.
Co-authored-by: Ivana Huckova <ivana.huckova@gmail.com>

* Revert "Explore: add functionality for supporting different step modes in prometheus (#37829)"

This reverts commit f433cfd8d9978ee37b0ceeb0b679b335a0acf193.
Co-authored-by: Ivana Huckova <ivana.huckova@gmail.com>

* Revert stepMode BE implementation from #36796
Co-authored-by: "Ivana Huckova" <ivana.huckova@gmail.com>
This commit is contained in:
Giordano Ricci
2021-09-09 13:05:08 +01:00
committed by GitHub
parent 419ead99aa
commit dc36f15fbb
20 changed files with 76 additions and 415 deletions

View File

@ -128,10 +128,7 @@ func calculateInterval(timeRange plugins.DataTimeRange, model *simplejson.Json,
calc := interval.NewCalculator()
interval, err := calc.Calculate(timeRange, minInterval, "min")
if err != nil {
return time.Duration(0), err
}
interval := calc.Calculate(timeRange, minInterval)
return interval.Value, nil
}

View File

@ -45,11 +45,7 @@ func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) run(ctx context.Context, t
return queryResult, cloudMonitoringResponse{}, "", nil
}
intervalCalculator := interval.NewCalculator(interval.CalculatorOptions{})
interval, err := intervalCalculator.Calculate(*tsdbQuery.TimeRange, time.Duration(timeSeriesQuery.IntervalMS/1000)*time.Second, "min")
if err != nil {
queryResult.Error = err
return queryResult, cloudMonitoringResponse{}, "", nil
}
interval := intervalCalculator.Calculate(*tsdbQuery.TimeRange, time.Duration(timeSeriesQuery.IntervalMS/1000)*time.Second)
timeFormat := "2006/01/02-15:04:05"
timeSeriesQuery.Query += fmt.Sprintf(" | graph_period %s | within d'%s', d'%s'", interval.Text, from.UTC().Format(timeFormat), to.UTC().Format(timeFormat))

View File

@ -70,12 +70,9 @@ func (e *timeSeriesQuery) processQuery(q *Query, ms *es.MultiSearchRequestBuilde
if err != nil {
return err
}
intrvl, err := e.intervalCalculator.Calculate(e.dataQueries[0].TimeRange, minInterval, intervalv2.Min)
if err != nil {
return err
}
interval := e.intervalCalculator.Calculate(e.dataQueries[0].TimeRange, minInterval)
b := ms.Search(intrvl)
b := ms.Search(interval)
b.Size(0)
filters := b.Query().Bool().Filter()
filters.AddDateRangeFilter(e.client.GetTimeField(), to, from, es.DateFormatEpochMS)

View File

@ -29,7 +29,7 @@ type intervalCalculator struct {
}
type Calculator interface {
Calculate(timeRange plugins.DataTimeRange, interval time.Duration, intervalMode string) (Interval, error)
Calculate(timeRange plugins.DataTimeRange, interval time.Duration) Interval
CalculateSafeInterval(timeRange plugins.DataTimeRange, resolution int64) Interval
}
@ -55,29 +55,17 @@ func (i *Interval) Milliseconds() int64 {
return i.Value.Nanoseconds() / int64(time.Millisecond)
}
func (ic *intervalCalculator) Calculate(timerange plugins.DataTimeRange, interval time.Duration, intervalMode string) (Interval, error) {
func (ic *intervalCalculator) Calculate(timerange plugins.DataTimeRange, minInterval time.Duration) Interval {
to := timerange.MustGetTo().UnixNano()
from := timerange.MustGetFrom().UnixNano()
calculatedInterval := time.Duration((to - from) / DefaultRes)
switch intervalMode {
case "min":
if calculatedInterval < interval {
return Interval{Text: FormatDuration(interval), Value: interval}, nil
}
case "max":
if calculatedInterval > interval {
return Interval{Text: FormatDuration(interval), Value: interval}, nil
}
case "exact":
return Interval{Text: FormatDuration(interval), Value: interval}, nil
default:
return Interval{}, fmt.Errorf("unrecognized intervalMode: %v", intervalMode)
if calculatedInterval < minInterval {
return Interval{Text: FormatDuration(minInterval), Value: minInterval}
}
rounded := roundInterval(calculatedInterval)
return Interval{Text: FormatDuration(rounded), Value: rounded}, nil
return Interval{Text: FormatDuration(rounded), Value: rounded}
}
func (ic *intervalCalculator) CalculateSafeInterval(timerange plugins.DataTimeRange, safeRes int64) Interval {

View File

@ -15,29 +15,19 @@ func TestIntervalCalculator_Calculate(t *testing.T) {
calculator := NewCalculator(CalculatorOptions{})
testCases := []struct {
name string
timeRange plugins.DataTimeRange
intervalMode string
expected string
name string
timeRange plugins.DataTimeRange
expected string
}{
{"from 5m to now", plugins.NewDataTimeRange("5m", "now"), "min", "200ms"},
{"from 5m to now", plugins.NewDataTimeRange("5m", "now"), "exact", "1ms"},
{"from 5m to now", plugins.NewDataTimeRange("5m", "now"), "max", "1ms"},
{"from 15m to now", plugins.NewDataTimeRange("15m", "now"), "min", "500ms"},
{"from 15m to now", plugins.NewDataTimeRange("15m", "now"), "max", "1ms"},
{"from 15m to now", plugins.NewDataTimeRange("15m", "now"), "exact", "1ms"},
{"from 30m to now", plugins.NewDataTimeRange("30m", "now"), "min", "1s"},
{"from 30m to now", plugins.NewDataTimeRange("30m", "now"), "max", "1ms"},
{"from 30m to now", plugins.NewDataTimeRange("30m", "now"), "exact", "1ms"},
{"from 24h to now", plugins.NewDataTimeRange("24h", "now"), "min", "1m"},
{"from 24h to now", plugins.NewDataTimeRange("24h", "now"), "max", "1ms"},
{"from 24h to now", plugins.NewDataTimeRange("24h", "now"), "exact", "1ms"},
{"from 5m to now", plugins.NewDataTimeRange("5m", "now"), "200ms"},
{"from 15m to now", plugins.NewDataTimeRange("15m", "now"), "500ms"},
{"from 30m to now", plugins.NewDataTimeRange("30m", "now"), "1s"},
{"from 1h to now", plugins.NewDataTimeRange("1h", "now"), "2s"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
interval, err := calculator.Calculate(tc.timeRange, time.Millisecond*1, tc.intervalMode)
require.Nil(t, err)
interval := calculator.Calculate(tc.timeRange, time.Millisecond*1)
assert.Equal(t, tc.expected, interval.Text)
})
}

View File

@ -17,14 +17,6 @@ var (
day = time.Hour * 24
)
type IntervalMode string
const (
Min IntervalMode = "min"
Max IntervalMode = "max"
Exact IntervalMode = "exact"
)
type Interval struct {
Text string
Value time.Duration
@ -35,7 +27,7 @@ type intervalCalculator struct {
}
type Calculator interface {
Calculate(timerange backend.TimeRange, minInterval time.Duration, intervalMode IntervalMode) (Interval, error)
Calculate(timerange backend.TimeRange, minInterval time.Duration) Interval
CalculateSafeInterval(timerange backend.TimeRange, resolution int64) Interval
}
@ -61,29 +53,18 @@ func (i *Interval) Milliseconds() int64 {
return i.Value.Nanoseconds() / int64(time.Millisecond)
}
func (ic *intervalCalculator) Calculate(timerange backend.TimeRange, intrvl time.Duration, intervalMode IntervalMode) (Interval, error) {
func (ic *intervalCalculator) Calculate(timerange backend.TimeRange, minInterval time.Duration) Interval {
to := timerange.To.UnixNano()
from := timerange.From.UnixNano()
calculatedIntrvl := time.Duration((to - from) / defaultRes)
switch intervalMode {
case Min:
if calculatedIntrvl < intrvl {
return Interval{Text: interval.FormatDuration(intrvl), Value: intrvl}, nil
}
case Max:
if calculatedIntrvl > intrvl {
return Interval{Text: interval.FormatDuration(intrvl), Value: intrvl}, nil
}
case Exact:
return Interval{Text: interval.FormatDuration(intrvl), Value: intrvl}, nil
default:
return Interval{}, fmt.Errorf("unrecognized intervalMode: %v", intervalMode)
if calculatedIntrvl < minInterval {
return Interval{Text: interval.FormatDuration(minInterval), Value: minInterval}
}
rounded := roundInterval(calculatedIntrvl)
return Interval{Text: interval.FormatDuration(rounded), Value: rounded}, nil
return Interval{Text: interval.FormatDuration(rounded), Value: rounded}
}
func (ic *intervalCalculator) CalculateSafeInterval(timerange backend.TimeRange, safeRes int64) Interval {

View File

@ -7,7 +7,6 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIntervalCalculator_Calculate(t *testing.T) {
@ -16,29 +15,19 @@ func TestIntervalCalculator_Calculate(t *testing.T) {
timeNow := time.Now()
testCases := []struct {
name string
timeRange backend.TimeRange
intervalMode IntervalMode
expected string
name string
timeRange backend.TimeRange
expected string
}{
{"from 5m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(5 * time.Minute)}, Min, "200ms"},
{"from 5m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(5 * time.Minute)}, Max, "1ms"},
{"from 5m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(5 * time.Minute)}, Exact, "1ms"},
{"from 15m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(15 * time.Minute)}, Min, "500ms"},
{"from 15m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(15 * time.Minute)}, Max, "1ms"},
{"from 15m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(15 * time.Minute)}, Exact, "1ms"},
{"from 30m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(30 * time.Minute)}, Min, "1s"},
{"from 30m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(30 * time.Minute)}, Max, "1ms"},
{"from 30m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(30 * time.Minute)}, Exact, "1ms"},
{"from 1h to now", backend.TimeRange{From: timeNow, To: timeNow.Add(1440 * time.Minute)}, Min, "1m"},
{"from 1h to now", backend.TimeRange{From: timeNow, To: timeNow.Add(1440 * time.Minute)}, Max, "1ms"},
{"from 1h to now", backend.TimeRange{From: timeNow, To: timeNow.Add(1440 * time.Minute)}, Exact, "1ms"},
{"from 5m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(5 * time.Minute)}, "200ms"},
{"from 15m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(15 * time.Minute)}, "500ms"},
{"from 30m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(30 * time.Minute)}, "1s"},
{"from 1h to now", backend.TimeRange{From: timeNow, To: timeNow.Add(60 * time.Minute)}, "2s"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
interval, err := calculator.Calculate(tc.timeRange, time.Millisecond*1, tc.intervalMode)
require.Nil(t, err)
interval := calculator.Calculate(tc.timeRange, time.Millisecond*1)
assert.Equal(t, tc.expected, interval.Text)
})
}

View File

@ -200,10 +200,7 @@ func (s *Service) parseQuery(dsInfo *datasourceInfo, queryContext *backend.Query
return nil, fmt.Errorf("failed to parse Interval: %v", err)
}
interval, err := s.intervalCalculator.Calculate(query.TimeRange, dsInterval, intervalv2.Min)
if err != nil {
return nil, err
}
interval := s.intervalCalculator.Calculate(query.TimeRange, dsInterval)
var resolution int64 = 1
if model.Resolution >= 1 && model.Resolution <= 5 || model.Resolution == 10 {

View File

@ -178,8 +178,8 @@ type mockCalculator struct {
interval intervalv2.Interval
}
func (m mockCalculator) Calculate(timerange backend.TimeRange, minInterval time.Duration, intervalMode intervalv2.IntervalMode) (intervalv2.Interval, error) {
return m.interval, nil
func (m mockCalculator) Calculate(timerange backend.TimeRange, minInterval time.Duration) intervalv2.Interval {
return m.interval
}
func (m mockCalculator) CalculateSafeInterval(timerange backend.TimeRange, resolution int64) intervalv2.Interval {

View File

@ -225,11 +225,7 @@ func formatLegend(metric model.Metric, query *PrometheusQuery) string {
return string(result)
}
func (s *Service) parseQuery(queries []backend.DataQuery, dsInfo *DatasourceInfo) (
[]*PrometheusQuery, error) {
var intervalMode string
var adjustedInterval time.Duration
func (s *Service) parseQuery(queries []backend.DataQuery, dsInfo *DatasourceInfo) ([]*PrometheusQuery, error) {
qs := []*PrometheusQuery{}
for _, queryModel := range queries {
jsonModel, err := simplejson.NewJson(queryModel.JSON)
@ -247,30 +243,18 @@ func (s *Service) parseQuery(queries []backend.DataQuery, dsInfo *DatasourceInfo
end := queryModel.TimeRange.To
queryInterval := jsonModel.Get("interval").MustString("")
foundInterval, err := intervalv2.GetIntervalFrom(dsInfo.TimeInterval, queryInterval, 0, 15*time.Second)
hasQueryInterval := queryInterval != ""
// Only use stepMode if we have interval in query, otherwise use "min"
if hasQueryInterval {
intervalMode = jsonModel.Get("stepMode").MustString("min")
} else {
intervalMode = "min"
}
// Calculate interval value from query or data source settings or use default value
minInterval, err := intervalv2.GetIntervalFrom(dsInfo.TimeInterval, queryInterval, 0, 15*time.Second)
if err != nil {
return nil, err
}
calculatedInterval, err := s.intervalCalculator.Calculate(queries[0].TimeRange, foundInterval, intervalv2.IntervalMode(intervalMode))
if err != nil {
return nil, err
}
calculatedInterval := s.intervalCalculator.Calculate(queries[0].TimeRange, minInterval)
safeInterval := s.intervalCalculator.CalculateSafeInterval(queries[0].TimeRange, int64(safeRes))
adjustedInterval := safeInterval.Value
if calculatedInterval.Value > safeInterval.Value {
adjustedInterval = calculatedInterval.Value
} else {
adjustedInterval = safeInterval.Value
}
intervalFactor := jsonModel.Get("intervalFactor").MustInt64(1)

View File

@ -45,7 +45,7 @@ func TestPrometheus(t *testing.T) {
require.Equal(t, `http_request_total{app="backend", device="mobile"}`, formatLegend(metric, query))
})
t.Run("parsing query model with step and default stepMode", func(t *testing.T) {
t.Run("parsing query model with step", func(t *testing.T) {
query := queryContext(`{
"expr": "go_goroutines",
"format": "time_series",
@ -61,78 +61,6 @@ func TestPrometheus(t *testing.T) {
require.Equal(t, time.Second*30, models[0].Step)
})
t.Run("parsing query model with step and exact stepMode", func(t *testing.T) {
query := queryContext(`{
"expr": "go_goroutines",
"format": "time_series",
"refId": "A",
"stepMode": "exact",
"interval": "7s"
}`)
timeRange := backend.TimeRange{
From: now,
To: now.Add(12 * time.Hour),
}
query.TimeRange = timeRange
models, err := service.parseQuery([]backend.DataQuery{query}, &DatasourceInfo{})
require.NoError(t, err)
require.Equal(t, time.Second*7, models[0].Step)
})
t.Run("parsing query model with short step and max stepMode", func(t *testing.T) {
query := queryContext(`{
"expr": "go_goroutines",
"format": "time_series",
"refId": "A",
"stepMode": "max",
"interval": "6s"
}`)
timeRange := backend.TimeRange{
From: now,
To: now.Add(12 * time.Hour),
}
query.TimeRange = timeRange
models, err := service.parseQuery([]backend.DataQuery{query}, &DatasourceInfo{})
require.NoError(t, err)
require.Equal(t, time.Second*6, models[0].Step)
})
t.Run("parsing query model with long step and max stepMode", func(t *testing.T) {
query := queryContext(`{
"expr": "go_goroutines",
"format": "time_series",
"refId": "A",
"stepMode": "max",
"interval": "100s"
}`)
timeRange := backend.TimeRange{
From: now,
To: now.Add(12 * time.Hour),
}
query.TimeRange = timeRange
models, err := service.parseQuery([]backend.DataQuery{query}, &DatasourceInfo{})
require.NoError(t, err)
require.Equal(t, time.Second*30, models[0].Step)
})
t.Run("parsing query model with unsafe interval", func(t *testing.T) {
query := queryContext(`{
"expr": "go_goroutines",
"format": "time_series",
"refId": "A",
"stepMode": "max",
"interval": "2s"
}`)
timeRange := backend.TimeRange{
From: now,
To: now.Add(12 * time.Hour),
}
query.TimeRange = timeRange
models, err := service.parseQuery([]backend.DataQuery{query}, &DatasourceInfo{})
require.NoError(t, err)
require.Equal(t, time.Second*5, models[0].Step)
})
t.Run("parsing query model without step parameter", func(t *testing.T) {
query := queryContext(`{
"expr": "go_goroutines",

View File

@ -377,10 +377,7 @@ var Interpolate = func(query backend.DataQuery, timeRange backend.TimeRange, tim
if err != nil {
return "", err
}
interval, err := sqlIntervalCalculator.Calculate(timeRange, minInterval, "min")
if err != nil {
return "", err
}
interval := sqlIntervalCalculator.Calculate(timeRange, minInterval)
sql = strings.ReplaceAll(sql, "$__interval_ms", strconv.FormatInt(interval.Milliseconds(), 10))
sql = strings.ReplaceAll(sql, "$__interval", interval.Text)

View File

@ -3,20 +3,16 @@ import React, { memo } from 'react';
import { css, cx } from '@emotion/css';
// Types
import { InlineFormLabel, RadioButtonGroup, Select } from '@grafana/ui';
import { PromQuery, StepMode } from '../types';
import { InlineFormLabel, RadioButtonGroup } from '@grafana/ui';
import { PromQuery } from '../types';
import { PromExemplarField } from './PromExemplarField';
import { PrometheusDatasource } from '../datasource';
import { STEP_MODES } from './PromQueryEditor';
import { SelectableValue } from '@grafana/data';
export interface PromExploreExtraFieldProps {
queryType: string;
stepValue: string;
stepMode: StepMode;
query: PromQuery;
onStepModeChange: (option: SelectableValue<StepMode>) => void;
onStepIntervalChange: (e: React.SyntheticEvent<HTMLInputElement>) => void;
onStepChange: (e: React.SyntheticEvent<HTMLInputElement>) => void;
onKeyDownFunc: (e: React.KeyboardEvent<HTMLInputElement>) => void;
onQueryTypeChange: (value: string) => void;
onChange: (value: PromQuery) => void;
@ -24,18 +20,7 @@ export interface PromExploreExtraFieldProps {
}
export const PromExploreExtraField: React.FC<PromExploreExtraFieldProps> = memo(
({
queryType,
stepValue,
stepMode,
query,
onChange,
onStepModeChange,
onStepIntervalChange,
onQueryTypeChange,
onKeyDownFunc,
datasource,
}) => {
({ queryType, stepValue, query, onChange, onStepChange, onQueryTypeChange, onKeyDownFunc, datasource }) => {
const rangeOptions = [
{ value: 'range', label: 'Range', description: 'Run query over a range of time.' },
{
@ -82,20 +67,11 @@ export const PromExploreExtraField: React.FC<PromExploreExtraFieldProps> = memo(
>
Step
</InlineFormLabel>
<Select
menuShouldPortal
className={'select-container'}
width={16}
isSearchable={false}
options={STEP_MODES}
onChange={onStepModeChange}
value={stepMode}
/>
<input
type={'text'}
className="gf-form-input width-4"
placeholder={'auto'}
onChange={onStepIntervalChange}
onChange={onStepChange}
onKeyDown={onKeyDownFunc}
value={stepValue}
/>

View File

@ -1,10 +1,10 @@
import React, { memo, FC, useEffect } from 'react';
// Types
import { ExploreQueryFieldProps, SelectableValue } from '@grafana/data';
import { ExploreQueryFieldProps } from '@grafana/data';
import { PrometheusDatasource } from '../datasource';
import { PromQuery, PromOptions, StepMode } from '../types';
import { PromQuery, PromOptions } from '../types';
import PromQueryField from './PromQueryField';
import { PromExploreExtraField } from './PromExploreExtraField';
@ -26,19 +26,7 @@ export const PromExploreQueryEditor: FC<Props> = (props: Props) => {
onChange(nextQuery);
}
function onChangeStepMode(mode: StepMode) {
const { query, onChange } = props;
const nextQuery = { ...query, stepMode: mode };
onChange(nextQuery);
}
function onStepModeChange(option: SelectableValue<StepMode>) {
if (option.value) {
onChangeStepMode(option.value);
}
}
function onStepIntervalChange(e: React.SyntheticEvent<HTMLInputElement>) {
function onStepChange(e: React.SyntheticEvent<HTMLInputElement>) {
if (e.currentTarget.value !== query.interval) {
onChangeQueryStep(e.currentTarget.value);
}
@ -78,10 +66,8 @@ export const PromExploreQueryEditor: FC<Props> = (props: Props) => {
// Select "both" as default option when Explore is opened. In legacy requests, range and instant can be undefined. In this case, we want to run queries with "both".
queryType={query.range === query.instant ? 'both' : query.instant ? 'instant' : 'range'}
stepValue={query.interval || ''}
stepMode={query.stepMode || 'min'}
onQueryTypeChange={onQueryTypeChange}
onStepModeChange={onStepModeChange}
onStepIntervalChange={onStepIntervalChange}
onStepChange={onStepChange}
onKeyDownFunc={onReturnKeyDown}
query={query}
onChange={onChange}

View File

@ -4,7 +4,7 @@ import React, { PureComponent } from 'react';
// Types
import { InlineFormLabel, LegacyForms, Select } from '@grafana/ui';
import { SelectableValue } from '@grafana/data';
import { PromQuery, StepMode } from '../types';
import { PromQuery } from '../types';
import PromQueryField from './PromQueryField';
import PromLink from './PromLink';
@ -24,29 +24,11 @@ const INTERVAL_FACTOR_OPTIONS: Array<SelectableValue<number>> = map([1, 2, 3, 4,
label: '1/' + value,
}));
export const DEFAULT_STEP_MODE: SelectableValue<StepMode> = {
value: 'min',
label: 'Minimum',
};
export const STEP_MODES: Array<SelectableValue<StepMode>> = [
DEFAULT_STEP_MODE,
{
value: 'max',
label: 'Maximum',
},
{
value: 'exact',
label: 'Exact',
},
];
interface State {
legendFormat?: string;
formatOption: SelectableValue<string>;
interval?: string;
intervalFactorOption: SelectableValue<number>;
stepMode: SelectableValue<StepMode>;
instant: boolean;
exemplar: boolean;
}
@ -63,7 +45,6 @@ export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State>
legendFormat: '',
interval: '',
exemplar: true,
stepMode: DEFAULT_STEP_MODE.value,
};
const query = Object.assign({}, defaultQuery, props.query);
this.query = query;
@ -76,8 +57,6 @@ export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State>
formatOption: FORMAT_OPTIONS.find((option) => option.value === query.format) || FORMAT_OPTIONS[0],
intervalFactorOption:
INTERVAL_FACTOR_OPTIONS.find((option) => option.value === query.intervalFactor) || INTERVAL_FACTOR_OPTIONS[0],
// Step mode
stepMode: STEP_MODES.find((option) => option.value === query.stepMode) || DEFAULT_STEP_MODE,
// Switch options
instant: Boolean(query.instant),
exemplar: Boolean(query.exemplar),
@ -110,11 +89,6 @@ export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State>
this.setState({ intervalFactorOption: option }, this.onRunQuery);
};
onStepChange = (option: SelectableValue<StepMode>) => {
this.query.stepMode = option.value;
this.setState({ stepMode: option }, this.onRunQuery);
};
onLegendChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
const legendFormat = e.currentTarget.value;
this.query.legendFormat = legendFormat;
@ -136,7 +110,7 @@ export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State>
render() {
const { datasource, query, range, data } = this.props;
const { formatOption, instant, interval, intervalFactorOption, stepMode, legendFormat, exemplar } = this.state;
const { formatOption, instant, interval, intervalFactorOption, legendFormat, exemplar } = this.state;
return (
<PromQueryField
@ -170,37 +144,27 @@ export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State>
<div className="gf-form">
<InlineFormLabel
width={5}
width={7}
tooltip={
<>
Use &apos;Minimum&apos; or &apos;Maximum&apos; step mode to set the lower or upper bounds
respectively on the interval between data points. For example, set &quot;minimum 1h&quot; to hint
that measurements were not taken more frequently. Use the &apos;Exact&apos; step mode to set an
exact interval between data points. <code>$__interval</code> and <code>$__rate_interval</code> are
supported.
An additional lower limit for the step parameter of the Prometheus query and for the{' '}
<code>$__interval</code> and <code>$__rate_interval</code> variables. The limit is absolute and not
modified by the &quot;Resolution&quot; setting.
</>
}
>
Step
Min step
</InlineFormLabel>
<Select
menuShouldPortal
className={'select-container'}
width={16}
isSearchable={false}
options={STEP_MODES}
onChange={this.onStepChange}
value={stepMode}
/>
<input
type="text"
className="gf-form-input width-4"
placeholder="15s"
className="gf-form-input width-8"
placeholder={interval}
onChange={this.onIntervalChange}
onBlur={this.onRunQuery}
value={interval}
/>
</div>
<div className="gf-form">
<div className="gf-form-label">Resolution</div>
<Select
@ -211,11 +175,12 @@ export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State>
value={intervalFactorOption}
/>
</div>
<div className="gf-form">
<div className="gf-form-label width-7">Format</div>
<Select
menuShouldPortal
className={'select-container'}
className="select-container"
width={16}
isSearchable={false}
options={FORMAT_OPTIONS}

View File

@ -16,8 +16,7 @@ exports[`PromExploreQueryEditor should render component 1`] = `
onChange={[MockFunction]}
onKeyDownFunc={[Function]}
onQueryTypeChange={[Function]}
onStepIntervalChange={[Function]}
onStepModeChange={[Function]}
onStepChange={[Function]}
query={
Object {
"expr": "",
@ -26,7 +25,6 @@ exports[`PromExploreQueryEditor should render component 1`] = `
}
}
queryType="both"
stepMode="min"
stepValue="1s"
/>
}

View File

@ -31,7 +31,8 @@ exports[`Render PromQueryEditor with basic options should render 1`] = `
<FormLabel
tooltip={
<React.Fragment>
Use 'Minimum' or 'Maximum' step mode to set the lower or upper bounds respectively on the interval between data points. For example, set "minimum 1h" to hint that measurements were not taken more frequently. Use the 'Exact' step mode to set an exact interval between data points.
An additional lower limit for the step parameter of the Prometheus query and for the
<code>
$__interval
</code>
@ -39,47 +40,18 @@ exports[`Render PromQueryEditor with basic options should render 1`] = `
<code>
$__rate_interval
</code>
are supported.
variables. The limit is absolute and not modified by the "Resolution" setting.
</React.Fragment>
}
width={5}
width={7}
>
Step
Min step
</FormLabel>
<Select
className="select-container"
isSearchable={false}
menuShouldPortal={true}
onChange={[Function]}
options={
Array [
Object {
"label": "Minimum",
"value": "min",
},
Object {
"label": "Maximum",
"value": "max",
},
Object {
"label": "Exact",
"value": "exact",
},
]
}
value={
Object {
"label": "Minimum",
"value": "min",
}
}
width={16}
/>
<input
className="gf-form-input width-4"
className="gf-form-input width-8"
onBlur={[Function]}
onChange={[Function]}
placeholder="15s"
placeholder=""
type="text"
value=""
/>
@ -192,7 +164,6 @@ exports[`Render PromQueryEditor with basic options should render 1`] = `
"interval": "",
"legendFormat": "",
"refId": "A",
"stepMode": "min",
}
}
/>

View File

@ -18,7 +18,7 @@ import {
prometheusRegularEscape,
prometheusSpecialRegexEscape,
} from './datasource';
import { PromOptions, PromQuery, StepMode } from './types';
import { PromOptions, PromQuery } from './types';
import { VariableHide } from '../../../features/variables/types';
import { describe } from '../../../../test/lib/common';
import { QueryOptions } from 'app/types';
@ -1685,60 +1685,6 @@ describe('PrometheusDatasource', () => {
templateSrvStub.replace = jest.fn((a: string) => a);
});
});
describe('adjustInterval', () => {
const dynamicInterval = 15;
const stepInterval = 35;
const range = 1642;
describe('when max step option is used', () => {
it('should return the minimum interval', () => {
let intervalFactor = 1;
let interval = ds.adjustInterval(dynamicInterval, stepInterval, range, intervalFactor, 'max');
expect(interval).toBe(dynamicInterval * intervalFactor);
intervalFactor = 3;
interval = ds.adjustInterval(dynamicInterval, stepInterval, range, intervalFactor, 'max');
expect(interval).toBe(stepInterval);
});
});
describe('when min step option is used', () => {
it('should return the maximum interval', () => {
let intervalFactor = 1;
let interval = ds.adjustInterval(dynamicInterval, stepInterval, range, intervalFactor, 'min');
expect(interval).toBe(stepInterval);
intervalFactor = 3;
interval = ds.adjustInterval(dynamicInterval, stepInterval, range, intervalFactor, 'min');
expect(interval).toBe(dynamicInterval * intervalFactor);
});
});
describe('when exact step option is used', () => {
it('should return the stepInterval * intervalFactor', () => {
let intervalFactor = 3;
let interval = ds.adjustInterval(dynamicInterval, stepInterval, range, intervalFactor, 'exact');
expect(interval).toBe(stepInterval * intervalFactor);
});
});
it('should not return a value less than the safe interval', () => {
let newStepInterval = 0.13;
let intervalFactor = 1;
let stepMode: StepMode = 'min';
let safeInterval = range / 11000;
if (safeInterval > 1) {
safeInterval = Math.ceil(safeInterval);
}
let interval = ds.adjustInterval(dynamicInterval, newStepInterval, range, intervalFactor, stepMode);
expect(interval).toBeGreaterThanOrEqual(safeInterval);
stepMode = 'max';
interval = ds.adjustInterval(dynamicInterval, newStepInterval, range, intervalFactor, stepMode);
expect(interval).toBeGreaterThanOrEqual(safeInterval);
stepMode = 'exact';
interval = ds.adjustInterval(dynamicInterval, newStepInterval, range, intervalFactor, stepMode);
expect(interval).toBeGreaterThanOrEqual(safeInterval);
});
});
});
describe('PrometheusDatasource for POST', () => {

View File

@ -39,11 +39,9 @@ import {
PromQueryRequest,
PromScalarData,
PromVectorData,
StepMode,
} from './types';
import { PrometheusVariableSupport } from './variables';
import PrometheusMetricFindQuery from './metric_find_query';
import { DEFAULT_STEP_MODE } from './components/PromQueryEditor';
export const ANNOTATION_QUERY_STEP_DEFAULT = '60s';
const EXEMPLARS_NOT_AVAILABLE = 'Exemplars for this query are not available.';
@ -417,12 +415,11 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
end: 0,
};
const range = Math.ceil(end - start);
// target.stepMode specifies whether to use min, max or exact step
const stepMode = target.stepMode || (DEFAULT_STEP_MODE.value as StepMode);
// options.interval is the dynamically calculated interval
let interval: number = rangeUtil.intervalToSeconds(options.interval);
// Minimum interval ("Min step"), if specified for the query, or same as interval otherwise.
const stepInterval = rangeUtil.intervalToSeconds(
const minInterval = rangeUtil.intervalToSeconds(
this.templateSrv.replace(target.interval || options.interval, options.scopedVars)
);
// Scrape interval as specified for the query ("Min step") or otherwise taken from the datasource.
@ -433,7 +430,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
const intervalFactor = target.intervalFactor || 1;
// Adjust the interval to take into account any specified minimum and interval factor plus Prometheus limits
const adjustedInterval = this.adjustInterval(interval, stepInterval, range, intervalFactor, stepMode);
const adjustedInterval = this.adjustInterval(interval, minInterval, range, intervalFactor);
let scopedVars = {
...options.scopedVars,
...this.getRangeScopedVars(options.range),
@ -486,13 +483,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
return { __rate_interval: { text: rateInterval + 's', value: rateInterval + 's' } };
}
adjustInterval(
dynamicInterval: number,
stepInterval: number,
range: number,
intervalFactor: number,
stepMode: StepMode
) {
adjustInterval(interval: number, minInterval: number, range: number, intervalFactor: number) {
// Prometheus will drop queries that might return more than 11000 data points.
// Calculate a safe interval as an additional minimum to take into account.
// Fractional safeIntervals are allowed, however serve little purpose if the interval is greater than 1
@ -501,20 +492,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
if (safeInterval > 1) {
safeInterval = Math.ceil(safeInterval);
}
//Calculate adjusted interval based on the current step option
let adjustedInterval = safeInterval;
if (stepMode === 'min') {
adjustedInterval = Math.max(dynamicInterval * intervalFactor, stepInterval, safeInterval);
} else if (stepMode === 'max') {
adjustedInterval = Math.min(dynamicInterval * intervalFactor, stepInterval);
if (adjustedInterval < safeInterval) {
adjustedInterval = safeInterval;
}
} else if (stepMode === 'exact') {
adjustedInterval = Math.max(stepInterval * intervalFactor, safeInterval);
}
return adjustedInterval;
return Math.max(interval * intervalFactor, minInterval, safeInterval);
}
performTimeSeriesQuery(query: PromQueryRequest, start: number, end: number) {

View File

@ -10,7 +10,6 @@ export interface PromQuery extends DataQuery {
hinting?: boolean;
interval?: string;
intervalFactor?: number;
stepMode?: StepMode;
legendFormat?: string;
valueWithRefId?: boolean;
requestId?: string;
@ -18,8 +17,6 @@ export interface PromQuery extends DataQuery {
showingTable?: boolean;
}
export type StepMode = 'min' | 'max' | 'exact';
export interface PromOptions extends DataSourceJsonData {
timeInterval: string;
queryTimeout: string;