2019-07-12 17:18:52 +03:00
package metrics
import (
"fmt"
"io"
2023-12-17 16:25:33 +02:00
"log"
2023-11-29 22:46:41 +01:00
"math"
2019-07-12 17:18:52 +03:00
"runtime"
2023-11-29 22:46:41 +01:00
runtimemetrics "runtime/metrics"
2023-11-30 01:47:03 +02:00
"strings"
2019-07-12 17:18:52 +03:00
"github.com/valyala/histogram"
)
2023-11-29 22:46:41 +01:00
// See https://pkg.go.dev/runtime/metrics#hdr-Supported_metrics
var runtimeMetrics = [ ] [ 2 ] string {
{ "/sched/latencies:seconds" , "go_sched_latencies_seconds" } ,
{ "/sync/mutex/wait/total:seconds" , "go_mutex_wait_seconds_total" } ,
{ "/cpu/classes/gc/mark/assist:cpu-seconds" , "go_gc_mark_assist_cpu_seconds_total" } ,
{ "/cpu/classes/gc/total:cpu-seconds" , "go_gc_cpu_seconds_total" } ,
2023-11-30 00:56:43 +02:00
{ "/gc/pauses:seconds" , "go_gc_pauses_seconds" } ,
2023-11-29 22:46:41 +01:00
{ "/cpu/classes/scavenge/total:cpu-seconds" , "go_scavenge_cpu_seconds_total" } ,
{ "/gc/gomemlimit:bytes" , "go_memlimit_bytes" } ,
}
2023-12-17 16:25:33 +02:00
var supportedRuntimeMetrics = initSupportedRuntimeMetrics ( runtimeMetrics )
func initSupportedRuntimeMetrics ( rms [ ] [ 2 ] string ) [ ] [ 2 ] string {
exposedMetrics := make ( map [ string ] struct { } )
for _ , d := range runtimemetrics . All ( ) {
exposedMetrics [ d . Name ] = struct { } { }
}
var supportedMetrics [ ] [ 2 ] string
for _ , rm := range rms {
metricName := rm [ 0 ]
if _ , ok := exposedMetrics [ metricName ] ; ok {
supportedMetrics = append ( supportedMetrics , rm )
} else {
log . Printf ( "github.com/VictoriaMetrics/metrics: do not expose %s metric, since the corresponding metric %s isn't supported in the current Go runtime" , rm [ 1 ] , metricName )
}
}
return supportedMetrics
}
2019-07-12 17:18:52 +03:00
func writeGoMetrics ( w io . Writer ) {
2023-11-29 22:46:41 +01:00
writeRuntimeMetrics ( w )
2019-07-12 17:18:52 +03:00
var ms runtime . MemStats
runtime . ReadMemStats ( & ms )
2023-12-19 01:36:54 +01:00
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 ( ) ) )
2019-07-12 17:18:52 +03:00
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 ) )
2023-12-19 03:07:21 +02:00
WriteMetadataIfNeeded ( w , "go_gc_duration_seconds" , "summary" )
2019-07-12 17:18:52 +03:00
for i , q := range gcPauses . Quantiles ( quantiles [ : 0 ] , phis ) {
fmt . Fprintf ( w , ` go_gc_duration_seconds { quantile="%g"} %g ` + "\n" , phis [ i ] , q )
}
2023-12-19 01:36:54 +01:00
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 )
2019-07-12 17:18:52 +03:00
2023-12-19 01:36:54 +01:00
WriteCounterUint64 ( w , "go_gc_forced_count" , uint64 ( ms . NumForcedGC ) )
WriteGaugeUint64 ( w , "go_gomaxprocs" , uint64 ( runtime . GOMAXPROCS ( 0 ) ) )
WriteGaugeUint64 ( w , "go_goroutines" , uint64 ( runtime . NumGoroutine ( ) ) )
2019-07-12 17:18:52 +03:00
numThread , _ := runtime . ThreadCreateProfile ( nil )
2023-12-19 01:36:54 +01:00
WriteGaugeUint64 ( w , "go_threads" , uint64 ( numThread ) )
2019-07-12 17:18:52 +03:00
// Export build details.
2023-12-19 03:07:21 +02:00
WriteMetadataIfNeeded ( w , "go_info" , "gauge" )
2019-07-12 17:18:52 +03:00
fmt . Fprintf ( w , "go_info{version=%q} 1\n" , runtime . Version ( ) )
2023-12-19 01:36:54 +01:00
2023-12-19 03:07:21 +02:00
WriteMetadataIfNeeded ( w , "go_info_ext" , "gauge" )
2019-07-12 17:18:52 +03:00
fmt . Fprintf ( w , "go_info_ext{compiler=%q, GOARCH=%q, GOOS=%q, GOROOT=%q} 1\n" ,
runtime . Compiler , runtime . GOARCH , runtime . GOOS , runtime . GOROOT ( ) )
}
2023-11-29 22:46:41 +01:00
func writeRuntimeMetrics ( w io . Writer ) {
2023-12-17 16:25:33 +02:00
samples := make ( [ ] runtimemetrics . Sample , len ( supportedRuntimeMetrics ) )
for i , rm := range supportedRuntimeMetrics {
2023-11-29 22:46:41 +01:00
samples [ i ] . Name = rm [ 0 ]
}
runtimemetrics . Read ( samples )
2023-12-17 16:25:33 +02:00
for i , rm := range supportedRuntimeMetrics {
2023-11-29 22:46:41 +01:00
writeRuntimeMetric ( w , rm [ 1 ] , & samples [ i ] )
}
}
func writeRuntimeMetric ( w io . Writer , name string , sample * runtimemetrics . Sample ) {
2023-12-17 16:25:33 +02:00
kind := sample . Value . Kind ( )
switch kind {
2023-11-29 22:46:41 +01:00
case runtimemetrics . KindBad :
panic ( fmt . Errorf ( "BUG: unexpected runtimemetrics.KindBad for sample.Name=%q" , sample . Name ) )
case runtimemetrics . KindUint64 :
2023-12-19 01:36:54 +01:00
v := sample . Value . Uint64 ( )
if strings . HasSuffix ( name , "_total" ) {
WriteCounterUint64 ( w , name , v )
} else {
WriteGaugeUint64 ( w , name , v )
}
2023-11-29 22:46:41 +01:00
case runtimemetrics . KindFloat64 :
2023-12-19 01:36:54 +01:00
v := sample . Value . Float64 ( )
if isCounterName ( name ) {
WriteCounterFloat64 ( w , name , v )
} else {
WriteGaugeFloat64 ( w , name , v )
}
2023-11-29 22:46:41 +01:00
case runtimemetrics . KindFloat64Histogram :
2023-12-19 01:36:54 +01:00
h := sample . Value . Float64Histogram ( )
writeRuntimeHistogramMetric ( w , name , h )
2023-12-17 16:25:33 +02:00
default :
panic ( fmt . Errorf ( "unexpected metric kind=%d" , kind ) )
2023-11-29 22:46:41 +01:00
}
}
func writeRuntimeHistogramMetric ( w io . Writer , name string , h * runtimemetrics . Float64Histogram ) {
buckets := h . Buckets
2023-11-30 00:56:43 +02:00
counts := h . Counts
2023-11-30 01:47:03 +02:00
if len ( buckets ) != len ( counts ) + 1 {
panic ( fmt . Errorf ( "the number of buckets must be bigger than the number of counts by 1 in histogram %s; got buckets=%d, counts=%d" , name , len ( buckets ) , len ( counts ) ) )
}
tailCount := uint64 ( 0 )
if strings . HasSuffix ( name , "_seconds" ) {
// Limit the maximum bucket to 1 second, since Go runtime exposes buckets with 10K seconds,
// which have little sense. At the same time such buckets may lead to high cardinality issues
// at the scraper side.
for len ( buckets ) > 0 && buckets [ len ( buckets ) - 1 ] > 1 {
buckets = buckets [ : len ( buckets ) - 1 ]
tailCount += counts [ len ( counts ) - 1 ]
counts = counts [ : len ( counts ) - 1 ]
}
}
iStep := float64 ( len ( buckets ) ) / maxRuntimeHistogramBuckets
2023-11-30 00:56:43 +02:00
totalCount := uint64 ( 0 )
2023-11-30 01:47:03 +02:00
iNext := 0.0
2023-12-19 03:07:21 +02:00
WriteMetadataIfNeeded ( w , name , "histogram" )
2023-11-30 01:47:03 +02:00
for i , count := range counts {
2023-11-30 00:56:43 +02:00
totalCount += count
2023-11-30 01:47:03 +02:00
if float64 ( i ) >= iNext {
iNext += iStep
le := buckets [ i + 1 ]
if ! math . IsInf ( le , 1 ) {
fmt . Fprintf ( w , ` %s_bucket { le="%g"} %d ` + "\n" , name , le , totalCount )
2023-11-30 00:56:43 +02:00
}
}
2023-11-29 22:46:41 +01:00
}
2023-11-30 01:47:03 +02:00
totalCount += tailCount
fmt . Fprintf ( w , ` %s_bucket { le="+Inf"} %d ` + "\n" , name , totalCount )
2023-11-29 22:46:41 +01:00
}
2023-11-30 01:47:03 +02:00
// Limit the number of buckets for Go runtime histograms in order to prevent from high cardinality issues at scraper side.
const maxRuntimeHistogramBuckets = 30