Merge branch 'master' into beorn7/histogram

This commit is contained in:
beorn7 2021-04-30 21:43:19 +02:00
commit ce36ee3182
3 changed files with 275 additions and 42 deletions

View File

@ -78,12 +78,12 @@ ifneq ($(shell which gotestsum),)
endif endif
endif endif
PROMU_VERSION ?= 0.11.1 PROMU_VERSION ?= 0.12.0
PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
GOLANGCI_LINT := GOLANGCI_LINT :=
GOLANGCI_LINT_OPTS ?= GOLANGCI_LINT_OPTS ?=
GOLANGCI_LINT_VERSION ?= v1.36.0 GOLANGCI_LINT_VERSION ?= v1.39.0
# golangci-lint only supports linux, darwin and windows platforms on i386/amd64. # golangci-lint only supports linux, darwin and windows platforms on i386/amd64.
# windows isn't included here because of the path separator being different. # windows isn't included here because of the path separator being different.
ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))

View File

@ -123,6 +123,7 @@ const (
epAlertManagers = apiPrefix + "/alertmanagers" epAlertManagers = apiPrefix + "/alertmanagers"
epQuery = apiPrefix + "/query" epQuery = apiPrefix + "/query"
epQueryRange = apiPrefix + "/query_range" epQueryRange = apiPrefix + "/query_range"
epQueryExemplars = apiPrefix + "/query_exemplars"
epLabels = apiPrefix + "/labels" epLabels = apiPrefix + "/labels"
epLabelValues = apiPrefix + "/label/:name/values" epLabelValues = apiPrefix + "/label/:name/values"
epSeries = apiPrefix + "/series" epSeries = apiPrefix + "/series"
@ -239,6 +240,8 @@ type API interface {
Query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, error) Query(ctx context.Context, query string, ts time.Time) (model.Value, Warnings, error)
// QueryRange performs a query for the given range. // QueryRange performs a query for the given range.
QueryRange(ctx context.Context, query string, r Range) (model.Value, Warnings, error) QueryRange(ctx context.Context, query string, r Range) (model.Value, Warnings, error)
// QueryExemplars performs a query for exemplars by the given query and time range.
QueryExemplars(ctx context.Context, query string, startTime time.Time, endTime time.Time) ([]ExemplarQueryResult, error)
// Buildinfo returns various build information properties about the Prometheus server // Buildinfo returns various build information properties about the Prometheus server
Buildinfo(ctx context.Context) (BuildinfoResult, error) Buildinfo(ctx context.Context) (BuildinfoResult, error)
// Runtimeinfo returns the various runtime information properties about the Prometheus server. // Runtimeinfo returns the various runtime information properties about the Prometheus server.
@ -344,23 +347,28 @@ type Rules []interface{}
// AlertingRule models a alerting rule. // AlertingRule models a alerting rule.
type AlertingRule struct { type AlertingRule struct {
Name string `json:"name"` Name string `json:"name"`
Query string `json:"query"` Query string `json:"query"`
Duration float64 `json:"duration"` Duration float64 `json:"duration"`
Labels model.LabelSet `json:"labels"` Labels model.LabelSet `json:"labels"`
Annotations model.LabelSet `json:"annotations"` Annotations model.LabelSet `json:"annotations"`
Alerts []*Alert `json:"alerts"` Alerts []*Alert `json:"alerts"`
Health RuleHealth `json:"health"` Health RuleHealth `json:"health"`
LastError string `json:"lastError,omitempty"` LastError string `json:"lastError,omitempty"`
EvaluationTime float64 `json:"evaluationTime"`
LastEvaluation time.Time `json:"lastEvaluation"`
State string `json:"state"`
} }
// RecordingRule models a recording rule. // RecordingRule models a recording rule.
type RecordingRule struct { type RecordingRule struct {
Name string `json:"name"` Name string `json:"name"`
Query string `json:"query"` Query string `json:"query"`
Labels model.LabelSet `json:"labels,omitempty"` Labels model.LabelSet `json:"labels,omitempty"`
Health RuleHealth `json:"health"` Health RuleHealth `json:"health"`
LastError string `json:"lastError,omitempty"` LastError string `json:"lastError,omitempty"`
EvaluationTime float64 `json:"evaluationTime"`
LastEvaluation time.Time `json:"lastEvaluation"`
} }
// Alert models an active alert. // Alert models an active alert.
@ -380,12 +388,15 @@ type TargetsResult struct {
// ActiveTarget models an active Prometheus scrape target. // ActiveTarget models an active Prometheus scrape target.
type ActiveTarget struct { type ActiveTarget struct {
DiscoveredLabels map[string]string `json:"discoveredLabels"` DiscoveredLabels map[string]string `json:"discoveredLabels"`
Labels model.LabelSet `json:"labels"` Labels model.LabelSet `json:"labels"`
ScrapeURL string `json:"scrapeUrl"` ScrapePool string `json:"scrapePool"`
LastError string `json:"lastError"` ScrapeURL string `json:"scrapeUrl"`
LastScrape time.Time `json:"lastScrape"` GlobalURL string `json:"globalUrl"`
Health HealthStatus `json:"health"` LastError string `json:"lastError"`
LastScrape time.Time `json:"lastScrape"`
LastScrapeDuration float64 `json:"lastScrapeDuration"`
Health HealthStatus `json:"health"`
} }
// DroppedTarget models a dropped Prometheus scrape target. // DroppedTarget models a dropped Prometheus scrape target.
@ -480,14 +491,17 @@ func (r *AlertingRule) UnmarshalJSON(b []byte) error {
} }
rule := struct { rule := struct {
Name string `json:"name"` Name string `json:"name"`
Query string `json:"query"` Query string `json:"query"`
Duration float64 `json:"duration"` Duration float64 `json:"duration"`
Labels model.LabelSet `json:"labels"` Labels model.LabelSet `json:"labels"`
Annotations model.LabelSet `json:"annotations"` Annotations model.LabelSet `json:"annotations"`
Alerts []*Alert `json:"alerts"` Alerts []*Alert `json:"alerts"`
Health RuleHealth `json:"health"` Health RuleHealth `json:"health"`
LastError string `json:"lastError,omitempty"` LastError string `json:"lastError,omitempty"`
EvaluationTime float64 `json:"evaluationTime"`
LastEvaluation time.Time `json:"lastEvaluation"`
State string `json:"state"`
}{} }{}
if err := json.Unmarshal(b, &rule); err != nil { if err := json.Unmarshal(b, &rule); err != nil {
return err return err
@ -500,6 +514,9 @@ func (r *AlertingRule) UnmarshalJSON(b []byte) error {
r.Duration = rule.Duration r.Duration = rule.Duration
r.Labels = rule.Labels r.Labels = rule.Labels
r.LastError = rule.LastError r.LastError = rule.LastError
r.EvaluationTime = rule.EvaluationTime
r.LastEvaluation = rule.LastEvaluation
r.State = rule.State
return nil return nil
} }
@ -519,11 +536,13 @@ func (r *RecordingRule) UnmarshalJSON(b []byte) error {
} }
rule := struct { rule := struct {
Name string `json:"name"` Name string `json:"name"`
Query string `json:"query"` Query string `json:"query"`
Labels model.LabelSet `json:"labels,omitempty"` Labels model.LabelSet `json:"labels,omitempty"`
Health RuleHealth `json:"health"` Health RuleHealth `json:"health"`
LastError string `json:"lastError,omitempty"` LastError string `json:"lastError,omitempty"`
EvaluationTime float64 `json:"evaluationTime"`
LastEvaluation time.Time `json:"lastEvaluation"`
}{} }{}
if err := json.Unmarshal(b, &rule); err != nil { if err := json.Unmarshal(b, &rule); err != nil {
return err return err
@ -533,6 +552,8 @@ func (r *RecordingRule) UnmarshalJSON(b []byte) error {
r.Name = rule.Name r.Name = rule.Name
r.LastError = rule.LastError r.LastError = rule.LastError
r.Query = rule.Query r.Query = rule.Query
r.EvaluationTime = rule.EvaluationTime
r.LastEvaluation = rule.LastEvaluation
return nil return nil
} }
@ -570,6 +591,18 @@ func (qr *queryResult) UnmarshalJSON(b []byte) error {
return err return err
} }
// Exemplar is additional information associated with a time series.
type Exemplar struct {
Labels model.LabelSet `json:"labels"`
Value model.SampleValue `json:"value"`
Timestamp model.Time `json:"timestamp"`
}
type ExemplarQueryResult struct {
SeriesLabels model.LabelSet `json:"seriesLabels"`
Exemplars []Exemplar `json:"exemplars"`
}
// NewAPI returns a new API for the client. // NewAPI returns a new API for the client.
// //
// It is safe to use the returned API from multiple goroutines. // It is safe to use the returned API from multiple goroutines.
@ -949,7 +982,29 @@ func (h *httpAPI) TSDB(ctx context.Context) (TSDBResult, error) {
var res TSDBResult var res TSDBResult
return res, json.Unmarshal(body, &res) return res, json.Unmarshal(body, &res)
}
func (h *httpAPI) QueryExemplars(ctx context.Context, query string, startTime time.Time, endTime time.Time) ([]ExemplarQueryResult, error) {
u := h.client.URL(epQueryExemplars, nil)
q := u.Query()
q.Set("query", query)
q.Set("start", formatTime(startTime))
q.Set("end", formatTime(endTime))
u.RawQuery = q.Encode()
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return nil, err
}
_, body, _, err := h.client.Do(ctx, req)
if err != nil {
return nil, err
}
var res []ExemplarQueryResult
return res, json.Unmarshal(body, &res)
} }
// Warnings is an array of non critical errors // Warnings is an array of non critical errors

