diff --git a/README.md b/README.md index e1a2537..b01d81e 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/counter.go b/counter.go index dfe9477..25973a8 100644 --- a/counter.go +++ b/counter.go @@ -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. diff --git a/floatcounter.go b/floatcounter.go index f898790..8bd9fa6 100644 --- a/floatcounter.go +++ b/floatcounter.go @@ -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. diff --git a/gauge.go b/gauge.go index 9084fc4..d40b730 100644 --- a/gauge.go +++ b/gauge.go @@ -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. diff --git a/go.mod b/go.mod index 6a8d5f2..6498324 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go_metrics.go b/go_metrics.go index 1d0c7e2..15c5d34 100644 --- a/go_metrics.go +++ b/go_metrics.go @@ -47,34 +47,34 @@ 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) + 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) - 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) + 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) - fmt.Fprintf(w, "go_cgo_calls_count %d\n", runtime.NumCgoCall()) - fmt.Fprintf(w, "go_cpu_count %d\n", runtime.NumCPU()) + 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[:] { @@ -82,20 +82,25 @@ func writeGoMetrics(w io.Writer) { } 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()) } @@ -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)) } @@ -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 { diff --git a/histogram.go b/histogram.go index a576681..5f0f6b3 100644 --- a/histogram.go +++ b/histogram.go @@ -228,3 +228,7 @@ func (h *Histogram) getSum() float64 { h.mu.Unlock() return sum } + +func (h *Histogram) metricType() string { + return "histogram" +} diff --git a/metrics.go b/metrics.go index e9560b2..8ef1058 100644 --- a/metrics.go +++ b/metrics.go @@ -13,9 +13,12 @@ package metrics import ( + "fmt" "io" "sort" + "strings" "sync" + "sync/atomic" "unsafe" ) @@ -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() @@ -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) +} diff --git a/metrics_test.go b/metrics_test.go index df7f762..7c33dab 100644 --- a/metrics_test.go +++ b/metrics_test.go @@ -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 { diff --git a/process_metrics_linux.go b/process_metrics_linux.go index 48def1c..5c793f8 100644 --- a/process_metrics_linux.go +++ b/process_metrics_linux.go @@ -74,15 +74,15 @@ func writeProcessMetrics(w io.Writer) { utime := float64(p.Utime) / userHZ stime := float64(p.Stime) / userHZ - fmt.Fprintf(w, "process_cpu_seconds_system_total %g\n", stime) - fmt.Fprintf(w, "process_cpu_seconds_total %g\n", utime+stime) - fmt.Fprintf(w, "process_cpu_seconds_user_total %g\n", utime) - fmt.Fprintf(w, "process_major_pagefaults_total %d\n", p.Majflt) - fmt.Fprintf(w, "process_minor_pagefaults_total %d\n", p.Minflt) - fmt.Fprintf(w, "process_num_threads %d\n", p.NumThreads) - fmt.Fprintf(w, "process_resident_memory_bytes %d\n", p.Rss*4096) - fmt.Fprintf(w, "process_start_time_seconds %d\n", startTimeSeconds) - fmt.Fprintf(w, "process_virtual_memory_bytes %d\n", p.Vsize) + WriteCounterFloat64(w, "process_cpu_seconds_system_total", stime) + WriteCounterFloat64(w, "process_cpu_seconds_total", utime+stime) + WriteCounterFloat64(w, "process_cpu_seconds_user_total", utime) + WriteCounterUint64(w, "process_major_pagefaults_total", uint64(p.Majflt)) + WriteCounterUint64(w, "process_minor_pagefaults_total", uint64(p.Minflt)) + WriteGaugeUint64(w, "process_num_threads", uint64(p.NumThreads)) + WriteGaugeUint64(w, "process_resident_memory_bytes", uint64(p.Rss)*4096) + WriteGaugeUint64(w, "process_start_time_seconds", uint64(startTimeSeconds)) + WriteGaugeUint64(w, "process_virtual_memory_bytes", uint64(p.Vsize)) writeProcessMemMetrics(w) writeIOMetrics(w) } @@ -133,12 +133,12 @@ func writeIOMetrics(w io.Writer) { writeBytes = getInt(s) } } - fmt.Fprintf(w, "process_io_read_bytes_total %d\n", rchar) - fmt.Fprintf(w, "process_io_written_bytes_total %d\n", wchar) - fmt.Fprintf(w, "process_io_read_syscalls_total %d\n", syscr) - fmt.Fprintf(w, "process_io_write_syscalls_total %d\n", syscw) - fmt.Fprintf(w, "process_io_storage_read_bytes_total %d\n", readBytes) - fmt.Fprintf(w, "process_io_storage_written_bytes_total %d\n", writeBytes) + WriteGaugeUint64(w, "process_io_read_bytes_total", uint64(rchar)) + WriteGaugeUint64(w, "process_io_written_bytes_total", uint64(wchar)) + WriteGaugeUint64(w, "process_io_read_syscalls_total", uint64(syscr)) + WriteGaugeUint64(w, "process_io_write_syscalls_total", uint64(syscw)) + WriteGaugeUint64(w, "process_io_storage_read_bytes_total", uint64(readBytes)) + WriteGaugeUint64(w, "process_io_storage_written_bytes_total", uint64(writeBytes)) } 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) return } - fmt.Fprintf(w, "process_max_fds %d\n", maxOpenFDs) - fmt.Fprintf(w, "process_open_fds %d\n", totalOpenFDs) + WriteGaugeUint64(w, "process_max_fds", maxOpenFDs) + WriteGaugeUint64(w, "process_open_fds", totalOpenFDs) } 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) return } - fmt.Fprintf(w, "process_virtual_memory_peak_bytes %d\n", ms.vmPeak) - fmt.Fprintf(w, "process_resident_memory_peak_bytes %d\n", ms.rssPeak) - fmt.Fprintf(w, "process_resident_memory_anon_bytes %d\n", ms.rssAnon) - fmt.Fprintf(w, "process_resident_memory_file_bytes %d\n", ms.rssFile) - fmt.Fprintf(w, "process_resident_memory_shared_bytes %d\n", ms.rssShmem) + WriteGaugeUint64(w, "process_virtual_memory_peak_bytes", ms.vmPeak) + WriteGaugeUint64(w, "process_resident_memory_peak_bytes", ms.rssPeak) + WriteGaugeUint64(w, "process_resident_memory_anon_bytes", ms.rssAnon) + WriteGaugeUint64(w, "process_resident_memory_file_bytes", ms.rssFile) + WriteGaugeUint64(w, "process_resident_memory_shared_bytes", ms.rssShmem) } diff --git a/process_metrics_windows.go b/process_metrics_windows.go index e824ada..bda7c82 100644 --- a/process_metrics_windows.go +++ b/process_metrics_windows.go @@ -4,7 +4,6 @@ package metrics import ( - "fmt" "io" "log" "syscall" @@ -55,16 +54,16 @@ func writeProcessMetrics(w io.Writer) { log.Printf("ERROR: metrics: cannot read process memory information: %s", err) return } - stimeSeconds := (uint64(stime.HighDateTime)<<32 + uint64(stime.LowDateTime)) / 1e7 - utimeSeconds := (uint64(utime.HighDateTime)<<32 + uint64(utime.LowDateTime)) / 1e7 - fmt.Fprintf(w, "process_cpu_seconds_system_total %d\n", stimeSeconds) - fmt.Fprintf(w, "process_cpu_seconds_total %d\n", stimeSeconds+utimeSeconds) - fmt.Fprintf(w, "process_cpu_seconds_user_total %d\n", stimeSeconds) - fmt.Fprintf(w, "process_pagefaults_total %d\n", mc.PageFaultCount) - fmt.Fprintf(w, "process_start_time_seconds %d\n", startTime.Nanoseconds()/1e9) - fmt.Fprintf(w, "process_virtual_memory_bytes %d\n", mc.PrivateUsage) - fmt.Fprintf(w, "process_resident_memory_peak_bytes %d\n", mc.PeakWorkingSetSize) - fmt.Fprintf(w, "process_resident_memory_bytes %d\n", mc.WorkingSetSize) + stimeSeconds := float64(uint64(stime.HighDateTime)<<32+uint64(stime.LowDateTime)) / 1e7 + utimeSeconds := float64(uint64(utime.HighDateTime)<<32+uint64(utime.LowDateTime)) / 1e7 + WriteCounterFloat64(w, "process_cpu_seconds_system_total", stimeSeconds) + WriteCounterFloat64(w, "process_cpu_seconds_total", stimeSeconds+utimeSeconds) + WriteCounterFloat64(w, "process_cpu_seconds_user_total", stimeSeconds) + WriteCounterUint64(w, "process_pagefaults_total", uint64(mc.PageFaultCount)) + WriteGaugeUint64(w, "process_start_time_seconds", uint64(startTime.Nanoseconds())/1e9) + WriteGaugeUint64(w, "process_virtual_memory_bytes", uint64(mc.PrivateUsage)) + WriteGaugeUint64(w, "process_resident_memory_peak_bytes", uint64(mc.PeakWorkingSetSize)) + WriteGaugeUint64(w, "process_resident_memory_bytes", uint64(mc.WorkingSetSize)) } 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 // 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) - fmt.Fprintf(w, "process_open_fds %d\n", count) + WriteGaugeUint64(w, "process_max_fds", 16777216) + WriteGaugeUint64(w, "process_open_fds", uint64(count)) } diff --git a/set.go b/set.go index 79355ea..338e2c0 100644 --- a/set.go +++ b/set.go @@ -47,14 +47,30 @@ func (s *Set) WritePrometheus(w io.Writer) { sa := append([]*namedMetric(nil), s.a...) s.mu.Unlock() - // 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. + 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 + // can call a callback, which, in turn, can try calling s.mu.Lock again. nm.metric.marshalTo(nm.name, &bb) } 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. // // name must be valid Prometheus-compatible metric with possible labels. diff --git a/set_example_test.go b/set_example_test.go index 50845d3..17c1b36 100644 --- a/set_example_test.go +++ b/set_example_test.go @@ -23,3 +23,56 @@ func ExampleSet() { // set_counter 1 // 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 +} diff --git a/summary.go b/summary.go index 52183d2..057b67b 100644 --- a/summary.go +++ b/summary.go @@ -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) { n := strings.IndexByte(name, '{') 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 { if len(name) == 0 || name[len(name)-1] != '}' { return fmt.Sprintf("%s{%s}", name, tag) diff --git a/vendor/github.com/valyala/fastrand/go.mod b/vendor/github.com/valyala/fastrand/go.mod deleted file mode 100644 index 958910b..0000000 --- a/vendor/github.com/valyala/fastrand/go.mod +++ /dev/null @@ -1 +0,0 @@ -module github.com/valyala/fastrand diff --git a/vendor/github.com/valyala/histogram/go.mod b/vendor/github.com/valyala/histogram/go.mod deleted file mode 100644 index cc65b00..0000000 --- a/vendor/github.com/valyala/histogram/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/valyala/histogram - -go 1.12 - -require github.com/valyala/fastrand v1.1.0 diff --git a/vendor/github.com/valyala/histogram/go.sum b/vendor/github.com/valyala/histogram/go.sum deleted file mode 100644 index c5ca588..0000000 --- a/vendor/github.com/valyala/histogram/go.sum +++ /dev/null @@ -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= diff --git a/vendor/modules.txt b/vendor/modules.txt index f8aabff..682dbd5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,8 +1,9 @@ # github.com/valyala/fastrand v1.1.0 +## explicit github.com/valyala/fastrand # github.com/valyala/histogram v1.2.0 -## explicit +## explicit; go 1.12 github.com/valyala/histogram # golang.org/x/sys v0.15.0 -## explicit +## explicit; go 1.18 golang.org/x/sys/windows