Skip to content

Commit

Permalink
Add support for passing URL query params to pod metric endpoints (#109)
Browse files Browse the repository at this point in the history
Adds a new metric-config option named `rawQuery`. The value of
this option will be appended to the metric `path` as URL query
parameters to be used by the pod's application. E.g.,:
```
metric-config.pods.requests-per-second.json-path/rawQuery: "foo=bar&baz=bop"
```
will apppend `?foo=bar&baz=bop` to the URL.

Signed-off-by: Abe Friesen <[email protected]>
  • Loading branch information
doyshinda authored Mar 10, 2020
1 parent c661075 commit 5bf87cb
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 11 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,17 @@ values of JSONPath expressions that evaluate to arrays/slices of numbers.
It's optional but when the expression evaluates to an array/slice, it's absence will
produce an error. The supported aggregation functions are `avg`, `max`, `min` and `sum`.

The `raw-query` configuration option specifies the query params to send along to the endpoint:
```yaml
metric-config.pods.requests-per-second.json-path/path: /metrics
metric-config.pods.requests-per-second.json-path/port: "9090"
metric-config.pods.requests-per-second.json-path/raw-query: "foo=bar&baz=bop"
```
will create a URL like this:
```
http://<podIP>:9090/metrics?foo=bar&baz=bop
```

## Prometheus collector

The Prometheus collector is a generic collector which can map Prometheus
Expand Down
35 changes: 24 additions & 11 deletions pkg/collector/json_path_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type JSONPathMetricsGetter struct {
port int
aggregator string
client *http.Client
rawQuery string
}

// NewJSONPathMetricsGetter initializes a new JSONPathMetricsGetter.
Expand All @@ -49,6 +50,10 @@ func NewJSONPathMetricsGetter(config map[string]string) (*JSONPathMetricsGetter,
getter.path = v
}

if v, ok := config["raw-query"]; ok {
getter.rawQuery = v
}

if v, ok := config["port"]; ok {
n, err := strconv.Atoi(v)
if err != nil {
Expand Down Expand Up @@ -83,7 +88,7 @@ func defaultHTTPClient() *http.Client {
// endpoint and extracting the desired value using the specified json path
// query.
func (g *JSONPathMetricsGetter) GetMetric(pod *corev1.Pod) (float64, error) {
data, err := g.getPodMetrics(pod, g.scheme, g.path, g.port)
data, err := g.getPodMetrics(pod)
if err != nil {
return 0, err
}
Expand Down Expand Up @@ -140,20 +145,12 @@ func castSlice(in []interface{}) ([]float64, error) {
}

// getPodMetrics returns the content of the pods metrics endpoint.
func (g *JSONPathMetricsGetter) getPodMetrics(pod *corev1.Pod, scheme, path string, port int) ([]byte, error) {
func (g *JSONPathMetricsGetter) getPodMetrics(pod *corev1.Pod) ([]byte, error) {
if pod.Status.PodIP == "" {
return nil, fmt.Errorf("pod %s/%s does not have a pod IP", pod.Namespace, pod.Namespace)
}

if scheme == "" {
scheme = "http"
}

metricsURL := url.URL{
Scheme: scheme,
Host: fmt.Sprintf("%s:%d", pod.Status.PodIP, port),
Path: path,
}
metricsURL := g.buildMetricsURL(pod.Status.PodIP)

request, err := http.NewRequest(http.MethodGet, metricsURL.String(), nil)
if err != nil {
Expand All @@ -178,6 +175,22 @@ func (g *JSONPathMetricsGetter) getPodMetrics(pod *corev1.Pod, scheme, path stri
return data, nil
}

// buildMetricsURL will build the full URL needed to hit the pod metric endpoint.
func (g *JSONPathMetricsGetter) buildMetricsURL(podIP string) url.URL {
var scheme = g.scheme

if scheme == "" {
scheme = "http"
}

return url.URL{
Scheme: scheme,
Host: fmt.Sprintf("%s:%d", podIP, g.port),
Path: g.path,
RawQuery: g.rawQuery,
}
}

// reduce will reduce a slice of numbers given a aggregator function's name. If it's empty or not recognized, an error is returned.
func reduce(values []float64, aggregator string) (float64, error) {
switch aggregator {
Expand Down
61 changes: 61 additions & 0 deletions pkg/collector/json_path_collector_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package collector

import (
"fmt"
"testing"

"github.com/oliveagle/jsonpath"
Expand Down Expand Up @@ -70,6 +71,25 @@ func TestNewJSONPathMetricsGetter(t *testing.T) {

_, err4 := NewJSONPathMetricsGetter(configErrorPort)
require.Error(t, err4)

configWithRawQuery := map[string]string{
"json-key": "$.values",
"scheme": "http",
"path": "/metrics",
"port": "9090",
"raw-query": "foo=bar&baz=bop",
}
jpath5, _ := jsonpath.Compile(configWithRawQuery["json-key"])
getterWithRawQuery, err5 := NewJSONPathMetricsGetter(configWithRawQuery)

require.NoError(t, err5)
compareMetricsGetter(t, &JSONPathMetricsGetter{
jsonPath: jpath5,
scheme: "http",
path: "/metrics",
port: 9090,
rawQuery: "foo=bar&baz=bop",
}, getterWithRawQuery)
}

func TestCastSlice(t *testing.T) {
Expand Down Expand Up @@ -110,3 +130,44 @@ func TestReduce(t *testing.T) {
_, err5 := reduce([]float64{1, 2, 3}, "inexistent_function")
require.Errorf(t, err5, "slice of numbers was returned by JSONPath, but no valid aggregator function was specified: %v", "inexistent_function")
}

func TestBuildMetricsURL(t *testing.T) {
scheme := "http"
ip := "1.2.3.4"
port := "9090"
path := "/v1/test/"
rawQuery := "foo=bar&baz=bop"

// Test building URL with rawQuery
configWithRawQuery := map[string]string{
"json-key": "$.value",
"scheme": scheme,
"path": path,
"port": port,
"raw-query": rawQuery,
}
_, err := jsonpath.Compile(configWithRawQuery["json-key"])
require.NoError(t, err)
getterWithRawQuery, err1 := NewJSONPathMetricsGetter(configWithRawQuery)
require.NoError(t, err1)

expectedURLWithQuery := fmt.Sprintf("%s://%s:%s%s?%s", scheme, ip, port, path, rawQuery)
receivedURLWithQuery := getterWithRawQuery.buildMetricsURL(ip)
require.Equal(t, receivedURLWithQuery.String(), expectedURLWithQuery)

// Test building URL without rawQuery
configWithNoQuery := map[string]string{
"json-key": "$.value",
"scheme": scheme,
"path": path,
"port": port,
}
_, err2 := jsonpath.Compile(configWithNoQuery["json-key"])
require.NoError(t, err2)
getterWithNoQuery, err3 := NewJSONPathMetricsGetter(configWithNoQuery)
require.NoError(t, err3)

expectedURLNoQuery := fmt.Sprintf("%s://%s:%s%s", scheme, ip, port, path)
receivedURLNoQuery := getterWithNoQuery.buildMetricsURL(ip)
require.Equal(t, receivedURLNoQuery.String(), expectedURLNoQuery)
}

0 comments on commit 5bf87cb

Please sign in to comment.