Skip to content

Commit

Permalink
allow exposing meta information for registered metrics (#61)
Browse files Browse the repository at this point in the history
* allow exposing meta information for registered metrics

New public method `ExposeMetadata` allows enabling exposition
of dummy meta-info for all exposed metrics across all Sets.

This feature is needed to improve compatibility
with 3rd-party scrapers that require meta information to be present.

This commit doesn't update exposition of default system/process
metrics to keep the list of changes small. This change should
be added in a follow-up commit.

#48

* cleanup

* wip

* wip

* wip

* wip

---------

Co-authored-by: Aliaksandr Valialkin <[email protected]>
  • Loading branch information
hagen1778 and valyala authored Dec 19, 2023
1 parent fd25889 commit 9dc7358
Show file tree
Hide file tree
Showing 18 changed files with 333 additions and 86 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,11 @@ http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) {
metrics.InitPush("http://victoria-metrics:8428/api/v1/import/prometheus", 10*time.Second, `instance="foobar"`, true)
```

See [docs](http://godoc.org/github.com/VictoriaMetrics/metrics) for more info.
By default, exposed metrics [do not have](https://github.com/VictoriaMetrics/metrics/issues/48#issuecomment-1620765811)
`TYPE` or `HELP` meta information. Call [`ExposeMetadata(true)`](https://pkg.go.dev/github.com/VictoriaMetrics/metrics#ExposeMetadata)
in order to generate `TYPE` and `HELP` meta information per each metric.

See [docs](https://pkg.go.dev/github.com/VictoriaMetrics/metrics) for more info.

### Users

Expand Down
4 changes: 4 additions & 0 deletions counter.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ func (c *Counter) marshalTo(prefix string, w io.Writer) {
fmt.Fprintf(w, "%s %d\n", prefix, v)
}

func (c *Counter) metricType() string {
return "counter"
}

// GetOrCreateCounter returns registered counter with the given name
// or creates new counter if the registry doesn't contain counter with
// the given name.
Expand Down
4 changes: 4 additions & 0 deletions floatcounter.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ func (fc *FloatCounter) marshalTo(prefix string, w io.Writer) {
fmt.Fprintf(w, "%s %g\n", prefix, v)
}

func (fc *FloatCounter) metricType() string {
return "counter"
}

// GetOrCreateFloatCounter returns registered FloatCounter with the given name
// or creates new FloatCounter if the registry doesn't contain FloatCounter with
// the given name.
Expand Down
4 changes: 4 additions & 0 deletions gauge.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ func (g *Gauge) marshalTo(prefix string, w io.Writer) {
}
}

func (g *Gauge) metricType() string {
return "gauge"
}

// GetOrCreateGauge returns registered gauge with the given name
// or creates new gauge if the registry doesn't contain gauge with
// the given name.
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ require (
golang.org/x/sys v0.15.0
)

go 1.16
require github.com/valyala/fastrand v1.1.0 // indirect

go 1.17
91 changes: 54 additions & 37 deletions go_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,55 +47,60 @@ func writeGoMetrics(w io.Writer) {

var ms runtime.MemStats
runtime.ReadMemStats(&ms)
fmt.Fprintf(w, "go_memstats_alloc_bytes %d\n", ms.Alloc)
fmt.Fprintf(w, "go_memstats_alloc_bytes_total %d\n", ms.TotalAlloc)
fmt.Fprintf(w, "go_memstats_buck_hash_sys_bytes %d\n", ms.BuckHashSys)
fmt.Fprintf(w, "go_memstats_frees_total %d\n", ms.Frees)
fmt.Fprintf(w, "go_memstats_gc_cpu_fraction %g\n", ms.GCCPUFraction)
fmt.Fprintf(w, "go_memstats_gc_sys_bytes %d\n", ms.GCSys)

fmt.Fprintf(w, "go_memstats_heap_alloc_bytes %d\n", ms.HeapAlloc)
fmt.Fprintf(w, "go_memstats_heap_idle_bytes %d\n", ms.HeapIdle)
fmt.Fprintf(w, "go_memstats_heap_inuse_bytes %d\n", ms.HeapInuse)
fmt.Fprintf(w, "go_memstats_heap_objects %d\n", ms.HeapObjects)
fmt.Fprintf(w, "go_memstats_heap_released_bytes %d\n", ms.HeapReleased)
fmt.Fprintf(w, "go_memstats_heap_sys_bytes %d\n", ms.HeapSys)
fmt.Fprintf(w, "go_memstats_last_gc_time_seconds %g\n", float64(ms.LastGC)/1e9)
fmt.Fprintf(w, "go_memstats_lookups_total %d\n", ms.Lookups)
fmt.Fprintf(w, "go_memstats_mallocs_total %d\n", ms.Mallocs)
fmt.Fprintf(w, "go_memstats_mcache_inuse_bytes %d\n", ms.MCacheInuse)
fmt.Fprintf(w, "go_memstats_mcache_sys_bytes %d\n", ms.MCacheSys)
fmt.Fprintf(w, "go_memstats_mspan_inuse_bytes %d\n", ms.MSpanInuse)
fmt.Fprintf(w, "go_memstats_mspan_sys_bytes %d\n", ms.MSpanSys)
fmt.Fprintf(w, "go_memstats_next_gc_bytes %d\n", ms.NextGC)
fmt.Fprintf(w, "go_memstats_other_sys_bytes %d\n", ms.OtherSys)
fmt.Fprintf(w, "go_memstats_stack_inuse_bytes %d\n", ms.StackInuse)
fmt.Fprintf(w, "go_memstats_stack_sys_bytes %d\n", ms.StackSys)
fmt.Fprintf(w, "go_memstats_sys_bytes %d\n", ms.Sys)

fmt.Fprintf(w, "go_cgo_calls_count %d\n", runtime.NumCgoCall())
fmt.Fprintf(w, "go_cpu_count %d\n", runtime.NumCPU())
WriteGaugeUint64(w, "go_memstats_alloc_bytes", ms.Alloc)
WriteCounterUint64(w, "go_memstats_alloc_bytes_total", ms.TotalAlloc)
WriteGaugeUint64(w, "go_memstats_buck_hash_sys_bytes", ms.BuckHashSys)
WriteCounterUint64(w, "go_memstats_frees_total", ms.Frees)
WriteGaugeFloat64(w, "go_memstats_gc_cpu_fraction", ms.GCCPUFraction)
WriteGaugeUint64(w, "go_memstats_gc_sys_bytes", ms.GCSys)

WriteGaugeUint64(w, "go_memstats_heap_alloc_bytes", ms.HeapAlloc)
WriteGaugeUint64(w, "go_memstats_heap_idle_bytes", ms.HeapIdle)
WriteGaugeUint64(w, "go_memstats_heap_inuse_bytes", ms.HeapInuse)
WriteGaugeUint64(w, "go_memstats_heap_objects", ms.HeapObjects)
WriteGaugeUint64(w, "go_memstats_heap_released_bytes", ms.HeapReleased)
WriteGaugeUint64(w, "go_memstats_heap_sys_bytes", ms.HeapSys)
WriteGaugeFloat64(w, "go_memstats_last_gc_time_seconds", float64(ms.LastGC)/1e9)
WriteCounterUint64(w, "go_memstats_lookups_total", ms.Lookups)
WriteCounterUint64(w, "go_memstats_mallocs_total", ms.Mallocs)
WriteGaugeUint64(w, "go_memstats_mcache_inuse_bytes", ms.MCacheInuse)
WriteGaugeUint64(w, "go_memstats_mcache_sys_bytes", ms.MCacheSys)
WriteGaugeUint64(w, "go_memstats_mspan_inuse_bytes", ms.MSpanInuse)
WriteGaugeUint64(w, "go_memstats_mspan_sys_bytes", ms.MSpanSys)
WriteGaugeUint64(w, "go_memstats_next_gc_bytes", ms.NextGC)
WriteGaugeUint64(w, "go_memstats_other_sys_bytes", ms.OtherSys)
WriteGaugeUint64(w, "go_memstats_stack_inuse_bytes", ms.StackInuse)
WriteGaugeUint64(w, "go_memstats_stack_sys_bytes", ms.StackSys)
WriteGaugeUint64(w, "go_memstats_sys_bytes", ms.Sys)

WriteCounterUint64(w, "go_cgo_calls_count", uint64(runtime.NumCgoCall()))
WriteGaugeUint64(w, "go_cpu_count", uint64(runtime.NumCPU()))

gcPauses := histogram.NewFast()
for _, pauseNs := range ms.PauseNs[:] {
gcPauses.Update(float64(pauseNs) / 1e9)
}
phis := []float64{0, 0.25, 0.5, 0.75, 1}
quantiles := make([]float64, 0, len(phis))
writeMetadataIfNeeded(w, "go_gc_duration_seconds", "summary")
for i, q := range gcPauses.Quantiles(quantiles[:0], phis) {
fmt.Fprintf(w, `go_gc_duration_seconds{quantile="%g"} %g`+"\n", phis[i], q)
}
fmt.Fprintf(w, `go_gc_duration_seconds_sum %g`+"\n", float64(ms.PauseTotalNs)/1e9)
fmt.Fprintf(w, `go_gc_duration_seconds_count %d`+"\n", ms.NumGC)
fmt.Fprintf(w, `go_gc_forced_count %d`+"\n", ms.NumForcedGC)
fmt.Fprintf(w, "go_gc_duration_seconds_sum %g\n", float64(ms.PauseTotalNs)/1e9)
fmt.Fprintf(w, "go_gc_duration_seconds_count %d\n", ms.NumGC)

fmt.Fprintf(w, `go_gomaxprocs %d`+"\n", runtime.GOMAXPROCS(0))
fmt.Fprintf(w, `go_goroutines %d`+"\n", runtime.NumGoroutine())
WriteCounterUint64(w, "go_gc_forced_count", uint64(ms.NumForcedGC))

WriteGaugeUint64(w, "go_gomaxprocs", uint64(runtime.GOMAXPROCS(0)))
WriteGaugeUint64(w, "go_goroutines", uint64(runtime.NumGoroutine()))
numThread, _ := runtime.ThreadCreateProfile(nil)
fmt.Fprintf(w, `go_threads %d`+"\n", numThread)
WriteGaugeUint64(w, "go_threads", uint64(numThread))

// Export build details.
writeMetadataIfNeeded(w, "go_info", "gauge")
fmt.Fprintf(w, "go_info{version=%q} 1\n", runtime.Version())

writeMetadataIfNeeded(w, "go_info_ext", "gauge")
fmt.Fprintf(w, "go_info_ext{compiler=%q, GOARCH=%q, GOOS=%q, GOROOT=%q} 1\n",
runtime.Compiler, runtime.GOARCH, runtime.GOOS, runtime.GOROOT())
}
Expand All @@ -117,11 +122,22 @@ func writeRuntimeMetric(w io.Writer, name string, sample *runtimemetrics.Sample)
case runtimemetrics.KindBad:
panic(fmt.Errorf("BUG: unexpected runtimemetrics.KindBad for sample.Name=%q", sample.Name))
case runtimemetrics.KindUint64:
fmt.Fprintf(w, "%s %d\n", name, sample.Value.Uint64())
v := sample.Value.Uint64()
if strings.HasSuffix(name, "_total") {
WriteCounterUint64(w, name, v)
} else {
WriteGaugeUint64(w, name, v)
}
case runtimemetrics.KindFloat64:
fmt.Fprintf(w, "%s %g\n", name, sample.Value.Float64())
v := sample.Value.Float64()
if isCounterName(name) {
WriteCounterFloat64(w, name, v)
} else {
WriteGaugeFloat64(w, name, v)
}
case runtimemetrics.KindFloat64Histogram:
writeRuntimeHistogramMetric(w, name, sample.Value.Float64Histogram())
h := sample.Value.Float64Histogram()
writeRuntimeHistogramMetric(w, name, h)
default:
panic(fmt.Errorf("unexpected metric kind=%d", kind))
}
Expand Down Expand Up @@ -149,6 +165,7 @@ func writeRuntimeHistogramMetric(w io.Writer, name string, h *runtimemetrics.Flo

totalCount := uint64(0)
iNext := 0.0
writeMetadataIfNeeded(w, name, "histogram")
for i, count := range counts {
totalCount += count
if float64(i) >= iNext {
Expand Down
4 changes: 4 additions & 0 deletions histogram.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,7 @@ func (h *Histogram) getSum() float64 {
h.mu.Unlock()
return sum
}

func (h *Histogram) metricType() string {
return "histogram"
}
65 changes: 65 additions & 0 deletions metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
package metrics

import (
"fmt"
"io"
"sort"
"strings"
"sync"
"sync/atomic"
"unsafe"
)

Expand All @@ -25,8 +28,17 @@ type namedMetric struct {
isAux bool
}

func (nm *namedMetric) family() string {
n := strings.IndexByte(nm.name, '{')
if n < 0 {
return nm.name
}
return nm.name[:n]
}

type metric interface {
marshalTo(prefix string, w io.Writer)
metricType() string
}

var defaultSet = NewSet()
Expand Down Expand Up @@ -241,3 +253,56 @@ func ListMetricNames() []string {
func GetDefaultSet() *Set {
return defaultSet
}

// ExposeMetadata allows enabling adding TYPE and HELP metadata to the exposed metrics globally.
//
// It is safe to call this method multiple times. It is allowed to change it in runtime.
// ExposeMetadata is set to false by default.
func ExposeMetadata(v bool) {
n := 0
if v {
n = 1
}
atomic.StoreUint32(&exposeMetadata, uint32(n))
}

func isMetadataEnabled() bool {
n := atomic.LoadUint32(&exposeMetadata)
return n != 0
}

var exposeMetadata uint32

func isCounterName(name string) bool {
return strings.HasSuffix(name, "_total")
}

// WriteGaugeUint64 writes gauge metric with the given name and value to w in Prometheus text exposition format.
func WriteGaugeUint64(w io.Writer, name string, value uint64) {
writeMetricUint64(w, name, "gauge", value)
}

// WriteGaugeFloat64 writes gauge metric with the given name and value to w in Prometheus text exposition format.
func WriteGaugeFloat64(w io.Writer, name string, value float64) {
writeMetricFloat64(w, name, "gauge", value)
}

// WriteCounterUint64 writes counter metric with the given name and value to w in Prometheus text exposition format.
func WriteCounterUint64(w io.Writer, name string, value uint64) {
writeMetricUint64(w, name, "counter", value)
}

// WriteCounterFloat64 writes counter metric with the given name and value to w in Prometheus text exposition format.
func WriteCounterFloat64(w io.Writer, name string, value float64) {
writeMetricFloat64(w, name, "counter", value)
}

func writeMetricUint64(w io.Writer, metricName, metricType string, value uint64) {
writeMetadataIfNeeded(w, metricName, metricType)
fmt.Fprintf(w, "%s %d\n", metricName, value)
}

func writeMetricFloat64(w io.Writer, metricName, metricType string, value float64) {
writeMetadataIfNeeded(w, metricName, metricType)
fmt.Fprintf(w, "%s %g\n", metricName, value)
}
75 changes: 75 additions & 0 deletions metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,81 @@ import (
"time"
)

func TestWriteMetrics(t *testing.T) {
t.Run("gauge_uint64", func(t *testing.T) {
var bb bytes.Buffer

WriteGaugeUint64(&bb, "foo", 123)
sExpected := "foo 123\n"
if s := bb.String(); s != sExpected {
t.Fatalf("unexpected value; got\n%s\nwant\n%s", s, sExpected)
}

ExposeMetadata(true)
bb.Reset()
WriteGaugeUint64(&bb, "foo", 123)
sExpected = "# HELP foo\n# TYPE foo gauge\nfoo 123\n"
ExposeMetadata(false)
if s := bb.String(); s != sExpected {
t.Fatalf("unexpected value; got\n%s\nwant\n%s", s, sExpected)
}
})
t.Run("gauge_float64", func(t *testing.T) {
var bb bytes.Buffer

WriteGaugeFloat64(&bb, "foo", 1.23)
sExpected := "foo 1.23\n"
if s := bb.String(); s != sExpected {
t.Fatalf("unexpected value; got\n%s\nwant\n%s", s, sExpected)
}

ExposeMetadata(true)
bb.Reset()
WriteGaugeFloat64(&bb, "foo", 1.23)
sExpected = "# HELP foo\n# TYPE foo gauge\nfoo 1.23\n"
ExposeMetadata(false)
if s := bb.String(); s != sExpected {
t.Fatalf("unexpected value; got\n%s\nwant\n%s", s, sExpected)
}
})
t.Run("counter_uint64", func(t *testing.T) {
var bb bytes.Buffer

WriteCounterUint64(&bb, "foo_total", 123)
sExpected := "foo_total 123\n"
if s := bb.String(); s != sExpected {
t.Fatalf("unexpected value; got\n%s\nwant\n%s", s, sExpected)
}

ExposeMetadata(true)
bb.Reset()
WriteCounterUint64(&bb, "foo_total", 123)
sExpected = "# HELP foo_total\n# TYPE foo_total counter\nfoo_total 123\n"
ExposeMetadata(false)
if s := bb.String(); s != sExpected {
t.Fatalf("unexpected value; got\n%s\nwant\n%s", s, sExpected)
}
})
t.Run("counter_float64", func(t *testing.T) {
var bb bytes.Buffer

WriteCounterFloat64(&bb, "foo_total", 1.23)
sExpected := "foo_total 1.23\n"
if s := bb.String(); s != sExpected {
t.Fatalf("unexpected value; got\n%s\nwant\n%s", s, sExpected)
}

ExposeMetadata(true)
bb.Reset()
WriteCounterFloat64(&bb, "foo_total", 1.23)
sExpected = "# HELP foo_total\n# TYPE foo_total counter\nfoo_total 1.23\n"
ExposeMetadata(false)
if s := bb.String(); s != sExpected {
t.Fatalf("unexpected value; got\n%s\nwant\n%s", s, sExpected)
}
})
}

func TestGetDefaultSet(t *testing.T) {
s := GetDefaultSet()
if s != defaultSet {
Expand Down
Loading

0 comments on commit 9dc7358

Please sign in to comment.