pprof: integrate pprof into appsrv framework

This commit is contained in:
Zexi
2019-07-15 16:07:43 +08:00
parent 97fdb03cd1
commit 847f177bfb
9 changed files with 211 additions and 5 deletions
+87
View File
@@ -0,0 +1,87 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package shell
import (
"fmt"
"io"
"io/ioutil"
"os"
"syscall"
"yunion.io/x/pkg/util/signalutils"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/util/netutils2"
"yunion.io/x/onecloud/pkg/util/procutils"
)
func init() {
type TraceOptions struct {
Second int `help:"pprof seconds" short-token:"s"`
SERVICE string `help:"Service type"`
}
downloadToTemp := func(input io.Reader, pattern string) (string, error) {
tmpfile, err := ioutil.TempFile("", pattern)
if err != nil {
return "", err
}
defer tmpfile.Close()
if _, err := io.Copy(tmpfile, input); err != nil {
return "", err
}
return tmpfile.Name(), nil
}
pprofRun := func(s *mcclient.ClientSession, svcType, pType string, second int, args ...string) error {
src, err := modules.GetPProfByType(s, svcType, pType, second)
if err != nil {
return err
}
tempfile, err := downloadToTemp(src, pType)
if err != nil {
return err
}
defer func() { os.Remove(tempfile) }()
signalutils.RegisterSignal(func() {
os.Remove(tempfile)
os.Exit(0)
}, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
signalutils.StartTrap()
cmd := procutils.NewCommand("go", "tool")
cmd.Args = append(cmd.Args, args...)
cmd.Args = append(cmd.Args, tempfile)
if _, err := cmd.Run(); err != nil {
return err
}
return nil
}
R(&TraceOptions{}, "pprof-trace", "pprof trace of backend service", func(s *mcclient.ClientSession, args *TraceOptions) error {
return pprofRun(s, args.SERVICE, "trace", args.Second, "trace")
})
R(&TraceOptions{}, "pprof-profile", "pprof profile of backend service", func(s *mcclient.ClientSession, args *TraceOptions) error {
port, err := netutils2.GetFreePort()
if err != nil {
return err
}
return pprofRun(s, args.SERVICE, "profile", args.Second, "pprof", fmt.Sprintf("-http=:%d", port))
})
}
+11 -2
View File
@@ -249,8 +249,16 @@ func (app *Application) defaultHandle(w http.ResponseWriter, r *http.Request, ri
if to == 0 {
to = app.processTimeout
}
ctx, cancel := context.WithTimeout(app.context, to)
defer cancel()
var (
ctx context.Context = app.context
cancel context.CancelFunc = nil
)
if to > 0 {
ctx, cancel = context.WithTimeout(app.context, to)
}
if cancel != nil {
defer cancel()
}
session := hand.workerMan
if session == nil {
if r.Method == "GET" || r.Method == "HEAD" {
@@ -434,6 +442,7 @@ func (app *Application) ListenAndServeWithoutCleanup(addr, certFile, keyFile str
func (app *Application) ListenAndServeTLSWithCleanup2(addr string, certFile, keyFile string, onStop func(), isMaster bool) {
if isMaster {
app.addDefaultHandlers()
AddPProfHandler(app)
}
s := app.initServer(addr)
if isMaster {
+5
View File
@@ -115,6 +115,11 @@ func (hi *SHandlerInfo) SetProcessTimeout(to time.Duration) *SHandlerInfo {
return hi
}
func (hi *SHandlerInfo) SetProcessNoTimeout() *SHandlerInfo {
hi.processTimeout = -1
return hi
}
func (hi *SHandlerInfo) SetWorkerManager(workerMan *SWorkerManager) *SHandlerInfo {
hi.workerMan = workerMan
return hi
+31
View File
@@ -18,6 +18,7 @@ import (
"context"
"fmt"
"net/http"
"net/http/pprof"
"yunion.io/x/pkg/util/version"
)
@@ -47,3 +48,33 @@ func CORSHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Max-Age", "86400")
}
}*/
func AddPProfHandler(app *Application) {
prefix := "/debug/pprof"
app.AddHandler("GET", fmt.Sprintf("%s/", prefix), profIndex).SetProcessNoTimeout()
app.AddHandler("GET", fmt.Sprintf("%s/cmdline", prefix), profCmdline).SetProcessNoTimeout()
app.AddHandler("GET", fmt.Sprintf("%s/profile", prefix), profProfile).SetProcessNoTimeout()
app.AddHandler("GET", fmt.Sprintf("%s/symbol", prefix), profSymbol).SetProcessNoTimeout()
app.AddHandler("POST", fmt.Sprintf("%s/symbol", prefix), profSymbol).SetProcessNoTimeout()
app.AddHandler("GET", fmt.Sprintf("%s/trace", prefix), profTrace).SetProcessNoTimeout()
}
func profIndex(_ context.Context, w http.ResponseWriter, r *http.Request) {
pprof.Index(w, r)
}
func profCmdline(_ context.Context, w http.ResponseWriter, r *http.Request) {
pprof.Cmdline(w, r)
}
func profProfile(_ context.Context, w http.ResponseWriter, r *http.Request) {
pprof.Profile(w, r)
}
func profSymbol(_ context.Context, w http.ResponseWriter, r *http.Request) {
pprof.Symbol(w, r)
}
func profTrace(_ context.Context, w http.ResponseWriter, r *http.Request) {
pprof.Trace(w, r)
}
+14
View File
@@ -97,6 +97,20 @@ func (this *BaseManager) rawRequest(session *mcclient.ClientSession,
header, body, this.GetApiVersion())
}
func (this *BaseManager) rawBaseUrlRequest(s *mcclient.ClientSession,
method httputils.THttpMethod, path string,
header http.Header, body io.Reader) (*http.Response, error) {
baseUrlF := func(baseurl string) string {
obj, _ := url.Parse(baseurl)
obj.Path = ""
return obj.String()
}
return s.RawBaseUrlRequest(
this.serviceType, this.endpointType,
method, this.versionedURL(path),
header, body, this.GetApiVersion(), baseUrlF)
}
type ListResult struct {
Data []jsonutils.JSONObject
Total int
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package modules
import (
"fmt"
"io"
"yunion.io/x/onecloud/pkg/mcclient"
)
func GetPProfByType(s *mcclient.ClientSession, serviceType string, profileType string, seconds int) (io.Reader, error) {
man := &BaseManager{serviceType: serviceType}
if seconds <= 0 {
seconds = 15
}
resp, err := man.rawBaseUrlRequest(s, "GET", fmt.Sprintf("/debug/pprof/%s?seconds=%d", profileType, seconds), nil, nil)
if err != nil {
return nil, err
}
return resp.Body, nil
}
+1 -1
View File
@@ -22,7 +22,7 @@ import (
func GetVersion(s *mcclient.ClientSession, serviceType string) (string, error) {
man := &BaseManager{serviceType: serviceType}
resp, err := man.rawRequest(s, "GET", "/version", nil, nil)
resp, err := man.rawBaseUrlRequest(s, "GET", "/version", nil, nil)
if err != nil {
return "", err
}
+15 -2
View File
@@ -182,15 +182,20 @@ func (this *ClientSession) getBaseUrl(service, endpointType, apiVersion string)
}
}
func (this *ClientSession) RawVersionRequest(
service, endpointType string, method httputils.THttpMethod, url string,
func (this *ClientSession) RawBaseUrlRequest(
service, endpointType string,
method httputils.THttpMethod, url string,
headers http.Header, body io.Reader,
apiVersion string,
baseurlFactory func(string) string,
) (*http.Response, error) {
baseurl, err := this.getBaseUrl(service, endpointType, apiVersion)
if err != nil {
return nil, err
}
if baseurlFactory != nil {
baseurl = baseurlFactory(baseurl)
}
tmpHeader := http.Header{}
if headers != nil {
populateHeader(&tmpHeader, headers)
@@ -205,6 +210,14 @@ func (this *ClientSession) RawVersionRequest(
method, url, tmpHeader, body)
}
func (this *ClientSession) RawVersionRequest(
service, endpointType string, method httputils.THttpMethod, url string,
headers http.Header, body io.Reader,
apiVersion string,
) (*http.Response, error) {
return this.RawBaseUrlRequest(service, endpointType, method, url, headers, body, apiVersion, nil)
}
func (this *ClientSession) RawRequest(service, endpointType string, method httputils.THttpMethod, url string, headers http.Header, body io.Reader) (*http.Response, error) {
return this.RawVersionRequest(service, endpointType, method, url, headers, body, "")
}
+13
View File
@@ -44,6 +44,19 @@ var PRIVATE_PREFIXES = []string{
"192.168.0.0/16",
}
func GetFreePort() (int, error) {
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
return 0, err
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return 0, err
}
defer l.Close()
return l.Addr().(*net.TCPAddr).Port, nil
}
func IsTcpPortUsed(addr string, port int) bool {
conn, _ := net.Dial("tcp", fmt.Sprintf("%s:%d", addr, port))
if conn != nil {