@ -8,6 +8,7 @@ import (
"math"
"net/http"
"regexp"
"sort"
"strconv"
"strings"
"time"
@ -142,16 +143,16 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest)
span . SetTag ( "stop_unixnano" , query . End . UnixNano ( ) )
defer span . Finish ( )
response := make ( map [ PrometheusQueryType ] model . Value )
response := make ( map [ PrometheusQueryType ] interface { } )
if query . RangeQuery {
timeRange := apiv1 . Range {
Step : query . Step ,
// Align query range to step. It rounds start and end down to a multiple of step.
Start : time . Unix ( int64 ( math . Floor ( ( float64 ( query . Start . Unix ( ) + query . UtcOffsetSec ) / query . Step . Seconds ( ) ) ) * query . Step . Seconds ( ) - float64 ( query . UtcOffsetSec ) ) , 0 ) ,
End : time . Unix ( int64 ( math . Floor ( ( float64 ( query . End . Unix ( ) + query . UtcOffsetSec ) / query . Step . Seconds ( ) ) ) * query . Step . Seconds ( ) - float64 ( query . UtcOffsetSec ) ) , 0 ) ,
}
timeRange := apiv1 . Range {
Step : query . Step ,
// Align query range to step. It rounds start and end down to a multiple of step.
Start : time . Unix ( int64 ( math . Floor ( ( float64 ( query . Start . Unix ( ) + query . UtcOffsetSec ) / query . Step . Seconds ( ) ) ) * query . Step . Seconds ( ) - float64 ( query . UtcOffsetSec ) ) , 0 ) ,
End : time . Unix ( int64 ( math . Floor ( ( float64 ( query . End . Unix ( ) + query . UtcOffsetSec ) / query . Step . Seconds ( ) ) ) * query . Step . Seconds ( ) - float64 ( query . UtcOffsetSec ) ) , 0 ) ,
}
if query . RangeQuery {
rangeResponse , _ , err := client . QueryRange ( ctx , query . Expr , timeRange )
if err != nil {
return & result , fmt . Errorf ( "query: %s failed with: %v" , query . Expr , err )
@ -166,6 +167,15 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest)
}
response [ Instant ] = instantResponse
}
// For now, we ignore exemplar errors and continue with processing of other results
if query . ExemplarQuery {
exemplarResponse , err := client . QueryExemplars ( ctx , query . Expr , timeRange . Start , timeRange . End )
if err != nil {
exemplarResponse = nil
plog . Error ( "Exemplar query" , query . Expr , "failed with" , err )
}
response [ Exemplar ] = exemplarResponse
}
frames , err := parseResponse ( response , query )
if err != nil {
@ -299,53 +309,54 @@ func (s *Service) parseQuery(queryContext *backend.QueryDataRequest, dsInfo *Dat
}
qs = append ( qs , & PrometheusQuery {
Expr : expr ,
Step : interval ,
LegendFormat : model . LegendFormat ,
Start : query . TimeRange . From ,
End : query . TimeRange . To ,
RefId : query . RefID ,
InstantQuery : model . InstantQuery ,
RangeQuery : rangeQuery ,
UtcOffsetSec : model . UtcOffsetSec ,
Expr : expr ,
Step : interval ,
LegendFormat : model . LegendFormat ,
Start : query . TimeRange . From ,
End : query . TimeRange . To ,
RefId : query . RefID ,
InstantQuery : model . InstantQuery ,
RangeQuery : rangeQuery ,
ExemplarQuery : model . ExemplarQuery ,
UtcOffsetSec : model . UtcOffsetSec ,
} )
}
return qs , nil
}
func parseResponse ( value map [ PrometheusQueryType ] model . Value , query * PrometheusQuery ) ( data . Frames , error ) {
allFrames := data . Frames { }
for queryType , value := range value {
var frames data . Frames
func parseResponse ( value map [ PrometheusQueryType ] interface { } , query * PrometheusQuery ) ( data . Frames , error ) {
frames := data . Frames { }
for _ , value := range value {
matrix , ok := value . ( model . Matrix )
if ok {
frames = matrixToDataFrames ( matrix , query , queryType )
matrixFrames := matrixToDataFrames ( matrix , query )
frames = append ( frames , matrixFrames ... )
continue
}
vector , ok := value . ( model . Vector )
if ok {
frames = vectorToDataFrames ( vector , query , queryType )
vectorFrames := vectorToDataFrames ( vector , query )
frames = append ( frames , vectorFrames ... )
continue
}
scalar , ok := value . ( * model . Scalar )
if ok {
frames = scalarToDataFrames ( scalar , query , queryType )
scalarFrames := scalarToDataFrames ( scalar , query )
frames = append ( frames , scalarFrames ... )
continue
}
for _ , frame := range frames {
frame . Meta = & data . FrameMeta {
Custom : map [ string ] PrometheusQueryType {
"queryType" : queryType ,
} ,
}
exemplar , ok := value . ( [ ] apiv1 . ExemplarQueryResult )
if ok {
exemplarFrames := exemplarToDataFrames ( exemplar , query )
frames = append ( frames , exemplarFrames ... )
continue
}
allFrames = append ( allFrames , frames ... )
}
return allFrames , nil
return frames , nil
}
// IsAPIError returns whether err is or wraps a Prometheus error.
@ -378,7 +389,7 @@ func calculateRateInterval(interval time.Duration, scrapeInterval string, interv
return rateInterval
}
func matrixToDataFrames ( matrix model . Matrix , query * PrometheusQuery , queryType PrometheusQueryType ) data . Frames {
func matrixToDataFrames ( matrix model . Matrix , query * PrometheusQuery ) data . Frames {
frames := data . Frames { }
for _ , v := range matrix {
@ -396,23 +407,34 @@ func matrixToDataFrames(matrix model.Matrix, query *PrometheusQuery, queryType P
frame := data . NewFrame ( name ,
data . NewField ( "Time" , nil , timeVector ) ,
data . NewField ( "Value" , tags , values ) . SetConfig ( & data . FieldConfig { DisplayNameFromDS : name } ) )
frame . Meta = & data . FrameMeta {
Custom : map [ string ] string {
"resultType" : "matrix" ,
} ,
}
frames = append ( frames , frame )
}
return frames
}
func scalarToDataFrames ( scalar * model . Scalar , query * PrometheusQuery , queryType PrometheusQueryType ) data . Frames {
func scalarToDataFrames ( scalar * model . Scalar , query * PrometheusQuery ) data . Frames {
timeVector := [ ] time . Time { time . Unix ( scalar . Timestamp . Unix ( ) , 0 ) . UTC ( ) }
values := [ ] float64 { float64 ( scalar . Value ) }
name := fmt . Sprintf ( "%g" , values [ 0 ] )
frame := data . NewFrame ( name ,
data . NewField ( "Time" , nil , timeVector ) ,
data . NewField ( "Value" , nil , values ) . SetConfig ( & data . FieldConfig { DisplayNameFromDS : name } ) )
frame . Meta = & data . FrameMeta {
Custom : map [ string ] string {
"resultType" : "scalar" ,
} ,
}
frames := data . Frames { frame }
return frames
}
func vectorToDataFrames ( vector model . Vector , query * PrometheusQuery , queryType PrometheusQueryType ) data . Frames {
func vectorToDataFrames ( vector model . Vector , query * PrometheusQuery ) data . Frames {
frames := data . Frames { }
for _ , v := range vector {
name := formatLegend ( v . Metric , query )
@ -425,8 +447,146 @@ func vectorToDataFrames(vector model.Vector, query *PrometheusQuery, queryType P
frame := data . NewFrame ( name ,
data . NewField ( "Time" , nil , timeVector ) ,
data . NewField ( "Value" , tags , values ) . SetConfig ( & data . FieldConfig { DisplayNameFromDS : name } ) )
frame . Meta = & data . FrameMeta {
Custom : map [ string ] string {
"resultType" : "vector" ,
} ,
}
frames = append ( frames , frame )
}
return frames
}
func exemplarToDataFrames ( response [ ] apiv1 . ExemplarQueryResult , query * PrometheusQuery ) data . Frames {
frames := data . Frames { }
events := make ( [ ] ExemplarEvent , 0 )
for _ , exemplarData := range response {
for _ , exemplar := range exemplarData . Exemplars {
event := ExemplarEvent { }
exemplarTime := time . Unix ( exemplar . Timestamp . Unix ( ) , 0 ) . UTC ( )
event . Time = exemplarTime
event . Value = float64 ( exemplar . Value )
event . Labels = make ( map [ string ] string )
for label , value := range exemplar . Labels {
event . Labels [ string ( label ) ] = string ( value )
}
for seriesLabel , seriesValue := range exemplarData . SeriesLabels {
event . Labels [ string ( seriesLabel ) ] = string ( seriesValue )
}
events = append ( events , event )
}
}
//Sampling of exemplars
bucketedExemplars := make ( map [ string ] [ ] ExemplarEvent )
values := make ( [ ] float64 , 0 )
//Create bucketed exemplars based on aligned timestamp
for _ , event := range events {
alignedTs := fmt . Sprintf ( "%.0f" , math . Floor ( float64 ( event . Time . Unix ( ) ) / query . Step . Seconds ( ) ) * query . Step . Seconds ( ) )
_ , ok := bucketedExemplars [ alignedTs ]
if ! ok {
bucketedExemplars [ alignedTs ] = make ( [ ] ExemplarEvent , 0 )
}
bucketedExemplars [ alignedTs ] = append ( bucketedExemplars [ alignedTs ] , event )
values = append ( values , event . Value )
}
//Calculate standard deviation
standardDeviation := deviation ( values )
//Create slice with all of the bucketed exemplars
sampledBuckets := make ( [ ] string , len ( bucketedExemplars ) )
for bucketTimes := range bucketedExemplars {
sampledBuckets = append ( sampledBuckets , bucketTimes )
}
sort . Strings ( sampledBuckets )
//Sample exemplars based ona value, so we are not showing too many of them
sampleExemplars := make ( [ ] ExemplarEvent , 0 )
for _ , bucket := range sampledBuckets {
exemplarsInBucket := bucketedExemplars [ bucket ]
if len ( exemplarsInBucket ) == 1 {
sampleExemplars = append ( sampleExemplars , exemplarsInBucket [ 0 ] )
} else {
bucketValues := make ( [ ] float64 , len ( exemplarsInBucket ) )
for _ , exemplar := range exemplarsInBucket {
bucketValues = append ( bucketValues , exemplar . Value )
}
sort . Slice ( bucketValues , func ( i , j int ) bool {
return bucketValues [ i ] > bucketValues [ j ]
} )
sampledBucketValues := make ( [ ] float64 , 0 )
for _ , value := range bucketValues {
if len ( sampledBucketValues ) == 0 {
sampledBucketValues = append ( sampledBucketValues , value )
} else {
// Then take values only when at least 2 standard deviation distance to previously taken value
prev := sampledBucketValues [ len ( sampledBucketValues ) - 1 ]
if standardDeviation != 0 && prev - value >= float64 ( 2 ) * standardDeviation {
sampledBucketValues = append ( sampledBucketValues , value )
}
}
}
for _ , valueBucket := range sampledBucketValues {
for _ , exemplar := range exemplarsInBucket {
if exemplar . Value == valueBucket {
sampleExemplars = append ( sampleExemplars , exemplar )
}
}
}
}
}
// Create DF from sampled exemplars
timeVector := make ( [ ] time . Time , 0 , len ( sampleExemplars ) )
valuesVector := make ( [ ] float64 , 0 , len ( sampleExemplars ) )
labelsVector := make ( map [ string ] [ ] string , len ( sampleExemplars ) )
for _ , exemplar := range sampleExemplars {
timeVector = append ( timeVector , exemplar . Time )
valuesVector = append ( valuesVector , exemplar . Value )
for label , value := range exemplar . Labels {
if labelsVector [ label ] == nil {
labelsVector [ label ] = make ( [ ] string , 0 )
}
labelsVector [ label ] = append ( labelsVector [ label ] , value )
}
}
frame := data . NewFrame ( "exemplar" ,
data . NewField ( "Time" , nil , timeVector ) ,
data . NewField ( "Value" , nil , valuesVector ) )
for label , vector := range labelsVector {
frame . Fields = append ( frame . Fields , data . NewField ( label , nil , vector ) )
}
frame . Meta = & data . FrameMeta {
Custom : map [ string ] PrometheusQueryType {
"resultType" : "exemplar" ,
} ,
}
frames = append ( frames , frame )
return frames
}
func deviation ( values [ ] float64 ) float64 {
var sum , mean , sd float64
valuesLen := float64 ( len ( values ) )
for _ , value := range values {
sum += value
}
mean = sum / valuesLen
for j := 0 ; j < len ( values ) ; j ++ {
sd += math . Pow ( values [ j ] - mean , 2 )
}
return math . Sqrt ( sd / ( valuesLen - 1 ) )
}