From 9416ff209b60b9abde9b27321e644c9c0a442ace Mon Sep 17 00:00:00 2001 From: Sevag Hanssian Date: Wed, 31 Oct 2018 14:13:32 -0700 Subject: [PATCH 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 924d5919f3a0edff4d2479d05e07f9efe5e4d71c Mon Sep 17 00:00:00 2001 From: Sevag Hanssian Date: Fri, 2 Nov 2018 09:31:30 -0700 Subject: [PATCH 6/7] 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 88f422377813c4ba6a7161855d8850180748f479 Mon Sep 17 00:00:00 2001 From: Sevag Hanssian Date: Fri, 2 Nov 2018 09:58:51 -0700 Subject: [PATCH 7/7] 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) }