allow exposing meta information for registered metrics (#61)

* 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.

https://github.com/VictoriaMetrics/metrics/issues/48

* cleanup

* wip

* wip

* wip

* wip

---------

Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
Roman Khavronenko 2023-12-19 01:36:54 +01:00 committed by GitHub
parent fd25889711
commit 9dc7358869
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 331 additions and 84 deletions

View File

@ -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) 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 ### Users

View File

@ -58,6 +58,10 @@ func (c *Counter) marshalTo(prefix string, w io.Writer) {
fmt.Fprintf(w, "%s %d\n", prefix, v) fmt.Fprintf(w, "%s %d\n", prefix, v)
} }
func (c *Counter) metricType() string {
return "counter"
}
// GetOrCreateCounter returns registered counter with the given name // GetOrCreateCounter returns registered counter with the given name
// or creates new counter if the registry doesn't contain counter with // or creates new counter if the registry doesn't contain counter with
// the given name. // the given name.

View File

@ -63,6 +63,10 @@ func (fc *FloatCounter) marshalTo(prefix string, w io.Writer) {
fmt.Fprintf(w, "%s %g\n", prefix, v) fmt.Fprintf(w, "%s %g\n", prefix, v)
} }
func (fc *FloatCounter) metricType() string {
return "counter"
}
// GetOrCreateFloatCounter returns registered FloatCounter with the given name // GetOrCreateFloatCounter returns registered FloatCounter with the given name
// or creates new FloatCounter if the registry doesn't contain FloatCounter with // or creates new FloatCounter if the registry doesn't contain FloatCounter with
// the given name. // the given name.

View File

@ -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 // GetOrCreateGauge returns registered gauge with the given name
// or creates new gauge if the registry doesn't contain gauge with // or creates new gauge if the registry doesn't contain gauge with
// the given name. // the given name.

4
go.mod
View File

@ -5,4 +5,6 @@ require (
golang.org/x/sys v0.15.0 golang.org/x/sys v0.15.0
) )
go 1.16 require github.com/valyala/fastrand v1.1.0 // indirect
go 1.17

View File

