From f30f428035633da15d00d3dfefb0128c5e569ef4 Mon Sep 17 00:00:00 2001 From: Marcin Owsiany Date: Thu, 25 Oct 2018 19:44:21 +0200 Subject: [PATCH 01/15] Add a way to return the body of a 5xx response. (#484) expose body when handling HTTP errors Signed-off-by: Marcin Owsiany --- api/prometheus/v1/api.go | 23 +++- api/prometheus/v1/api_test.go | 194 ++++++++++++++++++++++------------ 2 files changed, 147 insertions(+), 70 deletions(-) diff --git a/api/prometheus/v1/api.go b/api/prometheus/v1/api.go index 6a19fac..255f3ba 100644 --- a/api/prometheus/v1/api.go +++ b/api/prometheus/v1/api.go @@ -60,6 +60,8 @@ const ( ErrCanceled = "canceled" ErrExec = "execution" ErrBadResponse = "bad_response" + ErrServer = "server_error" + ErrClient = "client_error" // Possible values for HealthStatus. HealthGood HealthStatus = "up" @@ -69,8 +71,9 @@ const ( // Error is an error returned by the API. type Error struct { - Type ErrorType - Msg string + Type ErrorType + Msg string + Detail string } func (e *Error) Error() string { @@ -460,6 +463,16 @@ func apiError(code int) bool { return code == statusAPIError || code == http.StatusBadRequest } +func errorTypeAndMsgFor(resp *http.Response) (ErrorType, string) { + switch resp.StatusCode / 100 { + case 4: + return ErrClient, fmt.Sprintf("client error: %d", resp.StatusCode) + case 5: + return ErrServer, fmt.Sprintf("server error: %d", resp.StatusCode) + } + 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 { @@ -469,9 +482,11 @@ func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, [ code := resp.StatusCode if code/100 != 2 && !apiError(code) { + errorType, errorMsg := errorTypeAndMsgFor(resp) return resp, body, &Error{ - Type: ErrBadResponse, - Msg: fmt.Sprintf("bad response code %d", resp.StatusCode), + Type: errorType, + Msg: errorMsg, + Detail: string(body), } } diff --git a/api/prometheus/v1/api_test.go b/api/prometheus/v1/api_test.go index 1f7fda7..8492a5c 100644 --- a/api/prometheus/v1/api_test.go +++ b/api/prometheus/v1/api_test.go @@ -18,6 +18,7 @@ package v1 import ( "context" "encoding/json" + "errors" "fmt" "net/http" "net/url" @@ -30,9 +31,10 @@ import ( ) type apiTest struct { - do func() (interface{}, error) - inErr error - inRes interface{} + do func() (interface{}, error) + inErr error + inStatusCode int + inRes interface{} reqPath string reqParam url.Values @@ -75,7 +77,9 @@ func (c *apiTestClient) Do(ctx context.Context, req *http.Request) (*http.Respon } resp := &http.Response{} - if test.inErr != nil { + if test.inStatusCode != 0 { + resp.StatusCode = test.inStatusCode + } else if test.inErr != nil { resp.StatusCode = statusAPIError } else { resp.StatusCode = http.StatusOK @@ -194,6 +198,42 @@ func TestAPIs(t *testing.T) { }, err: fmt.Errorf("some error"), }, + { + do: doQuery("2", testTime), + inRes: "some body", + inStatusCode: 500, + inErr: &Error{ + Type: ErrServer, + Msg: "server error: 500", + Detail: "some body", + }, + + reqMethod: "GET", + reqPath: "/api/v1/query", + reqParam: url.Values{ + "query": []string{"2"}, + "time": []string{testTime.Format(time.RFC3339Nano)}, + }, + err: errors.New("server_error: server error: 500"), + }, + { + do: doQuery("2", testTime), + inRes: "some body", + inStatusCode: 404, + inErr: &Error{ + Type: ErrClient, + Msg: "client error: 404", + Detail: "some body", + }, + + reqMethod: "GET", + reqPath: "/api/v1/query", + reqParam: url.Values{ + "query": []string{"2"}, + "time": []string{testTime.Format(time.RFC3339Nano)}, + }, + err: errors.New("client_error: client error: 404"), + }, { do: doQueryRange("2", Range{ @@ -498,29 +538,34 @@ func TestAPIs(t *testing.T) { var tests []apiTest tests = append(tests, queryTests...) - for _, test := range tests { - client.curTest = test + for i, test := range tests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + client.curTest = test - res, err := test.do() + res, err := test.do() - if test.err != nil { - if err == nil { - t.Errorf("expected error %q but got none", test.err) - continue + if test.err != nil { + if err == nil { + t.Fatalf("expected error %q but got none", test.err) + } + if err.Error() != test.err.Error() { + t.Errorf("unexpected error: want %s, got %s", test.err, err) + } + if apiErr, ok := err.(*Error); ok { + if apiErr.Detail != test.inRes { + t.Errorf("%q should be %q", apiErr.Detail, test.inRes) + } + } + return } - if err.Error() != test.err.Error() { - t.Errorf("unexpected error: want %s, got %s", test.err, err) + if err != nil { + t.Fatalf("unexpected error: %s", err) } - continue - } - if err != nil { - t.Errorf("unexpected error: %s", err) - continue - } - if !reflect.DeepEqual(res, test.res) { - t.Errorf("unexpected result: want %v, got %v", test.res, res) - } + if !reflect.DeepEqual(res, test.res) { + t.Errorf("unexpected result: want %v, got %v", test.res, res) + } + }) } } @@ -532,10 +577,10 @@ type testClient struct { } type apiClientTest struct { - code int - response interface{} - expected string - err *Error + code int + response interface{} + expectedBody string + expectedErr *Error } func (c *testClient) URL(ep string, args map[string]string) *url.URL { @@ -575,98 +620,108 @@ func (c *testClient) Do(ctx context.Context, req *http.Request) (*http.Response, func TestAPIClientDo(t *testing.T) { tests := []apiClientTest{ { + code: statusAPIError, response: &apiResponse{ Status: "error", Data: json.RawMessage(`null`), ErrorType: ErrBadData, Error: "failed", }, - err: &Error{ + expectedErr: &Error{ Type: ErrBadData, Msg: "failed", }, - code: statusAPIError, - expected: `null`, + expectedBody: `null`, }, { + code: statusAPIError, response: &apiResponse{ Status: "error", Data: json.RawMessage(`"test"`), ErrorType: ErrTimeout, Error: "timed out", }, - err: &Error{ + expectedErr: &Error{ Type: ErrTimeout, Msg: "timed out", }, - code: statusAPIError, - expected: `test`, + expectedBody: `test`, }, { - response: "bad json", - err: &Error{ - Type: ErrBadResponse, - Msg: "bad response code 500", + code: http.StatusInternalServerError, + response: "500 error details", + expectedErr: &Error{ + Type: ErrServer, + Msg: "server error: 500", + Detail: "500 error details", }, - code: http.StatusInternalServerError, }, { + code: http.StatusNotFound, + response: "404 error details", + expectedErr: &Error{ + Type: ErrClient, + Msg: "client error: 404", + Detail: "404 error details", + }, + }, + { + code: http.StatusBadRequest, response: &apiResponse{ Status: "error", Data: json.RawMessage(`null`), ErrorType: ErrBadData, Error: "end timestamp must not be before start time", }, - err: &Error{ + expectedErr: &Error{ Type: ErrBadData, Msg: "end timestamp must not be before start time", }, - code: http.StatusBadRequest, }, { + code: statusAPIError, response: "bad json", - err: &Error{ + expectedErr: &Error{ Type: ErrBadResponse, Msg: "invalid character 'b' looking for beginning of value", }, - code: statusAPIError, }, { + code: statusAPIError, response: &apiResponse{ Status: "success", Data: json.RawMessage(`"test"`), }, - err: &Error{ + expectedErr: &Error{ Type: ErrBadResponse, Msg: "inconsistent body for response code", }, - code: statusAPIError, }, { + code: statusAPIError, response: &apiResponse{ Status: "success", Data: json.RawMessage(`"test"`), ErrorType: ErrTimeout, Error: "timed out", }, - err: &Error{ + expectedErr: &Error{ Type: ErrBadResponse, Msg: "inconsistent body for response code", }, - code: statusAPIError, }, { + code: http.StatusOK, response: &apiResponse{ Status: "error", Data: json.RawMessage(`"test"`), ErrorType: ErrTimeout, Error: "timed out", }, - err: &Error{ + expectedErr: &Error{ Type: ErrBadResponse, Msg: "inconsistent body for response code", }, - code: http.StatusOK, }, } @@ -677,30 +732,37 @@ func TestAPIClientDo(t *testing.T) { } client := &apiClient{tc} - for _, test := range tests { + for i, test := range tests { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - tc.ch <- test + tc.ch <- test - _, body, err := client.Do(context.Background(), tc.req) + _, body, err := client.Do(context.Background(), tc.req) - if test.err != nil { - if err == nil { - t.Errorf("expected error %q but got none", test.err) - continue + if test.expectedErr != nil { + if err == nil { + t.Fatalf("expected error %q but got none", test.expectedErr) + } + if test.expectedErr.Error() != err.Error() { + t.Errorf("unexpected error: want %q, got %q", test.expectedErr, err) + } + 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) + } + } + return } - if test.err.Error() != err.Error() { - t.Errorf("unexpected error: want %q, got %q", test.err, err) + if err != nil { + t.Fatalf("unexpeceted error %s", err) } - continue - } - if err != nil { - t.Errorf("unexpeceted error %s", err) - continue - } - want, got := test.expected, string(body) - if want != got { - t.Errorf("unexpected body: want %q, got %q", want, got) - } + want, got := test.expectedBody, string(body) + if want != got { + t.Errorf("unexpected body: want %q, got %q", want, got) + } + }) + } } From 902733d08016893ebd8487bd6a4845627b0f0b7f Mon Sep 17 00:00:00 2001 From: Peter Jausovec Date: Wed, 31 Oct 2018 10:59:13 -0700 Subject: [PATCH 02/15] Add more info to the inconsistent cardinality errors Signed-off-by: Peter Jausovec --- prometheus/counter.go | 3 ++- prometheus/gauge.go | 3 ++- prometheus/histogram.go | 3 ++- prometheus/labels.go | 2 +- prometheus/summary.go | 3 ++- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/prometheus/counter.go b/prometheus/counter.go index 765e455..2f87690 100644 --- a/prometheus/counter.go +++ b/prometheus/counter.go @@ -136,7 +136,8 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec { return &CounterVec{ metricVec: newMetricVec(desc, func(lvs ...string) Metric { if len(lvs) != len(desc.variableLabels) { - panic(errInconsistentCardinality) + err := fmt.Errorf("%s: labels and values for '%s' don't match: labels %v, values %v", errInconsistentCardinality, desc.fqName, desc.variableLabels, lvs) + panic(err) } result := &counter{desc: desc, labelPairs: makeLabelPairs(desc, lvs)} result.init(result) // Init self-collection. diff --git a/prometheus/gauge.go b/prometheus/gauge.go index 17c72d7..f335458 100644 --- a/prometheus/gauge.go +++ b/prometheus/gauge.go @@ -147,7 +147,8 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec { return &GaugeVec{ metricVec: newMetricVec(desc, func(lvs ...string) Metric { if len(lvs) != len(desc.variableLabels) { - panic(errInconsistentCardinality) + err := fmt.Errorf("%s: labels and values for '%s' don't match: labels %v, values %v", errInconsistentCardinality, desc.fqName, desc.variableLabels, labelValues) + panic(err) } result := &gauge{desc: desc, labelPairs: makeLabelPairs(desc, lvs)} result.init(result) // Init self-collection. diff --git a/prometheus/histogram.go b/prometheus/histogram.go index 4d7fa97..58b4f3b 100644 --- a/prometheus/histogram.go +++ b/prometheus/histogram.go @@ -165,7 +165,8 @@ func NewHistogram(opts HistogramOpts) Histogram { func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram { if len(desc.variableLabels) != len(labelValues) { - panic(errInconsistentCardinality) + err := fmt.Errorf("%s: labels and values for '%s' don't match: labels %v, values %v", errInconsistentCardinality, desc.fqName, desc.variableLabels, labelValues) + panic(err) } for _, n := range desc.variableLabels { diff --git a/prometheus/labels.go b/prometheus/labels.go index e68f132..8173f56 100644 --- a/prometheus/labels.go +++ b/prometheus/labels.go @@ -39,7 +39,7 @@ var errInconsistentCardinality = errors.New("inconsistent label cardinality") func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error { if len(labels) != expectedNumberOfValues { - return errInconsistentCardinality + return fmt.Errorf("%s: number of labels and values don't match: expected %d, actual %d, labels %v", errInconsistentCardinality, expectedNumberOfValues, len(labels), labels) } for name, val := range labels { diff --git a/prometheus/summary.go b/prometheus/summary.go index f7e92d8..402d73d 100644 --- a/prometheus/summary.go +++ b/prometheus/summary.go @@ -181,7 +181,8 @@ func NewSummary(opts SummaryOpts) Summary { func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary { if len(desc.variableLabels) != len(labelValues) { - panic(errInconsistentCardinality) + err := fmt.Errorf("%s: labels and values for '%s' don't match: labels %v, values %v", errInconsistentCardinality, desc.fqName, desc.variableLabels, labelValues) + panic(err) } for _, n := range desc.variableLabels { From 4fe949f2fdc6741a4527e7c44c9ec5af822c1a1b Mon Sep 17 00:00:00 2001 From: Peter Jausovec Date: Wed, 31 Oct 2018 11:08:30 -0700 Subject: [PATCH 03/15] Add missing fmt import Signed-off-by: Peter Jausovec --- prometheus/counter.go | 1 + prometheus/gauge.go | 1 + 2 files changed, 2 insertions(+) diff --git a/prometheus/counter.go b/prometheus/counter.go index 2f87690..03965f9 100644 --- a/prometheus/counter.go +++ b/prometheus/counter.go @@ -15,6 +15,7 @@ package prometheus import ( "errors" + "fmt" "math" "sync/atomic" diff --git a/prometheus/gauge.go b/prometheus/gauge.go index f335458..6b0ac42 100644 --- a/prometheus/gauge.go +++ b/prometheus/gauge.go @@ -14,6 +14,7 @@ package prometheus import ( + "fmt" "math" "sync/atomic" "time" From ceb819208d49dfac81eb35186fb44010eb85d4c5 Mon Sep 17 00:00:00 2001 From: Peter Jausovec Date: Wed, 31 Oct 2018 11:16:04 -0700 Subject: [PATCH 04/15] copy/pasta error Signed-off-by: Peter Jausovec --- prometheus/gauge.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prometheus/gauge.go b/prometheus/gauge.go index 6b0ac42..f80d72b 100644 --- a/prometheus/gauge.go +++ b/prometheus/gauge.go @@ -148,7 +148,7 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec { return &GaugeVec{ metricVec: newMetricVec(desc, func(lvs ...string) Metric { if len(lvs) != len(desc.variableLabels) { - err := fmt.Errorf("%s: labels and values for '%s' don't match: labels %v, values %v", errInconsistentCardinality, desc.fqName, desc.variableLabels, labelValues) + err := fmt.Errorf("%s: labels and values for '%s' don't match: labels %v, values %v", errInconsistentCardinality, desc.fqName, desc.variableLabels, lvs) panic(err) } result := &gauge{desc: desc, labelPairs: makeLabelPairs(desc, lvs)} From 9416ff209b60b9abde9b27321e644c9c0a442ace Mon Sep 17 00:00:00 2001 From: Sevag Hanssian Date: Wed, 31 Oct 2018 14:13:32 -0700 Subject: [PATCH 05/15] First commit - WriteToTextfile Signed-off-by: Sevag Hanssian --- prometheus/registry.go | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/prometheus/registry.go b/prometheus/registry.go index e422ef3..fab67bf 100644 --- a/prometheus/registry.go +++ b/prometheus/registry.go @@ -533,6 +533,50 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) { return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap() } +func (r *Registry) WriteToTextfile(path string) error { + metricFamilies, err := r.Gather() + if err != nil { + return err + } + output := []string{} + for _, metricFamily := range metricFamilies { + output = append(output, fmt.Sprintf("# HELP %s %s", metricFamily.GetName(), metricFamily.GetHelp())) + output = append(output, fmt.Sprintf("# TYPE %s %s", metricFamily.GetName(), metricFamily.GetType().String())) + for _, metric := range metricFamily.GetMetric() { + labelString := "" + if metric.GetLabel() != nil { + labelStrings := []string{} + for _, labelPair := range metric.GetLabel() { + labelStrings = append(labelStrings, fmt.Sprintf("%s=\"%s\"", labelPair.GetName(), labelPair.GetValue())) + } + labelString = fmt.Sprintf("{%s}", strings.Join(labelStrings, ",")) + } + timestampString := "" + if metric.TimestampMs != nil { + timestampString = fmt.Sprintf(" %d", int(float64(metric.GetTimestampMs())*1000)) + } + var value float64 + switch metricFamily.GetType() { + case dto.MetricType_COUNTER: + value = metric.GetCounter().GetValue() + case dto.MetricType_GAUGE: + value = metric.GetGauge().GetValue() + case dto.MetricType_SUMMARY: + //value = metric.GetSummary().GetValue() + //what to do here + case dto.MetricType_HISTOGRAM: + //same + //value = metric.GetHistogram().GetValue() + case dto.MetricType_UNTYPED: + value = metric.GetUntyped().GetValue() + } + output = append(output, fmt.Sprintf("%s%s %f%s", metricFamily.GetName(), labelString, value, timestampString)) + } + } + fmt.Println(strings.Join(output, "\n")) + return nil +} + // processMetric is an internal helper method only used by the Gather method. func processMetric( metric Metric, From e6fe89ce2208f54dcd767f8e34b082bbdfb3aeb3 Mon Sep 17 00:00:00 2001 From: Sevag Hanssian Date: Wed, 31 Oct 2018 15:27:08 -0700 Subject: [PATCH 06/15] Add support for histograms and summaries Signed-off-by: Sevag Hanssian --- prometheus/registry.go | 51 ++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/prometheus/registry.go b/prometheus/registry.go index fab67bf..693b47b 100644 --- a/prometheus/registry.go +++ b/prometheus/registry.go @@ -18,6 +18,7 @@ import ( "fmt" "runtime" "sort" + "strconv" "strings" "sync" "unicode/utf8" @@ -543,34 +544,56 @@ func (r *Registry) WriteToTextfile(path string) error { output = append(output, fmt.Sprintf("# HELP %s %s", metricFamily.GetName(), metricFamily.GetHelp())) output = append(output, fmt.Sprintf("# TYPE %s %s", metricFamily.GetName(), metricFamily.GetType().String())) for _, metric := range metricFamily.GetMetric() { - labelString := "" + labelStrings := []string{} if metric.GetLabel() != nil { - labelStrings := []string{} for _, labelPair := range metric.GetLabel() { labelStrings = append(labelStrings, fmt.Sprintf("%s=\"%s\"", labelPair.GetName(), labelPair.GetValue())) } - labelString = fmt.Sprintf("{%s}", strings.Join(labelStrings, ",")) } timestampString := "" if metric.TimestampMs != nil { timestampString = fmt.Sprintf(" %d", int(float64(metric.GetTimestampMs())*1000)) } - var value float64 switch metricFamily.GetType() { case dto.MetricType_COUNTER: - value = metric.GetCounter().GetValue() + value := strconv.FormatFloat(metric.GetCounter().GetValue(), 'f', -1, 64) + labelString := fmt.Sprintf("{%s}", strings.Join(labelStrings, ",")) + output = append(output, fmt.Sprintf("%s%s %s%s", metricFamily.GetName(), labelString, value, timestampString)) case dto.MetricType_GAUGE: - value = metric.GetGauge().GetValue() - case dto.MetricType_SUMMARY: - //value = metric.GetSummary().GetValue() - //what to do here - case dto.MetricType_HISTOGRAM: - //same - //value = metric.GetHistogram().GetValue() + value := strconv.FormatFloat(metric.GetGauge().GetValue(), 'f', -1, 64) + labelString := fmt.Sprintf("{%s}", strings.Join(labelStrings, ",")) + output = append(output, fmt.Sprintf("%s%s %is%s", metricFamily.GetName(), labelString, value, timestampString)) case dto.MetricType_UNTYPED: - value = metric.GetUntyped().GetValue() + value := strconv.FormatFloat(metric.GetUntyped().GetValue(), 'f', -1, 64) + labelString := fmt.Sprintf("{%s}", strings.Join(labelStrings, ",")) + output = append(output, fmt.Sprintf("%s%s %s%s", metricFamily.GetName(), labelString, value, timestampString)) + case dto.MetricType_SUMMARY: + labelString := fmt.Sprintf("{%s}", strings.Join(labelStrings, ",")) + count := metric.GetSummary().GetSampleCount() + output = append(output, fmt.Sprintf("%s_count%s %d%s", metricFamily.GetName(), labelString, count, timestampString)) + sum := strconv.FormatFloat(metric.GetSummary().GetSampleSum(), 'f', -1, 64) + output = append(output, fmt.Sprintf("%s_sum%s %s%s", metricFamily.GetName(), labelString, sum, timestampString)) + for _, quantile := range metric.GetSummary().GetQuantile() { + quantileName := strconv.FormatFloat(quantile.GetQuantile(), 'f', -1, 64) + quantileLabelStrings := append(labelStrings, fmt.Sprintf("quantile=\"%s\"", quantileName)) + labelString = fmt.Sprintf("{%s}", strings.Join(quantileLabelStrings, ",")) + value := strconv.FormatFloat(quantile.GetValue(), 'f', -1, 64) + output = append(output, fmt.Sprintf("%s_quantile%s %s%s", metricFamily.GetName(), labelString, value, timestampString)) + } + case dto.MetricType_HISTOGRAM: + labelString := fmt.Sprintf("{%s}", strings.Join(labelStrings, ",")) + count := metric.GetHistogram().GetSampleCount() + output = append(output, fmt.Sprintf("%s_count%s %f%s", metricFamily.GetName(), labelString, float64(count), timestampString)) + sum := metric.GetHistogram().GetSampleSum() + output = append(output, fmt.Sprintf("%s_sum%s %f%s", metricFamily.GetName(), labelString, sum, timestampString)) + for _, bucket := range metric.GetHistogram().GetBucket() { + bucketUpperBound := strconv.FormatFloat(bucket.GetUpperBound(), 'f', -1, 64) + bucketLabelStrings := append(labelStrings, fmt.Sprintf("le=\"%s\"", bucketUpperBound)) + labelString = fmt.Sprintf("{%s}", strings.Join(bucketLabelStrings, ",")) + value := bucket.GetCumulativeCount() + output = append(output, fmt.Sprintf("%s_bucket%s %d%s", metricFamily.GetName(), labelString, value, timestampString)) + } } - output = append(output, fmt.Sprintf("%s%s %f%s", metricFamily.GetName(), labelString, value, timestampString)) } } fmt.Println(strings.Join(output, "\n")) From c5bdd15ac3223ee284d9b4587d3582b819fac1c1 Mon Sep 17 00:00:00 2001 From: Sevag Hanssian Date: Wed, 31 Oct 2018 15:31:37 -0700 Subject: [PATCH 07/15] Remove typo Signed-off-by: Sevag Hanssian --- prometheus/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prometheus/registry.go b/prometheus/registry.go index 693b47b..cc83725 100644 --- a/prometheus/registry.go +++ b/prometheus/registry.go @@ -562,7 +562,7 @@ func (r *Registry) WriteToTextfile(path string) error { case dto.MetricType_GAUGE: value := strconv.FormatFloat(metric.GetGauge().GetValue(), 'f', -1, 64) labelString := fmt.Sprintf("{%s}", strings.Join(labelStrings, ",")) - output = append(output, fmt.Sprintf("%s%s %is%s", metricFamily.GetName(), labelString, value, timestampString)) + output = append(output, fmt.Sprintf("%s%s %s%s", metricFamily.GetName(), labelString, value, timestampString)) case dto.MetricType_UNTYPED: value := strconv.FormatFloat(metric.GetUntyped().GetValue(), 'f', -1, 64) labelString := fmt.Sprintf("{%s}", strings.Join(labelStrings, ",")) From 1d54dabd430c5ad9d2b257ac004ad925f5f5ef7d Mon Sep 17 00:00:00 2001 From: Sevag Hanssian Date: Wed, 31 Oct 2018 23:34:50 -0700 Subject: [PATCH 08/15] Add WriteToTextfile test Signed-off-by: Sevag Hanssian --- prometheus/registry.go | 55 ++++++++++++++++----- prometheus/registry_test.go | 99 +++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 12 deletions(-) diff --git a/prometheus/registry.go b/prometheus/registry.go index cc83725..ba429ef 100644 --- a/prometheus/registry.go +++ b/prometheus/registry.go @@ -16,6 +16,7 @@ package prometheus import ( "bytes" "fmt" + "os" "runtime" "sort" "strconv" @@ -539,10 +540,12 @@ func (r *Registry) WriteToTextfile(path string) error { if err != nil { return err } - output := []string{} + outputMap := map[string][]string{} + metricNames := []string{} for _, metricFamily := range metricFamilies { + output := []string{} output = append(output, fmt.Sprintf("# HELP %s %s", metricFamily.GetName(), metricFamily.GetHelp())) - output = append(output, fmt.Sprintf("# TYPE %s %s", metricFamily.GetName(), metricFamily.GetType().String())) + output = append(output, fmt.Sprintf("# TYPE %s %s", metricFamily.GetName(), strings.ToLower(metricFamily.GetType().String()))) for _, metric := range metricFamily.GetMetric() { labelStrings := []string{} if metric.GetLabel() != nil { @@ -570,33 +573,61 @@ func (r *Registry) WriteToTextfile(path string) error { case dto.MetricType_SUMMARY: labelString := fmt.Sprintf("{%s}", strings.Join(labelStrings, ",")) count := metric.GetSummary().GetSampleCount() - output = append(output, fmt.Sprintf("%s_count%s %d%s", metricFamily.GetName(), labelString, count, timestampString)) sum := strconv.FormatFloat(metric.GetSummary().GetSampleSum(), 'f', -1, 64) - output = append(output, fmt.Sprintf("%s_sum%s %s%s", metricFamily.GetName(), labelString, sum, timestampString)) for _, quantile := range metric.GetSummary().GetQuantile() { quantileName := strconv.FormatFloat(quantile.GetQuantile(), 'f', -1, 64) quantileLabelStrings := append(labelStrings, fmt.Sprintf("quantile=\"%s\"", quantileName)) - labelString = fmt.Sprintf("{%s}", strings.Join(quantileLabelStrings, ",")) + loopLabelString := fmt.Sprintf("{%s}", strings.Join(quantileLabelStrings, ",")) value := strconv.FormatFloat(quantile.GetValue(), 'f', -1, 64) - output = append(output, fmt.Sprintf("%s_quantile%s %s%s", metricFamily.GetName(), labelString, value, timestampString)) + output = append(output, fmt.Sprintf("%s%s %s%s", metricFamily.GetName(), loopLabelString, value, timestampString)) } + output = append(output, fmt.Sprintf("%s_sum%s %s%s", metricFamily.GetName(), labelString, sum, timestampString)) + output = append(output, fmt.Sprintf("%s_count%s %d%s", metricFamily.GetName(), labelString, count, timestampString)) case dto.MetricType_HISTOGRAM: labelString := fmt.Sprintf("{%s}", strings.Join(labelStrings, ",")) count := metric.GetHistogram().GetSampleCount() - output = append(output, fmt.Sprintf("%s_count%s %f%s", metricFamily.GetName(), labelString, float64(count), timestampString)) - sum := metric.GetHistogram().GetSampleSum() - output = append(output, fmt.Sprintf("%s_sum%s %f%s", metricFamily.GetName(), labelString, sum, timestampString)) + sum := strconv.FormatFloat(metric.GetHistogram().GetSampleSum(), 'f', -1, 64) for _, bucket := range metric.GetHistogram().GetBucket() { bucketUpperBound := strconv.FormatFloat(bucket.GetUpperBound(), 'f', -1, 64) bucketLabelStrings := append(labelStrings, fmt.Sprintf("le=\"%s\"", bucketUpperBound)) - labelString = fmt.Sprintf("{%s}", strings.Join(bucketLabelStrings, ",")) + loopLabelString := fmt.Sprintf("{%s}", strings.Join(bucketLabelStrings, ",")) value := bucket.GetCumulativeCount() - output = append(output, fmt.Sprintf("%s_bucket%s %d%s", metricFamily.GetName(), labelString, value, timestampString)) + output = append(output, fmt.Sprintf("%s_bucket%s %d%s", metricFamily.GetName(), loopLabelString, value, timestampString)) } + infBucketLabelStrings := append(labelStrings, "le=\"+Inf\"") + infLabelString := fmt.Sprintf("{%s}", strings.Join(infBucketLabelStrings, ",")) + output = append(output, fmt.Sprintf("%s_bucket%s %d%s", metricFamily.GetName(), infLabelString, count, timestampString)) + output = append(output, fmt.Sprintf("%s_sum%s %s%s", metricFamily.GetName(), labelString, sum, timestampString)) + output = append(output, fmt.Sprintf("%s_count%s %d%s", metricFamily.GetName(), labelString, count, timestampString)) } } + outputMap[metricFamily.GetName()] = output + metricNames = append(metricNames, metricFamily.GetName()) } - fmt.Println(strings.Join(output, "\n")) + + tmppath := fmt.Sprintf("%s.%d", path, os.Getpid()) + + f, err := os.Create(tmppath) + if err != nil { + return err + } + + sort.Strings(metricNames) + + outStr := "" + for _, metricName := range metricNames { + outStr = fmt.Sprintf("%s%s\n", outStr, strings.Join(outputMap[metricName], "\n")) + } + + _, err = f.WriteString(outStr) + if err != nil { + return err + } + + if err := os.Rename(tmppath, path); err != nil { + return err + } + return nil } diff --git a/prometheus/registry_test.go b/prometheus/registry_test.go index 3172960..3ebd443 100644 --- a/prometheus/registry_test.go +++ b/prometheus/registry_test.go @@ -21,9 +21,11 @@ package prometheus_test import ( "bytes" + "io/ioutil" "math/rand" "net/http" "net/http/httptest" + "os" "sync" "testing" "time" @@ -871,3 +873,100 @@ func TestHistogramVecRegisterGatherConcurrency(t *testing.T) { close(quit) wg.Wait() } + +func TestWriteToTextfile(t *testing.T) { + expectedOut := `# HELP test_counter test counter +# TYPE test_counter counter +test_counter{name="qux"} 1 +# HELP test_gauge test gauge +# TYPE test_gauge gauge +test_gauge{name="baz"} 1.1 +# HELP test_hist test histogram +# TYPE test_hist histogram +test_hist_bucket{name="bar",le="0.005"} 0 +test_hist_bucket{name="bar",le="0.01"} 0 +test_hist_bucket{name="bar",le="0.025"} 0 +test_hist_bucket{name="bar",le="0.05"} 0 +test_hist_bucket{name="bar",le="0.1"} 0 +test_hist_bucket{name="bar",le="0.25"} 0 +test_hist_bucket{name="bar",le="0.5"} 0 +test_hist_bucket{name="bar",le="1"} 1 +test_hist_bucket{name="bar",le="2.5"} 1 +test_hist_bucket{name="bar",le="5"} 2 +test_hist_bucket{name="bar",le="10"} 2 +test_hist_bucket{name="bar",le="+Inf"} 2 +test_hist_sum{name="bar"} 3.64 +test_hist_count{name="bar"} 2 +# HELP test_summary test summary +# TYPE test_summary summary +test_summary{name="foo",quantile="0.5"} 10 +test_summary{name="foo",quantile="0.9"} 20 +test_summary{name="foo",quantile="0.99"} 20 +test_summary_sum{name="foo"} 30 +test_summary_count{name="foo"} 2 +` + + registry := prometheus.NewRegistry() + + summary := prometheus.NewSummaryVec( + prometheus.SummaryOpts{ + Name: "test_summary", + Help: "test summary", + }, + []string{"name"}, + ) + + histogram := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "test_hist", + Help: "test histogram", + }, + []string{"name"}, + ) + + gauge := prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "test_gauge", + Help: "test gauge", + }, + []string{"name"}, + ) + + counter := prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "test_counter", + Help: "test counter", + }, + []string{"name"}, + ) + + registry.MustRegister(summary) + registry.MustRegister(histogram) + registry.MustRegister(gauge) + registry.MustRegister(counter) + + summary.With(prometheus.Labels{"name": "foo"}).Observe(10) + summary.With(prometheus.Labels{"name": "foo"}).Observe(20) + histogram.With(prometheus.Labels{"name": "bar"}).Observe(0.93) + histogram.With(prometheus.Labels{"name": "bar"}).Observe(2.71) + gauge.With(prometheus.Labels{"name": "baz"}).Set(1.1) + counter.With(prometheus.Labels{"name": "qux"}).Inc() + + tmpfile, err := ioutil.TempFile("", "prom_registry_test") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpfile.Name()) + + registry.WriteToTextfile(tmpfile.Name()) + + fileBytes, err := ioutil.ReadFile(tmpfile.Name()) + if err != nil { + t.Fatal(err) + } + fileContents := string(fileBytes) + + if fileContents != expectedOut { + t.Error("file contents didn't match unexpected") + } +} From 42e6616334de7377f9fd566b33d0bb82a1997300 Mon Sep 17 00:00:00 2001 From: Sevag Hanssian Date: Fri, 2 Nov 2018 08:37:08 -0700 Subject: [PATCH 09/15] Use code that already existed Signed-off-by: Sevag Hanssian --- prometheus/registry.go | 107 +++++++----------------------------- prometheus/registry_test.go | 4 +- 2 files changed, 22 insertions(+), 89 deletions(-) diff --git a/prometheus/registry.go b/prometheus/registry.go index ba429ef..99cdc87 100644 --- a/prometheus/registry.go +++ b/prometheus/registry.go @@ -16,15 +16,17 @@ package prometheus import ( "bytes" "fmt" + "io/ioutil" "os" + "path/filepath" "runtime" "sort" - "strconv" "strings" "sync" "unicode/utf8" "github.com/golang/protobuf/proto" + "github.com/prometheus/common/expfmt" dto "github.com/prometheus/client_model/go" @@ -535,100 +537,29 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) { return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap() } -func (r *Registry) WriteToTextfile(path string) error { - metricFamilies, err := r.Gather() +// WriteToTextfile formats the metrics of the provided Gatherer interface and +// emits them in text format to the path. Intended for use with the node_exporter +// textfile collector +func WriteToTextfile(filename string, g Gatherer) error { + tmp, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)) if err != nil { return err } - outputMap := map[string][]string{} - metricNames := []string{} - for _, metricFamily := range metricFamilies { - output := []string{} - output = append(output, fmt.Sprintf("# HELP %s %s", metricFamily.GetName(), metricFamily.GetHelp())) - output = append(output, fmt.Sprintf("# TYPE %s %s", metricFamily.GetName(), strings.ToLower(metricFamily.GetType().String()))) - for _, metric := range metricFamily.GetMetric() { - labelStrings := []string{} - if metric.GetLabel() != nil { - for _, labelPair := range metric.GetLabel() { - labelStrings = append(labelStrings, fmt.Sprintf("%s=\"%s\"", labelPair.GetName(), labelPair.GetValue())) - } - } - timestampString := "" - if metric.TimestampMs != nil { - timestampString = fmt.Sprintf(" %d", int(float64(metric.GetTimestampMs())*1000)) - } - switch metricFamily.GetType() { - case dto.MetricType_COUNTER: - value := strconv.FormatFloat(metric.GetCounter().GetValue(), 'f', -1, 64) - labelString := fmt.Sprintf("{%s}", strings.Join(labelStrings, ",")) - output = append(output, fmt.Sprintf("%s%s %s%s", metricFamily.GetName(), labelString, value, timestampString)) - case dto.MetricType_GAUGE: - value := strconv.FormatFloat(metric.GetGauge().GetValue(), 'f', -1, 64) - labelString := fmt.Sprintf("{%s}", strings.Join(labelStrings, ",")) - output = append(output, fmt.Sprintf("%s%s %s%s", metricFamily.GetName(), labelString, value, timestampString)) - case dto.MetricType_UNTYPED: - value := strconv.FormatFloat(metric.GetUntyped().GetValue(), 'f', -1, 64) - labelString := fmt.Sprintf("{%s}", strings.Join(labelStrings, ",")) - output = append(output, fmt.Sprintf("%s%s %s%s", metricFamily.GetName(), labelString, value, timestampString)) - case dto.MetricType_SUMMARY: - labelString := fmt.Sprintf("{%s}", strings.Join(labelStrings, ",")) - count := metric.GetSummary().GetSampleCount() - sum := strconv.FormatFloat(metric.GetSummary().GetSampleSum(), 'f', -1, 64) - for _, quantile := range metric.GetSummary().GetQuantile() { - quantileName := strconv.FormatFloat(quantile.GetQuantile(), 'f', -1, 64) - quantileLabelStrings := append(labelStrings, fmt.Sprintf("quantile=\"%s\"", quantileName)) - loopLabelString := fmt.Sprintf("{%s}", strings.Join(quantileLabelStrings, ",")) - value := strconv.FormatFloat(quantile.GetValue(), 'f', -1, 64) - output = append(output, fmt.Sprintf("%s%s %s%s", metricFamily.GetName(), loopLabelString, value, timestampString)) - } - output = append(output, fmt.Sprintf("%s_sum%s %s%s", metricFamily.GetName(), labelString, sum, timestampString)) - output = append(output, fmt.Sprintf("%s_count%s %d%s", metricFamily.GetName(), labelString, count, timestampString)) - case dto.MetricType_HISTOGRAM: - labelString := fmt.Sprintf("{%s}", strings.Join(labelStrings, ",")) - count := metric.GetHistogram().GetSampleCount() - sum := strconv.FormatFloat(metric.GetHistogram().GetSampleSum(), 'f', -1, 64) - for _, bucket := range metric.GetHistogram().GetBucket() { - bucketUpperBound := strconv.FormatFloat(bucket.GetUpperBound(), 'f', -1, 64) - bucketLabelStrings := append(labelStrings, fmt.Sprintf("le=\"%s\"", bucketUpperBound)) - loopLabelString := fmt.Sprintf("{%s}", strings.Join(bucketLabelStrings, ",")) - value := bucket.GetCumulativeCount() - output = append(output, fmt.Sprintf("%s_bucket%s %d%s", metricFamily.GetName(), loopLabelString, value, timestampString)) - } - infBucketLabelStrings := append(labelStrings, "le=\"+Inf\"") - infLabelString := fmt.Sprintf("{%s}", strings.Join(infBucketLabelStrings, ",")) - output = append(output, fmt.Sprintf("%s_bucket%s %d%s", metricFamily.GetName(), infLabelString, count, timestampString)) - output = append(output, fmt.Sprintf("%s_sum%s %s%s", metricFamily.GetName(), labelString, sum, timestampString)) - output = append(output, fmt.Sprintf("%s_count%s %d%s", metricFamily.GetName(), labelString, count, timestampString)) - } + defer os.Remove(tmp.Name()) + + mfs, err := g.Gather() + if err != nil { + return err + } + for _, mf := range mfs { + if _, err := expfmt.MetricFamilyToText(tmp, mf); err != nil { + return err } - outputMap[metricFamily.GetName()] = output - metricNames = append(metricNames, metricFamily.GetName()) } - - tmppath := fmt.Sprintf("%s.%d", path, os.Getpid()) - - f, err := os.Create(tmppath) - if err != nil { + if err := tmp.Close(); err != nil { return err } - - sort.Strings(metricNames) - - outStr := "" - for _, metricName := range metricNames { - outStr = fmt.Sprintf("%s%s\n", outStr, strings.Join(outputMap[metricName], "\n")) - } - - _, err = f.WriteString(outStr) - if err != nil { - return err - } - - if err := os.Rename(tmppath, path); err != nil { - return err - } - - return nil + return os.Rename(tmp.Name(), filename) } // processMetric is an internal helper method only used by the Gather method. diff --git a/prometheus/registry_test.go b/prometheus/registry_test.go index 3ebd443..a96cb10 100644 --- a/prometheus/registry_test.go +++ b/prometheus/registry_test.go @@ -958,7 +958,9 @@ test_summary_count{name="foo"} 2 } defer os.Remove(tmpfile.Name()) - registry.WriteToTextfile(tmpfile.Name()) + if err := prometheus.WriteToTextfile(tmpfile.Name(), registry); err != nil { + t.Fatal(err) + } fileBytes, err := ioutil.ReadFile(tmpfile.Name()) if err != nil { From ea348d7c20ce64c3bccdac0311e8621285d1278a Mon Sep 17 00:00:00 2001 From: Peter Jausovec Date: Fri, 2 Nov 2018 09:01:14 -0700 Subject: [PATCH 10/15] Update code based on the PR feedback Signed-off-by: Peter Jausovec --- prometheus/counter.go | 3 +-- prometheus/gauge.go | 4 +--- prometheus/histogram.go | 3 +-- prometheus/labels.go | 21 +++++++++++++++++++-- prometheus/summary.go | 3 +-- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/prometheus/counter.go b/prometheus/counter.go index 03965f9..756bb49 100644 --- a/prometheus/counter.go +++ b/prometheus/counter.go @@ -137,8 +137,7 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec { return &CounterVec{ metricVec: newMetricVec(desc, func(lvs ...string) Metric { if len(lvs) != len(desc.variableLabels) { - err := fmt.Errorf("%s: labels and values for '%s' don't match: labels %v, values %v", errInconsistentCardinality, desc.fqName, desc.variableLabels, lvs) - panic(err) + panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs)) } result := &counter{desc: desc, labelPairs: makeLabelPairs(desc, lvs)} result.init(result) // Init self-collection. diff --git a/prometheus/gauge.go b/prometheus/gauge.go index f80d72b..71d406b 100644 --- a/prometheus/gauge.go +++ b/prometheus/gauge.go @@ -14,7 +14,6 @@ package prometheus import ( - "fmt" "math" "sync/atomic" "time" @@ -148,8 +147,7 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec { return &GaugeVec{ metricVec: newMetricVec(desc, func(lvs ...string) Metric { if len(lvs) != len(desc.variableLabels) { - err := fmt.Errorf("%s: labels and values for '%s' don't match: labels %v, values %v", errInconsistentCardinality, desc.fqName, desc.variableLabels, lvs) - panic(err) + panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs)) } result := &gauge{desc: desc, labelPairs: makeLabelPairs(desc, lvs)} result.init(result) // Init self-collection. diff --git a/prometheus/histogram.go b/prometheus/histogram.go index 58b4f3b..f88da70 100644 --- a/prometheus/histogram.go +++ b/prometheus/histogram.go @@ -165,8 +165,7 @@ func NewHistogram(opts HistogramOpts) Histogram { func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram { if len(desc.variableLabels) != len(labelValues) { - err := fmt.Errorf("%s: labels and values for '%s' don't match: labels %v, values %v", errInconsistentCardinality, desc.fqName, desc.variableLabels, labelValues) - panic(err) + panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues)) } for _, n := range desc.variableLabels { diff --git a/prometheus/labels.go b/prometheus/labels.go index 8173f56..2744443 100644 --- a/prometheus/labels.go +++ b/prometheus/labels.go @@ -37,9 +37,22 @@ const reservedLabelPrefix = "__" var errInconsistentCardinality = errors.New("inconsistent label cardinality") +func makeInconsistentCardinalityError(fqName string, labels, labelValues []string) error { + return fmt.Errorf( + "%s: %q has %d variable labels named %q but %d values %q were provided", + errInconsistentCardinality, fqName, + len(labels), labels, + len(labelValues), labelValues, + ) +} + func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error { if len(labels) != expectedNumberOfValues { - return fmt.Errorf("%s: number of labels and values don't match: expected %d, actual %d, labels %v", errInconsistentCardinality, expectedNumberOfValues, len(labels), labels) + return fmt.Errorf( + "%s: expected %d label values but got %d in %#v", + errInconsistentCardinality, expectedNumberOfValues, + len(labels), labels, + ) } for name, val := range labels { @@ -53,7 +66,11 @@ func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error { func validateLabelValues(vals []string, expectedNumberOfValues int) error { if len(vals) != expectedNumberOfValues { - return errInconsistentCardinality + return fmt.Errorf( + "%s: expected %d label values but got %d in %#v", + errInconsistentCardinality, expectedNumberOfValues, + len(vals), vals, + ) } for _, val := range vals { diff --git a/prometheus/summary.go b/prometheus/summary.go index 402d73d..2980614 100644 --- a/prometheus/summary.go +++ b/prometheus/summary.go @@ -181,8 +181,7 @@ func NewSummary(opts SummaryOpts) Summary { func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary { if len(desc.variableLabels) != len(labelValues) { - err := fmt.Errorf("%s: labels and values for '%s' don't match: labels %v, values %v", errInconsistentCardinality, desc.fqName, desc.variableLabels, labelValues) - panic(err) + panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues)) } for _, n := range desc.variableLabels { From b686a9e02e4eea8f7b7d42b04a43c98491d4173b Mon Sep 17 00:00:00 2001 From: Peter Jausovec Date: Fri, 2 Nov 2018 09:21:12 -0700 Subject: [PATCH 11/15] Remove fmt from import Signed-off-by: Peter Jausovec --- prometheus/counter.go | 1 - 1 file changed, 1 deletion(-) diff --git a/prometheus/counter.go b/prometheus/counter.go index 756bb49..d463e36 100644 --- a/prometheus/counter.go +++ b/prometheus/counter.go @@ -15,7 +15,6 @@ package prometheus import ( "errors" - "fmt" "math" "sync/atomic" From 924d5919f3a0edff4d2479d05e07f9efe5e4d71c Mon Sep 17 00:00:00 2001 From: Sevag Hanssian Date: Fri, 2 Nov 2018 09:31:30 -0700 Subject: [PATCH 12/15] Improve WriteToTextfile doc Signed-off-by: Sevag Hanssian --- prometheus/registry.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/prometheus/registry.go b/prometheus/registry.go index 99cdc87..0137d35 100644 --- a/prometheus/registry.go +++ b/prometheus/registry.go @@ -537,9 +537,12 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) { return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap() } -// WriteToTextfile formats the metrics of the provided Gatherer interface and -// emits them in text format to the path. Intended for use with the node_exporter -// textfile collector +// WriteToTextfile calls Gather on the provided Gatherer, encodes the result in the +// Prometheus text format, and writes it to a temporary file. Upon success, the +// temporary file is renamed to the provided filename. +// +// This is intended for use with the textfile collector of the node exporter. +// Note that the node exporter expects the filename to be suffixed with ".prom". func WriteToTextfile(filename string, g Gatherer) error { tmp, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)) if err != nil { From f1bec54758e6a1c253d275d1ae000903216a400d Mon Sep 17 00:00:00 2001 From: Peter Jausovec Date: Fri, 2 Nov 2018 09:46:03 -0700 Subject: [PATCH 13/15] Update the ExampleRegister test Signed-off-by: Peter Jausovec --- prometheus/examples_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prometheus/examples_test.go b/prometheus/examples_test.go index 538a239..a382479 100644 --- a/prometheus/examples_test.go +++ b/prometheus/examples_test.go @@ -282,7 +282,7 @@ func ExampleRegister() { // taskCounter unregistered. // taskCounterVec not registered: a previously registered descriptor with the same fully-qualified name as Desc{fqName: "worker_pool_completed_tasks_total", help: "Total number of tasks completed.", constLabels: {}, variableLabels: [worker_id]} has different label names or a different help string // taskCounterVec registered. - // Worker initialization failed: inconsistent label cardinality + // Worker initialization failed: inconsistent label cardinality: expected 1 label values but got 2 in []string{"42", "spurious arg"} // notMyCounter is nil. // taskCounterForWorker42 registered. // taskCounterForWorker2001 registered. From 88f422377813c4ba6a7161855d8850180748f479 Mon Sep 17 00:00:00 2001 From: Sevag Hanssian Date: Fri, 2 Nov 2018 09:58:51 -0700 Subject: [PATCH 14/15] Fix permissions of tempfile Signed-off-by: Sevag Hanssian --- prometheus/registry.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prometheus/registry.go b/prometheus/registry.go index 0137d35..f98c81a 100644 --- a/prometheus/registry.go +++ b/prometheus/registry.go @@ -562,6 +562,10 @@ func WriteToTextfile(filename string, g Gatherer) error { if err := tmp.Close(); err != nil { return err } + + if err := os.Chmod(tmp.Name(), 0644); err != nil { + return err + } return os.Rename(tmp.Name(), filename) } From 8aa7f38ed0fdb5c19ee5f45f7d83d3e40f1a08d9 Mon Sep 17 00:00:00 2001 From: beorn7 Date: Fri, 2 Nov 2018 20:06:07 +0100 Subject: [PATCH 15/15] Cut v0.9.1 This is only adding good things, not really a "patch" release, but our pre-1.0 versioning semantics is explained in the README.md. Signed-off-by: beorn7 --- CHANGELOG.md | 12 ++++++++++++ VERSION | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adcef04..5d93880 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## 0.9.1 / 2018-11-03 +* [FEATURE] Add `WriteToTextfile` function to facilitate the creation of + *.prom files for the textfile collector of the node exporter. #489 +* [ENHANCEMENT] More descriptive error messages for inconsistent label + cardinality. #487 +* [ENHANCEMENT] Exposition: Use a GZIP encoder pool to avoid allocations in + high-frequency scrape scenarios. #366 +* [ENHANCEMENT] Exposition: Streaming serving of metrics data while encoding. + #482 +* [ENHANCEMENT] API client: Add a way to return the body of a 5xx response. + #479 + ## 0.9.0 / 2018-10-15 * [CHANGE] Go1.6 is no longer supported. * [CHANGE] More refinements of the `Registry` consistency checks: Duplicated diff --git a/VERSION b/VERSION index ac39a10..f374f66 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.0 +0.9.1