diff --git a/cmd/daze/main.go b/cmd/daze/main.go index d6e5da5..a02f440 100644 --- a/cmd/daze/main.go +++ b/cmd/daze/main.go @@ -1,8 +1,6 @@ package main import ( - "encoding/json" - "expvar" "flag" "fmt" "log" @@ -16,6 +14,7 @@ import ( "github.com/libraries/daze" "github.com/libraries/daze/lib/doa" + "github.com/libraries/daze/lib/expvpp" "github.com/libraries/daze/lib/gracefulexit" "github.com/libraries/daze/lib/rate" "github.com/libraries/daze/protocol/ashe" @@ -63,31 +62,6 @@ func main() { if os.Getenv("ANDROID_ROOT") != "" { net.DefaultResolver = daze.ResolverDns(daze.ResolverPublic.Cloudflare.Dns) } - // Remove cmdline and memstats from expvar default exports. - // See: https://github.com/golang/go/issues/29105 - muxHttp := http.NewServeMux() - muxHttp.HandleFunc("/debug/vars", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - vars := new(expvar.Map).Init() - expvar.Do(func(kv expvar.KeyValue) { - vars.Set(kv.Key, kv.Value) - }) - vars.Delete("cmdline") - vars.Delete("memstats") - msg := map[string]any{} - err := json.Unmarshal([]byte(vars.String()), &msg) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - enc := json.NewEncoder(w) - enc.SetIndent("", " ") - enc.Encode(msg) - }) - muxHttp.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - http.DefaultServeMux.ServeHTTP(w, r) - }) resExec := filepath.Dir(doa.Try(os.Executable())) subCommand := os.Args[1] os.Args = os.Args[1:len(os.Args)] @@ -148,7 +122,7 @@ func main() { if *flGpprof != "" { _ = pprof.Handler log.Println("main: listen net/http/pprof on", *flGpprof) - go func() { doa.Nil(http.ListenAndServe(*flGpprof, muxHttp)) }() + go func() { doa.Nil(http.ListenAndServe(*flGpprof, expvpp.ServeMux())) }() } // Hang prevent program from exiting. gracefulexit.Wait() @@ -226,7 +200,7 @@ func main() { if *flGpprof != "" { _ = pprof.Handler log.Println("main: listen net/http/pprof on", *flGpprof) - go func() { doa.Nil(http.ListenAndServe(*flGpprof, muxHttp)) }() + go func() { doa.Nil(http.ListenAndServe(*flGpprof, expvpp.ServeMux())) }() } // Hang prevent program from exiting. gracefulexit.Wait() diff --git a/daze.go b/daze.go index 1d12411..50d8efb 100644 --- a/daze.go +++ b/daze.go @@ -28,6 +28,7 @@ import ( "time" "github.com/libraries/daze/lib/doa" + "github.com/libraries/daze/lib/expvpp" "github.com/libraries/daze/lib/lru" "github.com/libraries/daze/lib/pretty" "github.com/libraries/daze/lib/rate" @@ -66,15 +67,15 @@ var Conf = struct { var Expv = struct { RouterCacheCall *expvar.Int RouterCacheHits *expvar.Int - RouterCacheRate expvar.Func + RouterCacheRate *expvar.Func RouterIPNetCall *expvar.Int - RouterIPNetTime *ExpvarAverage + RouterIPNetTime *expvpp.ExpvarAverage }{ RouterCacheCall: expvar.NewInt("RouterCache.Call"), RouterCacheHits: expvar.NewInt("RouterCache.Hits"), - RouterCacheRate: NewExpvarPercent("RouterCache.Rate", "RouterCache.Hits", "RouterCache.Call"), + RouterCacheRate: expvpp.NewExpvarPercent("RouterCache.Rate", "RouterCache.Hits", "RouterCache.Call"), RouterIPNetCall: expvar.NewInt("RouterIPNet.Call"), - RouterIPNetTime: NewExpvarAverage("RouterIPNet.Time", 64), + RouterIPNetTime: expvpp.NewExpvarAverage("RouterIPNet.Time", 64), } // ResolverDns returns a DNS resolver. @@ -1059,37 +1060,6 @@ func Dial(network string, address string) (net.Conn, error) { return d.Dial(network, address) } -// ExpvarAverage is a structure to maintain a running average using expvar.Float. -type ExpvarAverage struct { - F *expvar.Float - L float64 -} - -// Adds a new value to the running average. This is not strictly concurrency-safe, but it won't have much impact on the -// data. -func (e *ExpvarAverage) Add(value float64) { - e.F.Add((value - e.F.Value()) / e.L) -} - -// NewExpvarAverage creates and initializes a new ExpvarAverage instance. -func NewExpvarAverage(name string, length int) *ExpvarAverage { - return &ExpvarAverage{ - F: expvar.NewFloat(name), - L: float64(length), - } -} - -// NewExpvarPercent creates a new expvar.Func that calculates the ratio of two expvar.Int or expvar.Float metrics. -func NewExpvarPercent(name string, n string, d string) expvar.Func { - f := expvar.Func(func() any { - v := doa.Try(strconv.ParseFloat(expvar.Get(n).String(), 64)) - w := doa.Try(strconv.ParseFloat(expvar.Get(d).String(), 64)) - return float64(v) / float64(max(1, w)) - }) - expvar.Publish(name, f) - return f -} - // GravityReader wraps an io.Reader with RC4 crypto. func GravityReader(r io.Reader, k []byte) io.Reader { cr := doa.Try(rc4.NewCipher(k)) diff --git a/lib/expvpp/expvpp.go b/lib/expvpp/expvpp.go new file mode 100644 index 0000000..05e9cf8 --- /dev/null +++ b/lib/expvpp/expvpp.go @@ -0,0 +1,68 @@ +package expvpp + +import ( + "encoding/json" + "expvar" + "net/http" + "strconv" +) + +// ExpvarAverage is a structure to maintain a running average using expvar.Float. +type ExpvarAverage struct { + F *expvar.Float + L float64 +} + +// Adds a new value to the running average. This is not strictly concurrency-safe, but it won't have much impact on the +// data. +func (e *ExpvarAverage) Add(value float64) { + e.F.Add((value - e.F.Value()) / e.L) +} + +// NewExpvarAverage creates and initializes a new ExpvarAverage instance. +func NewExpvarAverage(name string, length int) *ExpvarAverage { + return &ExpvarAverage{ + F: expvar.NewFloat(name), + L: float64(length), + } +} + +// NewExpvarPercent creates a new expvar.Func that calculates the ratio of two expvar.Int or expvar.Float metrics. +func NewExpvarPercent(name string, n string, d string) *expvar.Func { + f := expvar.Func(func() any { + v, _ := strconv.ParseFloat(expvar.Get(n).String(), 64) + w, _ := strconv.ParseFloat(expvar.Get(d).String(), 64) + return float64(v) / float64(max(1, w)) + }) + expvar.Publish(name, f) + return &f +} + +// ServeMux returns a new http.ServeMux that removes cmdline and memstats from expvar default exports. +// See: https://github.com/golang/go/issues/29105 +func ServeMux() *http.ServeMux { + mux := http.NewServeMux() + mux.HandleFunc("/debug/vars", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + vars := new(expvar.Map).Init() + expvar.Do(func(kv expvar.KeyValue) { + vars.Set(kv.Key, kv.Value) + }) + vars.Delete("cmdline") + vars.Delete("memstats") + msg := map[string]any{} + err := json.Unmarshal([]byte(vars.String()), &msg) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + enc.Encode(msg) + }) + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.DefaultServeMux.ServeHTTP(w, r) + }) + return mux +} diff --git a/protocol/ashe/engine.go b/protocol/ashe/engine.go index baebb87..e782285 100644 --- a/protocol/ashe/engine.go +++ b/protocol/ashe/engine.go @@ -12,6 +12,7 @@ import ( "github.com/libraries/daze" "github.com/libraries/daze/lib/doa" + "github.com/libraries/daze/lib/expvpp" "github.com/libraries/daze/lib/rate" ) @@ -56,9 +57,9 @@ var Conf = struct { // Expv is a simple wrapper around the expvars package. var Expv = struct { - ServerClockSkew *daze.ExpvarAverage + ServerClockSkew *expvpp.ExpvarAverage }{ - ServerClockSkew: daze.NewExpvarAverage("Protocol.Ashe.Server.ClockSkew", 64), + ServerClockSkew: expvpp.NewExpvarAverage("Protocol.Ashe.Server.ClockSkew", 64), } // TCPConn is an implementation of the Conn interface for tcp network connections.