Skip to content

Commit

Permalink
web/api: replace /metrics/names with /label/:name/values endpoint.
Browse files Browse the repository at this point in the history
  • Loading branch information
fabxc committed Jun 8, 2015
1 parent 535c002 commit 75b0b74
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 14 deletions.
25 changes: 19 additions & 6 deletions web/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"

"github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"

clientmodel "github.com/prometheus/client_golang/model"

Expand All @@ -29,7 +30,8 @@ const (
type errorType string

const (
errorTimeout errorType = "timeout"
errorNone errorType = ""
errorTimeout = "timeout"
errorCanceled = "canceled"
errorExec = "execution"
errorBadData = "bad_data"
Expand All @@ -56,6 +58,8 @@ type response struct {
type API struct {
Storage local.Storage
QueryEngine *promql.Engine

context func(r *http.Request) context.Context
}

// Enables cross-site script calls.
Expand All @@ -70,6 +74,10 @@ type apiFunc func(r *http.Request) (interface{}, *apiError)

// Register the API's endpoints in the given router.
func (api *API) Register(r *route.Router) {
if api.context == nil {
api.context = route.Context
}

instr := func(name string, f apiFunc) http.HandlerFunc {
return prometheus.InstrumentHandlerFunc(name, func(w http.ResponseWriter, r *http.Request) {
setCORS(w)
Expand All @@ -84,7 +92,7 @@ func (api *API) Register(r *route.Router) {
r.Get("/query", instr("query", api.query))
r.Get("/query_range", instr("query_range", api.queryRange))

r.Get("/metrics/names", instr("metric_names", api.metricNames))
r.Get("/label/:name/values", instr("label_values", api.labelValues))
}

type queryData struct {
Expand Down Expand Up @@ -154,11 +162,16 @@ func (api *API) queryRange(r *http.Request) (interface{}, *apiError) {
}, nil
}

func (api *API) metricNames(r *http.Request) (interface{}, *apiError) {
metricNames := api.Storage.LabelValuesForLabelName(clientmodel.MetricNameLabel)
sort.Sort(metricNames)
func (api *API) labelValues(r *http.Request) (interface{}, *apiError) {
name := route.Param(api.context(r), "name")

if !clientmodel.LabelNameRE.MatchString(name) {
return nil, &apiError{errorBadData, fmt.Errorf("invalid label name: %q", name)}
}
vals := api.Storage.LabelValuesForLabelName(clientmodel.LabelName(name))
sort.Sort(vals)

return metricNames, nil
return vals, nil
}

func respond(w http.ResponseWriter, data interface{}) {
Expand Down
49 changes: 41 additions & 8 deletions web/api/v1/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ import (
"testing"
"time"

"golang.org/x/net/context"

clientmodel "github.com/prometheus/client_golang/model"

"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/util/route"
)

func TestEndpoints(t *testing.T) {
Expand All @@ -40,6 +43,7 @@ func TestEndpoints(t *testing.T) {
start := clientmodel.Timestamp(0)
var tests = []struct {
endpoint apiFunc
params map[string]string
query url.Values
response interface{}
errType errorType
Expand Down Expand Up @@ -87,30 +91,59 @@ func TestEndpoints(t *testing.T) {
},
},
{
endpoint: api.metricNames,
endpoint: api.labelValues,
params: map[string]string{
"name": "__name__",
},
response: clientmodel.LabelValues{
"test_metric1",
"test_metric2",
},
},
{
endpoint: api.labelValues,
params: map[string]string{
"name": "not!!!allowed",
},
errType: errorBadData,
},
{
endpoint: api.labelValues,
params: map[string]string{
"name": "foo",
},
response: clientmodel.LabelValues{
"bar",
"boo",
},
},
}

for _, test := range tests {
// Build a context with the correct request params.
ctx := context.Background()
for p, v := range test.params {
ctx = route.WithParam(ctx, p, v)
}
api.context = func(r *http.Request) context.Context {
return ctx
}

req, err := http.NewRequest("ANY", fmt.Sprintf("http://example.com?%s", test.query.Encode()), nil)
if err != nil {
t.Fatal(err)
}
resp, apierr := test.endpoint(req)
if apierr != nil {
if test.errType == "" {
t.Fatalf("Unexpected error: %s", apierr)
resp, apiErr := test.endpoint(req)
if apiErr != nil {
if test.errType == errorNone {
t.Fatalf("Unexpected error: %s", apiErr)
}
if test.errType != apierr.typ {
t.Fatalf("Expected error of type %q but got type %q", test.errType, apierr.typ)
if test.errType != apiErr.typ {
t.Fatalf("Expected error of type %q but got type %q", test.errType, apiErr.typ)
}
continue
}
if apierr == nil && test.errType != "" {
if apiErr == nil && test.errType != errorNone {
t.Fatalf("Expected error of type %q but got none", test.errType)
}
if !reflect.DeepEqual(resp, test.response) {
Expand Down

0 comments on commit 75b0b74

Please sign in to comment.