View File

@ -230,6 +230,13 @@ func TestAPIs(t *testing.T) {
} }
} }
doQueryExemplars := func(query string, startTime time.Time, endTime time.Time) func() (interface{}, Warnings, error) {
return func() (interface{}, Warnings, error) {
v, err := promAPI.QueryExemplars(context.Background(), query, startTime, endTime)
return v, nil, err
}
}
queryTests := []apiTest{ queryTests := []apiTest{
{ {
do: doQuery("2", testTime), do: doQuery("2", testTime),
@ -846,6 +853,111 @@ func TestAPIs(t *testing.T) {
}, },
}, },
// This has the newer API elements like lastEvaluation, evaluationTime, etc.
{
do: doRules(),
reqMethod: "GET",
reqPath: "/api/v1/rules",
inRes: map[string]interface{}{
"groups": []map[string]interface{}{
{
"file": "/rules.yaml",
"interval": 60,
"name": "example",
"rules": []map[string]interface{}{
{
"alerts": []map[string]interface{}{
{
"activeAt": testTime.UTC().Format(time.RFC3339Nano),
"annotations": map[string]interface{}{
"summary": "High request latency",
},
"labels": map[string]interface{}{
"alertname": "HighRequestLatency",
"severity": "page",
},
"state": "firing",
"value": "1e+00",
},
},
"annotations": map[string]interface{}{
"summary": "High request latency",
},
"duration": 600,
"health": "ok",
"labels": map[string]interface{}{
"severity": "page",
},
"name": "HighRequestLatency",
"query": "job:request_latency_seconds:mean5m{job=\"myjob\"} > 0.5",
"type": "alerting",
"evaluationTime": 0.5,
"lastEvaluation": "2020-05-18T15:52:53.4503113Z",
"state": "firing",
},
{
"health": "ok",
"name": "job:http_inprogress_requests:sum",
"query": "sum(http_inprogress_requests) by (job)",
"type": "recording",
"evaluationTime": 0.3,
"lastEvaluation": "2020-05-18T15:52:53.4503113Z",
},
},
},
},
},
res: RulesResult{
Groups: []RuleGroup{
{
Name: "example",
File: "/rules.yaml",
Interval: 60,
Rules: []interface{}{
AlertingRule{
Alerts: []*Alert{
{
ActiveAt: testTime.UTC(),
Annotations: model.LabelSet{
"summary": "High request latency",
},
Labels: model.LabelSet{
"alertname": "HighRequestLatency",
"severity": "page",
},
State: AlertStateFiring,
Value: "1e+00",
},
},
Annotations: model.LabelSet{
"summary": "High request latency",
},
Labels: model.LabelSet{
"severity": "page",
},
Duration: 600,
Health: RuleHealthGood,
Name: "HighRequestLatency",
Query: "job:request_latency_seconds:mean5m{job=\"myjob\"} > 0.5",
LastError: "",
EvaluationTime: 0.5,
LastEvaluation: time.Date(2020, 5, 18, 15, 52, 53, 450311300, time.UTC),
State: "firing",
},
RecordingRule{
Health: RuleHealthGood,
Name: "job:http_inprogress_requests:sum",
Query: "sum(http_inprogress_requests) by (job)",
LastError: "",
EvaluationTime: 0.3,
LastEvaluation: time.Date(2020, 5, 18, 15, 52, 53, 450311300, time.UTC),
},
},
},
},
},
},
{ {
do: doRules(), do: doRules(),
reqMethod: "GET", reqMethod: "GET",
@ -871,10 +983,13 @@ func TestAPIs(t *testing.T) {
"instance": "127.0.0.1:9090", "instance": "127.0.0.1:9090",
"job": "prometheus", "job": "prometheus",
}, },
"scrapeUrl": "http://127.0.0.1:9090", "scrapePool": "prometheus",
"lastError": "error while scraping target", "scrapeUrl": "http://127.0.0.1:9090",
"lastScrape": testTime.UTC().Format(time.RFC3339Nano), "globalUrl": "http://127.0.0.1:9090",
"health": "up", "lastError": "error while scraping target",
"lastScrape": testTime.UTC().Format(time.RFC3339Nano),
"lastScrapeDuration": 0.001146115,
"health": "up",
}, },
}, },
"droppedTargets": []map[string]interface{}{ "droppedTargets": []map[string]interface{}{
@ -901,10 +1016,13 @@ func TestAPIs(t *testing.T) {
"instance": "127.0.0.1:9090", "instance": "127.0.0.1:9090",
"job": "prometheus", "job": "prometheus",
}, },
ScrapeURL: "http://127.0.0.1:9090", ScrapePool: "prometheus",
LastError: "error while scraping target", ScrapeURL: "http://127.0.0.1:9090",
LastScrape: testTime.UTC(), GlobalURL: "http://127.0.0.1:9090",
Health: HealthGood, LastError: "error while scraping target",
LastScrape: testTime.UTC(),
LastScrapeDuration: 0.001146115,
Health: HealthGood,
}, },
}, },
Dropped: []DroppedTarget{ Dropped: []DroppedTarget{
@ -1079,6 +1197,66 @@ func TestAPIs(t *testing.T) {
}, },
}, },
}, },
{
do: doQueryExemplars("tns_request_duration_seconds_bucket", testTime.Add(-1*time.Minute), testTime),
reqMethod: "GET",
reqPath: "/api/v1/query_exemplars",
inErr: fmt.Errorf("some error"),
err: fmt.Errorf("some error"),
},
{
do: doQueryExemplars("tns_request_duration_seconds_bucket", testTime.Add(-1*time.Minute), testTime),
reqMethod: "GET",
reqPath: "/api/v1/query_exemplars",
inRes: []interface{}{
map[string]interface{}{
"seriesLabels": map[string]interface{}{
"__name__": "tns_request_duration_seconds_bucket",
"instance": "app:80",
"job": "tns/app",
},
"exemplars": []interface{}{
map[string]interface{}{
"labels": map[string]interface{}{
"traceID": "19fd8c8a33975a23",
},
"value": "0.003863295",
"timestamp": model.TimeFromUnixNano(testTime.UnixNano()),
},
map[string]interface{}{
"labels": map[string]interface{}{
"traceID": "67f743f07cc786b0",
},
"value": "0.001535405",
"timestamp": model.TimeFromUnixNano(testTime.UnixNano()),
},
},
},
},
res: []ExemplarQueryResult{
{
SeriesLabels: model.LabelSet{
"__name__": "tns_request_duration_seconds_bucket",
"instance": "app:80",
"job": "tns/app",
},
Exemplars: []Exemplar{
{
Labels: model.LabelSet{"traceID": "19fd8c8a33975a23"},
Value: 0.003863295,
Timestamp: model.TimeFromUnixNano(testTime.UnixNano()),
},
{
Labels: model.LabelSet{"traceID": "67f743f07cc786b0"},
Value: 0.001535405,
Timestamp: model.TimeFromUnixNano(testTime.UnixNano()),
},
},
},
},
},
} }
var tests []apiTest var tests []apiTest