@ -47,34 +47,34 @@ func writeGoMetrics(w io.Writer) {
var ms runtime.MemStats var ms runtime.MemStats
runtime.ReadMemStats(&ms) runtime.ReadMemStats(&ms)
fmt.Fprintf(w, "go_memstats_alloc_bytes %d\n", ms.Alloc) WriteGaugeUint64(w, "go_memstats_alloc_bytes", ms.Alloc)
fmt.Fprintf(w, "go_memstats_alloc_bytes_total %d\n", ms.TotalAlloc) WriteCounterUint64(w, "go_memstats_alloc_bytes_total", ms.TotalAlloc)
fmt.Fprintf(w, "go_memstats_buck_hash_sys_bytes %d\n", ms.BuckHashSys) WriteGaugeUint64(w, "go_memstats_buck_hash_sys_bytes", ms.BuckHashSys)
fmt.Fprintf(w, "go_memstats_frees_total %d\n", ms.Frees) WriteCounterUint64(w, "go_memstats_frees_total", ms.Frees)
fmt.Fprintf(w, "go_memstats_gc_cpu_fraction %g\n", ms.GCCPUFraction) WriteGaugeFloat64(w, "go_memstats_gc_cpu_fraction", ms.GCCPUFraction)
fmt.Fprintf(w, "go_memstats_gc_sys_bytes %d\n", ms.GCSys) WriteGaugeUint64(w, "go_memstats_gc_sys_bytes", ms.GCSys)
fmt.Fprintf(w, "go_memstats_heap_alloc_bytes %d\n", ms.HeapAlloc) WriteGaugeUint64(w, "go_memstats_heap_alloc_bytes", ms.HeapAlloc)
fmt.Fprintf(w, "go_memstats_heap_idle_bytes %d\n", ms.HeapIdle) WriteGaugeUint64(w, "go_memstats_heap_idle_bytes", ms.HeapIdle)
fmt.Fprintf(w, "go_memstats_heap_inuse_bytes %d\n", ms.HeapInuse) WriteGaugeUint64(w, "go_memstats_heap_inuse_bytes", ms.HeapInuse)
fmt.Fprintf(w, "go_memstats_heap_objects %d\n", ms.HeapObjects) WriteGaugeUint64(w, "go_memstats_heap_objects", ms.HeapObjects)
fmt.Fprintf(w, "go_memstats_heap_released_bytes %d\n", ms.HeapReleased) WriteGaugeUint64(w, "go_memstats_heap_released_bytes", ms.HeapReleased)
fmt.Fprintf(w, "go_memstats_heap_sys_bytes %d\n", ms.HeapSys) WriteGaugeUint64(w, "go_memstats_heap_sys_bytes", ms.HeapSys)
fmt.Fprintf(w, "go_memstats_last_gc_time_seconds %g\n", float64(ms.LastGC)/1e9) WriteGaugeFloat64(w, "go_memstats_last_gc_time_seconds", float64(ms.LastGC)/1e9)
fmt.Fprintf(w, "go_memstats_lookups_total %d\n", ms.Lookups) WriteCounterUint64(w, "go_memstats_lookups_total", ms.Lookups)
fmt.Fprintf(w, "go_memstats_mallocs_total %d\n", ms.Mallocs) WriteCounterUint64(w, "go_memstats_mallocs_total", ms.Mallocs)
fmt.Fprintf(w, "go_memstats_mcache_inuse_bytes %d\n", ms.MCacheInuse) WriteGaugeUint64(w, "go_memstats_mcache_inuse_bytes", ms.MCacheInuse)
fmt.Fprintf(w, "go_memstats_mcache_sys_bytes %d\n", ms.MCacheSys) WriteGaugeUint64(w, "go_memstats_mcache_sys_bytes", ms.MCacheSys)
fmt.Fprintf(w, "go_memstats_mspan_inuse_bytes %d\n", ms.MSpanInuse) WriteGaugeUint64(w, "go_memstats_mspan_inuse_bytes", ms.MSpanInuse)
fmt.Fprintf(w, "go_memstats_mspan_sys_bytes %d\n", ms.MSpanSys) WriteGaugeUint64(w, "go_memstats_mspan_sys_bytes", ms.MSpanSys)
fmt.Fprintf(w, "go_memstats_next_gc_bytes %d\n", ms.NextGC) WriteGaugeUint64(w, "go_memstats_next_gc_bytes", ms.NextGC)
fmt.Fprintf(w, "go_memstats_other_sys_bytes %d\n", ms.OtherSys) WriteGaugeUint64(w, "go_memstats_other_sys_bytes", ms.OtherSys)
fmt.Fprintf(w, "go_memstats_stack_inuse_bytes %d\n", ms.StackInuse) WriteGaugeUint64(w, "go_memstats_stack_inuse_bytes", ms.StackInuse)
fmt.Fprintf(w, "go_memstats_stack_sys_bytes %d\n", ms.StackSys) WriteGaugeUint64(w, "go_memstats_stack_sys_bytes", ms.StackSys)
fmt.Fprintf(w, "go_memstats_sys_bytes %d\n", ms.Sys) WriteGaugeUint64(w, "go_memstats_sys_bytes", ms.Sys)
fmt.Fprintf(w, "go_cgo_calls_count %d\n", runtime.NumCgoCall()) WriteCounterUint64(w, "go_cgo_calls_count", uint64(runtime.NumCgoCall()))
fmt.Fprintf(w, "go_cpu_count %d\n", runtime.NumCPU()) WriteGaugeUint64(w, "go_cpu_count", uint64(runtime.NumCPU()))
gcPauses := histogram.NewFast() gcPauses := histogram.NewFast()
for _, pauseNs := range ms.PauseNs[:] { for _, pauseNs := range ms.PauseNs[:] {
@ -82,20 +82,25 @@ func writeGoMetrics(w io.Writer) {
} }
phis := []float64{0, 0.25, 0.5, 0.75, 1} phis := []float64{0, 0.25, 0.5, 0.75, 1}
quantiles := make([]float64, 0, len(phis)) quantiles := make([]float64, 0, len(phis))
writeMetadataIfNeeded(w, "go_gc_duration_seconds", "summary")
for i, q := range gcPauses.Quantiles(quantiles[:0], phis) { 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{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_sum %g\n", float64(ms.PauseTotalNs)/1e9)
fmt.Fprintf(w, `go_gc_duration_seconds_count %d`+"\n", ms.NumGC) 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_gomaxprocs %d`+"\n", runtime.GOMAXPROCS(0)) WriteCounterUint64(w, "go_gc_forced_count", uint64(ms.NumForcedGC))
fmt.Fprintf(w, `go_goroutines %d`+"\n", runtime.NumGoroutine())
WriteGaugeUint64(w, "go_gomaxprocs", uint64(runtime.GOMAXPROCS(0)))
WriteGaugeUint64(w, "go_goroutines", uint64(runtime.NumGoroutine()))
numThread, _ := runtime.ThreadCreateProfile(nil) numThread, _ := runtime.ThreadCreateProfile(nil)
fmt.Fprintf(w, `go_threads %d`+"\n", numThread) WriteGaugeUint64(w, "go_threads", uint64(numThread))
// Export build details. // Export build details.
writeMetadataIfNeeded(w, "go_info", "gauge")
fmt.Fprintf(w, "go_info{version=%q} 1\n", runtime.Version()) 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", fmt.Fprintf(w, "go_info_ext{compiler=%q, GOARCH=%q, GOOS=%q, GOROOT=%q} 1\n",
runtime.Compiler, runtime.GOARCH, runtime.GOOS, runtime.GOROOT()) runtime.Compiler, runtime.GOARCH, runtime.GOOS, runtime.GOROOT())
} }
@ -117,11 +122,22 @@ func writeRuntimeMetric(w io.Writer, name string, sample *runtimemetrics.Sample)
case runtimemetrics.KindBad: case runtimemetrics.KindBad:
panic(fmt.Errorf("BUG: unexpected runtimemetrics.KindBad for sample.Name=%q", sample.Name)) panic(fmt.Errorf("BUG: unexpected runtimemetrics.KindBad for sample.Name=%q", sample.Name))
case runtimemetrics.KindUint64: 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: 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: case runtimemetrics.KindFloat64Histogram:
writeRuntimeHistogramMetric(w, name, sample.Value.Float64Histogram()) h := sample.Value.Float64Histogram()
writeRuntimeHistogramMetric(w, name, h)
default: default:
panic(fmt.Errorf("unexpected metric kind=%d", kind)) panic(fmt.Errorf("unexpected metric kind=%d", kind))
} }
@ -149,6 +165,7 @@ func writeRuntimeHistogramMetric(w io.Writer, name string, h *runtimemetrics.Flo
totalCount := uint64(0) totalCount := uint64(0)
iNext := 0.0 iNext := 0.0
writeMetadataIfNeeded(w, name, "histogram")
for i, count := range counts { for i, count := range counts {
totalCount += count totalCount += count
if float64(i) >= iNext { if float64(i) >= iNext {

View File

@ -228,3 +228,7 @@ func (h *Histogram) getSum() float64 {
h.mu.Unlock() h.mu.Unlock()
return sum return sum
} }
func (h *Histogram) metricType() string {
return "histogram"
}

View File

@ -13,9 +13,12 @@
package metrics package metrics
import ( import (
"fmt"
"io" "io"
"sort" "sort"
"strings"
"sync" "sync"
"sync/atomic"
"unsafe" "unsafe"
) )
@ -25,8 +28,17 @@ type namedMetric struct {
isAux bool 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 { type metric interface {
marshalTo(prefix string, w io.Writer) marshalTo(prefix string, w io.Writer)
metricType() string
} }
var defaultSet = NewSet() var defaultSet = NewSet()
@ -241,3 +253,56 @@ func ListMetricNames() []string {
func GetDefaultSet() *Set { func GetDefaultSet() *Set {
return defaultSet 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)
}

View File

@ -8,6 +8,81 @@ import (
"time" "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) { func TestGetDefaultSet(t *testing.T) {
s := GetDefaultSet() s := GetDefaultSet()
if s != defaultSet { if s != defaultSet {

View File

@ -74,15 +74,15 @@ func writeProcessMetrics(w io.Writer) {
utime := float64(p.Utime) / userHZ utime := float64(p.Utime) / userHZ
stime := float64(p.Stime) / userHZ stime := float64(p.Stime) / userHZ
fmt.Fprintf(w, "process_cpu_seconds_system_total %g\n", stime) WriteCounterFloat64(w, "process_cpu_seconds_system_total", stime)
fmt.Fprintf(w, "process_cpu_seconds_total %g\n", utime+stime) WriteCounterFloat64(w, "process_cpu_seconds_total", utime+stime)
fmt.Fprintf(w, "process_cpu_seconds_user_total %g\n", utime) WriteCounterFloat64(w, "process_cpu_seconds_user_total", utime)
fmt.Fprintf(w, "process_major_pagefaults_total %d\n", p.Majflt) WriteCounterUint64(w, "process_major_pagefaults_total", uint64(p.Majflt))
fmt.Fprintf(w, "process_minor_pagefaults_total %d\n", p.Minflt) WriteCounterUint64(w, "process_minor_pagefaults_total", uint64(p.Minflt))
fmt.Fprintf(w, "process_num_threads %d\n", p.NumThreads) WriteGaugeUint64(w, "process_num_threads", uint64(p.NumThreads))
fmt.Fprintf(w, "process_resident_memory_bytes %d\n", p.Rss*4096) WriteGaugeUint64(w, "process_resident_memory_bytes", uint64(p.Rss)*4096)
fmt.Fprintf(w, "process_start_time_seconds %d\n", startTimeSeconds) WriteGaugeUint64(w, "process_start_time_seconds", uint64(startTimeSeconds))
fmt.Fprintf(w, "process_virtual_memory_bytes %d\n", p.Vsize) WriteGaugeUint64(w, "process_virtual_memory_bytes", uint64(p.Vsize))
writeProcessMemMetrics(w) writeProcessMemMetrics(w)
writeIOMetrics(w) writeIOMetrics(w)
} }
@ -133,12 +133,12 @@ func writeIOMetrics(w io.Writer) {
writeBytes = getInt(s) writeBytes = getInt(s)
} }
} }
fmt.Fprintf(w, "process_io_read_bytes_total %d\n", rchar) WriteGaugeUint64(w, "process_io_read_bytes_total", uint64(rchar))
fmt.Fprintf(w, "process_io_written_bytes_total %d\n", wchar) WriteGaugeUint64(w, "process_io_written_bytes_total", uint64(wchar))
fmt.Fprintf(w, "process_io_read_syscalls_total %d\n", syscr) WriteGaugeUint64(w, "process_io_read_syscalls_total", uint64(syscr))
fmt.Fprintf(w, "process_io_write_syscalls_total %d\n", syscw) WriteGaugeUint64(w, "process_io_write_syscalls_total", uint64(syscw))
fmt.Fprintf(w, "process_io_storage_read_bytes_total %d\n", readBytes) WriteGaugeUint64(w, "process_io_storage_read_bytes_total", uint64(readBytes))
fmt.Fprintf(w, "process_io_storage_written_bytes_total %d\n", writeBytes) WriteGaugeUint64(w, "process_io_storage_written_bytes_total", uint64(writeBytes))
} }
var startTimeSeconds = time.Now().Unix() var startTimeSeconds = time.Now().Unix()
@ -155,8 +155,8 @@ func writeFDMetrics(w io.Writer) {
log.Printf("ERROR: metrics: cannot determine the limit on open file descritors: %s", err) log.Printf("ERROR: metrics: cannot determine the limit on open file descritors: %s", err)
return return
} }
fmt.Fprintf(w, "process_max_fds %d\n", maxOpenFDs) WriteGaugeUint64(w, "process_max_fds", maxOpenFDs)
fmt.Fprintf(w, "process_open_fds %d\n", totalOpenFDs) WriteGaugeUint64(w, "process_open_fds", totalOpenFDs)
} }
func getOpenFDsCount(path string) (uint64, error) { func getOpenFDsCount(path string) (uint64, error) {
@ -224,11 +224,11 @@ func writeProcessMemMetrics(w io.Writer) {
log.Printf("ERROR: metrics: cannot determine memory status: %s", err) log.Printf("ERROR: metrics: cannot determine memory status: %s", err)
return return
} }
fmt.Fprintf(w, "process_virtual_memory_peak_bytes %d\n", ms.vmPeak) WriteGaugeUint64(w, "process_virtual_memory_peak_bytes", ms.vmPeak)
fmt.Fprintf(w, "process_resident_memory_peak_bytes %d\n", ms.rssPeak) WriteGaugeUint64(w, "process_resident_memory_peak_bytes", ms.rssPeak)
fmt.Fprintf(w, "process_resident_memory_anon_bytes %d\n", ms.rssAnon) WriteGaugeUint64(w, "process_resident_memory_anon_bytes", ms.rssAnon)
fmt.Fprintf(w, "process_resident_memory_file_bytes %d\n", ms.rssFile) WriteGaugeUint64(w, "process_resident_memory_file_bytes", ms.rssFile)
fmt.Fprintf(w, "process_resident_memory_shared_bytes %d\n", ms.rssShmem) WriteGaugeUint64(w, "process_resident_memory_shared_bytes", ms.rssShmem)
} }

View File

@ -4,7 +4,6 @@
package metrics package metrics
import ( import (
"fmt"
"io" "io"
"log" "log"
"syscall" "syscall"
@ -55,16 +54,16 @@ func writeProcessMetrics(w io.Writer) {
log.Printf("ERROR: metrics: cannot read process memory information: %s", err) log.Printf("ERROR: metrics: cannot read process memory information: %s", err)
return return
} }
stimeSeconds := (uint64(stime.HighDateTime)<<32 + uint64(stime.LowDateTime)) / 1e7 stimeSeconds := float64(uint64(stime.HighDateTime)<<32+uint64(stime.LowDateTime)) / 1e7
utimeSeconds := (uint64(utime.HighDateTime)<<32 + uint64(utime.LowDateTime)) / 1e7 utimeSeconds := float64(uint64(utime.HighDateTime)<<32+uint64(utime.LowDateTime)) / 1e7
fmt.Fprintf(w, "process_cpu_seconds_system_total %d\n", stimeSeconds) WriteCounterFloat64(w, "process_cpu_seconds_system_total", stimeSeconds)
fmt.Fprintf(w, "process_cpu_seconds_total %d\n", stimeSeconds+utimeSeconds) WriteCounterFloat64(w, "process_cpu_seconds_total", stimeSeconds+utimeSeconds)
fmt.Fprintf(w, "process_cpu_seconds_user_total %d\n", stimeSeconds) WriteCounterFloat64(w, "process_cpu_seconds_user_total", stimeSeconds)
fmt.Fprintf(w, "process_pagefaults_total %d\n", mc.PageFaultCount) WriteCounterUint64(w, "process_pagefaults_total", uint64(mc.PageFaultCount))
fmt.Fprintf(w, "process_start_time_seconds %d\n", startTime.Nanoseconds()/1e9) WriteGaugeUint64(w, "process_start_time_seconds", uint64(startTime.Nanoseconds())/1e9)
fmt.Fprintf(w, "process_virtual_memory_bytes %d\n", mc.PrivateUsage) WriteGaugeUint64(w, "process_virtual_memory_bytes", uint64(mc.PrivateUsage))
fmt.Fprintf(w, "process_resident_memory_peak_bytes %d\n", mc.PeakWorkingSetSize) WriteGaugeUint64(w, "process_resident_memory_peak_bytes", uint64(mc.PeakWorkingSetSize))
fmt.Fprintf(w, "process_resident_memory_bytes %d\n", mc.WorkingSetSize) WriteGaugeUint64(w, "process_resident_memory_bytes", uint64(mc.WorkingSetSize))
} }
func writeFDMetrics(w io.Writer) { func writeFDMetrics(w io.Writer) {
@ -80,6 +79,6 @@ func writeFDMetrics(w io.Writer) {
} }
// it seems to be hard-coded limit for 64-bit systems // it seems to be hard-coded limit for 64-bit systems
// https://learn.microsoft.com/en-us/archive/blogs/markrussinovich/pushing-the-limits-of-windows-handles#maximum-number-of-handles // https://learn.microsoft.com/en-us/archive/blogs/markrussinovich/pushing-the-limits-of-windows-handles#maximum-number-of-handles
fmt.Fprintf(w, "process_max_fds %d\n", 16777216) WriteGaugeUint64(w, "process_max_fds", 16777216)
fmt.Fprintf(w, "process_open_fds %d\n", count) WriteGaugeUint64(w, "process_open_fds", uint64(count))
} }

18
set.go
View File

@ -47,14 +47,30 @@ func (s *Set) WritePrometheus(w io.Writer) {
sa := append([]*namedMetric(nil), s.a...) sa := append([]*namedMetric(nil), s.a...)
s.mu.Unlock() s.mu.Unlock()
prevMetricFamily := ""
for _, nm := range sa {
metricFamily := nm.family()
if metricFamily != prevMetricFamily {
// write meta info only once per metric family
metricType := nm.metric.metricType()
writeMetadataIfNeeded(&bb, metricFamily, metricType)
prevMetricFamily = metricFamily
}
// Call marshalTo without the global lock, since certain metric types such as Gauge // Call marshalTo without the global lock, since certain metric types such as Gauge
// can call a callback, which, in turn, can try calling s.mu.Lock again. // can call a callback, which, in turn, can try calling s.mu.Lock again.
for _, nm := range sa {
nm.metric.marshalTo(nm.name, &bb) nm.metric.marshalTo(nm.name, &bb)
} }
w.Write(bb.Bytes()) w.Write(bb.Bytes())
} }
func writeMetadataIfNeeded(w io.Writer, metricFamily, metricType string) {
if !isMetadataEnabled() {
return
}
fmt.Fprintf(w, "# HELP %s\n", metricFamily)
fmt.Fprintf(w, "# TYPE %s %s\n", metricFamily, metricType)
}
// NewHistogram creates and returns new histogram in s with the given name. // NewHistogram creates and returns new histogram in s with the given name.
// //
// name must be valid Prometheus-compatible metric with possible labels. // name must be valid Prometheus-compatible metric with possible labels.

View File

@ -23,3 +23,56 @@ func ExampleSet() {
// set_counter 1 // set_counter 1
// set_gauge{foo="bar"} 42 // set_gauge{foo="bar"} 42
} }
func ExampleExposeMetadata() {
metrics.ExposeMetadata(true)
defer metrics.ExposeMetadata(false)
s := metrics.NewSet()
sc := s.NewCounter("set_counter")
sc.Inc()
s.NewGauge(`unused_bytes{foo="bar"}`, func() float64 { return 58 })
s.NewGauge(`used_bytes{foo="bar"}`, func() float64 { return 42 })
s.NewGauge(`used_bytes{foo="baz"}`, func() float64 { return 43 })
h := s.NewHistogram(`request_duration_seconds{path="/foo/bar"}`)
h.Update(1)
h.Update(2)
s.NewSummary("response_size_bytes").Update(1)
// Dump metrics from s.
var bb bytes.Buffer
s.WritePrometheus(&bb)
fmt.Printf("set metrics:\n%s\n", bb.String())
// Output:
// set metrics:
// # HELP request_duration_seconds
// # TYPE request_duration_seconds histogram
// request_duration_seconds_bucket{path="/foo/bar",vmrange="8.799e-01...1.000e+00"} 1
// request_duration_seconds_bucket{path="/foo/bar",vmrange="1.896e+00...2.154e+00"} 1
// request_duration_seconds_sum{path="/foo/bar"} 3
// request_duration_seconds_count{path="/foo/bar"} 2
// # HELP response_size_bytes
// # TYPE response_size_bytes summary
// response_size_bytes_sum 1
// response_size_bytes_count 1
// response_size_bytes{quantile="0.5"} 1
// response_size_bytes{quantile="0.9"} 1
// response_size_bytes{quantile="0.97"} 1
// response_size_bytes{quantile="0.99"} 1
// response_size_bytes{quantile="1"} 1
// # HELP set_counter
// # TYPE set_counter counter
// set_counter 1
// # HELP unused_bytes
// # TYPE unused_bytes gauge
// unused_bytes{foo="bar"} 58
// # HELP used_bytes
// # TYPE used_bytes gauge
// used_bytes{foo="bar"} 42
// used_bytes{foo="baz"} 43
}

View File

@ -119,6 +119,10 @@ func (sm *Summary) marshalTo(prefix string, w io.Writer) {
} }
} }
func (sm *Summary) metricType() string {
return "summary"
}
func splitMetricName(name string) (string, string) { func splitMetricName(name string) (string, string) {
n := strings.IndexByte(name, '{') n := strings.IndexByte(name, '{')
if n < 0 { if n < 0 {
@ -196,6 +200,10 @@ func (qv *quantileValue) marshalTo(prefix string, w io.Writer) {
} }
} }
func (qv *quantileValue) metricType() string {
return "unsupported"
}
func addTag(name, tag string) string { func addTag(name, tag string) string {
if len(name) == 0 || name[len(name)-1] != '}' { if len(name) == 0 || name[len(name)-1] != '}' {
return fmt.Sprintf("%s{%s}", name, tag) return fmt.Sprintf("%s{%s}", name, tag)

View File

@ -1 +0,0 @@
module github.com/valyala/fastrand

View File

@ -1,5 +0,0 @@
module github.com/valyala/histogram
go 1.12
require github.com/valyala/fastrand v1.1.0

View File

@ -1,2 +0,0 @@
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=

5
vendor/modules.txt vendored
View File

@ -1,8 +1,9 @@
# github.com/valyala/fastrand v1.1.0 # github.com/valyala/fastrand v1.1.0
## explicit
github.com/valyala/fastrand github.com/valyala/fastrand
# github.com/valyala/histogram v1.2.0 # github.com/valyala/histogram v1.2.0
## explicit ## explicit; go 1.12
github.com/valyala/histogram github.com/valyala/histogram
# golang.org/x/sys v0.15.0 # golang.org/x/sys v0.15.0
## explicit ## explicit; go 1.18
golang.org/x/sys/windows golang.org/x/sys/windows