Skip to content

Commit

Permalink
feat: update prometheus api to support range.step (#1801)
Browse files Browse the repository at this point in the history
Signed-off-by: Rakshit Gondwal <[email protected]>
Signed-off-by: Florian Bacher <[email protected]>
Co-authored-by: Florian Bacher <[email protected]>
  • Loading branch information
rakshitgondwal and bacherfl authored Aug 10, 2023
1 parent 193f520 commit e64fcd6
Show file tree
Hide file tree
Showing 2 changed files with 236 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package prometheus

import (
"context"
"encoding/json"
"fmt" //nolint:gci
"net/http" //nolint:gci
"time"
Expand All @@ -13,6 +14,10 @@ import (
"github.com/prometheus/common/model"
)

var errCouldNotCast = fmt.Errorf("could not cast result")
var errNoValues = fmt.Errorf("no values in query result")
var errTooManyValues = fmt.Errorf("too many values in query result")

type KeptnPrometheusProvider struct {
Log logr.Logger
HttpClient http.Client
Expand All @@ -37,7 +42,7 @@ func (r *KeptnPrometheusProvider) EvaluateQuery(ctx context.Context, metric metr
if len(warnings) != 0 {
r.Log.Info("Prometheus API returned warnings: " + warnings[0])
}
return getResultForMatrix(result, r)
return getResultForMatrix(result)
} else {
result, warnings, err := evaluateQueryWithoutRange(ctx, metric, r, api)
if err != nil {
Expand All @@ -46,29 +51,60 @@ func (r *KeptnPrometheusProvider) EvaluateQuery(ctx context.Context, metric metr
if len(warnings) != 0 {
r.Log.Info("Prometheus API returned warnings: " + warnings[0])
}
return getResultForVector(result, r)
return getResultForVector(result)
}
}

// EvaluateQueryForStep fetches the metric values from prometheus provider
func (r *KeptnPrometheusProvider) EvaluateQueryForStep(ctx context.Context, metric metricsapi.KeptnMetric, provider metricsapi.KeptnMetricsProvider) ([]string, []byte, error) {
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
defer cancel()

client, err := promapi.NewClient(promapi.Config{Address: provider.Spec.TargetServer, Client: &r.HttpClient})
if err != nil {
return nil, nil, err
}

api := prometheus.NewAPI(client)
result, warnings, err := evaluateQueryWithRange(ctx, metric, r, api)
if err != nil {
return nil, nil, err
}
if len(warnings) != 0 {
r.Log.Info("Prometheus API returned warnings: " + warnings[0])
}
return getResultForStepMatrix(result)
}

func evaluateQueryWithRange(ctx context.Context, metric metricsapi.KeptnMetric, r *KeptnPrometheusProvider, api prometheus.API) (model.Value, prometheus.Warnings, error) {
queryTime := time.Now().UTC()
// Get the duration
queryInterval, err := time.ParseDuration(metric.Spec.Range.Interval)
if err != nil {
return nil, nil, err
}
var stepInterval time.Duration
if metric.Spec.Range.Step != "" {
stepTime := metric.Spec.Range.Step
stepInterval, err = time.ParseDuration(stepTime)
if err != nil {
return nil, nil, err
}
} else {
stepInterval = queryInterval
}
// Convert type Duration to type Time
startTime := queryTime.Add(-queryInterval).UTC()
r.Log.Info(fmt.Sprintf(
"Running query: /api/v1/query_range?query=%s&start=%d&end=%d&step=%v",
metric.Spec.Query,
startTime.Unix(), queryTime.Unix(),
queryInterval,
stepInterval,
))
queryRange := prometheus.Range{
Start: startTime,
End: queryTime,
Step: queryInterval,
Step: stepInterval,
}
result, warnings, err := api.QueryRange(
ctx,
Expand Down Expand Up @@ -101,23 +137,21 @@ func evaluateQueryWithoutRange(ctx context.Context, metric metricsapi.KeptnMetri
return result, warnings, nil
}

func getResultForMatrix(result model.Value, r *KeptnPrometheusProvider) (string, []byte, error) {
func getResultForMatrix(result model.Value) (string, []byte, error) {
// check if we can cast the result to a matrix
resultMatrix, ok := result.(model.Matrix)
if !ok {
return "", nil, fmt.Errorf("could not cast result")
return "", nil, errCouldNotCast
}
// We are only allowed to return one value, if not the query may be malformed
// we are using two different errors to give the user more information about the result
// There can be more than 1 values in the matrixResults but we are defining the step
// parameter as the interval itself, hence there can only be one value.
// This logic should be changed, once we work onto the aggregation functions.
if len(resultMatrix) == 0 {
r.Log.Info("No values in query result")
return "", nil, fmt.Errorf("no values in query result")
return "", nil, errNoValues
} else if len(resultMatrix) > 1 {
r.Log.Info("Too many values in the query result")
return "", nil, fmt.Errorf("too many values in the query result")
return "", nil, errTooManyValues
}
value := resultMatrix[0].Values[0].Value.String()
b, err := resultMatrix[0].Values[0].Value.MarshalJSON()
Expand All @@ -127,20 +161,18 @@ func getResultForMatrix(result model.Value, r *KeptnPrometheusProvider) (string,
return value, b, nil
}

func getResultForVector(result model.Value, r *KeptnPrometheusProvider) (string, []byte, error) {
func getResultForVector(result model.Value) (string, []byte, error) {
// check if we can cast the result to a vector
resultVector, ok := result.(model.Vector)
if !ok {
return "", nil, fmt.Errorf("could not cast result")
return "", nil, errCouldNotCast
}
// We are only allowed to return one value, if not the query may be malformed
// we are using two different errors to give the user more information about the result
if len(resultVector) == 0 {
r.Log.Info("No values in query result")
return "", nil, fmt.Errorf("no values in query result")
return "", nil, errNoValues
} else if len(resultVector) > 1 {
r.Log.Info("Too many values in the query result")
return "", nil, fmt.Errorf("too many values in the query result")
return "", nil, errTooManyValues
}
value := resultVector[0].Value.String()
b, err := resultVector[0].Value.MarshalJSON()
Expand All @@ -149,3 +181,29 @@ func getResultForVector(result model.Value, r *KeptnPrometheusProvider) (string,
}
return value, b, nil
}

func getResultForStepMatrix(result model.Value) ([]string, []byte, error) {
// check if we can cast the result to a matrix
resultMatrix, ok := result.(model.Matrix)
if !ok {
return nil, nil, errCouldNotCast
}

if len(resultMatrix) == 0 {
return nil, nil, errNoValues
} else if len(resultMatrix) > 1 {
return nil, nil, errTooManyValues
}

resultSlice := make([]string, len(resultMatrix[0].Values))
for i, value := range resultMatrix[0].Values {
resultSlice[i] = value.Value.String()
}

b, err := json.Marshal(resultSlice)
if err != nil {
return nil, nil, err
}

return resultSlice, b, nil
}
Loading

0 comments on commit e64fcd6

Please sign in to comment.