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() calc := interval.NewCalculator()
interval, err := calc.Calculate(timeRange, minInterval, "min") interval := calc.Calculate(timeRange, minInterval)
if err != nil {
return time.Duration(0), err
}
return interval.Value, nil return interval.Value, nil
} }

View File

@ -45,11 +45,7 @@ func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) run(ctx context.Context, t
return queryResult, cloudMonitoringResponse{}, "", nil return queryResult, cloudMonitoringResponse{}, "", nil
} }
intervalCalculator := interval.NewCalculator(interval.CalculatorOptions{}) intervalCalculator := interval.NewCalculator(interval.CalculatorOptions{})
interval, err := intervalCalculator.Calculate(*tsdbQuery.TimeRange, time.Duration(timeSeriesQuery.IntervalMS/1000)*time.Second, "min") interval := intervalCalculator.Calculate(*tsdbQuery.TimeRange, time.Duration(timeSeriesQuery.IntervalMS/1000)*time.Second)
if err != nil {
queryResult.Error = err
return queryResult, cloudMonitoringResponse{}, "", nil
}
timeFormat := "2006/01/02-15:04:05" 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)) 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 { if err != nil {
return err return err
} }
intrvl, err := e.intervalCalculator.Calculate(e.dataQueries[0].TimeRange, minInterval, intervalv2.Min) interval := e.intervalCalculator.Calculate(e.dataQueries[0].TimeRange, minInterval)
if err != nil {
return err
}
b := ms.Search(intrvl) b := ms.Search(interval)
b.Size(0) b.Size(0)
filters := b.Query().Bool().Filter() filters := b.Query().Bool().Filter()
filters.AddDateRangeFilter(e.client.GetTimeField(), to, from, es.DateFormatEpochMS) filters.AddDateRangeFilter(e.client.GetTimeField(), to, from, es.DateFormatEpochMS)

View File

