Skip to content

Commit

Permalink
add prometheus format
Browse files Browse the repository at this point in the history
  • Loading branch information
Friedrich Albert Kyuri committed Feb 14, 2024
1 parent 03656b1 commit a951086
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 48 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ require (
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gopkg.in/yaml.v3 v3.0.1
k8s.io/apiextensions-apiserver v0.29.0 // indirect
k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect
k8s.io/kms v0.29.0 // indirect
Expand Down
48 changes: 36 additions & 12 deletions pkg/scalers/metrics_api_scaler.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package scalers

import (
"bufio"
"bytes"
"context"
"encoding/xml"
"errors"
"fmt"
"gopkg.in/yaml.v3"
"io"
"net/http"
neturl "net/url"
"strconv"
"strings"

"gopkg.in/yaml.v3"

"github.com/go-logr/logr"
"github.com/tidwall/gjson"
v2 "k8s.io/api/autoscaling/v2"
Expand Down Expand Up @@ -73,17 +76,15 @@ type APIFormat string

// Enum for APIFormat:
const (
PrometheusFormat APIFormat = "prometheus"
OpenMetricsFormat APIFormat = "openmetrics"
JSONFormat APIFormat = "json"
XMLFormat APIFormat = "xml"
YAMLFormat APIFormat = "yaml"
PrometheusFormat APIFormat = "prometheus"
JSONFormat APIFormat = "json"
XMLFormat APIFormat = "xml"
YAMLFormat APIFormat = "yaml"
)

var (
supportedFormats = []APIFormat{
//PrometheusFormat,
//OpenMetricsFormat,
PrometheusFormat,
JSONFormat,
XMLFormat,
YAMLFormat,
Expand Down Expand Up @@ -249,10 +250,8 @@ func parseMetricsAPIMetadata(config *scalersconfig.ScalerConfig) (*metricsAPISca
// GetValueFromResponse uses provided valueLocation to access the numeric value in provided body using the format specified.
func GetValueFromResponse(body []byte, valueLocation string, format APIFormat) (float64, error) {
switch format {
//case PrometheusFormat:
// return getValueFromPrometheusResponse(body, valueLocation)
//case OpenMetricsFormat:
// return getValueFromOpenMetricsResponse(body, valueLocation)
case PrometheusFormat:
return getValueFromPrometheusResponse(body, valueLocation)
case JSONFormat:
return getValueFromJSONResponse(body, valueLocation)
case XMLFormat:
Expand All @@ -264,6 +263,31 @@ func GetValueFromResponse(body []byte, valueLocation string, format APIFormat) (
return 0, fmt.Errorf("format %s not supported", format)
}

// getValueFromPrometheusResponse uses provided valueLocation to access the numeric value in provided body
func getValueFromPrometheusResponse(body []byte, valueLocation string) (float64, error) {
scanner := bufio.NewScanner(bytes.NewReader(body))
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) == 0 || strings.HasPrefix(fields[0], "#") {
continue
}
if len(fields) == 2 && strings.HasPrefix(fields[0], valueLocation) {
value, err := strconv.ParseFloat(fields[1], 64)
if err != nil {
return 0, err
}
return value, nil
}
}

if err := scanner.Err(); err != nil {
return 0, err
}

return 0, fmt.Errorf("Value %s not found", valueLocation)
}

// getValueFromJSONResponse uses provided valueLocation to access the numeric value in provided body using GSON
func getValueFromJSONResponse(body []byte, valueLocation string) (float64, error) {
r := gjson.GetBytes(body, valueLocation)
Expand Down
65 changes: 30 additions & 35 deletions pkg/scalers/metrics_api_scaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,45 +123,40 @@ func TestMetricsAPIGetMetricSpecForScaling(t *testing.T) {
}

func TestGetValueFromResponse(t *testing.T) {
t.Run("JSON", func(t *testing.T) {
d := []byte(`{"components":[{"id": "82328e93e", "tasks": 32, "str": "64", "k":"1k","wrong":"NaN"}],"count":2.43}`)
v, err := GetValueFromResponse(d, "components.0.tasks", JSONFormat)
if err != nil {
t.Error("Expected success but got error", err)
}
if v != 32 {
t.Errorf("Expected %d got %f", 32, v)
}

v, err = GetValueFromResponse(d, "count", JSONFormat)
if err != nil {
t.Error("Expected success but got error", err)
}
if v != 2.43 {
t.Errorf("Expected %d got %f", 2, v)
}
testCases := []struct {
name string
input []byte
key string
format APIFormat
expectVal interface{}
expectErr bool
}{
{name: "integer", input: []byte(`{"components":[{"id": "82328e93e", "tasks": 32, "str": "64", "k":"1k","wrong":"NaN"}],"count":2.43}`), key: "count", format: JSONFormat, expectVal: 2.43},
{name: "string", input: []byte(`{"components":[{"id": "82328e93e", "tasks": 32, "str": "64", "k":"1k","wrong":"NaN"}],"count":2.43}`), key: "components.0.str", format: JSONFormat, expectVal: 64},
{name: "{}.[].{}", input: []byte(`{"components":[{"id": "82328e93e", "tasks": 32, "str": "64", "k":"1k","wrong":"NaN"}],"count":2.43}`), key: "components.0.tasks", format: JSONFormat, expectVal: 32},
{name: "invalid data", input: []byte(`{"components":[{"id": "82328e93e", "tasks": 32, "str": "64", "k":"1k","wrong":"NaN"}],"count":2.43}`), key: "components.0.wrong", format: JSONFormat, expectErr: true},
{name: "integer", input: []byte(`components: [{id: "82328e93e", tasks: 32, str: "64", k: "1k", wrong: "NaN"}] count: 2.43`), key: "count", format: YAMLFormat, expectVal: 2.43},
{name: "string", input: []byte(`components: [{id: "82328e93e", tasks: 32, str: "64", k: "1k", wrong: "NaN"}] count: 2.43`), key: "components.0.str", format: YAMLFormat, expectVal: 64},
{name: "{}.[].{}", input: []byte(`components: [{id: "82328e93e", tasks: 32, str: "64", k: "1k", wrong: "NaN"}] count: 2.43`), key: "components.0.tasks", format: YAMLFormat, expectVal: 32},
{name: "invalid data", input: []byte(`components: [{id: "82328e93e", tasks: 32, str: "64", k: "1k", wrong: "NaN"}] count: 2.43`), key: "components.0.wrong", format: YAMLFormat, expectErr: true},
}

v, err = GetValueFromResponse(d, "components.0.str", JSONFormat)
if err != nil {
t.Error("Expected success but got error", err)
}
if v != 64 {
t.Errorf("Expected %d got %f", 64, v)
}
for _, tc := range testCases {
t.Run(string(tc.format)+": "+tc.name, func(t *testing.T) {
v, err := GetValueFromResponse(tc.input, tc.key, tc.format)

v, err = GetValueFromResponse(d, "components.0.k", JSONFormat)
if err != nil {
t.Error("Expected success but got error", err)
}
if v != 1000 {
t.Errorf("Expected %d got %f", 1000, v)
}
if tc.expectErr && err == nil {
t.Error("Expected error but got success")
} else if !tc.expectErr && err != nil {
t.Error("Expected success but got error:", err)
}

_, err = GetValueFromResponse(d, "components.0.wrong", JSONFormat)
if err == nil {
t.Error("Expected error but got success", err)
}
})
if !tc.expectErr && v != tc.expectVal {
t.Errorf("Expected %v, got %v", tc.expectVal, v)
}
})
}
}

func TestMetricAPIScalerAuthParams(t *testing.T) {
Expand Down

0 comments on commit a951086

Please sign in to comment.