diff --git a/pkg/collector/http_collector.go b/pkg/collector/http_collector.go index 7537f69..eacb475 100644 --- a/pkg/collector/http_collector.go +++ b/pkg/collector/http_collector.go @@ -62,7 +62,7 @@ func (p *HTTPCollectorPlugin) NewCollector(_ context.Context, hpa *autoscalingv2 return nil, err } } - jsonPathGetter, err := httpmetrics.NewJSONPathMetricsGetter(httpmetrics.DefaultMetricsHTTPClient(), aggFunc, jsonPath) + jsonPathGetter, err := httpmetrics.NewJSONPathMetricsGetter(httpmetrics.DefaultMetricsHTTPClient(), aggFunc, jsonPath, "") if err != nil { return nil, err } diff --git a/pkg/collector/httpmetrics/json_path.go b/pkg/collector/httpmetrics/json_path.go index 72384aa..04f4bb8 100644 --- a/pkg/collector/httpmetrics/json_path.go +++ b/pkg/collector/httpmetrics/json_path.go @@ -16,18 +16,22 @@ import ( // the json path query. type JSONPathMetricsGetter struct { jsonPath string + jsonEval string aggregator AggregatorFunc client *http.Client } // NewJSONPathMetricsGetter initializes a new JSONPathMetricsGetter. -func NewJSONPathMetricsGetter(httpClient *http.Client, aggregatorFunc AggregatorFunc, jsonPath string) (*JSONPathMetricsGetter, error) { +func NewJSONPathMetricsGetter(httpClient *http.Client, aggregatorFunc AggregatorFunc, jsonPath string, jsonEval string) (*JSONPathMetricsGetter, error) { // check that jsonPath parses - _, err := ajson.ParseJSONPath(jsonPath) - if err != nil { - return nil, err + if jsonPath != "" { + _, err := ajson.ParseJSONPath(jsonPath) + if err != nil { + return nil, err + } } - return &JSONPathMetricsGetter{client: httpClient, aggregator: aggregatorFunc, jsonPath: jsonPath}, nil + + return &JSONPathMetricsGetter{client: httpClient, aggregator: aggregatorFunc, jsonPath: jsonPath, jsonEval: jsonEval}, nil } var DefaultRequestTimeout = 15 * time.Second @@ -67,9 +71,19 @@ func (g *JSONPathMetricsGetter) GetMetric(metricsURL url.URL) (float64, error) { return 0, err } - nodes, err := root.JSONPath(g.jsonPath) - if err != nil { - return 0, err + var nodes []*ajson.Node + if g.jsonPath != "" { + nodes, err = root.JSONPath(g.jsonPath) + if err != nil { + return 0, err + } + } else { + result, err := ajson.Eval(root, g.jsonEval) + nodes = append(nodes, result) + + if err != nil { + return 0, err + } } if len(nodes) == 0 { diff --git a/pkg/collector/httpmetrics/json_path_test.go b/pkg/collector/httpmetrics/json_path_test.go index b2150c3..0005904 100644 --- a/pkg/collector/httpmetrics/json_path_test.go +++ b/pkg/collector/httpmetrics/json_path_test.go @@ -26,6 +26,7 @@ func TestJSONPathMetricsGetter(t *testing.T) { name string jsonResponse []byte jsonPath string + jsonEval string result float64 aggregator AggregatorFunc err error @@ -58,6 +59,19 @@ func TestJSONPathMetricsGetter(t *testing.T) { result: 5, aggregator: Average, }, + { + name: "evaluated script", + jsonResponse: []byte(`{"active processes":1,"total processes":10}`), + jsonEval: "ceil($['active processes'] / $['total processes'] * 100)", + result: 10, + aggregator: Average, + }, + { + name: "invalid script should error", + jsonResponse: []byte(`{"active processes":1,"total processes":10}`), + jsonEval: "ceil($['active processes'] ) $['total processes'] * 100)", + err: errors.New("wrong request: formula has no left parentheses"), + }, { name: "json path not resulting in array or number should lead to error", jsonResponse: []byte(`{"metric.value":5}`), @@ -74,7 +88,7 @@ func TestJSONPathMetricsGetter(t *testing.T) { t.Run(tc.name, func(t *testing.T) { server := makeTestHTTPServer(t, tc.jsonResponse) defer server.Close() - getter, err := NewJSONPathMetricsGetter(DefaultMetricsHTTPClient(), tc.aggregator, tc.jsonPath) + getter, err := NewJSONPathMetricsGetter(DefaultMetricsHTTPClient(), tc.aggregator, tc.jsonPath, tc.jsonEval) require.NoError(t, err) url, err := url.Parse(fmt.Sprintf("%s/metrics", server.URL)) require.NoError(t, err) diff --git a/pkg/collector/httpmetrics/pod_metrics.go b/pkg/collector/httpmetrics/pod_metrics.go index 35bc5f7..e22cc66 100644 --- a/pkg/collector/httpmetrics/pod_metrics.go +++ b/pkg/collector/httpmetrics/pod_metrics.go @@ -33,6 +33,7 @@ func NewPodMetricsJSONPathGetter(config map[string]string) (*PodMetricsJSONPathG getter := PodMetricsJSONPathGetter{} var ( jsonPath string + jsonEval string aggregator AggregatorFunc err error ) @@ -41,6 +42,16 @@ func NewPodMetricsJSONPathGetter(config map[string]string) (*PodMetricsJSONPathG jsonPath = v } + if v, ok := config["json-eval"]; ok { + jsonEval = v + } + + if jsonPath == "" && jsonEval == "" { + return nil, fmt.Errorf("config value json-key or json-eval must be set") + } else if jsonPath != "" && jsonEval != "" { + return nil, fmt.Errorf("config value json-key and json-eval are mutually exclusive") + } + if v, ok := config["scheme"]; ok { getter.scheme = v } @@ -93,7 +104,7 @@ func NewPodMetricsJSONPathGetter(config map[string]string) (*PodMetricsJSONPathG connectTimeout = d } - jsonPathGetter, err := NewJSONPathMetricsGetter(CustomMetricsHTTPClient(requestTimeout, connectTimeout), aggregator, jsonPath) + jsonPathGetter, err := NewJSONPathMetricsGetter(CustomMetricsHTTPClient(requestTimeout, connectTimeout), aggregator, jsonPath, jsonEval) if err != nil { return nil, err } diff --git a/pkg/collector/httpmetrics/pod_metrics_test.go b/pkg/collector/httpmetrics/pod_metrics_test.go index 202d79c..60b4f5d 100644 --- a/pkg/collector/httpmetrics/pod_metrics_test.go +++ b/pkg/collector/httpmetrics/pod_metrics_test.go @@ -86,6 +86,17 @@ func TestNewPodJSONPathMetricsGetter(t *testing.T) { port: 9090, rawQuery: "foo=bar&baz=bop", }, getterWithRawQuery) + + configErrorMixedPathEval := map[string]string{ + "json-key": "{}", + "json-eval": "avg($.values)", + "scheme": "http", + "path": "/metrics", + "port": "9090", + } + + _, err6 := NewPodMetricsJSONPathGetter(configErrorMixedPathEval) + require.Error(t, err6) } func TestBuildMetricsURL(t *testing.T) {