mirror of
https://github.com/tailscale/tailscale.git
synced 2025-05-25 16:58:30 +00:00
tsweb/varz: export GC CPU fraction gauge
We were missing this metric, but it can be important for some workloads. Varz memstats output allocation cost reduced from 30 allocs per invocation to 1 alloc per invocation. Updates tailscale/corp#28033 Signed-off-by: James Tucker <james@tailscale.com> Co-authored-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
189e03e741
commit
b95e8bf4a1
@ -199,7 +199,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||||||
golang.org/x/crypto/nacl/box from tailscale.com/types/key
|
golang.org/x/crypto/nacl/box from tailscale.com/types/key
|
||||||
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
||||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||||
W golang.org/x/exp/constraints from tailscale.com/util/winutil
|
golang.org/x/exp/constraints from tailscale.com/util/winutil+
|
||||||
golang.org/x/exp/maps from tailscale.com/util/syspolicy/setting+
|
golang.org/x/exp/maps from tailscale.com/util/syspolicy/setting+
|
||||||
L golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
L golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
||||||
golang.org/x/net/dns/dnsmessage from net+
|
golang.org/x/net/dns/dnsmessage from net+
|
||||||
|
@ -65,7 +65,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
|||||||
tailscale.com/types/ipproto from tailscale.com/tailcfg
|
tailscale.com/types/ipproto from tailscale.com/tailcfg
|
||||||
tailscale.com/types/key from tailscale.com/tailcfg
|
tailscale.com/types/key from tailscale.com/tailcfg
|
||||||
tailscale.com/types/lazy from tailscale.com/version+
|
tailscale.com/types/lazy from tailscale.com/version+
|
||||||
tailscale.com/types/logger from tailscale.com/tsweb
|
tailscale.com/types/logger from tailscale.com/tsweb+
|
||||||
tailscale.com/types/opt from tailscale.com/envknob+
|
tailscale.com/types/opt from tailscale.com/envknob+
|
||||||
tailscale.com/types/ptr from tailscale.com/tailcfg+
|
tailscale.com/types/ptr from tailscale.com/tailcfg+
|
||||||
tailscale.com/types/result from tailscale.com/util/lineiter
|
tailscale.com/types/result from tailscale.com/util/lineiter
|
||||||
@ -95,6 +95,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
|||||||
golang.org/x/crypto/nacl/box from tailscale.com/types/key
|
golang.org/x/crypto/nacl/box from tailscale.com/types/key
|
||||||
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
||||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||||
|
golang.org/x/exp/constraints from tailscale.com/tsweb/varz
|
||||||
golang.org/x/net/dns/dnsmessage from net+
|
golang.org/x/net/dns/dnsmessage from net+
|
||||||
golang.org/x/net/http/httpguts from net/http
|
golang.org/x/net/http/httpguts from net/http
|
||||||
golang.org/x/net/http/httpproxy from net/http
|
golang.org/x/net/http/httpproxy from net/http
|
||||||
|
@ -211,7 +211,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
||||||
golang.org/x/crypto/pbkdf2 from software.sslmate.com/src/go-pkcs12
|
golang.org/x/crypto/pbkdf2 from software.sslmate.com/src/go-pkcs12
|
||||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||||
W golang.org/x/exp/constraints from github.com/dblohm7/wingoes/pe+
|
golang.org/x/exp/constraints from github.com/dblohm7/wingoes/pe+
|
||||||
golang.org/x/exp/maps from tailscale.com/util/syspolicy/internal/metrics+
|
golang.org/x/exp/maps from tailscale.com/util/syspolicy/internal/metrics+
|
||||||
golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
||||||
golang.org/x/net/dns/dnsmessage from net+
|
golang.org/x/net/dns/dnsmessage from net+
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package varz
|
package varz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"cmp"
|
"cmp"
|
||||||
"expvar"
|
"expvar"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -13,13 +14,16 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
"tailscale.com/metrics"
|
"tailscale.com/metrics"
|
||||||
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/version"
|
"tailscale.com/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -316,21 +320,52 @@ type PrometheusMetricsReflectRooter interface {
|
|||||||
|
|
||||||
var expvarDo = expvar.Do // pulled out for tests
|
var expvarDo = expvar.Do // pulled out for tests
|
||||||
|
|
||||||
func writeMemstats(w io.Writer, ms *runtime.MemStats) {
|
func writeMemstat[V constraints.Integer | constraints.Float](bw *bufio.Writer, typ, name string, v V, help string) {
|
||||||
out := func(name, typ string, v uint64, help string) {
|
|
||||||
if help != "" {
|
if help != "" {
|
||||||
fmt.Fprintf(w, "# HELP memstats_%s %s\n", name, help)
|
bw.WriteString("# HELP memstats_")
|
||||||
|
bw.WriteString(name)
|
||||||
|
bw.WriteString(" ")
|
||||||
|
bw.WriteString(help)
|
||||||
|
bw.WriteByte('\n')
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "# TYPE memstats_%s %s\nmemstats_%s %v\n", name, typ, name, v)
|
bw.WriteString("# TYPE memstats_")
|
||||||
|
bw.WriteString(name)
|
||||||
|
bw.WriteString(" ")
|
||||||
|
bw.WriteString(typ)
|
||||||
|
bw.WriteByte('\n')
|
||||||
|
bw.WriteString("memstats_")
|
||||||
|
bw.WriteString(name)
|
||||||
|
bw.WriteByte(' ')
|
||||||
|
rt := reflect.TypeOf(v)
|
||||||
|
switch {
|
||||||
|
case rt == reflect.TypeFor[int]() ||
|
||||||
|
rt == reflect.TypeFor[uint]() ||
|
||||||
|
rt == reflect.TypeFor[int8]() ||
|
||||||
|
rt == reflect.TypeFor[uint8]() ||
|
||||||
|
rt == reflect.TypeFor[int16]() ||
|
||||||
|
rt == reflect.TypeFor[uint16]() ||
|
||||||
|
rt == reflect.TypeFor[int32]() ||
|
||||||
|
rt == reflect.TypeFor[uint32]() ||
|
||||||
|
rt == reflect.TypeFor[int64]() ||
|
||||||
|
rt == reflect.TypeFor[uint64]() ||
|
||||||
|
rt == reflect.TypeFor[uintptr]():
|
||||||
|
bw.Write(strconv.AppendInt(bw.AvailableBuffer(), int64(v), 10))
|
||||||
|
case rt == reflect.TypeFor[float32]() || rt == reflect.TypeFor[float64]():
|
||||||
|
bw.Write(strconv.AppendFloat(bw.AvailableBuffer(), float64(v), 'f', -1, 64))
|
||||||
}
|
}
|
||||||
g := func(name string, v uint64, help string) { out(name, "gauge", v, help) }
|
bw.WriteByte('\n')
|
||||||
c := func(name string, v uint64, help string) { out(name, "counter", v, help) }
|
}
|
||||||
g("heap_alloc", ms.HeapAlloc, "current bytes of allocated heap objects (up/down smoothly)")
|
|
||||||
c("total_alloc", ms.TotalAlloc, "cumulative bytes allocated for heap objects")
|
func writeMemstats(w io.Writer, ms *runtime.MemStats) {
|
||||||
g("sys", ms.Sys, "total bytes of memory obtained from the OS")
|
fmt.Fprintf(w, "%v", logger.ArgWriter(func(bw *bufio.Writer) {
|
||||||
c("mallocs", ms.Mallocs, "cumulative count of heap objects allocated")
|
writeMemstat(bw, "gauge", "heap_alloc", ms.HeapAlloc, "current bytes of allocated heap objects (up/down smoothly)")
|
||||||
c("frees", ms.Frees, "cumulative count of heap objects freed")
|
writeMemstat(bw, "counter", "total_alloc", ms.TotalAlloc, "cumulative bytes allocated for heap objects")
|
||||||
c("num_gc", uint64(ms.NumGC), "number of completed GC cycles")
|
writeMemstat(bw, "gauge", "sys", ms.Sys, "total bytes of memory obtained from the OS")
|
||||||
|
writeMemstat(bw, "counter", "mallocs", ms.Mallocs, "cumulative count of heap objects allocated")
|
||||||
|
writeMemstat(bw, "counter", "frees", ms.Frees, "cumulative count of heap objects freed")
|
||||||
|
writeMemstat(bw, "counter", "num_gc", ms.NumGC, "number of completed GC cycles")
|
||||||
|
writeMemstat(bw, "gauge", "gc_cpu_fraction", ms.GCCPUFraction, "fraction of CPU time used by GC")
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// sortedStructField is metadata about a struct field used both for sorting once
|
// sortedStructField is metadata about a struct field used both for sorting once
|
||||||
|
@ -4,14 +4,17 @@
|
|||||||
package varz
|
package varz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"expvar"
|
"expvar"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"tailscale.com/metrics"
|
"tailscale.com/metrics"
|
||||||
"tailscale.com/tstest"
|
"tailscale.com/tstest"
|
||||||
|
"tailscale.com/util/racebuild"
|
||||||
"tailscale.com/version"
|
"tailscale.com/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -418,3 +421,75 @@ func TestVarzHandlerSorting(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWriteMemestats(t *testing.T) {
|
||||||
|
memstats := &runtime.MemStats{
|
||||||
|
Alloc: 1,
|
||||||
|
TotalAlloc: 2,
|
||||||
|
Sys: 3,
|
||||||
|
Lookups: 4,
|
||||||
|
Mallocs: 5,
|
||||||
|
Frees: 6,
|
||||||
|
HeapAlloc: 7,
|
||||||
|
HeapSys: 8,
|
||||||
|
HeapIdle: 9,
|
||||||
|
HeapInuse: 10,
|
||||||
|
HeapReleased: 11,
|
||||||
|
HeapObjects: 12,
|
||||||
|
StackInuse: 13,
|
||||||
|
StackSys: 14,
|
||||||
|
MSpanInuse: 15,
|
||||||
|
MSpanSys: 16,
|
||||||
|
MCacheInuse: 17,
|
||||||
|
MCacheSys: 18,
|
||||||
|
BuckHashSys: 19,
|
||||||
|
GCSys: 20,
|
||||||
|
OtherSys: 21,
|
||||||
|
NextGC: 22,
|
||||||
|
LastGC: 23,
|
||||||
|
PauseTotalNs: 24,
|
||||||
|
// PauseNs: [256]int64{},
|
||||||
|
NumGC: 26,
|
||||||
|
NumForcedGC: 27,
|
||||||
|
GCCPUFraction: 0.28,
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writeMemstats(&buf, memstats)
|
||||||
|
lines := strings.Split(buf.String(), "\n")
|
||||||
|
|
||||||
|
checkFor := func(name, typ, value string) {
|
||||||
|
var foundType, foundValue bool
|
||||||
|
for _, line := range lines {
|
||||||
|
if line == "memstats_"+name+" "+value {
|
||||||
|
foundValue = true
|
||||||
|
}
|
||||||
|
if line == "# TYPE memstats_"+name+" "+typ {
|
||||||
|
foundType = true
|
||||||
|
}
|
||||||
|
if foundValue && foundType {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Errorf("memstats_%s foundType=%v foundValue=%v", name, foundType, foundValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("memstats:\n %s", buf.String())
|
||||||
|
|
||||||
|
checkFor("heap_alloc", "gauge", "7")
|
||||||
|
checkFor("total_alloc", "counter", "2")
|
||||||
|
checkFor("sys", "gauge", "3")
|
||||||
|
checkFor("mallocs", "counter", "5")
|
||||||
|
checkFor("frees", "counter", "6")
|
||||||
|
checkFor("num_gc", "counter", "26")
|
||||||
|
checkFor("gc_cpu_fraction", "gauge", "0.28")
|
||||||
|
|
||||||
|
if !racebuild.On {
|
||||||
|
if allocs := testing.AllocsPerRun(1000, func() {
|
||||||
|
buf.Reset()
|
||||||
|
writeMemstats(&buf, memstats)
|
||||||
|
}); allocs != 1 {
|
||||||
|
t.Errorf("allocs = %v; want max %v", allocs, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user