Skip to content

Commit

Permalink
feat: aggregation functions support for metrics controller (#1802)
Browse files Browse the repository at this point in the history
Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>
Signed-off-by: Rakshit Gondwal <98955085+rakshitgondwal@users.noreply.github.com>
  • Loading branch information
rakshitgondwal committed Sep 7, 2023
1 parent 68b411e commit 678c4c9
Show file tree
Hide file tree
Showing 5 changed files with 400 additions and 5 deletions.
75 changes: 75 additions & 0 deletions metrics-operator/controllers/common/aggregation/aggregation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package aggregation

import (
"math"
"sort"
)

func CalculateMax(values []float64) float64 {
if len(values) == 0 {
return 0.0
}

max := values[0]
for _, value := range values {
if value > max {
max = value
}
}
return max
}

func CalculateMin(values []float64) float64 {
if len(values) == 0 {
return 0.0
}

min := values[0]
for _, value := range values {
if value < min {
min = value
}
}
return min
}

func CalculateMedian(values []float64) float64 {
if len(values) == 0 {
return 0.0
}

// Sort the values
sortedValues := make([]float64, len(values))
copy(sortedValues, values)
sort.Float64s(sortedValues)

// Calculate the median
middle := len(sortedValues) / 2
if len(sortedValues)%2 == 0 {
return (sortedValues[middle-1] + sortedValues[middle]) / 2
}
return sortedValues[middle]
}

func CalculateAverage(values []float64) float64 {
sum := 0.0

for _, value := range values {
sum += value
}
if len(values) > 0 {
return sum / float64(len(values))
}

return 0.0
}

func CalculatePercentile(values sort.Float64Slice, perc float64) float64 {
if len(values) == 0 {
return 0.0
}
// Calculate the index for the requested percentile
i := math.Ceil(float64(len(values)) * perc / 100)

return values[int(i-1)]
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions metrics-operator/controllers/common/providers/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
//go:generate moq -pkg fake -skip-ensure -out ./fake/provider_mock.go . KeptnSLIProvider
type KeptnSLIProvider interface {
EvaluateQuery(ctx context.Context, metric metricsapi.KeptnMetric, provider metricsapi.KeptnMetricsProvider) (string, []byte, error)
EvaluateQueryForStep(ctx context.Context, metric metricsapi.KeptnMetric, provider metricsapi.KeptnMetricsProvider) ([]string, []byte, error)
FetchAnalysisValue(ctx context.Context, query string, spec metricsapi.AnalysisSpec, provider *metricsapi.KeptnMetricsProvider) (string, error)
}

Expand Down
74 changes: 72 additions & 2 deletions metrics-operator/controllers/metrics/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ package metrics

import (
"context"
"fmt"
"sort"
"strconv"
"time"

"github.com/go-logr/logr"
metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3"
"github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/aggregation"
"github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/providers"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -94,9 +98,9 @@ func (r *KeptnMetricReconciler) Reconcile(ctx context.Context, req ctrl.Request)
r.Log.Error(err2, "Failed to get the correct Metric Provider")
return ctrl.Result{Requeue: false}, err2
}

reconcile := ctrl.Result{Requeue: true, RequeueAfter: 10 * time.Second}
value, rawValue, err := provider.EvaluateQuery(ctx, *metric, *metricProvider)

value, rawValue, err := r.getResults(ctx, metric, provider, metricProvider)
if err != nil {
r.Log.Error(err, "Failed to evaluate the query", "Response from provider was:", (string)(rawValue))
metric.Status.ErrMsg = err.Error()
Expand All @@ -118,6 +122,33 @@ func (r *KeptnMetricReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return reconcile, err
}

func (r *KeptnMetricReconciler) getResults(ctx context.Context, metric *metricsapi.KeptnMetric, provider providers.KeptnSLIProvider, metricProvider *metricsapi.KeptnMetricsProvider) (string, []byte, error) {
if metric.Spec.Range != nil && metric.Spec.Range.Step != "" {
return r.getStepQueryResults(ctx, metric, provider, metricProvider)
}
return r.getQueryResults(ctx, metric, provider, metricProvider)
}
func (r *KeptnMetricReconciler) getQueryResults(ctx context.Context, metric *metricsapi.KeptnMetric, provider providers.KeptnSLIProvider, metricProvider *metricsapi.KeptnMetricsProvider) (string, []byte, error) {
value, rawValue, err := provider.EvaluateQuery(ctx, *metric, *metricProvider)
if err != nil {
r.Log.Error(err, "Failed to evaluate the query", "Response from provider was:", (string)(rawValue))
return "", cupSize(rawValue), err
}
return value, cupSize(rawValue), nil
}
func (r *KeptnMetricReconciler) getStepQueryResults(ctx context.Context, metric *metricsapi.KeptnMetric, provider providers.KeptnSLIProvider, metricProvider *metricsapi.KeptnMetricsProvider) (string, []byte, error) {
value, rawValue, err := provider.EvaluateQueryForStep(ctx, *metric, *metricProvider)
if err != nil {
r.Log.Error(err, "Failed to evaluate the query", "Response from provider was:", (string)(rawValue))
return "", cupSize(rawValue), err
}
aggValue, err := aggregateValues(value, metric.Spec.Range.Aggregation)
if err != nil {
return "", nil, err
}
return aggValue, cupSize(rawValue), nil
}

func cupSize(value []byte) []byte {
if len(value) == 0 {
return []byte{}
Expand All @@ -142,3 +173,42 @@ func (r *KeptnMetricReconciler) fetchProvider(ctx context.Context, namespacedMet
}
return provider, nil
}

func aggregateValues(stringSlice []string, aggFunc string) (string, error) {
floatSlice, err := stringSliceToFloatSlice(stringSlice)
if err != nil {
return "", err
}
var aggValue float64
switch aggFunc {
case "max":
aggValue = aggregation.CalculateMax(floatSlice)
case "min":
aggValue = aggregation.CalculateMin(floatSlice)
case "median":
aggValue = aggregation.CalculateMedian(floatSlice)
case "avg":
aggValue = aggregation.CalculateAverage(floatSlice)
case "p90":
aggValue = aggregation.CalculatePercentile(sort.Float64Slice(floatSlice), 90)
case "p95":
aggValue = aggregation.CalculatePercentile(sort.Float64Slice(floatSlice), 95)
case "p99":
aggValue = aggregation.CalculatePercentile(sort.Float64Slice(floatSlice), 99)
}
return fmt.Sprintf("%v", aggValue), nil
}

func stringSliceToFloatSlice(strSlice []string) ([]float64, error) {
floatSlice := make([]float64, len(strSlice))

for i, str := range strSlice {
floatValue, err := strconv.ParseFloat(str, 64)
if err != nil {
return nil, err
}
floatSlice[i] = floatValue
}

return floatSlice, nil
}
Loading

0 comments on commit 678c4c9

Please sign in to comment.