Skip to content

Commit

Permalink
add statsview
Browse files Browse the repository at this point in the history
  • Loading branch information
smallnest committed Nov 19, 2023
1 parent 4c02982 commit 3cda99a
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- support io_uring
- add CacheDiscovery
- add Oneshot method for XClient
- add statsview: http://xxx.xxx.xxx.xxx:xxxx/debug/statsview


## 1.8.0
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/apache/thrift v0.18.1
github.com/edwingeng/doublejump v1.0.1
github.com/fatih/color v1.14.1
github.com/go-echarts/go-echarts/v2 v2.3.2
github.com/go-ping/ping v1.1.0
github.com/go-redis/redis/v8 v8.11.5
github.com/go-redis/redis_rate/v9 v9.1.2
Expand All @@ -30,6 +31,7 @@ require (
github.com/rs/cors v1.8.3
github.com/rubyist/circuitbreaker v2.2.1+incompatible
github.com/smallnest/quick v0.1.0
github.com/smallnest/statsview v0.0.0-20231119053547-3d59443f9ae3
github.com/soheilhy/cmux v0.1.5
github.com/stretchr/testify v1.7.2
github.com/tinylib/msgp v1.1.8
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-echarts/go-echarts/v2 v2.3.2 h1:imRxqF5sLtEPBsv5HGwz9KklNuwCo0fTITZ31mrgfzo=
github.com/go-echarts/go-echarts/v2 v2.3.2/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
Expand Down Expand Up @@ -263,6 +265,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smallnest/quick v0.1.0 h1:7/a3mvWjBNSKpcwmuiizTi5Alcn7xRkHNNuKgRda1V8=
github.com/smallnest/quick v0.1.0/go.mod h1:mjmFVnOUd/Ruq8Gb7wxFjweGsrVILFsKrcwLz4QyX3g=
github.com/smallnest/statsview v0.0.0-20231119053547-3d59443f9ae3 h1:N7tssEnlhZlAEGQH+79XGYwrI1vuk5iVEdPiROKsQS4=
github.com/smallnest/statsview v0.0.0-20231119053547-3d59443f9ae3/go.mod h1:BMgxQ/U/wQ/mJY8WyPxIG+e3cx7HKbKSZL+DniC2tvc=
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
75 changes: 74 additions & 1 deletion server/gateway.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package server

import (
"bufio"
"context"
"errors"
"io"
Expand Down Expand Up @@ -38,8 +39,19 @@ func (s *Server) startGateway(network string, ln net.Listener) net.Listener {
go s.startJSONRPC2(jsonrpc2Ln)
}

if s.EnableProfile {
// debugLn := m.Match(http1Path("/debug/"))
debugLn := m.Match(cmux.HTTP1Fast())
vm := NewViewManager(debugLn)
go func() {
if err := vm.Start(); err != nil {
log.Errorf("start view manager failed: %v", err)
}
}()
}

if !s.DisableHTTPGateway {
httpLn := m.Match(cmux.HTTP1Fast())
httpLn := m.Match(cmux.HTTP1Fast()) // X-RPCX-MessageID
go s.startHTTP1APIGateway(httpLn)
}

Expand All @@ -48,6 +60,67 @@ func (s *Server) startGateway(network string, ln net.Listener) net.Listener {
return rpcxLn
}

func http1Path(prefix string) cmux.Matcher {
return func(r io.Reader) bool {
return matchHTTP1Field(r, prefix, func(gotValue string) bool {
br := bufio.NewReader(&io.LimitedReader{R: r, N: 1024})
l, part, err := br.ReadLine()
if err != nil || part {
return false
}

_, uri, _, ok := parseRequestLine(string(l))
if !ok {
return false
}

if strings.HasPrefix(uri, prefix) {
return true
}

u, err := url.Parse(uri)
if err != nil {
return false
}

return strings.HasPrefix(u.Path, prefix)
})
}
}

// grabbed from net/http.
func parseRequestLine(line string) (method, uri, proto string, ok bool) {
s1 := strings.Index(line, " ")
s2 := strings.Index(line[s1+1:], " ")
if s1 < 0 || s2 < 0 {
return
}
s2 += s1 + 1
return line[:s1], line[s1+1 : s2], line[s2+1:], true
}

func http1HeaderExist(name string) cmux.Matcher {
return func(r io.Reader) bool {
return matchHTTP1Field(r, name, func(gotValue string) bool {
req, err := http.ReadRequest(bufio.NewReader(r))
if err != nil {
return false
}

return req.Header.Get(name) != ""
})
}
}

func matchHTTP1Field(r io.Reader, name string, matches func(string) bool) (matched bool) {
req, err := http.ReadRequest(bufio.NewReader(r))
if err != nil {
return false
}

return matches(req.Header.Get(name))
}

func rpcxPrefixByteMatcher() cmux.Matcher {
magic := protocol.MagicNumber()
return func(r io.Reader) bool {
Expand Down
1 change: 1 addition & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ type Server struct {
jsonrpcHTTPServer *http.Server
DisableHTTPGateway bool // disable http invoke or not.
DisableJSONRPC bool // disable json rpc or not.
EnableProfile bool // enable profile and statsview or not
AsyncWrite bool // set true if your server only serves few clients
pool WorkerPool

Expand Down
134 changes: 134 additions & 0 deletions server/statsview.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package server

import (
"context"
"fmt"
"net"
"net/http"
"net/http/pprof"
"time"

"github.com/go-echarts/go-echarts/v2/components"
"github.com/go-echarts/go-echarts/v2/templates"
"github.com/rs/cors"
"github.com/smallnest/statsview/statics"
"github.com/smallnest/statsview/viewer"
)

// ViewManager
type ViewManager struct {
ln net.Listener
srv *http.Server

Smgr *viewer.StatsMgr
Ctx context.Context
Cancel context.CancelFunc
Views []viewer.Viewer
}

// Register registers views to the ViewManager
func (vm *ViewManager) Register(views ...viewer.Viewer) {
vm.Views = append(vm.Views, views...)

}

// Start runs a http server and begin to collect metrics
func (vm *ViewManager) Start() error {
return vm.srv.Serve(vm.ln)
}

// Stop shutdown the http server gracefully
func (vm *ViewManager) Stop() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
vm.srv.Shutdown(ctx)
vm.Cancel()
}

func init() {
templates.PageTpl = `
{{- define "page" }}
<!DOCTYPE html>
<html>
{{- template "header" . }}
<body>
<p>&nbsp;&nbsp;rpcx profiler</em></p>
<style> .box { justify-content:center; display:flex; flex-wrap:wrap } </style>
<div class="box"> {{- range .Charts }} {{ template "base" . }} {{- end }} </div>
</body>
</html>
{{ end }}
`
}

// NewViewManager creates a new ViewManager instance
func NewViewManager(ln net.Listener) *ViewManager {
viewer.SetConfiguration(viewer.WithAddr(ln.Addr().String()), viewer.WithLinkAddr(ln.Addr().String()))

page := components.NewPage()
page.PageTitle = "Statsview"
page.AssetsHost = fmt.Sprintf("http://%s/debug/statsview/statics/", viewer.LinkAddr())
page.Assets.JSAssets.Add("jquery.min.js")

srv := &http.Server{
ReadTimeout: time.Minute,
WriteTimeout: time.Minute,
MaxHeaderBytes: 1 << 20,
}

mgr := &ViewManager{
ln: ln,
srv: srv,
}

mgr.Ctx, mgr.Cancel = context.WithCancel(context.Background())
mgr.Register(
viewer.NewGoroutinesViewer(),
viewer.NewHeapViewer(),
viewer.NewStackViewer(),
viewer.NewGCNumViewer(),
viewer.NewGCSizeViewer(),
viewer.NewGCCPUFractionViewer(),
)
smgr := viewer.NewStatsMgr(mgr.Ctx)
for _, v := range mgr.Views {
v.SetStatsMgr(smgr)
}

mux := http.NewServeMux()
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)

for _, v := range mgr.Views {
page.AddCharts(v.View())
mux.HandleFunc("/debug/statsview/view/"+v.Name(), v.Serve)
}

mux.HandleFunc("/debug/statsview", func(w http.ResponseWriter, _ *http.Request) {
page.Render(w)
})

staticsPrev := "/debug/statsview/statics/"
mux.HandleFunc(staticsPrev+"echarts.min.js", func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte(statics.EchartJS))
})

mux.HandleFunc(staticsPrev+"jquery.min.js", func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte(statics.JqueryJS))
})

mux.HandleFunc(staticsPrev+"themes/westeros.js", func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte(statics.WesterosJS))
})

mux.HandleFunc(staticsPrev+"themes/macarons.js", func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte(statics.MacaronsJS))
})

mgr.srv.Handler = cors.AllowAll().Handler(mux)

return mgr
}
4 changes: 0 additions & 4 deletions tool/xgen/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@ func (v *visitor) Visit(n ast.Node) (w ast.Visitor) {
}
return v
case *ast.StructType:
// if isExported(v.name) {
//fmt.Printf("@@@@%s: %s\n", v.name, pretty.Sprint(n.Fields))
//v.StructNames = append(v.StructNames, v.name)
// }
return nil
case *ast.FuncDecl:
if isExported(v.name) {
Expand Down

0 comments on commit 3cda99a

Please sign in to comment.