@ -29,7 +29,7 @@ type intervalCalculator struct {
} }
type Calculator interface { 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 CalculateSafeInterval(timeRange plugins.DataTimeRange, resolution int64) Interval
} }
@ -55,29 +55,17 @@ func (i *Interval) Milliseconds() int64 {
return i.Value.Nanoseconds() / int64(time.Millisecond) 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() to := timerange.MustGetTo().UnixNano()
from := timerange.MustGetFrom().UnixNano() from := timerange.MustGetFrom().UnixNano()
calculatedInterval := time.Duration((to - from) / DefaultRes) calculatedInterval := time.Duration((to - from) / DefaultRes)
switch intervalMode { if calculatedInterval < minInterval {
case "min": return Interval{Text: FormatDuration(minInterval), Value: minInterval}
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)
} }
rounded := roundInterval(calculatedInterval) 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 { func (ic *intervalCalculator) CalculateSafeInterval(timerange plugins.DataTimeRange, safeRes int64) Interval {

View File

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

View File

@ -17,14 +17,6 @@ var (
day = time.Hour * 24 day = time.Hour * 24
) )
type IntervalMode string
const (
Min IntervalMode = "min"
Max IntervalMode = "max"
Exact IntervalMode = "exact"
)
type Interval struct { type Interval struct {
Text string Text string
Value time.Duration Value time.Duration
@ -35,7 +27,7 @@ type intervalCalculator struct {
} }
type Calculator interface { 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 CalculateSafeInterval(timerange backend.TimeRange, resolution int64) Interval
} }
@ -61,29 +53,18 @@ func (i *Interval) Milliseconds() int64 {
return i.Value.Nanoseconds() / int64(time.Millisecond) 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() to := timerange.To.UnixNano()
from := timerange.From.UnixNano() from := timerange.From.UnixNano()
calculatedIntrvl := time.Duration((to - from) / defaultRes) calculatedIntrvl := time.Duration((to - from) / defaultRes)
switch intervalMode { if calculatedIntrvl < minInterval {
case Min: return Interval{Text: interval.FormatDuration(minInterval), Value: minInterval}
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)
} }
rounded := roundInterval(calculatedIntrvl) 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 { 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-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestIntervalCalculator_Calculate(t *testing.T) { func TestIntervalCalculator_Calculate(t *testing.T) {
@ -18,27 +17,17 @@ func TestIntervalCalculator_Calculate(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
timeRange backend.TimeRange timeRange backend.TimeRange
intervalMode IntervalMode
expected string 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)}, "200ms"},
{"from 5m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(5 * time.Minute)}, Max, "1ms"}, {"from 15m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(15 * time.Minute)}, "500ms"},
{"from 5m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(5 * time.Minute)}, Exact, "1ms"}, {"from 30m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(30 * time.Minute)}, "1s"},
{"from 15m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(15 * time.Minute)}, Min, "500ms"}, {"from 1h to now", backend.TimeRange{From: timeNow, To: timeNow.Add(60 * time.Minute)}, "2s"},
{"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"},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
interval, err := calculator.Calculate(tc.timeRange, time.Millisecond*1, tc.intervalMode) interval := calculator.Calculate(tc.timeRange, time.Millisecond*1)
require.Nil(t, err)
assert.Equal(t, tc.expected, interval.Text) 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) return nil, fmt.Errorf("failed to parse Interval: %v", err)
} }
interval, err := s.intervalCalculator.Calculate(query.TimeRange, dsInterval, intervalv2.Min) interval := s.intervalCalculator.Calculate(query.TimeRange, dsInterval)
if err != nil {
return nil, err
}
var resolution int64 = 1 var resolution int64 = 1
if model.Resolution >= 1 && model.Resolution <= 5 || model.Resolution == 10 { if model.Resolution >= 1 && model.Resolution <= 5 || model.Resolution == 10 {

View File

@ -178,8 +178,8 @@ type mockCalculator struct {
interval intervalv2.Interval interval intervalv2.Interval
} }
func (m mockCalculator) Calculate(timerange backend.TimeRange, minInterval time.Duration, intervalMode intervalv2.IntervalMode) (intervalv2.Interval, error) { func (m mockCalculator) Calculate(timerange backend.TimeRange, minInterval time.Duration) intervalv2.Interval {
return m.interval, nil return m.interval
} }
func (m mockCalculator) CalculateSafeInterval(timerange backend.TimeRange, resolution int64) intervalv2.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) return string(result)
} }
func (s *Service) parseQuery(queries []backend.DataQuery, dsInfo *DatasourceInfo) ( func (s *Service) parseQuery(queries []backend.DataQuery, dsInfo *DatasourceInfo) ([]*PrometheusQuery, error) {
[]*PrometheusQuery, error) {
var intervalMode string
var adjustedInterval time.Duration
qs := []*PrometheusQuery{} qs := []*PrometheusQuery{}
for _, queryModel := range queries { for _, queryModel := range queries {
jsonModel, err := simplejson.NewJson(queryModel.JSON) jsonModel, err := simplejson.NewJson(queryModel.JSON)
@ -247,30 +243,18 @@ func (s *Service) parseQuery(queries []backend.DataQuery, dsInfo *DatasourceInfo
end := queryModel.TimeRange.To end := queryModel.TimeRange.To
queryInterval := jsonModel.Get("interval").MustString("") queryInterval := jsonModel.Get("interval").MustString("")
foundInterval, err := intervalv2.GetIntervalFrom(dsInfo.TimeInterval, queryInterval, 0, 15*time.Second) minInterval, 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
if err != nil { if err != nil {
return nil, err return nil, err
} }
calculatedInterval, err := s.intervalCalculator.Calculate(queries[0].TimeRange, foundInterval, intervalv2.IntervalMode(intervalMode)) calculatedInterval := s.intervalCalculator.Calculate(queries[0].TimeRange, minInterval)
if err != nil {
return nil, err
}
safeInterval := s.intervalCalculator.CalculateSafeInterval(queries[0].TimeRange, int64(safeRes)) safeInterval := s.intervalCalculator.CalculateSafeInterval(queries[0].TimeRange, int64(safeRes))
adjustedInterval := safeInterval.Value
if calculatedInterval.Value > safeInterval.Value { if calculatedInterval.Value > safeInterval.Value {
adjustedInterval = calculatedInterval.Value adjustedInterval = calculatedInterval.Value
} else {
adjustedInterval = safeInterval.Value
} }
intervalFactor := jsonModel.Get("intervalFactor").MustInt64(1) 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)) 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(`{ query := queryContext(`{
"expr": "go_goroutines", "expr": "go_goroutines",
"format": "time_series", "format": "time_series",
@ -61,78 +61,6 @@ func TestPrometheus(t *testing.T) {
require.Equal(t, time.Second*30, models[0].Step) 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) { t.Run("parsing query model without step parameter", func(t *testing.T) {
query := queryContext(`{ query := queryContext(`{
"expr": "go_goroutines", "expr": "go_goroutines",

View File

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

View File

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

View File

@ -1,10 +1,10 @@
import React, { memo, FC, useEffect } from 'react'; import React, { memo, FC, useEffect } from 'react';
// Types // Types
import { ExploreQueryFieldProps, SelectableValue } from '@grafana/data'; import { ExploreQueryFieldProps } from '@grafana/data';
import { PrometheusDatasource } from '../datasource'; import { PrometheusDatasource } from '../datasource';
import { PromQuery, PromOptions, StepMode } from '../types'; import { PromQuery, PromOptions } from '../types';
import PromQueryField from './PromQueryField'; import PromQueryField from './PromQueryField';
import { PromExploreExtraField } from './PromExploreExtraField'; import { PromExploreExtraField } from './PromExploreExtraField';
@ -26,19 +26,7 @@ export const PromExploreQueryEditor: FC<Props> = (props: Props) => {
onChange(nextQuery); onChange(nextQuery);
} }
function onChangeStepMode(mode: StepMode) { function onStepChange(e: React.SyntheticEvent<HTMLInputElement>) {
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>) {
if (e.currentTarget.value !== query.interval) { if (e.currentTarget.value !== query.interval) {
onChangeQueryStep(e.currentTarget.value); 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". // 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'} queryType={query.range === query.instant ? 'both' : query.instant ? 'instant' : 'range'}
stepValue={query.interval || ''} stepValue={query.interval || ''}
stepMode={query.stepMode || 'min'}
onQueryTypeChange={onQueryTypeChange} onQueryTypeChange={onQueryTypeChange}
onStepModeChange={onStepModeChange} onStepChange={onStepChange}
onStepIntervalChange={onStepIntervalChange}
onKeyDownFunc={onReturnKeyDown} onKeyDownFunc={onReturnKeyDown}
query={query} query={query}
onChange={onChange} onChange={onChange}

View File

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

View File

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

View File

@ -31,7 +31,8 @@ exports[`Render PromQueryEditor with basic options should render 1`] = `
<FormLabel <FormLabel
tooltip={ tooltip={
<React.Fragment> <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> <code>
$__interval $__interval
</code> </code>
@ -39,47 +40,18 @@ exports[`Render PromQueryEditor with basic options should render 1`] = `
<code> <code>
$__rate_interval $__rate_interval
</code> </code>
are supported. variables. The limit is absolute and not modified by the "Resolution" setting.
</React.Fragment> </React.Fragment>
} }
width={5} width={7}
> >
Step Min step
</FormLabel> </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 <input
className="gf-form-input width-4" className="gf-form-input width-8"
onBlur={[Function]} onBlur={[Function]}
onChange={[Function]} onChange={[Function]}
placeholder="15s" placeholder=""
type="text" type="text"
value="" value=""
/> />
@ -192,7 +164,6 @@ exports[`Render PromQueryEditor with basic options should render 1`] = `
"interval": "", "interval": "",
"legendFormat": "", "legendFormat": "",
"refId": "A", "refId": "A",
"stepMode": "min",
} }
} }
/> />

View File

@ -18,7 +18,7 @@ import {
prometheusRegularEscape, prometheusRegularEscape,
prometheusSpecialRegexEscape, prometheusSpecialRegexEscape,
} from './datasource'; } from './datasource';
import { PromOptions, PromQuery, StepMode } from './types'; import { PromOptions, PromQuery } from './types';
import { VariableHide } from '../../../features/variables/types'; import { VariableHide } from '../../../features/variables/types';
import { describe } from '../../../../test/lib/common'; import { describe } from '../../../../test/lib/common';
import { QueryOptions } from 'app/types'; import { QueryOptions } from 'app/types';
@ -1685,60 +1685,6 @@ describe('PrometheusDatasource', () => {
templateSrvStub.replace = jest.fn((a: string) => a); 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', () => { describe('PrometheusDatasource for POST', () => {

View File

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

View File

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