diff --git a/CHANGELOG.md b/CHANGELOG.md index e5e33f6..2e1588f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 0.9.4 / 2019-06-07 +* [CHANGE] API client: Switch to alert values as strings. #585 +* [FEATURE] Add a collector for Go module build information. #595 +* [FEATURE] promhttp: Add an counter for internal errors during HTTP exposition. #594 +* [FEATURE] API client: Support target metadata API. #590 +* [FEATURE] API client: Support storage warnings. #562 +* [ENHANCEMENT] API client: Improve performance handling JSON. #570 +* [BUGFIX] Reduce test flakiness. #573 + ## 0.9.3 / 2019-05-16 * [CHANGE] Required Go version is now 1.9+. #561 * [FEATURE] API client: Add POST with get fallback for Query/QueryRange. #557 diff --git a/Makefile.common b/Makefile.common index c7f9ea6..6b94f83 100644 --- a/Makefile.common +++ b/Makefile.common @@ -86,6 +86,7 @@ endif PREFIX ?= $(shell pwd) BIN_DIR ?= $(shell pwd) DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) +DOCKERFILE_PATH ?= ./ DOCKER_REPO ?= prom DOCKER_ARCHS ?= amd64 @@ -212,7 +213,7 @@ $(BUILD_DOCKER_ARCHS): common-docker-%: docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" \ --build-arg ARCH="$*" \ --build-arg OS="linux" \ - . + $(DOCKERFILE_PATH) .PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS) common-docker-publish: $(PUBLISH_DOCKER_ARCHS) diff --git a/VERSION b/VERSION index 965065d..a602fc9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.3 +0.9.4 diff --git a/api/client.go b/api/client.go index db78ce2..2e6a551 100644 --- a/api/client.go +++ b/api/client.go @@ -25,6 +25,42 @@ import ( "time" ) +func NewErrorAPI(err error, warnings []string) Error { + if err == nil && warnings == nil { + return nil + } + return &ErrorAPI{err, warnings} +} + +type ErrorAPI struct { + err error + warnings []string +} + +func (w *ErrorAPI) Err() error { + return w.err +} + +func (w *ErrorAPI) Error() string { + if w.err != nil { + return w.err.Error() + } + return "Warnings: " + strings.Join(w.warnings, " , ") +} + +func (w *ErrorAPI) Warnings() []string { + return w.warnings +} + +// Error encapsulates an error + warning +type Error interface { + error + // Err returns the underlying error. + Err() error + // Warnings returns a list of warnings. + Warnings() []string +} + // DefaultRoundTripper is used if no RoundTripper is set in Config. var DefaultRoundTripper http.RoundTripper = &http.Transport{ Proxy: http.ProxyFromEnvironment, @@ -55,14 +91,14 @@ func (cfg *Config) roundTripper() http.RoundTripper { // Client is the interface for an API client. type Client interface { URL(ep string, args map[string]string) *url.URL - Do(context.Context, *http.Request) (*http.Response, []byte, error) + Do(context.Context, *http.Request) (*http.Response, []byte, Error) } // DoGetFallback will attempt to do the request as-is, and on a 405 it will fallback to a GET request. -func DoGetFallback(c Client, ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, error) { +func DoGetFallback(c Client, ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Error) { req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(args.Encode())) if err != nil { - return nil, nil, err + return nil, nil, NewErrorAPI(err, nil) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") @@ -71,11 +107,14 @@ func DoGetFallback(c Client, ctx context.Context, u *url.URL, args url.Values) ( u.RawQuery = args.Encode() req, err = http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return nil, nil, err + return nil, nil, NewErrorAPI(err, nil) } } else { - return resp, body, err + if err != nil { + return resp, body, NewErrorAPI(err, nil) + } + return resp, body, nil } return c.Do(ctx, req) } @@ -115,7 +154,7 @@ func (c *httpClient) URL(ep string, args map[string]string) *url.URL { return &u } -func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { +func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, Error) { if ctx != nil { req = req.WithContext(ctx) } @@ -127,7 +166,7 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, }() if err != nil { - return nil, nil, err + return nil, nil, NewErrorAPI(err, nil) } var body []byte @@ -147,5 +186,5 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, case <-done: } - return resp, body, err + return resp, body, NewErrorAPI(err, nil) } diff --git a/api/client_test.go b/api/client_test.go index 7877e6a..b0ea306 100644 --- a/api/client_test.go +++ b/api/client_test.go @@ -20,8 +20,6 @@ import ( "net/http/httptest" "net/url" "testing" - - "github.com/prometheus/tsdb/testutil" ) func TestConfig(t *testing.T) { @@ -148,7 +146,9 @@ func TestDoGetFallback(t *testing.T) { defer server.Close() u, err := url.Parse(server.URL) - testutil.Ok(t, err) + if err != nil { + t.Fatal(err) + } client := &httpClient{client: *(server.Client())} // Do a post, and ensure that the post succeeds. @@ -158,7 +158,7 @@ func TestDoGetFallback(t *testing.T) { } resp := &testResponse{} if err := json.Unmarshal(b, resp); err != nil { - testutil.Ok(t, err) + t.Fatal(err) } if resp.Method != http.MethodPost { t.Fatalf("Mismatch method") @@ -174,7 +174,7 @@ func TestDoGetFallback(t *testing.T) { t.Fatalf("Error doing local request: %v", err) } if err := json.Unmarshal(b, resp); err != nil { - testutil.Ok(t, err) + t.Fatal(err) } if resp.Method != http.MethodGet { t.Fatalf("Mismatch method") diff --git a/api/prometheus/v1/api.go b/api/prometheus/v1/api.go index 8394c97..28cdaef 100644 --- a/api/prometheus/v1/api.go +++ b/api/prometheus/v1/api.go @@ -17,17 +17,105 @@ package v1 import ( "context" - "encoding/json" "errors" "fmt" + "math" "net/http" "strconv" + "strings" "time" + "unsafe" + + json "github.com/json-iterator/go" + + "github.com/prometheus/common/model" "github.com/prometheus/client_golang/api" - "github.com/prometheus/common/model" ) +func init() { + json.RegisterTypeEncoderFunc("model.SamplePair", marshalPointJSON, marshalPointJSONIsEmpty) + json.RegisterTypeDecoderFunc("model.SamplePair", unMarshalPointJSON) +} + +func unMarshalPointJSON(ptr unsafe.Pointer, iter *json.Iterator) { + p := (*model.SamplePair)(ptr) + if !iter.ReadArray() { + iter.ReportError("unmarshal model.SamplePair", "SamplePair must be [timestamp, value]") + return + } + t := iter.ReadNumber() + if err := p.Timestamp.UnmarshalJSON([]byte(t)); err != nil { + iter.ReportError("unmarshal model.SamplePair", err.Error()) + return + } + if !iter.ReadArray() { + iter.ReportError("unmarshal model.SamplePair", "SamplePair missing value") + return + } + + f, err := strconv.ParseFloat(iter.ReadString(), 64) + if err != nil { + iter.ReportError("unmarshal model.SamplePair", err.Error()) + return + } + p.Value = model.SampleValue(f) + + if iter.ReadArray() { + iter.ReportError("unmarshal model.SamplePair", "SamplePair has too many values, must be [timestamp, value]") + return + } +} + +func marshalPointJSON(ptr unsafe.Pointer, stream *json.Stream) { + p := *((*model.SamplePair)(ptr)) + stream.WriteArrayStart() + // Write out the timestamp as a float divided by 1000. + // This is ~3x faster than converting to a float. + t := int64(p.Timestamp) + if t < 0 { + stream.WriteRaw(`-`) + t = -t + } + stream.WriteInt64(t / 1000) + fraction := t % 1000 + if fraction != 0 { + stream.WriteRaw(`.`) + if fraction < 100 { + stream.WriteRaw(`0`) + } + if fraction < 10 { + stream.WriteRaw(`0`) + } + stream.WriteInt64(fraction) + } + stream.WriteMore() + stream.WriteRaw(`"`) + + // Taken from https://github.com/json-iterator/go/blob/master/stream_float.go#L71 as a workaround + // to https://github.com/json-iterator/go/issues/365 (jsoniter, to follow json standard, doesn't allow inf/nan) + buf := stream.Buffer() + abs := math.Abs(float64(p.Value)) + fmt := byte('f') + // Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right. + if abs != 0 { + if abs < 1e-6 || abs >= 1e21 { + fmt = 'e' + fmt = 'e' + } + } + buf = strconv.AppendFloat(buf, float64(p.Value), fmt, -1, 64) + stream.SetBuffer(buf) + + stream.WriteRaw(`"`) + stream.WriteArrayEnd() + +} + +func marshalPointJSONIsEmpty(ptr unsafe.Pointer) bool { + return false +} + const ( statusAPIError = 422 @@ -40,6 +128,7 @@ const ( epLabelValues = apiPrefix + "/label/:name/values" epSeries = apiPrefix + "/series" epTargets = apiPrefix + "/targets" + epTargetsMetadata = apiPrefix + "/targets/metadata" epRules = apiPrefix + "/rules" epSnapshot = apiPrefix + "/admin/tsdb/snapshot" epDeleteSeries = apiPrefix + "/admin/tsdb/delete_series" @@ -63,6 +152,9 @@ type RuleType string // RuleHealth models the health status of a rule. type RuleHealth string +// MetricType models the type of a metric. +type MetricType string + const ( // Possible values for AlertState. AlertStateFiring AlertState = "firing" @@ -91,17 +183,40 @@ const ( RuleHealthGood = "ok" RuleHealthUnknown = "unknown" RuleHealthBad = "err" + + // Possible values for MetricType + MetricTypeCounter MetricType = "counter" + MetricTypeGauge MetricType = "gauge" + MetricTypeHistogram MetricType = "histogram" + MetricTypeGaugeHistogram MetricType = "gaugehistogram" + MetricTypeSummary MetricType = "summary" + MetricTypeInfo MetricType = "info" + MetricTypeStateset MetricType = "stateset" + MetricTypeUnknown MetricType = "unknown" ) // Error is an error returned by the API. type Error struct { - Type ErrorType - Msg string - Detail string + Type ErrorType + Msg string + Detail string + warnings []string } func (e *Error) Error() string { - return fmt.Sprintf("%s: %s", e.Type, e.Msg) + if e.Type != "" || e.Msg != "" { + return fmt.Sprintf("%s: %s", e.Type, e.Msg) + } + + return "Warnings: " + strings.Join(e.warnings, " , ") +} + +func (w *Error) Err() error { + return w +} + +func (w *Error) Warnings() []string { + return w.warnings } // Range represents a sliced time range. @@ -115,32 +230,34 @@ type Range struct { // API provides bindings for Prometheus's v1 API. type API interface { // Alerts returns a list of all active alerts. - Alerts(ctx context.Context) (AlertsResult, error) + Alerts(ctx context.Context) (AlertsResult, api.Error) // AlertManagers returns an overview of the current state of the Prometheus alert manager discovery. - AlertManagers(ctx context.Context) (AlertManagersResult, error) + AlertManagers(ctx context.Context) (AlertManagersResult, api.Error) // CleanTombstones removes the deleted data from disk and cleans up the existing tombstones. - CleanTombstones(ctx context.Context) error + CleanTombstones(ctx context.Context) api.Error // Config returns the current Prometheus configuration. - Config(ctx context.Context) (ConfigResult, error) + Config(ctx context.Context) (ConfigResult, api.Error) // DeleteSeries deletes data for a selection of series in a time range. - DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error + DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) api.Error // Flags returns the flag values that Prometheus was launched with. - Flags(ctx context.Context) (FlagsResult, error) + Flags(ctx context.Context) (FlagsResult, api.Error) // LabelValues performs a query for the values of the given label. - LabelValues(ctx context.Context, label string) (model.LabelValues, error) + LabelValues(ctx context.Context, label string) (model.LabelValues, api.Error) // Query performs a query for the given time. - Query(ctx context.Context, query string, ts time.Time) (model.Value, error) + Query(ctx context.Context, query string, ts time.Time) (model.Value, api.Error) // QueryRange performs a query for the given range. - QueryRange(ctx context.Context, query string, r Range) (model.Value, error) + QueryRange(ctx context.Context, query string, r Range) (model.Value, api.Error) // Series finds series by label matchers. - Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, error) + Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, api.Error) // Snapshot creates a snapshot of all current data into snapshots/- // under the TSDB's data directory and returns the directory as response. - Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error) + Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, api.Error) // Rules returns a list of alerting and recording rules that are currently loaded. - Rules(ctx context.Context) (RulesResult, error) + Rules(ctx context.Context) (RulesResult, api.Error) // Targets returns an overview of the current state of the Prometheus target discovery. - Targets(ctx context.Context) (TargetsResult, error) + Targets(ctx context.Context) (TargetsResult, api.Error) + // TargetsMetadata returns metadata about metrics currently scraped by the target. + TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]MetricMetadata, api.Error) } // AlertsResult contains the result from querying the alerts endpoint. @@ -226,7 +343,7 @@ type Alert struct { Annotations model.LabelSet Labels model.LabelSet State AlertState - Value float64 + Value string } // TargetsResult contains the result from querying the targets endpoint. @@ -250,6 +367,15 @@ type DroppedTarget struct { DiscoveredLabels map[string]string `json:"discoveredLabels"` } +// MetricMetadata models the metadata of a metric. +type MetricMetadata struct { + Target map[string]string `json:"target"` + Metric string `json:"metric,omitempty"` + Type MetricType `json:"type"` + Help string `json:"help"` + Unit string `json:"unit"` +} + // queryResult contains result data for a query. type queryResult struct { Type model.ValueType `json:"resultType"` @@ -408,73 +534,73 @@ type httpAPI struct { client api.Client } -func (h *httpAPI) Alerts(ctx context.Context) (AlertsResult, error) { +func (h *httpAPI) Alerts(ctx context.Context) (AlertsResult, api.Error) { u := h.client.URL(epAlerts, nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return AlertsResult{}, err + return AlertsResult{}, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return AlertsResult{}, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return AlertsResult{}, apiErr } var res AlertsResult err = json.Unmarshal(body, &res) - return res, err + return res, api.NewErrorAPI(err, nil) } -func (h *httpAPI) AlertManagers(ctx context.Context) (AlertManagersResult, error) { +func (h *httpAPI) AlertManagers(ctx context.Context) (AlertManagersResult, api.Error) { u := h.client.URL(epAlertManagers, nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return AlertManagersResult{}, err + return AlertManagersResult{}, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return AlertManagersResult{}, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return AlertManagersResult{}, apiErr } var res AlertManagersResult err = json.Unmarshal(body, &res) - return res, err + return res, api.NewErrorAPI(err, nil) } -func (h *httpAPI) CleanTombstones(ctx context.Context) error { +func (h *httpAPI) CleanTombstones(ctx context.Context) api.Error { u := h.client.URL(epCleanTombstones, nil) req, err := http.NewRequest(http.MethodPost, u.String(), nil) if err != nil { - return err + return api.NewErrorAPI(err, nil) } - _, _, err = h.client.Do(ctx, req) - return err + _, _, apiErr := h.client.Do(ctx, req) + return apiErr } -func (h *httpAPI) Config(ctx context.Context) (ConfigResult, error) { +func (h *httpAPI) Config(ctx context.Context) (ConfigResult, api.Error) { u := h.client.URL(epConfig, nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return ConfigResult{}, err + return ConfigResult{}, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return ConfigResult{}, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return ConfigResult{}, apiErr } var res ConfigResult err = json.Unmarshal(body, &res) - return res, err + return res, api.NewErrorAPI(err, nil) } -func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error { +func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) api.Error { u := h.client.URL(epDeleteSeries, nil) q := u.Query() @@ -489,47 +615,47 @@ func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime req, err := http.NewRequest(http.MethodPost, u.String(), nil) if err != nil { - return err + return api.NewErrorAPI(err, nil) } - _, _, err = h.client.Do(ctx, req) - return err + _, _, apiErr := h.client.Do(ctx, req) + return apiErr } -func (h *httpAPI) Flags(ctx context.Context) (FlagsResult, error) { +func (h *httpAPI) Flags(ctx context.Context) (FlagsResult, api.Error) { u := h.client.URL(epFlags, nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return FlagsResult{}, err + return FlagsResult{}, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return FlagsResult{}, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return FlagsResult{}, apiErr } var res FlagsResult err = json.Unmarshal(body, &res) - return res, err + return res, api.NewErrorAPI(err, nil) } -func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, error) { +func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, api.Error) { u := h.client.URL(epLabelValues, map[string]string{"name": label}) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return nil, err + return nil, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return nil, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return nil, apiErr } var labelValues model.LabelValues err = json.Unmarshal(body, &labelValues) - return labelValues, err + return labelValues, api.NewErrorAPI(err, nil) } -func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, error) { +func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, api.Error) { u := h.client.URL(epQuery, nil) q := u.Query() @@ -538,18 +664,16 @@ func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model. q.Set("time", ts.Format(time.RFC3339Nano)) } - _, body, err := api.DoGetFallback(h.client, ctx, u, q) - if err != nil { - return nil, err + _, body, apiErr := api.DoGetFallback(h.client, ctx, u, q) + if apiErr != nil { + return nil, apiErr } var qres queryResult - err = json.Unmarshal(body, &qres) - - return model.Value(qres.v), err + return model.Value(qres.v), api.NewErrorAPI(json.Unmarshal(body, &qres), nil) } -func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, error) { +func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, api.Error) { u := h.client.URL(epQueryRange, nil) q := u.Query() @@ -564,18 +688,17 @@ func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model. q.Set("end", end) q.Set("step", step) - _, body, err := api.DoGetFallback(h.client, ctx, u, q) - if err != nil { - return nil, err + _, body, apiErr := api.DoGetFallback(h.client, ctx, u, q) + if apiErr != nil { + return nil, apiErr } var qres queryResult - err = json.Unmarshal(body, &qres) - return model.Value(qres.v), err + return model.Value(qres.v), api.NewErrorAPI(json.Unmarshal(body, &qres), nil) } -func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, error) { +func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, api.Error) { u := h.client.URL(epSeries, nil) q := u.Query() @@ -590,20 +713,20 @@ func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.T req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return nil, err + return nil, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return nil, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return nil, apiErr } var mset []model.LabelSet err = json.Unmarshal(body, &mset) - return mset, err + return mset, api.NewErrorAPI(err, nil) } -func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error) { +func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, api.Error) { u := h.client.URL(epSnapshot, nil) q := u.Query() @@ -613,53 +736,78 @@ func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, req, err := http.NewRequest(http.MethodPost, u.String(), nil) if err != nil { - return SnapshotResult{}, err + return SnapshotResult{}, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return SnapshotResult{}, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return SnapshotResult{}, apiErr } var res SnapshotResult err = json.Unmarshal(body, &res) - return res, err + return res, api.NewErrorAPI(err, nil) } -func (h *httpAPI) Rules(ctx context.Context) (RulesResult, error) { +func (h *httpAPI) Rules(ctx context.Context) (RulesResult, api.Error) { u := h.client.URL(epRules, nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return RulesResult{}, err + return RulesResult{}, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return RulesResult{}, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return RulesResult{}, apiErr } var res RulesResult err = json.Unmarshal(body, &res) - return res, err + return res, api.NewErrorAPI(err, nil) } -func (h *httpAPI) Targets(ctx context.Context) (TargetsResult, error) { +func (h *httpAPI) Targets(ctx context.Context) (TargetsResult, api.Error) { u := h.client.URL(epTargets, nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return TargetsResult{}, err + return TargetsResult{}, api.NewErrorAPI(err, nil) } - _, body, err := h.client.Do(ctx, req) - if err != nil { - return TargetsResult{}, err + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return TargetsResult{}, apiErr } var res TargetsResult err = json.Unmarshal(body, &res) - return res, err + return res, api.NewErrorAPI(err, nil) +} + +func (h *httpAPI) TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]MetricMetadata, api.Error) { + u := h.client.URL(epTargetsMetadata, nil) + q := u.Query() + + q.Set("match_target", matchTarget) + q.Set("metric", metric) + q.Set("limit", limit) + + u.RawQuery = q.Encode() + + req, err := http.NewRequest(http.MethodGet, u.String(), nil) + if err != nil { + return nil, api.NewErrorAPI(err, nil) + } + + _, body, apiErr := h.client.Do(ctx, req) + if apiErr != nil { + return nil, apiErr + } + + var res []MetricMetadata + err = json.Unmarshal(body, &res) + return res, api.NewErrorAPI(err, nil) } // apiClient wraps a regular client and processes successful API responses. @@ -673,6 +821,7 @@ type apiResponse struct { Data json.RawMessage `json:"data"` ErrorType ErrorType `json:"errorType"` Error string `json:"error"` + Warnings []string `json:"warnings,omitempty"` } func apiError(code int) bool { @@ -690,14 +839,16 @@ func errorTypeAndMsgFor(resp *http.Response) (ErrorType, string) { return ErrBadResponse, fmt.Sprintf("bad response code %d", resp.StatusCode) } -func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { - resp, body, err := c.Client.Do(ctx, req) - if err != nil { - return resp, body, err +func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Error) { + resp, body, apiErr := c.Client.Do(ctx, req) + if apiErr != nil { + return resp, body, apiErr } code := resp.StatusCode + var err api.Error + if code/100 != 2 && !apiError(code) { errorType, errorMsg := errorTypeAndMsgFor(resp) return resp, body, &Error{ @@ -710,27 +861,30 @@ func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, [ var result apiResponse if http.StatusNoContent != code { - if err = json.Unmarshal(body, &result); err != nil { + if jsonErr := json.Unmarshal(body, &result); jsonErr != nil { return resp, body, &Error{ Type: ErrBadResponse, - Msg: err.Error(), + Msg: jsonErr.Error(), } } } if apiError(code) != (result.Status == "error") { err = &Error{ - Type: ErrBadResponse, - Msg: "inconsistent body for response code", + Type: ErrBadResponse, + Msg: "inconsistent body for response code", + warnings: result.Warnings, } } if apiError(code) && result.Status == "error" { err = &Error{ - Type: result.ErrorType, - Msg: result.Error, + Type: result.ErrorType, + Msg: result.Error, + warnings: result.Warnings, } } return resp, []byte(result.Data), err + } diff --git a/api/prometheus/v1/api_bench_test.go b/api/prometheus/v1/api_bench_test.go new file mode 100644 index 0000000..764895a --- /dev/null +++ b/api/prometheus/v1/api_bench_test.go @@ -0,0 +1,112 @@ +// Copyright 2019 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package v1 + +import ( + "encoding/json" + "strconv" + "testing" + "time" + + jsoniter "github.com/json-iterator/go" + + "github.com/prometheus/common/model" +) + +func generateData(timeseries, datapoints int) model.Matrix { + m := make(model.Matrix, 0) + + for i := 0; i < timeseries; i++ { + lset := map[model.LabelName]model.LabelValue{ + model.MetricNameLabel: model.LabelValue("timeseries_" + strconv.Itoa(i)), + } + now := model.Now() + values := make([]model.SamplePair, datapoints) + + for x := datapoints; x > 0; x-- { + values[x-1] = model.SamplePair{ + // Set the time back assuming a 15s interval. Since this is used for + // Marshal/Unmarshal testing the actual interval doesn't matter. + Timestamp: now.Add(time.Second * -15 * time.Duration(x)), + Value: model.SampleValue(float64(x)), + } + } + + ss := &model.SampleStream{ + Metric: model.Metric(lset), + Values: values, + } + + m = append(m, ss) + } + return m +} + +func BenchmarkSamplesJsonSerialization(b *testing.B) { + for _, timeseriesCount := range []int{10, 100, 1000} { + b.Run(strconv.Itoa(timeseriesCount), func(b *testing.B) { + for _, datapointCount := range []int{10, 100, 1000} { + b.Run(strconv.Itoa(datapointCount), func(b *testing.B) { + data := generateData(timeseriesCount, datapointCount) + + dataBytes, err := json.Marshal(data) + if err != nil { + b.Fatalf("Error marshaling: %v", err) + } + + b.Run("marshal", func(b *testing.B) { + b.Run("encoding/json", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if _, err := json.Marshal(data); err != nil { + b.Fatal(err) + } + } + }) + + b.Run("jsoniter", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if _, err := jsoniter.Marshal(data); err != nil { + b.Fatal(err) + } + } + }) + }) + + b.Run("unmarshal", func(b *testing.B) { + b.Run("encoding/json", func(b *testing.B) { + b.ReportAllocs() + var m model.Matrix + for i := 0; i < b.N; i++ { + if err := json.Unmarshal(dataBytes, &m); err != nil { + b.Fatal(err) + } + } + }) + + b.Run("jsoniter", func(b *testing.B) { + b.ReportAllocs() + var m model.Matrix + for i := 0; i < b.N; i++ { + if err := jsoniter.Unmarshal(dataBytes, &m); err != nil { + b.Fatal(err) + } + } + }) + }) + }) + } + }) + } +} diff --git a/api/prometheus/v1/api_test.go b/api/prometheus/v1/api_test.go index f195f2b..0b8fe2b 100644 --- a/api/prometheus/v1/api_test.go +++ b/api/prometheus/v1/api_test.go @@ -15,9 +15,9 @@ package v1 import ( "context" - "encoding/json" "errors" "fmt" + "math" "net/http" "net/url" "reflect" @@ -25,11 +25,16 @@ import ( "testing" "time" + json "github.com/json-iterator/go" + "github.com/prometheus/common/model" + + "github.com/prometheus/client_golang/api" ) type apiTest struct { - do func() (interface{}, error) + do func() (interface{}, api.Error) + inWarnings []string inErr error inStatusCode int inRes interface{} @@ -58,7 +63,7 @@ func (c *apiTestClient) URL(ep string, args map[string]string) *url.URL { return u } -func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { +func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Error) { test := c.curTest @@ -83,7 +88,7 @@ func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Respon resp.StatusCode = http.StatusOK } - return resp, b, test.inErr + return resp, b, api.NewErrorAPI(test.inErr, test.inWarnings) } func TestAPIs(t *testing.T) { @@ -96,78 +101,84 @@ func TestAPIs(t *testing.T) { client: client, } - doAlertManagers := func() func() (interface{}, error) { - return func() (interface{}, error) { + doAlertManagers := func() func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.AlertManagers(context.Background()) } } - doCleanTombstones := func() func() (interface{}, error) { - return func() (interface{}, error) { + doCleanTombstones := func() func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return nil, promAPI.CleanTombstones(context.Background()) } } - doConfig := func() func() (interface{}, error) { - return func() (interface{}, error) { + doConfig := func() func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.Config(context.Background()) } } - doDeleteSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, error) { - return func() (interface{}, error) { + doDeleteSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return nil, promAPI.DeleteSeries(context.Background(), []string{matcher}, startTime, endTime) } } - doFlags := func() func() (interface{}, error) { - return func() (interface{}, error) { + doFlags := func() func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.Flags(context.Background()) } } - doLabelValues := func(label string) func() (interface{}, error) { - return func() (interface{}, error) { + doLabelValues := func(label string) func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.LabelValues(context.Background(), label) } } - doQuery := func(q string, ts time.Time) func() (interface{}, error) { - return func() (interface{}, error) { + doQuery := func(q string, ts time.Time) func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.Query(context.Background(), q, ts) } } - doQueryRange := func(q string, rng Range) func() (interface{}, error) { - return func() (interface{}, error) { + doQueryRange := func(q string, rng Range) func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.QueryRange(context.Background(), q, rng) } } - doSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, error) { - return func() (interface{}, error) { + doSeries := func(matcher string, startTime time.Time, endTime time.Time) func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.Series(context.Background(), []string{matcher}, startTime, endTime) } } - doSnapshot := func(skipHead bool) func() (interface{}, error) { - return func() (interface{}, error) { + doSnapshot := func(skipHead bool) func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.Snapshot(context.Background(), skipHead) } } - doRules := func() func() (interface{}, error) { - return func() (interface{}, error) { + doRules := func() func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.Rules(context.Background()) } } - doTargets := func() func() (interface{}, error) { - return func() (interface{}, error) { + doTargets := func() func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { return promAPI.Targets(context.Background()) } } + doTargetsMetadata := func(matchTarget string, metric string, limit string) func() (interface{}, api.Error) { + return func() (interface{}, api.Error) { + return promAPI.TargetsMetadata(context.Background(), matchTarget, metric, limit) + } + } + queryTests := []apiTest{ { do: doQuery("2", testTime), @@ -290,7 +301,7 @@ func TestAPIs(t *testing.T) { "end": []string{testTime.Format(time.RFC3339Nano)}, }, res: []model.LabelSet{ - model.LabelSet{ + { "__name__": "up", "job": "prometheus", "instance": "localhost:9090", @@ -487,7 +498,7 @@ func TestAPIs(t *testing.T) { "severity": "page", }, "state": "firing", - "value": 1, + "value": "1e+00", }, }, "annotations": map[string]interface{}{ @@ -531,7 +542,7 @@ func TestAPIs(t *testing.T) { "severity": "page", }, State: AlertStateFiring, - Value: 1, + Value: "1e+00", }, }, Annotations: model.LabelSet{ @@ -639,6 +650,52 @@ func TestAPIs(t *testing.T) { inErr: fmt.Errorf("some error"), err: fmt.Errorf("some error"), }, + + { + do: doTargetsMetadata("{job=\"prometheus\"}", "go_goroutines", "1"), + inRes: []map[string]interface{}{ + { + "target": map[string]interface{}{ + "instance": "127.0.0.1:9090", + "job": "prometheus", + }, + "type": "gauge", + "help": "Number of goroutines that currently exist.", + "unit": "", + }, + }, + reqMethod: "GET", + reqPath: "/api/v1/targets/metadata", + reqParam: url.Values{ + "match_target": []string{"{job=\"prometheus\"}"}, + "metric": []string{"go_goroutines"}, + "limit": []string{"1"}, + }, + res: []MetricMetadata{ + { + Target: map[string]string{ + "instance": "127.0.0.1:9090", + "job": "prometheus", + }, + Type: "gauge", + Help: "Number of goroutines that currently exist.", + Unit: "", + }, + }, + }, + + { + do: doTargetsMetadata("{job=\"prometheus\"}", "go_goroutines", "1"), + inErr: fmt.Errorf("some error"), + reqMethod: "GET", + reqPath: "/api/v1/targets/metadata", + reqParam: url.Values{ + "match_target": []string{"{job=\"prometheus\"}"}, + "metric": []string{"go_goroutines"}, + "limit": []string{"1"}, + }, + err: fmt.Errorf("some error"), + }, } var tests []apiTest @@ -693,7 +750,7 @@ func (c *testClient) URL(ep string, args map[string]string) *url.URL { return nil } -func (c *testClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { +func (c *testClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Error) { if ctx == nil { c.Fatalf("context was not passed down") } @@ -789,7 +846,7 @@ func TestAPIClientDo(t *testing.T) { response: "bad json", expectedErr: &Error{ Type: ErrBadResponse, - Msg: "invalid character 'b' looking for beginning of value", + Msg: "readObjectStart: expect { or n, but found b, error found in #1 byte of ...|bad json|..., bigger context ...|bad json|...", }, }, { @@ -829,6 +886,21 @@ func TestAPIClientDo(t *testing.T) { Msg: "inconsistent body for response code", }, }, + { + code: http.StatusOK, + response: &apiResponse{ + Status: "error", + Data: json.RawMessage(`"test"`), + ErrorType: ErrTimeout, + Error: "timed out", + Warnings: []string{"a"}, + }, + expectedErr: &Error{ + Type: ErrBadResponse, + Msg: "inconsistent body for response code", + warnings: []string{"a"}, + }, + }, } tc := &testClient{ @@ -847,28 +919,135 @@ func TestAPIClientDo(t *testing.T) { if test.expectedErr != nil { if err == nil { - t.Fatalf("expected error %q but got none", test.expectedErr) + t.Fatal("expected error, but got none") } + if test.expectedErr.Error() != err.Error() { - t.Errorf("unexpected error: want %q, got %q", test.expectedErr, err) + t.Fatalf("expected error:%v, but got:%v", test.expectedErr.Error(), err.Error()) } + if test.expectedErr.Detail != "" { apiErr := err.(*Error) if apiErr.Detail != test.expectedErr.Detail { - t.Errorf("unexpected error details: want %q, got %q", test.expectedErr.Detail, apiErr.Detail) + t.Fatalf("expected error detail :%v, but got:%v", apiErr.Detail, test.expectedErr.Detail) + } + } + + if len(test.expectedErr.Warnings()) != len(err.Warnings()) { + t.Fatalf("expected warnings length :%v, but got:%v", len(test.expectedErr.Warnings()), len(err.Warnings())) + } + + for x, warning := range test.expectedErr.Warnings() { + if warning != err.Warnings()[x] { + t.Fatalf("expected warning :%v, but got:%v", warning, err.Warnings()[x]) } } return } - if err != nil { - t.Fatalf("unexpeceted error %s", err) - } - want, got := test.expectedBody, string(body) - if want != got { - t.Errorf("unexpected body: want %q, got %q", want, got) + if err != nil { + t.Fatalf("unexpected error:%v", err) + } + if test.expectedBody != string(body) { + t.Fatalf("expected body :%v, but got:%v", test.expectedBody, string(body)) } }) } } + +func TestSamplesJsonSerialization(t *testing.T) { + tests := []struct { + point model.SamplePair + expected string + }{ + { + point: model.SamplePair{0, 0}, + expected: `[0,"0"]`, + }, + { + point: model.SamplePair{1, 20}, + expected: `[0.001,"20"]`, + }, + { + point: model.SamplePair{10, 20}, + expected: `[0.010,"20"]`, + }, + { + point: model.SamplePair{100, 20}, + expected: `[0.100,"20"]`, + }, + { + point: model.SamplePair{1001, 20}, + expected: `[1.001,"20"]`, + }, + { + point: model.SamplePair{1010, 20}, + expected: `[1.010,"20"]`, + }, + { + point: model.SamplePair{1100, 20}, + expected: `[1.100,"20"]`, + }, + { + point: model.SamplePair{12345678123456555, 20}, + expected: `[12345678123456.555,"20"]`, + }, + { + point: model.SamplePair{-1, 20}, + expected: `[-0.001,"20"]`, + }, + { + point: model.SamplePair{0, model.SampleValue(math.NaN())}, + expected: `[0,"NaN"]`, + }, + { + point: model.SamplePair{0, model.SampleValue(math.Inf(1))}, + expected: `[0,"+Inf"]`, + }, + { + point: model.SamplePair{0, model.SampleValue(math.Inf(-1))}, + expected: `[0,"-Inf"]`, + }, + { + point: model.SamplePair{0, model.SampleValue(1.2345678e6)}, + expected: `[0,"1234567.8"]`, + }, + { + point: model.SamplePair{0, 1.2345678e-6}, + expected: `[0,"0.0000012345678"]`, + }, + { + point: model.SamplePair{0, 1.2345678e-67}, + expected: `[0,"1.2345678e-67"]`, + }, + } + + for _, test := range tests { + t.Run(test.expected, func(t *testing.T) { + b, err := json.Marshal(test.point) + if err != nil { + t.Fatal(err) + } + if string(b) != test.expected { + t.Fatalf("Mismatch marshal expected=%s actual=%s", test.expected, string(b)) + } + + // To test Unmarshal we will Unmarshal then re-Marshal this way we + // can do a string compare, otherwise Nan values don't show equivalence + // properly. + var sp model.SamplePair + if err = json.Unmarshal(b, &sp); err != nil { + t.Fatal(err) + } + + b, err = json.Marshal(sp) + if err != nil { + t.Fatal(err) + } + if string(b) != test.expected { + t.Fatalf("Mismatch marshal expected=%s actual=%s", test.expected, string(b)) + } + }) + } +} diff --git a/examples/random/main.go b/examples/random/main.go index eef50d2..cb78b07 100644 --- a/examples/random/main.go +++ b/examples/random/main.go @@ -63,6 +63,8 @@ func init() { // Register the summary and the histogram with Prometheus's default registry. prometheus.MustRegister(rpcDurations) prometheus.MustRegister(rpcDurationsHistogram) + // Add Go module build info. + prometheus.MustRegister(prometheus.NewBuildInfoCollector()) } func main() { diff --git a/go.mod b/go.mod index e2c8b8b..97cee81 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,12 @@ module github.com/prometheus/client_golang require ( github.com/beorn7/perks v1.0.0 - github.com/go-logfmt/logfmt v0.4.0 // indirect github.com/golang/protobuf v1.3.1 + github.com/json-iterator/go v1.1.6 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 - github.com/prometheus/common v0.4.0 - github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 - github.com/prometheus/tsdb v0.7.1 + github.com/prometheus/common v0.4.1 + github.com/prometheus/procfs v0.0.2 + github.com/stretchr/testify v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 00c60f6..21c7bc0 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,16 @@ -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -21,33 +18,39 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= @@ -55,7 +58,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/prometheus/build_info.go b/prometheus/build_info.go new file mode 100644 index 0000000..288f0e8 --- /dev/null +++ b/prometheus/build_info.go @@ -0,0 +1,29 @@ +// Copyright 2019 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build go1.12 + +package prometheus + +import "runtime/debug" + +// readBuildInfo is a wrapper around debug.ReadBuildInfo for Go 1.12+. +func readBuildInfo() (path, version, sum string) { + path, version, sum = "unknown", "unknown", "unknown" + if bi, ok := debug.ReadBuildInfo(); ok { + path = bi.Main.Path + version = bi.Main.Version + sum = bi.Main.Sum + } + return +} diff --git a/prometheus/build_info_pre_1.12.go b/prometheus/build_info_pre_1.12.go new file mode 100644 index 0000000..6609e28 --- /dev/null +++ b/prometheus/build_info_pre_1.12.go @@ -0,0 +1,22 @@ +// Copyright 2019 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !go1.12 + +package prometheus + +// readBuildInfo is a wrapper around debug.ReadBuildInfo for Go versions before +// 1.12. Remove this whole file once the minimum supported Go version is 1.12. +func readBuildInfo() (path, version, sum string) { + return "unknown", "unknown", "unknown" +} diff --git a/prometheus/go_collector.go b/prometheus/go_collector.go index b108ec5..dc9247f 100644 --- a/prometheus/go_collector.go +++ b/prometheus/go_collector.go @@ -36,7 +36,7 @@ type goCollector struct { msMaxAge time.Duration // Maximum allowed age of old memstats. } -// NewGoCollector returns a collector which exports metrics about the current Go +// NewGoCollector returns a collector that exports metrics about the current Go // process. This includes memory stats. To collect those, runtime.ReadMemStats // is called. This requires to “stop the world”, which usually only happens for // garbage collection (GC). Take the following implications into account when @@ -364,3 +364,33 @@ type memStatsMetrics []struct { eval func(*runtime.MemStats) float64 valType ValueType } + +// NewBuildInfoCollector returns a collector collecting a single metric +// "go_build_info" with the constant value 1 and three labels "path", "version", +// and "checksum". Their label values contain the main module path, version, and +// checksum, respectively. The labels will only have meaningful values if the +// binary is built with Go module support and from source code retrieved from +// the source repository (rather than the local file system). This is usually +// accomplished by building from outside of GOPATH, specifying the full address +// of the main package, e.g. "GO111MODULE=on go run +// github.com/prometheus/client_golang/examples/random". If built without Go +// module support, all label values will be "unknown". If built with Go module +// support but using the source code from the local file system, the "path" will +// be set appropriately, but "checksum" will be empty and "version" will be +// "(devel)". +// +// This collector uses only the build information for the main module. See +// https://github.com/povilasv/prommod for an example of a collector for the +// module dependencies. +func NewBuildInfoCollector() Collector { + path, version, sum := readBuildInfo() + c := &selfCollector{MustNewConstMetric( + NewDesc( + "go_build_info", + "Build information about the main Go module.", + nil, Labels{"path": path, "version": version, "checksum": sum}, + ), + GaugeValue, 1)} + c.init(c.self) + return c +} diff --git a/prometheus/go_collector_test.go b/prometheus/go_collector_test.go index f55aff9..3d8d94e 100644 --- a/prometheus/go_collector_test.go +++ b/prometheus/go_collector_test.go @@ -138,11 +138,11 @@ func TestGoCollectorGC(t *testing.T) { close(waitCh) continue } - if diff := *pb.GetSummary().SampleCount - oldGC; diff != 1 { - t.Errorf("want 1 new garbage collection run, got %d", diff) + if diff := *pb.GetSummary().SampleCount - oldGC; diff < 1 { + t.Errorf("want at least 1 new garbage collection run, got %d", diff) } if diff := *pb.GetSummary().SampleSum - oldPause; diff <= 0 { - t.Errorf("want moar pause, got %f", diff) + t.Errorf("want an increase in pause time, got a change of %f", diff) } case <-time.After(1 * time.Second): t.Fatalf("expected collect timed out") @@ -184,14 +184,14 @@ func TestGoCollectorMemStats(t *testing.T) { } } - // Speed up the timing to make the tast faster. - c.msMaxWait = time.Millisecond - c.msMaxAge = 10 * time.Millisecond + // Speed up the timing to make the test faster. + c.msMaxWait = 5 * time.Millisecond + c.msMaxAge = 50 * time.Millisecond // Scenario 1: msRead responds slowly, no previous memstats available, // msRead is executed anyway. c.msRead = func(ms *runtime.MemStats) { - time.Sleep(3 * time.Millisecond) + time.Sleep(20 * time.Millisecond) ms.Alloc = 1 } checkCollect(1) @@ -218,12 +218,12 @@ func TestGoCollectorMemStats(t *testing.T) { // Scenario 3: msRead responds slowly, previous memstats available, old // value collected. c.msRead = func(ms *runtime.MemStats) { - time.Sleep(3 * time.Millisecond) + time.Sleep(20 * time.Millisecond) ms.Alloc = 3 } checkCollect(2) // After waiting, new value is still set in msLast. - time.Sleep(12 * time.Millisecond) + time.Sleep(80 * time.Millisecond) c.msMtx.Lock() if want, got := uint64(3), c.msLast.Alloc; want != got { t.Errorf("unexpected of msLast.Alloc, want %d, got %d", want, got) @@ -233,7 +233,7 @@ func TestGoCollectorMemStats(t *testing.T) { // Scenario 4: msRead responds slowly, previous memstats is too old, new // value collected. c.msRead = func(ms *runtime.MemStats) { - time.Sleep(3 * time.Millisecond) + time.Sleep(20 * time.Millisecond) ms.Alloc = 4 } checkCollect(4) diff --git a/prometheus/process_collector.go b/prometheus/process_collector.go index 55176d5..37d2026 100644 --- a/prometheus/process_collector.go +++ b/prometheus/process_collector.go @@ -126,7 +126,7 @@ func NewProcessCollector(opts ProcessCollectorOpts) Collector { } // Set up process metric collection if supported by the runtime. - if _, err := procfs.NewStat(); err == nil { + if _, err := procfs.NewDefaultFS(); err == nil { c.collectFn = c.processCollect } else { c.collectFn = func(ch chan<- Metric) { @@ -166,7 +166,7 @@ func (c *processCollector) processCollect(ch chan<- Metric) { return } - if stat, err := p.NewStat(); err == nil { + if stat, err := p.Stat(); err == nil { ch <- MustNewConstMetric(c.cpuTotal, CounterValue, stat.CPUTime()) ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(stat.VirtualMemory())) ch <- MustNewConstMetric(c.rss, GaugeValue, float64(stat.ResidentMemory())) @@ -185,7 +185,7 @@ func (c *processCollector) processCollect(ch chan<- Metric) { c.reportError(ch, c.openFDs, err) } - if limits, err := p.NewLimits(); err == nil { + if limits, err := p.Limits(); err == nil { ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(limits.OpenFiles)) ch <- MustNewConstMetric(c.maxVsize, GaugeValue, float64(limits.AddressSpace)) } else { diff --git a/prometheus/promhttp/http.go b/prometheus/promhttp/http.go index b137c88..cea5a90 100644 --- a/prometheus/promhttp/http.go +++ b/prometheus/promhttp/http.go @@ -84,10 +84,32 @@ func Handler() http.Handler { // instrumentation. Use the InstrumentMetricHandler function to apply the same // kind of instrumentation as it is used by the Handler function. func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler { - var inFlightSem chan struct{} + var ( + inFlightSem chan struct{} + errCnt = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "promhttp_metric_handler_errors_total", + Help: "Total number of internal errors encountered by the promhttp metric handler.", + }, + []string{"cause"}, + ) + ) + if opts.MaxRequestsInFlight > 0 { inFlightSem = make(chan struct{}, opts.MaxRequestsInFlight) } + if opts.Registry != nil { + // Initialize all possibilites that can occur below. + errCnt.WithLabelValues("gathering") + errCnt.WithLabelValues("encoding") + if err := opts.Registry.Register(errCnt); err != nil { + if are, ok := err.(prometheus.AlreadyRegisteredError); ok { + errCnt = are.ExistingCollector.(*prometheus.CounterVec) + } else { + panic(err) + } + } + } h := http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) { if inFlightSem != nil { @@ -106,6 +128,7 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler { if opts.ErrorLog != nil { opts.ErrorLog.Println("error gathering metrics:", err) } + errCnt.WithLabelValues("gathering").Inc() switch opts.ErrorHandling { case PanicOnError: panic(err) @@ -146,6 +169,7 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler { if opts.ErrorLog != nil { opts.ErrorLog.Println("error encoding and sending metric family:", err) } + errCnt.WithLabelValues("encoding").Inc() switch opts.ErrorHandling { case PanicOnError: panic(err) @@ -236,9 +260,12 @@ const ( // Ignore errors and try to serve as many metrics as possible. However, // if no metrics can be served, serve an HTTP status code 500 and the // last error message in the body. Only use this in deliberate "best - // effort" metrics collection scenarios. It is recommended to at least - // log errors (by providing an ErrorLog in HandlerOpts) to not mask - // errors completely. + // effort" metrics collection scenarios. In this case, it is highly + // recommended to provide other means of detecting errors: By setting an + // ErrorLog in HandlerOpts, the errors are logged. By providing a + // Registry in HandlerOpts, the exposed metrics include an error counter + // "promhttp_metric_handler_errors_total", which can be used for + // alerts. ContinueOnError // Panic upon the first error encountered (useful for "crash only" apps). PanicOnError @@ -261,6 +288,18 @@ type HandlerOpts struct { // logged regardless of the configured ErrorHandling provided ErrorLog // is not nil. ErrorHandling HandlerErrorHandling + // If Registry is not nil, it is used to register a metric + // "promhttp_metric_handler_errors_total", partitioned by "cause". A + // failed registration causes a panic. Note that this error counter is + // different from the instrumentation you get from the various + // InstrumentHandler... helpers. It counts errors that don't necessarily + // result in a non-2xx HTTP status code. There are two typical cases: + // (1) Encoding errors that only happen after streaming of the HTTP body + // has already started (and the status code 200 has been sent). This + // should only happen with custom collectors. (2) Collection errors with + // no effect on the HTTP status code because ErrorHandling is set to + // ContinueOnError. + Registry prometheus.Registerer // If DisableCompression is true, the handler will never compress the // response, even if requested by the client. DisableCompression bool diff --git a/prometheus/promhttp/http_test.go b/prometheus/promhttp/http_test.go index 6e23e6c..781ea8f 100644 --- a/prometheus/promhttp/http_test.go +++ b/prometheus/promhttp/http_test.go @@ -59,7 +59,8 @@ func (b blockingCollector) Collect(ch chan<- prometheus.Metric) { func TestHandlerErrorHandling(t *testing.T) { // Create a registry that collects a MetricFamily with two elements, - // another with one, and reports an error. + // another with one, and reports an error. Further down, we'll use the + // same registry in the HandlerOpts. reg := prometheus.NewRegistry() cnt := prometheus.NewCounter(prometheus.CounterOpts{ @@ -92,14 +93,17 @@ func TestHandlerErrorHandling(t *testing.T) { errorHandler := HandlerFor(reg, HandlerOpts{ ErrorLog: logger, ErrorHandling: HTTPErrorOnError, + Registry: reg, }) continueHandler := HandlerFor(reg, HandlerOpts{ ErrorLog: logger, ErrorHandling: ContinueOnError, + Registry: reg, }) panicHandler := HandlerFor(reg, HandlerOpts{ ErrorLog: logger, ErrorHandling: PanicOnError, + Registry: reg, }) wantMsg := `error gathering metrics: error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error ` @@ -107,10 +111,29 @@ func TestHandlerErrorHandling(t *testing.T) { error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error ` - wantOKBody := `# HELP name docstring + wantOKBody1 := `# HELP name docstring # TYPE name counter name{constname="constvalue",labelname="val1"} 1 name{constname="constvalue",labelname="val2"} 1 +# HELP promhttp_metric_handler_errors_total Total number of internal errors encountered by the promhttp metric handler. +# TYPE promhttp_metric_handler_errors_total counter +promhttp_metric_handler_errors_total{cause="encoding"} 0 +promhttp_metric_handler_errors_total{cause="gathering"} 1 +# HELP the_count Ah-ah-ah! Thunder and lightning! +# TYPE the_count counter +the_count 0 +` + // It might happen that counting the gathering error makes it to the + // promhttp_metric_handler_errors_total counter before it is gathered + // itself. Thus, we have to bodies that are acceptable for the test. + wantOKBody2 := `# HELP name docstring +# TYPE name counter +name{constname="constvalue",labelname="val1"} 1 +name{constname="constvalue",labelname="val2"} 1 +# HELP promhttp_metric_handler_errors_total Total number of internal errors encountered by the promhttp metric handler. +# TYPE promhttp_metric_handler_errors_total counter +promhttp_metric_handler_errors_total{cause="encoding"} 0 +promhttp_metric_handler_errors_total{cause="gathering"} 2 # HELP the_count Ah-ah-ah! Thunder and lightning! # TYPE the_count counter the_count 0 @@ -137,8 +160,8 @@ the_count 0 if got := logBuf.String(); got != wantMsg { t.Errorf("got log message %q, want %q", got, wantMsg) } - if got := writer.Body.String(); got != wantOKBody { - t.Errorf("got body %q, want %q", got, wantOKBody) + if got := writer.Body.String(); got != wantOKBody1 && got != wantOKBody2 { + t.Errorf("got body %q, want either %q or %q", got, wantOKBody1, wantOKBody2) } defer func() { diff --git a/prometheus/registry_test.go b/prometheus/registry_test.go index b381a6b..3324c7c 100644 --- a/prometheus/registry_test.go +++ b/prometheus/registry_test.go @@ -53,7 +53,7 @@ func (u uncheckedCollector) Collect(c chan<- prometheus.Metric) { func testHandler(t testing.TB) { // TODO(beorn7): This test is a bit too "end-to-end". It tests quite a // few moving parts that are not strongly coupled. They could/should be - // tested separately. However, the changes planned for v0.10 will + // tested separately. However, the changes planned for v2 will // require a major rework of this test anyway, at which time I will // structure it in a better way. diff --git a/prometheus/summary.go b/prometheus/summary.go index 7bec8fa..288111e 100644 --- a/prometheus/summary.go +++ b/prometheus/summary.go @@ -39,7 +39,7 @@ const quantileLabel = "quantile" // A typical use-case is the observation of request latencies. By default, a // Summary provides the median, the 90th and the 99th percentile of the latency // as rank estimations. However, the default behavior will change in the -// upcoming v0.10 of the library. There will be no rank estimations at all by +// upcoming v1.0.0 of the library. There will be no rank estimations at all by // default. For a sane transition, it is recommended to set the desired rank // estimations explicitly. // @@ -78,7 +78,7 @@ const ( // mandatory to set Name to a non-empty string. While all other fields are // optional and can safely be left at their zero value, it is recommended to set // a help string and to explicitly set the Objectives field to the desired value -// as the default value will change in the upcoming v0.10 of the library. +// as the default value will change in the upcoming v1.0.0 of the library. type SummaryOpts struct { // Namespace, Subsystem, and Name are components of the fully-qualified // name of the Summary (created by joining these components with