Skip to content
Permalink
Browse files

prom: add support for default step param in backend

  • Loading branch information...
bergquist committed Nov 13, 2017
1 parent c44f6e2 commit 4700ed251ec196e1e26b3c25dd302b5a1615c430
@@ -42,7 +42,8 @@ func (query *Query) Build(queryContext *tsdb.TsdbQuery) (string, error) {
}

func getDefinedInterval(query *Query, queryContext *tsdb.TsdbQuery) (*tsdb.Interval, error) {
defaultInterval := tsdb.CalculateInterval(queryContext.TimeRange)
calculator := tsdb.NewIntervalCalculator(&tsdb.IntervalOptions{})
defaultInterval := calculator.Calculate(queryContext.TimeRange)

if query.Interval == "" {
return &defaultInterval, nil
@@ -6,25 +6,56 @@ import (
)

var (
defaultRes int64 = 1500
minInterval time.Duration = 1 * time.Millisecond
year time.Duration = time.Hour * 24 * 365
day time.Duration = time.Hour * 24 * 365
defaultRes int64 = 1500
defaultMinInterval time.Duration = 1 * time.Millisecond
year time.Duration = time.Hour * 24 * 365
day time.Duration = time.Hour * 24 * 365
)

type Interval struct {
Text string
Value time.Duration
}

func CalculateInterval(timerange *TimeRange) Interval {
interval := time.Duration((timerange.MustGetTo().UnixNano() - timerange.MustGetFrom().UnixNano()) / defaultRes)
type intervalCalculator struct {
minInterval time.Duration
}

type IntervalCalculator interface {
Calculate(*TimeRange) Interval
}

type IntervalOptions struct {
MinInterval time.Duration
}

func NewIntervalCalculator(opt *IntervalOptions) *intervalCalculator {
if opt == nil {
opt = &IntervalOptions{}
}

calc := &intervalCalculator{}

if opt.MinInterval == 0 {
calc.minInterval = defaultMinInterval
} else {
calc.minInterval = opt.MinInterval
}

return calc
}

func (ic *intervalCalculator) Calculate(timerange *TimeRange) Interval {
to := timerange.MustGetTo().UnixNano()
from := timerange.MustGetFrom().UnixNano()
interval := time.Duration((to - from) / defaultRes)

if interval < minInterval {
return Interval{Text: formatDuration(minInterval), Value: interval}
if interval < ic.minInterval {
return Interval{Text: formatDuration(ic.minInterval), Value: ic.minInterval}
}

return Interval{Text: formatDuration(roundInterval(interval)), Value: interval}
rounded := roundInterval(interval)
return Interval{Text: formatDuration(rounded), Value: rounded}
}

func formatDuration(inter time.Duration) string {
@@ -14,31 +14,33 @@ func TestInterval(t *testing.T) {
HomePath: "../../",
})

calculator := NewIntervalCalculator(&IntervalOptions{})

Convey("for 5min", func() {
tr := NewTimeRange("5m", "now")

interval := CalculateInterval(tr)
interval := calculator.Calculate(tr)
So(interval.Text, ShouldEqual, "200ms")
})

Convey("for 15min", func() {
tr := NewTimeRange("15m", "now")

interval := CalculateInterval(tr)
interval := calculator.Calculate(tr)
So(interval.Text, ShouldEqual, "500ms")
})

Convey("for 30min", func() {
tr := NewTimeRange("30m", "now")

interval := CalculateInterval(tr)
interval := calculator.Calculate(tr)
So(interval.Text, ShouldEqual, "1s")
})

Convey("for 1h", func() {
tr := NewTimeRange("1h", "now")

interval := CalculateInterval(tr)
interval := calculator.Calculate(tr)
So(interval.Text, ShouldEqual, "2s")
})

@@ -48,14 +48,16 @@ func NewPrometheusExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, e
}

var (
plog log.Logger
legendFormat *regexp.Regexp
plog log.Logger
legendFormat *regexp.Regexp
intervalCalculator tsdb.IntervalCalculator
)

func init() {
plog = log.New("tsdb.prometheus")
tsdb.RegisterTsdbQueryEndpoint("prometheus", NewPrometheusExecutor)
legendFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`)
intervalCalculator = tsdb.NewIntervalCalculator(&tsdb.IntervalOptions{MinInterval: time.Second * 1})
}

func (e *PrometheusExecutor) getClient(dsInfo *models.DataSource) (apiv1.API, error) {
@@ -146,11 +148,6 @@ func parseQuery(queries []*tsdb.Query, queryContext *tsdb.TsdbQuery) (*Prometheu
return nil, err
}

step, err := queryModel.Model.Get("step").Int64()
if err != nil {
return nil, err
}

format := queryModel.Model.Get("legendFormat").MustString("")

start, err := queryContext.TimeRange.ParseFrom()
@@ -163,9 +160,13 @@ func parseQuery(queries []*tsdb.Query, queryContext *tsdb.TsdbQuery) (*Prometheu
return nil, err
}

intervalFactor := queryModel.Model.Get("intervalFactor").MustInt64(1)
interval := intervalCalculator.Calculate(queryContext.TimeRange)
step := time.Duration(int64(interval.Value) * intervalFactor)

return &PrometheusQuery{
Expr: expr,
Step: time.Second * time.Duration(step),
Step: step,
LegendFormat: format,
Start: start,
End: end,
@@ -2,14 +2,17 @@ package prometheus

import (
"testing"
"time"

"github.com/grafana/grafana/pkg/tsdb"

"github.com/grafana/grafana/pkg/components/simplejson"
p "github.com/prometheus/common/model"
. "github.com/smartystreets/goconvey/convey"
)

func TestPrometheus(t *testing.T) {
Convey("Prometheus", t, func() {

Convey("converting metric name", func() {
metric := map[p.LabelName]p.LabelValue{
p.LabelName("app"): p.LabelValue("backend"),
@@ -36,5 +39,108 @@ func TestPrometheus(t *testing.T) {

So(formatLegend(metric, query), ShouldEqual, `http_request_total{app="backend", device="mobile"}`)
})

Convey("parsing query model with step", func() {
json := `{
"expr": "go_goroutines",
"format": "time_series",
"refId": "A"
}`
jsonModel, _ := simplejson.NewJson([]byte(json))
queryContext := &tsdb.TsdbQuery{}
queryModels := []*tsdb.Query{
{Model: jsonModel},
}

Convey("with 48h time range", func() {
queryContext.TimeRange = tsdb.NewTimeRange("12h", "now")

model, err := parseQuery(queryModels, queryContext)

So(err, ShouldBeNil)
So(model.Step, ShouldEqual, time.Second*30)
})
})

Convey("parsing query model without step parameter", func() {
json := `{
"expr": "go_goroutines",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`
jsonModel, _ := simplejson.NewJson([]byte(json))
queryContext := &tsdb.TsdbQuery{}
queryModels := []*tsdb.Query{
{Model: jsonModel},
}

Convey("with 48h time range", func() {
queryContext.TimeRange = tsdb.NewTimeRange("48h", "now")

model, err := parseQuery(queryModels, queryContext)

So(err, ShouldBeNil)
So(model.Step, ShouldEqual, time.Minute*2)
})

Convey("with 1h time range", func() {
queryContext.TimeRange = tsdb.NewTimeRange("1h", "now")

model, err := parseQuery(queryModels, queryContext)

So(err, ShouldBeNil)
So(model.Step, ShouldEqual, time.Second*2)
})
})

Convey("parsing query model with intervalFactor", func() {
Convey("high intervalFactor", func() {
json := `{
"expr": "go_goroutines",
"format": "time_series",
"intervalFactor": 10,
"refId": "A"
}`
jsonModel, _ := simplejson.NewJson([]byte(json))
queryContext := &tsdb.TsdbQuery{}
queryModels := []*tsdb.Query{
{Model: jsonModel},
}

Convey("with 48h time range", func() {
queryContext.TimeRange = tsdb.NewTimeRange("48h", "now")

model, err := parseQuery(queryModels, queryContext)

So(err, ShouldBeNil)
So(model.Step, ShouldEqual, time.Minute*20)
})
})

Convey("low intervalFactor", func() {
json := `{
"expr": "go_goroutines",
"format": "time_series",
"intervalFactor": 1,
"refId": "A"
}`
jsonModel, _ := simplejson.NewJson([]byte(json))
queryContext := &tsdb.TsdbQuery{}
queryModels := []*tsdb.Query{
{Model: jsonModel},
}

Convey("with 48h time range", func() {
queryContext.TimeRange = tsdb.NewTimeRange("48h", "now")

model, err := parseQuery(queryModels, queryContext)

So(err, ShouldBeNil)
So(model.Step, ShouldEqual, time.Minute*2)
})
})
})

})
}

0 comments on commit 4700ed2

Please sign in to comment.
You can’t perform that action at this time.