Prometheus: Implement Streaming JSON Parser (#48477)
use `prometheusStreamingJSONParser` feature toggle to enable
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
|
||||
)
|
||||
|
||||
//Internal interval and range variables
|
||||
const (
|
||||
varInterval = "$__interval"
|
||||
varIntervalMs = "$__interval_ms"
|
||||
varRange = "$__range"
|
||||
varRangeS = "$__range_s"
|
||||
varRangeMs = "$__range_ms"
|
||||
varRateInterval = "$__rate_interval"
|
||||
)
|
||||
|
||||
//Internal interval and range variables with {} syntax
|
||||
//Repetitive code, we should have functionality to unify these
|
||||
const (
|
||||
varIntervalAlt = "${__interval}"
|
||||
varIntervalMsAlt = "${__interval_ms}"
|
||||
varRangeAlt = "${__range}"
|
||||
varRangeSAlt = "${__range_s}"
|
||||
varRangeMsAlt = "${__range_ms}"
|
||||
varRateIntervalAlt = "${__rate_interval}"
|
||||
)
|
||||
|
||||
type TimeSeriesQueryType string
|
||||
|
||||
const (
|
||||
RangeQueryType TimeSeriesQueryType = "range"
|
||||
InstantQueryType TimeSeriesQueryType = "instant"
|
||||
ExemplarQueryType TimeSeriesQueryType = "exemplar"
|
||||
UnknownQueryType TimeSeriesQueryType = "unknown"
|
||||
)
|
||||
|
||||
var safeResolution = 11000
|
||||
|
||||
type QueryModel struct {
|
||||
Expr string `json:"expr"`
|
||||
LegendFormat string `json:"legendFormat"`
|
||||
Interval string `json:"interval"`
|
||||
IntervalMS int64 `json:"intervalMS"`
|
||||
StepMode string `json:"stepMode"`
|
||||
RangeQuery bool `json:"range"`
|
||||
InstantQuery bool `json:"instant"`
|
||||
ExemplarQuery bool `json:"exemplar"`
|
||||
IntervalFactor int64 `json:"intervalFactor"`
|
||||
UtcOffsetSec int64 `json:"utcOffsetSec"`
|
||||
}
|
||||
|
||||
type TimeRange struct {
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Step time.Duration
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
Expr string
|
||||
Step time.Duration
|
||||
LegendFormat string
|
||||
Start time.Time
|
||||
End time.Time
|
||||
RefId string
|
||||
InstantQuery bool
|
||||
RangeQuery bool
|
||||
ExemplarQuery bool
|
||||
UtcOffsetSec int64
|
||||
}
|
||||
|
||||
func Parse(query backend.DataQuery, timeInterval string, intervalCalculator intervalv2.Calculator, fromAlert bool) (*Query, error) {
|
||||
model := &QueryModel{}
|
||||
if err := json.Unmarshal(query.JSON, model); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Final interval value
|
||||
interval, err := calculatePrometheusInterval(model, timeInterval, query, intervalCalculator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Interpolate variables in expr
|
||||
timeRange := query.TimeRange.To.Sub(query.TimeRange.From)
|
||||
expr := interpolateVariables(model, interval, timeRange, intervalCalculator, timeInterval)
|
||||
rangeQuery := model.RangeQuery
|
||||
if !model.InstantQuery && !model.RangeQuery {
|
||||
// In older dashboards, we were not setting range query param and !range && !instant was run as range query
|
||||
rangeQuery = true
|
||||
}
|
||||
|
||||
// We never want to run exemplar query for alerting
|
||||
exemplarQuery := model.ExemplarQuery
|
||||
if fromAlert {
|
||||
exemplarQuery = false
|
||||
}
|
||||
|
||||
return &Query{
|
||||
Expr: expr,
|
||||
Step: interval,
|
||||
LegendFormat: model.LegendFormat,
|
||||
Start: query.TimeRange.From,
|
||||
End: query.TimeRange.To,
|
||||
RefId: query.RefID,
|
||||
InstantQuery: model.InstantQuery,
|
||||
RangeQuery: rangeQuery,
|
||||
ExemplarQuery: exemplarQuery,
|
||||
UtcOffsetSec: model.UtcOffsetSec,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (query *Query) Type() TimeSeriesQueryType {
|
||||
if query.InstantQuery {
|
||||
return InstantQueryType
|
||||
}
|
||||
if query.RangeQuery {
|
||||
return RangeQueryType
|
||||
}
|
||||
if query.ExemplarQuery {
|
||||
return ExemplarQueryType
|
||||
}
|
||||
return UnknownQueryType
|
||||
}
|
||||
|
||||
func (query *Query) TimeRange() TimeRange {
|
||||
return TimeRange{
|
||||
Step: query.Step,
|
||||
// Align query range to step. It rounds start and end down to a multiple of step.
|
||||
Start: alignTimeRange(query.Start, query.Step, query.UtcOffsetSec),
|
||||
End: alignTimeRange(query.End, query.Step, query.UtcOffsetSec),
|
||||
}
|
||||
}
|
||||
|
||||
func calculatePrometheusInterval(model *QueryModel, timeInterval string, query backend.DataQuery, intervalCalculator intervalv2.Calculator) (time.Duration, error) {
|
||||
queryInterval := model.Interval
|
||||
|
||||
//If we are using variable for interval/step, we will replace it with calculated interval
|
||||
if isVariableInterval(queryInterval) {
|
||||
queryInterval = ""
|
||||
}
|
||||
|
||||
minInterval, err := intervalv2.GetIntervalFrom(timeInterval, queryInterval, model.IntervalMS, 15*time.Second)
|
||||
if err != nil {
|
||||
return time.Duration(0), err
|
||||
}
|
||||
calculatedInterval := intervalCalculator.Calculate(query.TimeRange, minInterval, query.MaxDataPoints)
|
||||
safeInterval := intervalCalculator.CalculateSafeInterval(query.TimeRange, int64(safeResolution))
|
||||
|
||||
adjustedInterval := safeInterval.Value
|
||||
if calculatedInterval.Value > safeInterval.Value {
|
||||
adjustedInterval = calculatedInterval.Value
|
||||
}
|
||||
|
||||
if model.Interval == varRateInterval || model.Interval == varRateIntervalAlt {
|
||||
// Rate interval is final and is not affected by resolution
|
||||
return calculateRateInterval(adjustedInterval, timeInterval, intervalCalculator), nil
|
||||
} else {
|
||||
intervalFactor := model.IntervalFactor
|
||||
if intervalFactor == 0 {
|
||||
intervalFactor = 1
|
||||
}
|
||||
return time.Duration(int64(adjustedInterval) * intervalFactor), nil
|
||||
}
|
||||
}
|
||||
|
||||
func calculateRateInterval(interval time.Duration, scrapeInterval string, intervalCalculator intervalv2.Calculator) time.Duration {
|
||||
scrape := scrapeInterval
|
||||
if scrape == "" {
|
||||
scrape = "15s"
|
||||
}
|
||||
|
||||
scrapeIntervalDuration, err := intervalv2.ParseIntervalStringToTimeDuration(scrape)
|
||||
if err != nil {
|
||||
return time.Duration(0)
|
||||
}
|
||||
|
||||
rateInterval := time.Duration(int(math.Max(float64(interval+scrapeIntervalDuration), float64(4)*float64(scrapeIntervalDuration))))
|
||||
return rateInterval
|
||||
}
|
||||
|
||||
func interpolateVariables(model *QueryModel, interval time.Duration, timeRange time.Duration, intervalCalculator intervalv2.Calculator, timeInterval string) string {
|
||||
expr := model.Expr
|
||||
rangeMs := timeRange.Milliseconds()
|
||||
rangeSRounded := int64(math.Round(float64(rangeMs) / 1000.0))
|
||||
|
||||
var rateInterval time.Duration
|
||||
if model.Interval == varRateInterval || model.Interval == varRateIntervalAlt {
|
||||
rateInterval = interval
|
||||
} else {
|
||||
rateInterval = calculateRateInterval(interval, timeInterval, intervalCalculator)
|
||||
}
|
||||
|
||||
expr = strings.ReplaceAll(expr, varIntervalMs, strconv.FormatInt(int64(interval/time.Millisecond), 10))
|
||||
expr = strings.ReplaceAll(expr, varInterval, intervalv2.FormatDuration(interval))
|
||||
expr = strings.ReplaceAll(expr, varRangeMs, strconv.FormatInt(rangeMs, 10))
|
||||
expr = strings.ReplaceAll(expr, varRangeS, strconv.FormatInt(rangeSRounded, 10))
|
||||
expr = strings.ReplaceAll(expr, varRange, strconv.FormatInt(rangeSRounded, 10)+"s")
|
||||
expr = strings.ReplaceAll(expr, varRateInterval, rateInterval.String())
|
||||
|
||||
// Repetitive code, we should have functionality to unify these
|
||||
expr = strings.ReplaceAll(expr, varIntervalMsAlt, strconv.FormatInt(int64(interval/time.Millisecond), 10))
|
||||
expr = strings.ReplaceAll(expr, varIntervalAlt, intervalv2.FormatDuration(interval))
|
||||
expr = strings.ReplaceAll(expr, varRangeMsAlt, strconv.FormatInt(rangeMs, 10))
|
||||
expr = strings.ReplaceAll(expr, varRangeSAlt, strconv.FormatInt(rangeSRounded, 10))
|
||||
expr = strings.ReplaceAll(expr, varRangeAlt, strconv.FormatInt(rangeSRounded, 10)+"s")
|
||||
expr = strings.ReplaceAll(expr, varRateIntervalAlt, rateInterval.String())
|
||||
return expr
|
||||
}
|
||||
|
||||
func isVariableInterval(interval string) bool {
|
||||
if interval == varInterval || interval == varIntervalMs || interval == varRateInterval {
|
||||
return true
|
||||
}
|
||||
//Repetitive code, we should have functionality to unify these
|
||||
if interval == varIntervalAlt || interval == varIntervalMsAlt || interval == varRateIntervalAlt {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func alignTimeRange(t time.Time, step time.Duration, offset int64) time.Time {
|
||||
return time.Unix(int64(math.Floor((float64(t.Unix()+offset)/step.Seconds()))*step.Seconds()-float64(offset)), 0)
|
||||
}
|
||||
Reference in New Issue
Block a user