mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 02:32:37 +08:00
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:
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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}
|
||||
|
@ -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 '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. <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 "Resolution" 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}
|
||||
|
@ -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"
|
||||
/>
|
||||
}
|
||||
|
@ -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",
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
@ -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', () => {
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
Reference in New Issue
Block a user