From 52fac6e65c12bb051aa5da23a0ce73dbe5a1b0d1 Mon Sep 17 00:00:00 2001 From: Suhaha Date: Thu, 4 Mar 2021 22:34:17 +0800 Subject: [PATCH 01/11] refactor: profiling to fetch & write stage --- .../profiling/{ => fetcher}/fetcher.go | 72 +++++---- pkg/apiserver/profiling/fetcher/flamegraph.go | 32 ++++ .../profiling/{ => fetcher}/pprof.go | 142 +++++++++++------- pkg/apiserver/profiling/flamegraph.go | 62 -------- pkg/apiserver/profiling/model.go | 21 +-- pkg/apiserver/profiling/module.go | 25 ++- pkg/apiserver/profiling/profile.go | 56 +++++-- pkg/apiserver/profiling/profiler.go | 43 ++++++ pkg/apiserver/profiling/service.go | 11 +- 9 files changed, 288 insertions(+), 176 deletions(-) rename pkg/apiserver/profiling/{ => fetcher}/fetcher.go (57%) create mode 100644 pkg/apiserver/profiling/fetcher/flamegraph.go rename pkg/apiserver/profiling/{ => fetcher}/pprof.go (54%) delete mode 100644 pkg/apiserver/profiling/flamegraph.go create mode 100644 pkg/apiserver/profiling/profiler.go diff --git a/pkg/apiserver/profiling/fetcher.go b/pkg/apiserver/profiling/fetcher/fetcher.go similarity index 57% rename from pkg/apiserver/profiling/fetcher.go rename to pkg/apiserver/profiling/fetcher/fetcher.go index c532a21e2a..0ab341724a 100644 --- a/pkg/apiserver/profiling/fetcher.go +++ b/pkg/apiserver/profiling/fetcher/fetcher.go @@ -11,15 +11,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package profiling +package fetcher import ( "fmt" "net/http" "time" - "go.uber.org/fx" - + "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" "github.com/pingcap/tidb-dashboard/pkg/config" "github.com/pingcap/tidb-dashboard/pkg/pd" "github.com/pingcap/tidb-dashboard/pkg/tidb" @@ -31,69 +30,80 @@ const ( maxProfilingTimeout = time.Minute * 5 ) -type fetchOptions struct { - ip string - port int - path string +type ClientFetchOptions struct { + IP string + Port int + Path string +} + +type ClientFetcher interface { + Fetch(op *ClientFetchOptions) ([]byte, error) +} + +type ProfileFetchOptions struct { + Duration time.Duration } -type profileFetcher interface { - fetch(op *fetchOptions) ([]byte, error) +type ProfileFetcher interface { + Fetch(op *ProfileFetchOptions) ([]byte, error) } -type fetchers struct { - tikv profileFetcher - tiflash profileFetcher - tidb profileFetcher - pd profileFetcher +type FetcherMap map[model.NodeKind]ClientFetcher + +func (fm *FetcherMap) Get(kind model.NodeKind) (*ClientFetcher, error) { + f, ok := (*fm)[kind] + if !ok { + return nil, fmt.Errorf("unsupported target %s", kind) + } + return &f, nil } -var newFetchers = fx.Provide(func( +func NewFetcherMap( tikvClient *tikv.Client, tidbClient *tidb.Client, pdClient *pd.Client, tiflashClient *tiflash.Client, config *config.Config, -) *fetchers { - return &fetchers{ - tikv: &tikvFetcher{ +) *FetcherMap { + return &FetcherMap{ + model.NodeKindTiKV: &tikvFetcher{ client: tikvClient, }, - tiflash: &tiflashFetcher{ + model.NodeKindTiFlash: &tiflashFetcher{ client: tiflashClient, }, - tidb: &tidbFetcher{ + model.NodeKindTiDB: &tidbFetcher{ client: tidbClient, }, - pd: &pdFetcher{ + model.NodeKindPD: &pdFetcher{ client: pdClient, statusAPIHTTPScheme: config.GetClusterHTTPScheme(), }, } -}) +} type tikvFetcher struct { client *tikv.Client } -func (f *tikvFetcher) fetch(op *fetchOptions) ([]byte, error) { - return f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.ip, op.port, op.path) +func (f *tikvFetcher) Fetch(op *ClientFetchOptions) ([]byte, error) { + return f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.IP, op.Port, op.Path) } type tiflashFetcher struct { client *tiflash.Client } -func (f *tiflashFetcher) fetch(op *fetchOptions) ([]byte, error) { - return f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.ip, op.port, op.path) +func (f *tiflashFetcher) Fetch(op *ClientFetchOptions) ([]byte, error) { + return f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.IP, op.Port, op.Path) } type tidbFetcher struct { client *tidb.Client } -func (f *tidbFetcher) fetch(op *fetchOptions) ([]byte, error) { - return f.client.WithStatusAPIAddress(op.ip, op.port).WithStatusAPITimeout(maxProfilingTimeout).SendGetRequest(op.path) +func (f *tidbFetcher) Fetch(op *ClientFetchOptions) ([]byte, error) { + return f.client.WithStatusAPIAddress(op.IP, op.Port).WithStatusAPITimeout(maxProfilingTimeout).SendGetRequest(op.Path) } type pdFetcher struct { @@ -101,10 +111,10 @@ type pdFetcher struct { statusAPIHTTPScheme string } -func (f *pdFetcher) fetch(op *fetchOptions) ([]byte, error) { - baseURL := fmt.Sprintf("%s://%s:%d", f.statusAPIHTTPScheme, op.ip, op.port) +func (f *pdFetcher) Fetch(op *ClientFetchOptions) ([]byte, error) { + baseURL := fmt.Sprintf("%s://%s:%d", f.statusAPIHTTPScheme, op.IP, op.Port) f.client.WithBeforeRequest(func(req *http.Request) { req.Header.Add("PD-Allow-follower-handle", "true") }) - return f.client.WithTimeout(maxProfilingTimeout).WithBaseURL(baseURL).SendGetRequest(op.path) + return f.client.WithTimeout(maxProfilingTimeout).WithBaseURL(baseURL).SendGetRequest(op.Path) } diff --git a/pkg/apiserver/profiling/fetcher/flamegraph.go b/pkg/apiserver/profiling/fetcher/flamegraph.go new file mode 100644 index 0000000000..b2e1016c9a --- /dev/null +++ b/pkg/apiserver/profiling/fetcher/flamegraph.go @@ -0,0 +1,32 @@ +// Copyright 2021 PingCAP, Inc. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package fetcher + +import ( + "fmt" + + "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" +) + +var _ ProfileFetcher = (*FlameGraph)(nil) + +type FlameGraph struct { + Fetcher *ClientFetcher + Target *model.RequestTargetNode +} + +func (f *FlameGraph) Fetch(op *ProfileFetchOptions) ([]byte, error) { + path := fmt.Sprintf("/debug/pprof/profile?seconds=%d", op.Duration) + return (*f.Fetcher).Fetch(&ClientFetchOptions{IP: f.Target.IP, Port: f.Target.Port, Path: path}) +} diff --git a/pkg/apiserver/profiling/pprof.go b/pkg/apiserver/profiling/fetcher/pprof.go similarity index 54% rename from pkg/apiserver/profiling/pprof.go rename to pkg/apiserver/profiling/fetcher/pprof.go index 920ab9e227..6e01051bba 100644 --- a/pkg/apiserver/profiling/pprof.go +++ b/pkg/apiserver/profiling/fetcher/pprof.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package profiling +package fetcher import ( "flag" @@ -31,88 +31,117 @@ import ( ) var ( - _ driver.Fetcher = (*fetcher)(nil) + _ driver.Fetcher = (*pprofFetcher)(nil) + _ ProfileFetcher = (*Pprof)(nil) mu sync.Mutex ) -type pprofOptions struct { - duration uint - // frequency uint - fileNameWithoutExt string - - target *model.RequestTargetNode - fetcher *profileFetcher +type Pprof struct { + Fetcher *ClientFetcher + Target *model.RequestTargetNode + FileNameWithoutExt string } -func fetchPprofSVG(op *pprofOptions) (string, error) { - f, err := fetchPprof(op, "dot") +func (p *Pprof) Fetch(op *ProfileFetchOptions) (d []byte, err error) { + tmpfile, err := ioutil.TempFile("", p.FileNameWithoutExt) if err != nil { - return "", fmt.Errorf("failed to get DOT output from file: %v", err) + return d, fmt.Errorf("failed to create temp file: %v", err) } + defer tmpfile.Close() - b, err := ioutil.ReadFile(f) + format := "dot" + tmpPath := fmt.Sprintf("%s.%s", tmpfile.Name(), format) + format = "-" + format + args := []string{ + format, + // prevent printing stdout + "-output", "dummy", + "-seconds", strconv.Itoa(int(op.Duration)), + } + address := fmt.Sprintf("%s:%d", p.Target.IP, p.Target.Port) + args = append(args, address) + f := &flagSet{ + FlagSet: flag.NewFlagSet("pprof", flag.PanicOnError), + args: args, + } + if err := driver.PProf(&driver.Options{ + Fetch: &pprofFetcher{Pprof: p}, + Flagset: f, + UI: &blankPprofUI{}, + Writer: &oswriter{output: tmpPath}, + }); err != nil { + return d, fmt.Errorf("failed to generate profile report: %v", err) + } + + d, err = ioutil.ReadFile(tmpPath) if err != nil { - return "", fmt.Errorf("failed to get DOT output from file: %v", err) + return d, fmt.Errorf("failed to get DOT output from file: %v", err) } - tmpfile, err := ioutil.TempFile("", op.fileNameWithoutExt) + return +} + +type GraphvizSVGWriter struct { + Path string +} + +func (w *GraphvizSVGWriter) Write(b []byte) (int, error) { + tmpfile, err := ioutil.TempFile("", w.Path) if err != nil { - return "", fmt.Errorf("failed to create temp file: %v", err) + return 0, fmt.Errorf("failed to create temp file: %v", err) } defer tmpfile.Close() - tmpPath := fmt.Sprintf("%s.%s", tmpfile.Name(), "svg") g := graphviz.New() mu.Lock() defer mu.Unlock() graph, err := graphviz.ParseBytes(b) if err != nil { - return "", fmt.Errorf("failed to parse DOT file: %v", err) + return 0, fmt.Errorf("failed to parse DOT file: %v", err) } - if err := g.RenderFilename(graph, graphviz.SVG, tmpPath); err != nil { - return "", fmt.Errorf("failed to render SVG: %v", err) + if err := g.RenderFilename(graph, graphviz.SVG, tmpfile.Name()); err != nil { + return 0, fmt.Errorf("failed to render SVG: %v", err) } - return tmpPath, nil + return len(b), nil } +// func fetchPprofSVG(op *ProfileFetchOptions) (string, error) { +// f, err := fetchPprof(op, "dot") + +// b, err := ioutil.ReadFile(f) +// if err != nil { +// return "", fmt.Errorf("failed to get DOT output from file: %v", err) +// } + +// tmpfile, err := ioutil.TempFile("", op.fileNameWithoutExt) +// if err != nil { +// return "", fmt.Errorf("failed to create temp file: %v", err) +// } +// defer tmpfile.Close() +// tmpPath := fmt.Sprintf("%s.%s", tmpfile.Name(), "svg") + +// g := graphviz.New() +// mu.Lock() +// defer mu.Unlock() +// graph, err := graphviz.ParseBytes(b) +// if err != nil { +// return "", fmt.Errorf("failed to parse DOT file: %v", err) +// } + +// if err := g.RenderFilename(graph, graphviz.SVG, tmpPath); err != nil { +// return "", fmt.Errorf("failed to render SVG: %v", err) +// } + +// return tmpPath, nil +// } + type flagSet struct { *flag.FlagSet args []string } -func fetchPprof(op *pprofOptions, format string) (string, error) { - tmpfile, err := ioutil.TempFile("", op.fileNameWithoutExt) - if err != nil { - return "", fmt.Errorf("failed to create temp file: %v", err) - } - defer tmpfile.Close() - tmpPath := fmt.Sprintf("%s.%s", tmpfile.Name(), format) - format = "-" + format - args := []string{ - format, - // prevent printing stdout - "-output", "dummy", - "-seconds", strconv.Itoa(int(op.duration)), - } - address := fmt.Sprintf("%s:%d", op.target.IP, op.target.Port) - args = append(args, address) - f := &flagSet{ - FlagSet: flag.NewFlagSet("pprof", flag.PanicOnError), - args: args, - } - if err := driver.PProf(&driver.Options{ - Fetch: &fetcher{profileFetcher: op.fetcher, target: op.target}, - Flagset: f, - UI: &blankPprofUI{}, - Writer: &oswriter{output: tmpPath}, - }); err != nil { - return "", fmt.Errorf("failed to generate profile report: %v", err) - } - return tmpPath, nil -} - func (f *flagSet) StringList(o, d, c string) *[]*string { return &[]*string{f.String(o, d, c)} } @@ -139,16 +168,15 @@ func (o *oswriter) Open(name string) (io.WriteCloser, error) { return f, err } -type fetcher struct { - target *model.RequestTargetNode - profileFetcher *profileFetcher +type pprofFetcher struct { + *Pprof } -func (f *fetcher) Fetch(src string, duration, timeout time.Duration) (*profile.Profile, string, error) { +func (f *pprofFetcher) Fetch(src string, duration, timeout time.Duration) (*profile.Profile, string, error) { secs := strconv.Itoa(int(duration / time.Second)) url := "/debug/pprof/profile?seconds=" + secs - resp, err := (*f.profileFetcher).fetch(&fetchOptions{ip: f.target.IP, port: f.target.Port, path: url}) + resp, err := (*f.Fetcher).Fetch(&ClientFetchOptions{IP: f.Target.IP, Port: f.Target.Port, Path: url}) if err != nil { return nil, url, err } diff --git a/pkg/apiserver/profiling/flamegraph.go b/pkg/apiserver/profiling/flamegraph.go deleted file mode 100644 index 003e7b0a44..0000000000 --- a/pkg/apiserver/profiling/flamegraph.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2021 PingCAP, Inc. -// -// 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, -// See the License for the specific language governing permissions and -// limitations under the License. - -package profiling - -import ( - "fmt" - "io" - "io/ioutil" - "os" - - "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" -) - -type flameGraphOptions struct { - duration uint - // frequency uint - fileNameWithoutExt string - - target *model.RequestTargetNode - fetcher *profileFetcher -} - -func fetchFlameGraphSVG(op *flameGraphOptions) (string, error) { - path := fmt.Sprintf("/debug/pprof/profile?seconds=%d", op.duration) - resp, err := (*op.fetcher).fetch(&fetchOptions{ip: op.target.IP, port: op.target.Port, path: path}) - if err != nil { - return "", err - } - svgFilePath, err := writePprofRsSVG(resp, op.fileNameWithoutExt) - if err != nil { - return "", err - } - return svgFilePath, nil -} - -func writePprofRsSVG(body []byte, fileNameWithoutExt string) (string, error) { - file, err := ioutil.TempFile("", fileNameWithoutExt) - if err != nil { - return "", fmt.Errorf("failed to create temp file: %v", err) - } - _, err = io.WriteString(file, string(body)) - if err != nil { - return "", fmt.Errorf("failed to write temp file: %v", err) - } - svgFilePath := file.Name() + ".svg" - err = os.Rename(file.Name(), svgFilePath) - if err != nil { - return "", fmt.Errorf("failed to write SVG from temp file: %v", err) - } - return svgFilePath, nil -} diff --git a/pkg/apiserver/profiling/model.go b/pkg/apiserver/profiling/model.go index f03e97b860..885d3efb9b 100644 --- a/pkg/apiserver/profiling/model.go +++ b/pkg/apiserver/profiling/model.go @@ -19,6 +19,7 @@ import ( "time" "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" + "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" "github.com/pingcap/tidb-dashboard/pkg/dbstore" ) @@ -69,14 +70,14 @@ func autoMigrate(db *dbstore.DB) error { // Task is the unit to fetch profiling information. type Task struct { *TaskModel - ctx context.Context - cancel context.CancelFunc - taskGroup *TaskGroup - fetchers *fetchers + ctx context.Context + cancel context.CancelFunc + taskGroup *TaskGroup + fetcherMap *fetcher.FetcherMap } // NewTask creates a new profiling task. -func NewTask(ctx context.Context, taskGroup *TaskGroup, target model.RequestTargetNode, fts *fetchers) *Task { +func NewTask(ctx context.Context, taskGroup *TaskGroup, target model.RequestTargetNode, fm *fetcher.FetcherMap) *Task { ctx, cancel := context.WithCancel(ctx) return &Task{ TaskModel: &TaskModel{ @@ -85,16 +86,16 @@ func NewTask(ctx context.Context, taskGroup *TaskGroup, target model.RequestTarg Target: target, StartedAt: time.Now().Unix(), }, - ctx: ctx, - cancel: cancel, - taskGroup: taskGroup, - fetchers: fts, + ctx: ctx, + cancel: cancel, + taskGroup: taskGroup, + fetcherMap: fm, } } func (t *Task) run() { fileNameWithoutExt := fmt.Sprintf("profiling_%d_%d_%s", t.TaskGroupID, t.ID, t.Target.FileName()) - svgFilePath, err := profileAndWriteSVG(t.ctx, t.fetchers, &t.Target, fileNameWithoutExt, t.taskGroup.ProfileDurationSecs) + svgFilePath, err := profileAndWriteSVG(t.ctx, t.fetcherMap, &t.Target, fileNameWithoutExt, t.taskGroup.ProfileDurationSecs) if err != nil { t.Error = err.Error() t.State = TaskStateError diff --git a/pkg/apiserver/profiling/module.go b/pkg/apiserver/profiling/module.go index a76c7c44ab..6800d39763 100644 --- a/pkg/apiserver/profiling/module.go +++ b/pkg/apiserver/profiling/module.go @@ -1,5 +1,26 @@ +// Copyright 2021 PingCAP, Inc. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + package profiling -import "go.uber.org/fx" +import ( + "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" + "go.uber.org/fx" +) -var Module = fx.Options(newFetchers, newService) +var Module = fx.Options( + fx.Provide( + fetcher.NewFetcherMap, + newService, + ), +) diff --git a/pkg/apiserver/profiling/profile.go b/pkg/apiserver/profiling/profile.go index 4f94c92467..b10a20f960 100644 --- a/pkg/apiserver/profiling/profile.go +++ b/pkg/apiserver/profiling/profile.go @@ -16,21 +16,59 @@ package profiling import ( "context" "fmt" + "io/ioutil" + "time" "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" + "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" ) -func profileAndWriteSVG(ctx context.Context, fts *fetchers, target *model.RequestTargetNode, fileNameWithoutExt string, profileDurationSecs uint) (string, error) { +func profileAndWriteSVG(ctx context.Context, fm *fetcher.FetcherMap, target *model.RequestTargetNode, fileNameWithoutExt string, profileDurationSecs uint) (path string, err error) { + f, err := fm.Get(target.Kind) + if err != nil { + return "", err + } + + var p *profiler + + path = fmt.Sprintf("%s.%s", fileNameWithoutExt, "svg") + switch target.Kind { - case model.NodeKindTiKV: - return fetchFlameGraphSVG(&flameGraphOptions{duration: profileDurationSecs, fileNameWithoutExt: fileNameWithoutExt, target: target, fetcher: &fts.tikv}) - case model.NodeKindTiFlash: - return fetchFlameGraphSVG(&flameGraphOptions{duration: profileDurationSecs, fileNameWithoutExt: fileNameWithoutExt, target: target, fetcher: &fts.tiflash}) - case model.NodeKindTiDB: - return fetchPprofSVG(&pprofOptions{duration: profileDurationSecs, fileNameWithoutExt: fileNameWithoutExt, target: target, fetcher: &fts.tidb}) - case model.NodeKindPD: - return fetchPprofSVG(&pprofOptions{duration: profileDurationSecs, fileNameWithoutExt: fileNameWithoutExt, target: target, fetcher: &fts.pd}) + case model.NodeKindTiKV, model.NodeKindTiFlash: + p = &profiler{ + Fetcher: &fetcher.FlameGraph{ + Fetcher: f, + Target: target, + }, + Writer: &fileWriter{path: path}, + } + case model.NodeKindTiDB, model.NodeKindPD: + p = &profiler{ + Fetcher: &fetcher.Pprof{ + Fetcher: f, + Target: target, + FileNameWithoutExt: fileNameWithoutExt, + }, + Writer: &fetcher.GraphvizSVGWriter{Path: path}, + } default: return "", fmt.Errorf("unsupported target %s", target) } + + err = p.Profile(&profileOptions{ProfileFetchOptions: fetcher.ProfileFetchOptions{Duration: time.Duration(profileDurationSecs)}}) + + return path, err +} + +type fileWriter struct { + path string +} + +func (w *fileWriter) Write(p []byte) (int, error) { + f, err := ioutil.TempFile("", w.path) + if err != nil { + return 0, err + } + + return f.Write(p) } diff --git a/pkg/apiserver/profiling/profiler.go b/pkg/apiserver/profiling/profiler.go new file mode 100644 index 0000000000..3ad2c7ae16 --- /dev/null +++ b/pkg/apiserver/profiling/profiler.go @@ -0,0 +1,43 @@ +// Copyright 2020 PingCAP, Inc. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package profiling + +import ( + "io" + + "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" +) + +type profiler struct { + Fetcher fetcher.ProfileFetcher + Writer io.Writer +} + +type profileOptions struct { + fetcher.ProfileFetchOptions +} + +func (p *profiler) Profile(op *profileOptions) error { + resp, err := p.Fetcher.Fetch(&op.ProfileFetchOptions) + if err != nil { + return err + } + + _, err = p.Writer.Write(resp) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/apiserver/profiling/service.go b/pkg/apiserver/profiling/service.go index ad3d4a08f2..79061b6594 100644 --- a/pkg/apiserver/profiling/service.go +++ b/pkg/apiserver/profiling/service.go @@ -24,6 +24,7 @@ import ( "go.uber.org/zap" "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" + "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" "github.com/pingcap/tidb-dashboard/pkg/config" "github.com/pingcap/tidb-dashboard/pkg/dbstore" ) @@ -62,14 +63,14 @@ type Service struct { sessionCh chan *StartRequestSession lastTaskGroup *TaskGroup tasks sync.Map - fetchers *fetchers + fetcherMap *fetcher.FetcherMap } -var newService = fx.Provide(func(lc fx.Lifecycle, p ServiceParams, fts *fetchers) (*Service, error) { +func newService(lc fx.Lifecycle, p ServiceParams, fm *fetcher.FetcherMap) (*Service, error) { if err := autoMigrate(p.LocalStore); err != nil { return nil, err } - s := &Service{params: p, fetchers: fts} + s := &Service{params: p, fetcherMap: fm} lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { s.wg.Add(1) @@ -86,7 +87,7 @@ var newService = fx.Provide(func(lc fx.Lifecycle, p ServiceParams, fts *fetchers }) return s, nil -}) +} func (s *Service) serviceLoop(ctx context.Context) { cfgCh := s.params.ConfigManager.NewPushChannel() @@ -159,7 +160,7 @@ func (s *Service) startGroup(ctx context.Context, req *StartRequest) (*TaskGroup tasks := make([]*Task, 0, len(req.Targets)) for _, target := range req.Targets { - t := NewTask(ctx, taskGroup, target, s.fetchers) + t := NewTask(ctx, taskGroup, target, s.fetcherMap) s.params.LocalStore.Create(t.TaskModel) s.tasks.Store(t.ID, t) tasks = append(tasks, t) From 583ebd8b00f1ab4eda18cfb7a8d25c24a332fdba Mon Sep 17 00:00:00 2001 From: Suhaha Date: Thu, 4 Mar 2021 23:23:55 +0800 Subject: [PATCH 02/11] fix: tmp file path --- pkg/apiserver/profiling/fetcher/pprof.go | 63 +------------------- pkg/apiserver/profiling/profile.go | 26 ++------ pkg/apiserver/profiling/profiler.go | 15 ++--- pkg/apiserver/profiling/writer.go | 75 ++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 94 deletions(-) create mode 100644 pkg/apiserver/profiling/writer.go diff --git a/pkg/apiserver/profiling/fetcher/pprof.go b/pkg/apiserver/profiling/fetcher/pprof.go index 6e01051bba..8646ad35f8 100644 --- a/pkg/apiserver/profiling/fetcher/pprof.go +++ b/pkg/apiserver/profiling/fetcher/pprof.go @@ -20,10 +20,8 @@ import ( "io/ioutil" "os" "strconv" - "sync" "time" - "github.com/goccy/go-graphviz" "github.com/google/pprof/driver" "github.com/google/pprof/profile" @@ -31,9 +29,8 @@ import ( ) var ( - _ driver.Fetcher = (*pprofFetcher)(nil) - _ ProfileFetcher = (*Pprof)(nil) - mu sync.Mutex + _ driver.Fetcher = (*pprofFetcher)(nil) + _ ProfileFetcher = (*Pprof)(nil) ) type Pprof struct { @@ -81,62 +78,6 @@ func (p *Pprof) Fetch(op *ProfileFetchOptions) (d []byte, err error) { return } -type GraphvizSVGWriter struct { - Path string -} - -func (w *GraphvizSVGWriter) Write(b []byte) (int, error) { - tmpfile, err := ioutil.TempFile("", w.Path) - if err != nil { - return 0, fmt.Errorf("failed to create temp file: %v", err) - } - defer tmpfile.Close() - - g := graphviz.New() - mu.Lock() - defer mu.Unlock() - graph, err := graphviz.ParseBytes(b) - if err != nil { - return 0, fmt.Errorf("failed to parse DOT file: %v", err) - } - - if err := g.RenderFilename(graph, graphviz.SVG, tmpfile.Name()); err != nil { - return 0, fmt.Errorf("failed to render SVG: %v", err) - } - - return len(b), nil -} - -// func fetchPprofSVG(op *ProfileFetchOptions) (string, error) { -// f, err := fetchPprof(op, "dot") - -// b, err := ioutil.ReadFile(f) -// if err != nil { -// return "", fmt.Errorf("failed to get DOT output from file: %v", err) -// } - -// tmpfile, err := ioutil.TempFile("", op.fileNameWithoutExt) -// if err != nil { -// return "", fmt.Errorf("failed to create temp file: %v", err) -// } -// defer tmpfile.Close() -// tmpPath := fmt.Sprintf("%s.%s", tmpfile.Name(), "svg") - -// g := graphviz.New() -// mu.Lock() -// defer mu.Unlock() -// graph, err := graphviz.ParseBytes(b) -// if err != nil { -// return "", fmt.Errorf("failed to parse DOT file: %v", err) -// } - -// if err := g.RenderFilename(graph, graphviz.SVG, tmpPath); err != nil { -// return "", fmt.Errorf("failed to render SVG: %v", err) -// } - -// return tmpPath, nil -// } - type flagSet struct { *flag.FlagSet args []string diff --git a/pkg/apiserver/profiling/profile.go b/pkg/apiserver/profiling/profile.go index b10a20f960..f959442c32 100644 --- a/pkg/apiserver/profiling/profile.go +++ b/pkg/apiserver/profiling/profile.go @@ -16,14 +16,13 @@ package profiling import ( "context" "fmt" - "io/ioutil" "time" "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" ) -func profileAndWriteSVG(ctx context.Context, fm *fetcher.FetcherMap, target *model.RequestTargetNode, fileNameWithoutExt string, profileDurationSecs uint) (path string, err error) { +func profileAndWriteSVG(ctx context.Context, fm *fetcher.FetcherMap, target *model.RequestTargetNode, fileNameWithoutExt string, profileDurationSecs uint) (string, error) { f, err := fm.Get(target.Kind) if err != nil { return "", err @@ -31,8 +30,6 @@ func profileAndWriteSVG(ctx context.Context, fm *fetcher.FetcherMap, target *mod var p *profiler - path = fmt.Sprintf("%s.%s", fileNameWithoutExt, "svg") - switch target.Kind { case model.NodeKindTiKV, model.NodeKindTiFlash: p = &profiler{ @@ -40,7 +37,7 @@ func profileAndWriteSVG(ctx context.Context, fm *fetcher.FetcherMap, target *mod Fetcher: f, Target: target, }, - Writer: &fileWriter{path: path}, + Writer: &fileWriter{fileNameWithoutExt: fileNameWithoutExt, ext: "svg"}, } case model.NodeKindTiDB, model.NodeKindPD: p = &profiler{ @@ -49,26 +46,11 @@ func profileAndWriteSVG(ctx context.Context, fm *fetcher.FetcherMap, target *mod Target: target, FileNameWithoutExt: fileNameWithoutExt, }, - Writer: &fetcher.GraphvizSVGWriter{Path: path}, + Writer: &graphvizSVGWriter{fileNameWithoutExt: fileNameWithoutExt}, } default: return "", fmt.Errorf("unsupported target %s", target) } - err = p.Profile(&profileOptions{ProfileFetchOptions: fetcher.ProfileFetchOptions{Duration: time.Duration(profileDurationSecs)}}) - - return path, err -} - -type fileWriter struct { - path string -} - -func (w *fileWriter) Write(p []byte) (int, error) { - f, err := ioutil.TempFile("", w.path) - if err != nil { - return 0, err - } - - return f.Write(p) + return p.Profile(&profileOptions{ProfileFetchOptions: fetcher.ProfileFetchOptions{Duration: time.Duration(profileDurationSecs)}}) } diff --git a/pkg/apiserver/profiling/profiler.go b/pkg/apiserver/profiling/profiler.go index 3ad2c7ae16..cb551313b3 100644 --- a/pkg/apiserver/profiling/profiler.go +++ b/pkg/apiserver/profiling/profiler.go @@ -14,30 +14,23 @@ package profiling import ( - "io" - "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" ) type profiler struct { Fetcher fetcher.ProfileFetcher - Writer io.Writer + Writer Writer } type profileOptions struct { fetcher.ProfileFetchOptions } -func (p *profiler) Profile(op *profileOptions) error { +func (p *profiler) Profile(op *profileOptions) (string, error) { resp, err := p.Fetcher.Fetch(&op.ProfileFetchOptions) if err != nil { - return err - } - - _, err = p.Writer.Write(resp) - if err != nil { - return err + return "", err } - return nil + return p.Writer.Write(resp) } diff --git a/pkg/apiserver/profiling/writer.go b/pkg/apiserver/profiling/writer.go new file mode 100644 index 0000000000..d737ef1f72 --- /dev/null +++ b/pkg/apiserver/profiling/writer.go @@ -0,0 +1,75 @@ +// Copyright 2020 PingCAP, Inc. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package profiling + +import ( + "fmt" + "io/ioutil" + "os" + "sync" + + "github.com/goccy/go-graphviz" +) + +var mu sync.Mutex + +type Writer interface { + Write(p []byte) (string, error) +} + +type fileWriter struct { + fileNameWithoutExt string + ext string +} + +func (w *fileWriter) Write(p []byte) (string, error) { + f, err := ioutil.TempFile("", w.fileNameWithoutExt) + if err != nil { + return "", err + } + defer f.Close() + + path := fmt.Sprintf("%s.%s", f.Name(), w.ext) + os.Rename(f.Name(), path) + f.Write(p) + + return path, nil +} + +type graphvizSVGWriter struct { + fileNameWithoutExt string +} + +func (w *graphvizSVGWriter) Write(b []byte) (string, error) { + tmpfile, err := ioutil.TempFile("", w.fileNameWithoutExt) + if err != nil { + return "", fmt.Errorf("failed to create temp file: %v", err) + } + defer tmpfile.Close() + tmpPath := fmt.Sprintf("%s.%s", tmpfile.Name(), "svg") + + g := graphviz.New() + mu.Lock() + defer mu.Unlock() + graph, err := graphviz.ParseBytes(b) + if err != nil { + return "", fmt.Errorf("failed to parse DOT file: %v", err) + } + + if err := g.RenderFilename(graph, graphviz.SVG, tmpPath); err != nil { + return "", fmt.Errorf("failed to render SVG: %v", err) + } + + return tmpPath, nil +} From 8aa67acb25f389d05045a3a4877fce61173a9d36 Mon Sep 17 00:00:00 2001 From: Suhaha Date: Thu, 4 Mar 2021 23:40:31 +0800 Subject: [PATCH 03/11] chore(apiserver): profiling error msg --- pkg/apiserver/profiling/profile.go | 3 ++- pkg/apiserver/profiling/writer.go | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/pkg/apiserver/profiling/profile.go b/pkg/apiserver/profiling/profile.go index f959442c32..8d801210ad 100644 --- a/pkg/apiserver/profiling/profile.go +++ b/pkg/apiserver/profiling/profile.go @@ -18,6 +18,7 @@ import ( "fmt" "time" + "github.com/goccy/go-graphviz" "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" ) @@ -46,7 +47,7 @@ func profileAndWriteSVG(ctx context.Context, fm *fetcher.FetcherMap, target *mod Target: target, FileNameWithoutExt: fileNameWithoutExt, }, - Writer: &graphvizSVGWriter{fileNameWithoutExt: fileNameWithoutExt}, + Writer: &graphvizSVGWriter{fileNameWithoutExt: fileNameWithoutExt, ext: graphviz.SVG}, } default: return "", fmt.Errorf("unsupported target %s", target) diff --git a/pkg/apiserver/profiling/writer.go b/pkg/apiserver/profiling/writer.go index d737ef1f72..f9281d150a 100644 --- a/pkg/apiserver/profiling/writer.go +++ b/pkg/apiserver/profiling/writer.go @@ -40,15 +40,23 @@ func (w *fileWriter) Write(p []byte) (string, error) { } defer f.Close() + _, err = f.Write(p) + if err != nil { + return "", fmt.Errorf("failed to write temp file: %v", err) + } + path := fmt.Sprintf("%s.%s", f.Name(), w.ext) - os.Rename(f.Name(), path) - f.Write(p) + err = os.Rename(f.Name(), path) + if err != nil { + return "", fmt.Errorf("failed to write %s from temp file: %v", w.ext, err) + } return path, nil } type graphvizSVGWriter struct { fileNameWithoutExt string + ext graphviz.Format } func (w *graphvizSVGWriter) Write(b []byte) (string, error) { @@ -67,8 +75,8 @@ func (w *graphvizSVGWriter) Write(b []byte) (string, error) { return "", fmt.Errorf("failed to parse DOT file: %v", err) } - if err := g.RenderFilename(graph, graphviz.SVG, tmpPath); err != nil { - return "", fmt.Errorf("failed to render SVG: %v", err) + if err := g.RenderFilename(graph, w.ext, tmpPath); err != nil { + return "", fmt.Errorf("failed to render %s: %v", w.ext, err) } return tmpPath, nil From cefbd7a9d7833c3d3d7bf9f073d0742d9b8350b2 Mon Sep 17 00:00:00 2001 From: Suhaha Date: Thu, 4 Mar 2021 23:48:48 +0800 Subject: [PATCH 04/11] build(apiserver): profiling lint --- pkg/apiserver/profiling/fetcher/fetcher.go | 10 +++++----- pkg/apiserver/profiling/model.go | 4 ++-- pkg/apiserver/profiling/module.go | 5 +++-- pkg/apiserver/profiling/profile.go | 3 ++- pkg/apiserver/profiling/service.go | 4 ++-- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/pkg/apiserver/profiling/fetcher/fetcher.go b/pkg/apiserver/profiling/fetcher/fetcher.go index 0ab341724a..fd6106bfe9 100644 --- a/pkg/apiserver/profiling/fetcher/fetcher.go +++ b/pkg/apiserver/profiling/fetcher/fetcher.go @@ -48,9 +48,9 @@ type ProfileFetcher interface { Fetch(op *ProfileFetchOptions) ([]byte, error) } -type FetcherMap map[model.NodeKind]ClientFetcher +type ClientFetcherMap map[model.NodeKind]ClientFetcher -func (fm *FetcherMap) Get(kind model.NodeKind) (*ClientFetcher, error) { +func (fm *ClientFetcherMap) Get(kind model.NodeKind) (*ClientFetcher, error) { f, ok := (*fm)[kind] if !ok { return nil, fmt.Errorf("unsupported target %s", kind) @@ -58,14 +58,14 @@ func (fm *FetcherMap) Get(kind model.NodeKind) (*ClientFetcher, error) { return &f, nil } -func NewFetcherMap( +func NewClientFetcherMap( tikvClient *tikv.Client, tidbClient *tidb.Client, pdClient *pd.Client, tiflashClient *tiflash.Client, config *config.Config, -) *FetcherMap { - return &FetcherMap{ +) *ClientFetcherMap { + return &ClientFetcherMap{ model.NodeKindTiKV: &tikvFetcher{ client: tikvClient, }, diff --git a/pkg/apiserver/profiling/model.go b/pkg/apiserver/profiling/model.go index 885d3efb9b..16118526b5 100644 --- a/pkg/apiserver/profiling/model.go +++ b/pkg/apiserver/profiling/model.go @@ -73,11 +73,11 @@ type Task struct { ctx context.Context cancel context.CancelFunc taskGroup *TaskGroup - fetcherMap *fetcher.FetcherMap + fetcherMap *fetcher.ClientFetcherMap } // NewTask creates a new profiling task. -func NewTask(ctx context.Context, taskGroup *TaskGroup, target model.RequestTargetNode, fm *fetcher.FetcherMap) *Task { +func NewTask(ctx context.Context, taskGroup *TaskGroup, target model.RequestTargetNode, fm *fetcher.ClientFetcherMap) *Task { ctx, cancel := context.WithCancel(ctx) return &Task{ TaskModel: &TaskModel{ diff --git a/pkg/apiserver/profiling/module.go b/pkg/apiserver/profiling/module.go index 6800d39763..02c4dc540e 100644 --- a/pkg/apiserver/profiling/module.go +++ b/pkg/apiserver/profiling/module.go @@ -14,13 +14,14 @@ package profiling import ( - "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" "go.uber.org/fx" + + "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" ) var Module = fx.Options( fx.Provide( - fetcher.NewFetcherMap, + fetcher.NewClientFetcherMap, newService, ), ) diff --git a/pkg/apiserver/profiling/profile.go b/pkg/apiserver/profiling/profile.go index 8d801210ad..2150fdd2fc 100644 --- a/pkg/apiserver/profiling/profile.go +++ b/pkg/apiserver/profiling/profile.go @@ -19,11 +19,12 @@ import ( "time" "github.com/goccy/go-graphviz" + "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" ) -func profileAndWriteSVG(ctx context.Context, fm *fetcher.FetcherMap, target *model.RequestTargetNode, fileNameWithoutExt string, profileDurationSecs uint) (string, error) { +func profileAndWriteSVG(ctx context.Context, fm *fetcher.ClientFetcherMap, target *model.RequestTargetNode, fileNameWithoutExt string, profileDurationSecs uint) (string, error) { f, err := fm.Get(target.Kind) if err != nil { return "", err diff --git a/pkg/apiserver/profiling/service.go b/pkg/apiserver/profiling/service.go index 79061b6594..00ead28027 100644 --- a/pkg/apiserver/profiling/service.go +++ b/pkg/apiserver/profiling/service.go @@ -63,10 +63,10 @@ type Service struct { sessionCh chan *StartRequestSession lastTaskGroup *TaskGroup tasks sync.Map - fetcherMap *fetcher.FetcherMap + fetcherMap *fetcher.ClientFetcherMap } -func newService(lc fx.Lifecycle, p ServiceParams, fm *fetcher.FetcherMap) (*Service, error) { +func newService(lc fx.Lifecycle, p ServiceParams, fm *fetcher.ClientFetcherMap) (*Service, error) { if err := autoMigrate(p.LocalStore); err != nil { return nil, err } From 4373c5512337058d6c61ae6b1636a4e5830fa8d2 Mon Sep 17 00:00:00 2001 From: Suhaha Date: Thu, 4 Mar 2021 23:59:29 +0800 Subject: [PATCH 05/11] chore(apiserver): client fetcher rename --- pkg/apiserver/profiling/fetcher/fetcher.go | 12 +++++------ pkg/apiserver/profiling/fetcher/flamegraph.go | 6 +++--- pkg/apiserver/profiling/fetcher/pprof.go | 4 ++-- pkg/apiserver/profiling/model.go | 20 +++++++++---------- pkg/apiserver/profiling/module.go | 2 +- pkg/apiserver/profiling/profile.go | 10 +++++----- pkg/apiserver/profiling/service.go | 8 ++++---- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/pkg/apiserver/profiling/fetcher/fetcher.go b/pkg/apiserver/profiling/fetcher/fetcher.go index fd6106bfe9..4d89b49a29 100644 --- a/pkg/apiserver/profiling/fetcher/fetcher.go +++ b/pkg/apiserver/profiling/fetcher/fetcher.go @@ -36,7 +36,7 @@ type ClientFetchOptions struct { Path string } -type ClientFetcher interface { +type Client interface { Fetch(op *ClientFetchOptions) ([]byte, error) } @@ -48,9 +48,9 @@ type ProfileFetcher interface { Fetch(op *ProfileFetchOptions) ([]byte, error) } -type ClientFetcherMap map[model.NodeKind]ClientFetcher +type ClientMap map[model.NodeKind]Client -func (fm *ClientFetcherMap) Get(kind model.NodeKind) (*ClientFetcher, error) { +func (fm *ClientMap) Get(kind model.NodeKind) (*Client, error) { f, ok := (*fm)[kind] if !ok { return nil, fmt.Errorf("unsupported target %s", kind) @@ -58,14 +58,14 @@ func (fm *ClientFetcherMap) Get(kind model.NodeKind) (*ClientFetcher, error) { return &f, nil } -func NewClientFetcherMap( +func NewClientMap( tikvClient *tikv.Client, tidbClient *tidb.Client, pdClient *pd.Client, tiflashClient *tiflash.Client, config *config.Config, -) *ClientFetcherMap { - return &ClientFetcherMap{ +) *ClientMap { + return &ClientMap{ model.NodeKindTiKV: &tikvFetcher{ client: tikvClient, }, diff --git a/pkg/apiserver/profiling/fetcher/flamegraph.go b/pkg/apiserver/profiling/fetcher/flamegraph.go index b2e1016c9a..66a6af9989 100644 --- a/pkg/apiserver/profiling/fetcher/flamegraph.go +++ b/pkg/apiserver/profiling/fetcher/flamegraph.go @@ -22,11 +22,11 @@ import ( var _ ProfileFetcher = (*FlameGraph)(nil) type FlameGraph struct { - Fetcher *ClientFetcher - Target *model.RequestTargetNode + Client *Client + Target *model.RequestTargetNode } func (f *FlameGraph) Fetch(op *ProfileFetchOptions) ([]byte, error) { path := fmt.Sprintf("/debug/pprof/profile?seconds=%d", op.Duration) - return (*f.Fetcher).Fetch(&ClientFetchOptions{IP: f.Target.IP, Port: f.Target.Port, Path: path}) + return (*f.Client).Fetch(&ClientFetchOptions{IP: f.Target.IP, Port: f.Target.Port, Path: path}) } diff --git a/pkg/apiserver/profiling/fetcher/pprof.go b/pkg/apiserver/profiling/fetcher/pprof.go index 8646ad35f8..d87e4a33b8 100644 --- a/pkg/apiserver/profiling/fetcher/pprof.go +++ b/pkg/apiserver/profiling/fetcher/pprof.go @@ -34,7 +34,7 @@ var ( ) type Pprof struct { - Fetcher *ClientFetcher + Client *Client Target *model.RequestTargetNode FileNameWithoutExt string } @@ -117,7 +117,7 @@ func (f *pprofFetcher) Fetch(src string, duration, timeout time.Duration) (*prof secs := strconv.Itoa(int(duration / time.Second)) url := "/debug/pprof/profile?seconds=" + secs - resp, err := (*f.Fetcher).Fetch(&ClientFetchOptions{IP: f.Target.IP, Port: f.Target.Port, Path: url}) + resp, err := (*f.Client).Fetch(&ClientFetchOptions{IP: f.Target.IP, Port: f.Target.Port, Path: url}) if err != nil { return nil, url, err } diff --git a/pkg/apiserver/profiling/model.go b/pkg/apiserver/profiling/model.go index 16118526b5..9e3b787fb7 100644 --- a/pkg/apiserver/profiling/model.go +++ b/pkg/apiserver/profiling/model.go @@ -70,14 +70,14 @@ func autoMigrate(db *dbstore.DB) error { // Task is the unit to fetch profiling information. type Task struct { *TaskModel - ctx context.Context - cancel context.CancelFunc - taskGroup *TaskGroup - fetcherMap *fetcher.ClientFetcherMap + ctx context.Context + cancel context.CancelFunc + taskGroup *TaskGroup + clientMap *fetcher.ClientMap } // NewTask creates a new profiling task. -func NewTask(ctx context.Context, taskGroup *TaskGroup, target model.RequestTargetNode, fm *fetcher.ClientFetcherMap) *Task { +func NewTask(ctx context.Context, taskGroup *TaskGroup, target model.RequestTargetNode, cm *fetcher.ClientMap) *Task { ctx, cancel := context.WithCancel(ctx) return &Task{ TaskModel: &TaskModel{ @@ -86,16 +86,16 @@ func NewTask(ctx context.Context, taskGroup *TaskGroup, target model.RequestTarg Target: target, StartedAt: time.Now().Unix(), }, - ctx: ctx, - cancel: cancel, - taskGroup: taskGroup, - fetcherMap: fm, + ctx: ctx, + cancel: cancel, + taskGroup: taskGroup, + clientMap: cm, } } func (t *Task) run() { fileNameWithoutExt := fmt.Sprintf("profiling_%d_%d_%s", t.TaskGroupID, t.ID, t.Target.FileName()) - svgFilePath, err := profileAndWriteSVG(t.ctx, t.fetcherMap, &t.Target, fileNameWithoutExt, t.taskGroup.ProfileDurationSecs) + svgFilePath, err := profileAndWriteSVG(t.ctx, t.clientMap, &t.Target, fileNameWithoutExt, t.taskGroup.ProfileDurationSecs) if err != nil { t.Error = err.Error() t.State = TaskStateError diff --git a/pkg/apiserver/profiling/module.go b/pkg/apiserver/profiling/module.go index 02c4dc540e..2b1f7ac39e 100644 --- a/pkg/apiserver/profiling/module.go +++ b/pkg/apiserver/profiling/module.go @@ -21,7 +21,7 @@ import ( var Module = fx.Options( fx.Provide( - fetcher.NewClientFetcherMap, + fetcher.NewClientMap, newService, ), ) diff --git a/pkg/apiserver/profiling/profile.go b/pkg/apiserver/profiling/profile.go index 2150fdd2fc..2e97b6dfd1 100644 --- a/pkg/apiserver/profiling/profile.go +++ b/pkg/apiserver/profiling/profile.go @@ -24,8 +24,8 @@ import ( "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" ) -func profileAndWriteSVG(ctx context.Context, fm *fetcher.ClientFetcherMap, target *model.RequestTargetNode, fileNameWithoutExt string, profileDurationSecs uint) (string, error) { - f, err := fm.Get(target.Kind) +func profileAndWriteSVG(ctx context.Context, cm *fetcher.ClientMap, target *model.RequestTargetNode, fileNameWithoutExt string, profileDurationSecs uint) (string, error) { + c, err := cm.Get(target.Kind) if err != nil { return "", err } @@ -36,15 +36,15 @@ func profileAndWriteSVG(ctx context.Context, fm *fetcher.ClientFetcherMap, targe case model.NodeKindTiKV, model.NodeKindTiFlash: p = &profiler{ Fetcher: &fetcher.FlameGraph{ - Fetcher: f, - Target: target, + Client: c, + Target: target, }, Writer: &fileWriter{fileNameWithoutExt: fileNameWithoutExt, ext: "svg"}, } case model.NodeKindTiDB, model.NodeKindPD: p = &profiler{ Fetcher: &fetcher.Pprof{ - Fetcher: f, + Client: c, Target: target, FileNameWithoutExt: fileNameWithoutExt, }, diff --git a/pkg/apiserver/profiling/service.go b/pkg/apiserver/profiling/service.go index 00ead28027..68808ea4da 100644 --- a/pkg/apiserver/profiling/service.go +++ b/pkg/apiserver/profiling/service.go @@ -63,14 +63,14 @@ type Service struct { sessionCh chan *StartRequestSession lastTaskGroup *TaskGroup tasks sync.Map - fetcherMap *fetcher.ClientFetcherMap + clientMap *fetcher.ClientMap } -func newService(lc fx.Lifecycle, p ServiceParams, fm *fetcher.ClientFetcherMap) (*Service, error) { +func newService(lc fx.Lifecycle, p ServiceParams, cm *fetcher.ClientMap) (*Service, error) { if err := autoMigrate(p.LocalStore); err != nil { return nil, err } - s := &Service{params: p, fetcherMap: fm} + s := &Service{params: p, clientMap: cm} lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { s.wg.Add(1) @@ -160,7 +160,7 @@ func (s *Service) startGroup(ctx context.Context, req *StartRequest) (*TaskGroup tasks := make([]*Task, 0, len(req.Targets)) for _, target := range req.Targets { - t := NewTask(ctx, taskGroup, target, s.fetcherMap) + t := NewTask(ctx, taskGroup, target, s.clientMap) s.params.LocalStore.Create(t.TaskModel) s.tasks.Store(t.ID, t) tasks = append(tasks, t) From 864b8afb2d58ed67e89df58aa68513e987a0e19c Mon Sep 17 00:00:00 2001 From: Suhaha Date: Fri, 5 Mar 2021 00:08:56 +0800 Subject: [PATCH 06/11] chore(apiserver): fetcher client rename --- pkg/apiserver/profiling/fetcher/client.go | 112 +++++++++++++++++++++ pkg/apiserver/profiling/fetcher/fetcher.go | 94 ----------------- pkg/apiserver/profiling/profile.go | 2 +- pkg/apiserver/profiling/profiler.go | 2 +- pkg/apiserver/profiling/writer.go | 8 +- 5 files changed, 118 insertions(+), 100 deletions(-) create mode 100644 pkg/apiserver/profiling/fetcher/client.go diff --git a/pkg/apiserver/profiling/fetcher/client.go b/pkg/apiserver/profiling/fetcher/client.go new file mode 100644 index 0000000000..c2fbe5baa4 --- /dev/null +++ b/pkg/apiserver/profiling/fetcher/client.go @@ -0,0 +1,112 @@ +// Copyright 2021 PingCAP, Inc. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package fetcher + +import ( + "fmt" + "net/http" + "time" + + "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" + "github.com/pingcap/tidb-dashboard/pkg/config" + "github.com/pingcap/tidb-dashboard/pkg/pd" + "github.com/pingcap/tidb-dashboard/pkg/tidb" + "github.com/pingcap/tidb-dashboard/pkg/tiflash" + "github.com/pingcap/tidb-dashboard/pkg/tikv" +) + +const ( + maxProfilingTimeout = time.Minute * 5 +) + +type ClientFetchOptions struct { + IP string + Port int + Path string +} + +type Client interface { + Fetch(op *ClientFetchOptions) ([]byte, error) +} + +type ClientMap map[model.NodeKind]Client + +func (fm *ClientMap) Get(kind model.NodeKind) (*Client, error) { + f, ok := (*fm)[kind] + if !ok { + return nil, fmt.Errorf("unsupported target %s", kind) + } + return &f, nil +} + +func NewClientMap( + tikvc *tikv.Client, + tidbc *tidb.Client, + pdc *pd.Client, + tiflashc *tiflash.Client, + config *config.Config, +) *ClientMap { + return &ClientMap{ + model.NodeKindTiKV: &tikvClient{ + client: tikvc, + }, + model.NodeKindTiFlash: &tiflashClient{ + client: tiflashc, + }, + model.NodeKindTiDB: &tidbClient{ + client: tidbc, + }, + model.NodeKindPD: &pdClient{ + client: pdc, + statusAPIHTTPScheme: config.GetClusterHTTPScheme(), + }, + } +} + +type tikvClient struct { + client *tikv.Client +} + +func (f *tikvClient) Fetch(op *ClientFetchOptions) ([]byte, error) { + return f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.IP, op.Port, op.Path) +} + +type tiflashClient struct { + client *tiflash.Client +} + +func (f *tiflashClient) Fetch(op *ClientFetchOptions) ([]byte, error) { + return f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.IP, op.Port, op.Path) +} + +type tidbClient struct { + client *tidb.Client +} + +func (f *tidbClient) Fetch(op *ClientFetchOptions) ([]byte, error) { + return f.client.WithStatusAPIAddress(op.IP, op.Port).WithStatusAPITimeout(maxProfilingTimeout).SendGetRequest(op.Path) +} + +type pdClient struct { + client *pd.Client + statusAPIHTTPScheme string +} + +func (f *pdClient) Fetch(op *ClientFetchOptions) ([]byte, error) { + baseURL := fmt.Sprintf("%s://%s:%d", f.statusAPIHTTPScheme, op.IP, op.Port) + f.client.WithBeforeRequest(func(req *http.Request) { + req.Header.Add("PD-Allow-follower-handle", "true") + }) + return f.client.WithTimeout(maxProfilingTimeout).WithBaseURL(baseURL).SendGetRequest(op.Path) +} diff --git a/pkg/apiserver/profiling/fetcher/fetcher.go b/pkg/apiserver/profiling/fetcher/fetcher.go index 4d89b49a29..528bad3a4e 100644 --- a/pkg/apiserver/profiling/fetcher/fetcher.go +++ b/pkg/apiserver/profiling/fetcher/fetcher.go @@ -14,32 +14,9 @@ package fetcher import ( - "fmt" - "net/http" "time" - - "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" - "github.com/pingcap/tidb-dashboard/pkg/config" - "github.com/pingcap/tidb-dashboard/pkg/pd" - "github.com/pingcap/tidb-dashboard/pkg/tidb" - "github.com/pingcap/tidb-dashboard/pkg/tiflash" - "github.com/pingcap/tidb-dashboard/pkg/tikv" -) - -const ( - maxProfilingTimeout = time.Minute * 5 ) -type ClientFetchOptions struct { - IP string - Port int - Path string -} - -type Client interface { - Fetch(op *ClientFetchOptions) ([]byte, error) -} - type ProfileFetchOptions struct { Duration time.Duration } @@ -47,74 +24,3 @@ type ProfileFetchOptions struct { type ProfileFetcher interface { Fetch(op *ProfileFetchOptions) ([]byte, error) } - -type ClientMap map[model.NodeKind]Client - -func (fm *ClientMap) Get(kind model.NodeKind) (*Client, error) { - f, ok := (*fm)[kind] - if !ok { - return nil, fmt.Errorf("unsupported target %s", kind) - } - return &f, nil -} - -func NewClientMap( - tikvClient *tikv.Client, - tidbClient *tidb.Client, - pdClient *pd.Client, - tiflashClient *tiflash.Client, - config *config.Config, -) *ClientMap { - return &ClientMap{ - model.NodeKindTiKV: &tikvFetcher{ - client: tikvClient, - }, - model.NodeKindTiFlash: &tiflashFetcher{ - client: tiflashClient, - }, - model.NodeKindTiDB: &tidbFetcher{ - client: tidbClient, - }, - model.NodeKindPD: &pdFetcher{ - client: pdClient, - statusAPIHTTPScheme: config.GetClusterHTTPScheme(), - }, - } -} - -type tikvFetcher struct { - client *tikv.Client -} - -func (f *tikvFetcher) Fetch(op *ClientFetchOptions) ([]byte, error) { - return f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.IP, op.Port, op.Path) -} - -type tiflashFetcher struct { - client *tiflash.Client -} - -func (f *tiflashFetcher) Fetch(op *ClientFetchOptions) ([]byte, error) { - return f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.IP, op.Port, op.Path) -} - -type tidbFetcher struct { - client *tidb.Client -} - -func (f *tidbFetcher) Fetch(op *ClientFetchOptions) ([]byte, error) { - return f.client.WithStatusAPIAddress(op.IP, op.Port).WithStatusAPITimeout(maxProfilingTimeout).SendGetRequest(op.Path) -} - -type pdFetcher struct { - client *pd.Client - statusAPIHTTPScheme string -} - -func (f *pdFetcher) Fetch(op *ClientFetchOptions) ([]byte, error) { - baseURL := fmt.Sprintf("%s://%s:%d", f.statusAPIHTTPScheme, op.IP, op.Port) - f.client.WithBeforeRequest(func(req *http.Request) { - req.Header.Add("PD-Allow-follower-handle", "true") - }) - return f.client.WithTimeout(maxProfilingTimeout).WithBaseURL(baseURL).SendGetRequest(op.Path) -} diff --git a/pkg/apiserver/profiling/profile.go b/pkg/apiserver/profiling/profile.go index 2e97b6dfd1..fa452fa8aa 100644 --- a/pkg/apiserver/profiling/profile.go +++ b/pkg/apiserver/profiling/profile.go @@ -48,7 +48,7 @@ func profileAndWriteSVG(ctx context.Context, cm *fetcher.ClientMap, target *mode Target: target, FileNameWithoutExt: fileNameWithoutExt, }, - Writer: &graphvizSVGWriter{fileNameWithoutExt: fileNameWithoutExt, ext: graphviz.SVG}, + Writer: &graphvizWriter{fileNameWithoutExt: fileNameWithoutExt, ext: graphviz.SVG}, } default: return "", fmt.Errorf("unsupported target %s", target) diff --git a/pkg/apiserver/profiling/profiler.go b/pkg/apiserver/profiling/profiler.go index cb551313b3..bba71796cf 100644 --- a/pkg/apiserver/profiling/profiler.go +++ b/pkg/apiserver/profiling/profiler.go @@ -1,4 +1,4 @@ -// Copyright 2020 PingCAP, Inc. +// Copyright 2021 PingCAP, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/apiserver/profiling/writer.go b/pkg/apiserver/profiling/writer.go index f9281d150a..0feb9eb6e9 100644 --- a/pkg/apiserver/profiling/writer.go +++ b/pkg/apiserver/profiling/writer.go @@ -1,4 +1,4 @@ -// Copyright 2020 PingCAP, Inc. +// Copyright 2021 PingCAP, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -54,18 +54,18 @@ func (w *fileWriter) Write(p []byte) (string, error) { return path, nil } -type graphvizSVGWriter struct { +type graphvizWriter struct { fileNameWithoutExt string ext graphviz.Format } -func (w *graphvizSVGWriter) Write(b []byte) (string, error) { +func (w *graphvizWriter) Write(b []byte) (string, error) { tmpfile, err := ioutil.TempFile("", w.fileNameWithoutExt) if err != nil { return "", fmt.Errorf("failed to create temp file: %v", err) } defer tmpfile.Close() - tmpPath := fmt.Sprintf("%s.%s", tmpfile.Name(), "svg") + tmpPath := fmt.Sprintf("%s.%s", tmpfile.Name(), w.ext) g := graphviz.New() mu.Lock() From a572945c394a3cc9f0d14473e7d6062f9526fe70 Mon Sep 17 00:00:00 2001 From: Suhaha Date: Fri, 5 Mar 2021 23:40:18 +0800 Subject: [PATCH 07/11] fix(ui): disable row click event before profiling finish --- ui/lib/apps/InstanceProfiling/pages/Detail.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ui/lib/apps/InstanceProfiling/pages/Detail.tsx b/ui/lib/apps/InstanceProfiling/pages/Detail.tsx index ababbc0b45..b465bf57bf 100644 --- a/ui/lib/apps/InstanceProfiling/pages/Detail.tsx +++ b/ui/lib/apps/InstanceProfiling/pages/Detail.tsx @@ -32,9 +32,7 @@ function mapData(data) { return data } -function isFinished(data) { - return data?.task_group_status?.state === 2 -} +const isFinished = (state: number) => state === 2 export default function Page() { const { t } = useTranslation() @@ -43,7 +41,7 @@ export default function Page() { const { data: respData, isLoading, error } = useClientRequestWithPolling( (reqConfig) => client.getInstance().getProfilingGroupDetail(id, reqConfig), { - shouldPoll: (data) => !isFinished(data), + shouldPoll: (data) => !isFinished(data?.task_group_status?.state!), } ) @@ -101,6 +99,9 @@ export default function Page() { const handleRowClick = usePersistFn( async (rec, _idx, _ev: React.MouseEvent) => { + if (!isFinished(rec.state)) { + return + } const res = await client .getInstance() .getActionToken(rec.id, 'single_view') From 882749d4c7e0f582c0ed0f6717cc0a67beb9a0dc Mon Sep 17 00:00:00 2001 From: Suhaha Date: Sat, 13 Mar 2021 00:41:58 +0800 Subject: [PATCH 08/11] chore(profiling): move router register back to module --- pkg/apiserver/apiserver.go | 1 - pkg/apiserver/profiling/module.go | 1 + pkg/apiserver/profiling/router.go | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 8ac6280c96..da4ddc2717 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -136,7 +136,6 @@ func (s *Service) Start(ctx context.Context) error { user.RegisterRouter, info.RegisterRouter, clusterinfo.RegisterRouter, - profiling.RegisterRouter, logsearch.RegisterRouter, slowquery.RegisterRouter, statement.RegisterRouter, diff --git a/pkg/apiserver/profiling/module.go b/pkg/apiserver/profiling/module.go index 2b1f7ac39e..2b5e8f11ff 100644 --- a/pkg/apiserver/profiling/module.go +++ b/pkg/apiserver/profiling/module.go @@ -24,4 +24,5 @@ var Module = fx.Options( fetcher.NewClientMap, newService, ), + fx.Invoke(registerRouter), ) diff --git a/pkg/apiserver/profiling/router.go b/pkg/apiserver/profiling/router.go index 470b8577a9..b8c46be2f3 100644 --- a/pkg/apiserver/profiling/router.go +++ b/pkg/apiserver/profiling/router.go @@ -30,7 +30,7 @@ import ( ) // Register register the handlers to the service. -func RegisterRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) { +func registerRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) { endpoint := r.Group("/profiling") endpoint.GET("/group/list", auth.MWAuthRequired(), s.getGroupList) endpoint.POST("/group/start", auth.MWAuthRequired(), s.handleStartGroup) From 06e189d3ae816bfaacb19a040f7dd9fbea8fdfcd Mon Sep 17 00:00:00 2001 From: Suhaha Date: Wed, 24 Mar 2021 02:16:47 +0800 Subject: [PATCH 09/11] refactor(profiling): using delegate for fetcher --- pkg/apiserver/profiling/fetcher/client.go | 20 +++++------ pkg/apiserver/profiling/fetcher/fetcher.go | 35 ++++++++++++++++++- pkg/apiserver/profiling/fetcher/flamegraph.go | 11 +++--- pkg/apiserver/profiling/fetcher/pprof.go | 15 ++++---- pkg/apiserver/profiling/profile.go | 26 +++++--------- pkg/apiserver/profiling/profiler.go | 19 +++++++--- 6 files changed, 78 insertions(+), 48 deletions(-) diff --git a/pkg/apiserver/profiling/fetcher/client.go b/pkg/apiserver/profiling/fetcher/client.go index c2fbe5baa4..5d3a5e0ab4 100644 --- a/pkg/apiserver/profiling/fetcher/client.go +++ b/pkg/apiserver/profiling/fetcher/client.go @@ -42,33 +42,33 @@ type Client interface { type ClientMap map[model.NodeKind]Client -func (fm *ClientMap) Get(kind model.NodeKind) (*Client, error) { +func (fm *ClientMap) Get(kind model.NodeKind) (Client, error) { f, ok := (*fm)[kind] if !ok { return nil, fmt.Errorf("unsupported target %s", kind) } - return &f, nil + return f, nil } func NewClientMap( - tikvc *tikv.Client, - tidbc *tidb.Client, - pdc *pd.Client, - tiflashc *tiflash.Client, + tikvHttpClient *tikv.Client, + tiflashHttpClient *tiflash.Client, + tidbHttpClient *tidb.Client, + pdHttpClient *pd.Client, config *config.Config, ) *ClientMap { return &ClientMap{ model.NodeKindTiKV: &tikvClient{ - client: tikvc, + client: tikvHttpClient, }, model.NodeKindTiFlash: &tiflashClient{ - client: tiflashc, + client: tiflashHttpClient, }, model.NodeKindTiDB: &tidbClient{ - client: tidbc, + client: tidbHttpClient, }, model.NodeKindPD: &pdClient{ - client: pdc, + client: pdHttpClient, statusAPIHTTPScheme: config.GetClusterHTTPScheme(), }, } diff --git a/pkg/apiserver/profiling/fetcher/fetcher.go b/pkg/apiserver/profiling/fetcher/fetcher.go index 528bad3a4e..f733501ec8 100644 --- a/pkg/apiserver/profiling/fetcher/fetcher.go +++ b/pkg/apiserver/profiling/fetcher/fetcher.go @@ -15,12 +15,45 @@ package fetcher import ( "time" + + "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" ) type ProfileFetchOptions struct { Duration time.Duration } -type ProfileFetcher interface { +type ProfilerFetcher interface { Fetch(op *ProfileFetchOptions) ([]byte, error) } + +type Fetcher interface { + Fetch(client Client, target *model.RequestTargetNode, op *ProfileFetchOptions) ([]byte, error) +} + +type profilerFetcher struct { + fetcher Fetcher + client Client + target *model.RequestTargetNode +} + +func (p *profilerFetcher) Fetch(op *ProfileFetchOptions) ([]byte, error) { + return p.fetcher.Fetch(p.client, p.target, op) +} + +type FetcherFactory struct { + client Client + target *model.RequestTargetNode +} + +func (ff *FetcherFactory) Create(fetcher Fetcher) ProfilerFetcher { + return &profilerFetcher{ + fetcher: fetcher, + client: ff.client, + target: ff.target, + } +} + +func NewFetcherFactory(client Client, target *model.RequestTargetNode) *FetcherFactory { + return &FetcherFactory{client: client, target: target} +} diff --git a/pkg/apiserver/profiling/fetcher/flamegraph.go b/pkg/apiserver/profiling/fetcher/flamegraph.go index 66a6af9989..effd7d77ca 100644 --- a/pkg/apiserver/profiling/fetcher/flamegraph.go +++ b/pkg/apiserver/profiling/fetcher/flamegraph.go @@ -19,14 +19,11 @@ import ( "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" ) -var _ ProfileFetcher = (*FlameGraph)(nil) +var _ Fetcher = (*FlameGraph)(nil) -type FlameGraph struct { - Client *Client - Target *model.RequestTargetNode -} +type FlameGraph struct{} -func (f *FlameGraph) Fetch(op *ProfileFetchOptions) ([]byte, error) { +func (f *FlameGraph) Fetch(client Client, target *model.RequestTargetNode, op *ProfileFetchOptions) ([]byte, error) { path := fmt.Sprintf("/debug/pprof/profile?seconds=%d", op.Duration) - return (*f.Client).Fetch(&ClientFetchOptions{IP: f.Target.IP, Port: f.Target.Port, Path: path}) + return client.Fetch(&ClientFetchOptions{IP: target.IP, Port: target.Port, Path: path}) } diff --git a/pkg/apiserver/profiling/fetcher/pprof.go b/pkg/apiserver/profiling/fetcher/pprof.go index d87e4a33b8..ab70acd7ef 100644 --- a/pkg/apiserver/profiling/fetcher/pprof.go +++ b/pkg/apiserver/profiling/fetcher/pprof.go @@ -30,16 +30,14 @@ import ( var ( _ driver.Fetcher = (*pprofFetcher)(nil) - _ ProfileFetcher = (*Pprof)(nil) + _ Fetcher = (*Pprof)(nil) ) type Pprof struct { - Client *Client - Target *model.RequestTargetNode FileNameWithoutExt string } -func (p *Pprof) Fetch(op *ProfileFetchOptions) (d []byte, err error) { +func (p *Pprof) Fetch(client Client, target *model.RequestTargetNode, op *ProfileFetchOptions) (d []byte, err error) { tmpfile, err := ioutil.TempFile("", p.FileNameWithoutExt) if err != nil { return d, fmt.Errorf("failed to create temp file: %v", err) @@ -55,14 +53,14 @@ func (p *Pprof) Fetch(op *ProfileFetchOptions) (d []byte, err error) { "-output", "dummy", "-seconds", strconv.Itoa(int(op.Duration)), } - address := fmt.Sprintf("%s:%d", p.Target.IP, p.Target.Port) + address := fmt.Sprintf("%s:%d", target.IP, target.Port) args = append(args, address) f := &flagSet{ FlagSet: flag.NewFlagSet("pprof", flag.PanicOnError), args: args, } if err := driver.PProf(&driver.Options{ - Fetch: &pprofFetcher{Pprof: p}, + Fetch: &pprofFetcher{client: client, target: target}, Flagset: f, UI: &blankPprofUI{}, Writer: &oswriter{output: tmpPath}, @@ -110,14 +108,15 @@ func (o *oswriter) Open(name string) (io.WriteCloser, error) { } type pprofFetcher struct { - *Pprof + client Client + target *model.RequestTargetNode } func (f *pprofFetcher) Fetch(src string, duration, timeout time.Duration) (*profile.Profile, string, error) { secs := strconv.Itoa(int(duration / time.Second)) url := "/debug/pprof/profile?seconds=" + secs - resp, err := (*f.Client).Fetch(&ClientFetchOptions{IP: f.Target.IP, Port: f.Target.Port, Path: url}) + resp, err := f.client.Fetch(&ClientFetchOptions{IP: f.target.IP, Port: f.target.Port, Path: url}) if err != nil { return nil, url, err } diff --git a/pkg/apiserver/profiling/profile.go b/pkg/apiserver/profiling/profile.go index fa452fa8aa..bb6845d74f 100644 --- a/pkg/apiserver/profiling/profile.go +++ b/pkg/apiserver/profiling/profile.go @@ -30,29 +30,21 @@ func profileAndWriteSVG(ctx context.Context, cm *fetcher.ClientMap, target *mode return "", err } - var p *profiler + ff := fetcher.NewFetcherFactory(c, target) + var f fetcher.ProfilerFetcher + var w Writer switch target.Kind { case model.NodeKindTiKV, model.NodeKindTiFlash: - p = &profiler{ - Fetcher: &fetcher.FlameGraph{ - Client: c, - Target: target, - }, - Writer: &fileWriter{fileNameWithoutExt: fileNameWithoutExt, ext: "svg"}, - } + f = ff.Create(&fetcher.FlameGraph{}) + w = &fileWriter{fileNameWithoutExt: fileNameWithoutExt, ext: "svg"} case model.NodeKindTiDB, model.NodeKindPD: - p = &profiler{ - Fetcher: &fetcher.Pprof{ - Client: c, - Target: target, - FileNameWithoutExt: fileNameWithoutExt, - }, - Writer: &graphvizWriter{fileNameWithoutExt: fileNameWithoutExt, ext: graphviz.SVG}, - } + f = ff.Create(&fetcher.Pprof{FileNameWithoutExt: fileNameWithoutExt}) + w = &graphvizWriter{fileNameWithoutExt: fileNameWithoutExt, ext: graphviz.SVG} default: return "", fmt.Errorf("unsupported target %s", target) } - return p.Profile(&profileOptions{ProfileFetchOptions: fetcher.ProfileFetchOptions{Duration: time.Duration(profileDurationSecs)}}) + p := newProfiler(f, w) + return p.Profile(&profileOptions{Duration: time.Duration(profileDurationSecs)}) } diff --git a/pkg/apiserver/profiling/profiler.go b/pkg/apiserver/profiling/profiler.go index bba71796cf..576170e76e 100644 --- a/pkg/apiserver/profiling/profiler.go +++ b/pkg/apiserver/profiling/profiler.go @@ -14,23 +14,32 @@ package profiling import ( + "time" + "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" ) type profiler struct { - Fetcher fetcher.ProfileFetcher - Writer Writer + fetcher fetcher.ProfilerFetcher + writer Writer +} + +func newProfiler(fetcher fetcher.ProfilerFetcher, writer Writer) *profiler { + return &profiler{ + fetcher: fetcher, + writer: writer, + } } type profileOptions struct { - fetcher.ProfileFetchOptions + Duration time.Duration } func (p *profiler) Profile(op *profileOptions) (string, error) { - resp, err := p.Fetcher.Fetch(&op.ProfileFetchOptions) + resp, err := p.fetcher.Fetch(&fetcher.ProfileFetchOptions{Duration: op.Duration}) if err != nil { return "", err } - return p.Writer.Write(resp) + return p.writer.Write(resp) } From a6a5c84dbd904235dc026ae93e9e2fc4ae1ec5bf Mon Sep 17 00:00:00 2001 From: Suhaha Date: Wed, 24 Mar 2021 02:25:05 +0800 Subject: [PATCH 10/11] chore: lint --- pkg/apiserver/profiling/fetcher/client.go | 16 ++++++++-------- pkg/apiserver/profiling/fetcher/fetcher.go | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/apiserver/profiling/fetcher/client.go b/pkg/apiserver/profiling/fetcher/client.go index 5d3a5e0ab4..f9a1f3f514 100644 --- a/pkg/apiserver/profiling/fetcher/client.go +++ b/pkg/apiserver/profiling/fetcher/client.go @@ -51,24 +51,24 @@ func (fm *ClientMap) Get(kind model.NodeKind) (Client, error) { } func NewClientMap( - tikvHttpClient *tikv.Client, - tiflashHttpClient *tiflash.Client, - tidbHttpClient *tidb.Client, - pdHttpClient *pd.Client, + tikvHTTPClient *tikv.Client, + tiflashHTTPClient *tiflash.Client, + tidbHTTPClient *tidb.Client, + pdHTTPClient *pd.Client, config *config.Config, ) *ClientMap { return &ClientMap{ model.NodeKindTiKV: &tikvClient{ - client: tikvHttpClient, + client: tikvHTTPClient, }, model.NodeKindTiFlash: &tiflashClient{ - client: tiflashHttpClient, + client: tiflashHTTPClient, }, model.NodeKindTiDB: &tidbClient{ - client: tidbHttpClient, + client: tidbHTTPClient, }, model.NodeKindPD: &pdClient{ - client: pdHttpClient, + client: pdHTTPClient, statusAPIHTTPScheme: config.GetClusterHTTPScheme(), }, } diff --git a/pkg/apiserver/profiling/fetcher/fetcher.go b/pkg/apiserver/profiling/fetcher/fetcher.go index f733501ec8..85968cbf34 100644 --- a/pkg/apiserver/profiling/fetcher/fetcher.go +++ b/pkg/apiserver/profiling/fetcher/fetcher.go @@ -41,12 +41,12 @@ func (p *profilerFetcher) Fetch(op *ProfileFetchOptions) ([]byte, error) { return p.fetcher.Fetch(p.client, p.target, op) } -type FetcherFactory struct { +type Factory struct { client Client target *model.RequestTargetNode } -func (ff *FetcherFactory) Create(fetcher Fetcher) ProfilerFetcher { +func (ff *Factory) Create(fetcher Fetcher) ProfilerFetcher { return &profilerFetcher{ fetcher: fetcher, client: ff.client, @@ -54,6 +54,6 @@ func (ff *FetcherFactory) Create(fetcher Fetcher) ProfilerFetcher { } } -func NewFetcherFactory(client Client, target *model.RequestTargetNode) *FetcherFactory { - return &FetcherFactory{client: client, target: target} +func NewFetcherFactory(client Client, target *model.RequestTargetNode) *Factory { + return &Factory{client: client, target: target} } From d78c66322f9c37962b5f07b34752b3cf4f948b55 Mon Sep 17 00:00:00 2001 From: Suhaha Date: Wed, 24 Mar 2021 02:39:28 +0800 Subject: [PATCH 11/11] refactor(profiling): update client-map's controlling access --- pkg/apiserver/profiling/clientmap.go | 115 ++++++++++++++++++++++ pkg/apiserver/profiling/fetcher/client.go | 88 ----------------- pkg/apiserver/profiling/model.go | 5 +- pkg/apiserver/profiling/module.go | 4 +- pkg/apiserver/profiling/profile.go | 2 +- pkg/apiserver/profiling/service.go | 5 +- 6 files changed, 121 insertions(+), 98 deletions(-) create mode 100644 pkg/apiserver/profiling/clientmap.go diff --git a/pkg/apiserver/profiling/clientmap.go b/pkg/apiserver/profiling/clientmap.go new file mode 100644 index 0000000000..5aaa18aac6 --- /dev/null +++ b/pkg/apiserver/profiling/clientmap.go @@ -0,0 +1,115 @@ +// Copyright 2021 PingCAP, Inc. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package profiling + +import ( + "fmt" + "net/http" + "time" + + "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" + "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" + "github.com/pingcap/tidb-dashboard/pkg/config" + "github.com/pingcap/tidb-dashboard/pkg/pd" + "github.com/pingcap/tidb-dashboard/pkg/tidb" + "github.com/pingcap/tidb-dashboard/pkg/tiflash" + "github.com/pingcap/tidb-dashboard/pkg/tikv" +) + +const ( + maxProfilingTimeout = time.Minute * 5 +) + +type clientMap map[model.NodeKind]fetcher.Client + +func (fm *clientMap) Get(kind model.NodeKind) (fetcher.Client, error) { + f, ok := (*fm)[kind] + if !ok { + return nil, fmt.Errorf("unsupported target %s", kind) + } + return f, nil +} + +func newClientMap( + tikvHTTPClient *tikv.Client, + tiflashHTTPClient *tiflash.Client, + tidbHTTPClient *tidb.Client, + pdHTTPClient *pd.Client, + config *config.Config, +) *clientMap { + return &clientMap{ + model.NodeKindTiKV: &tikvClient{ + client: tikvHTTPClient, + }, + model.NodeKindTiFlash: &tiflashClient{ + client: tiflashHTTPClient, + }, + model.NodeKindTiDB: &tidbClient{ + client: tidbHTTPClient, + }, + model.NodeKindPD: &pdClient{ + client: pdHTTPClient, + statusAPIHTTPScheme: config.GetClusterHTTPScheme(), + }, + } +} + +// tikv +var _ fetcher.Client = (*tikvClient)(nil) + +type tikvClient struct { + client *tikv.Client +} + +func (f *tikvClient) Fetch(op *fetcher.ClientFetchOptions) ([]byte, error) { + return f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.IP, op.Port, op.Path) +} + +// tiflash +var _ fetcher.Client = (*tiflashClient)(nil) + +type tiflashClient struct { + client *tiflash.Client +} + +func (f *tiflashClient) Fetch(op *fetcher.ClientFetchOptions) ([]byte, error) { + return f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.IP, op.Port, op.Path) +} + +// tidb +var _ fetcher.Client = (*tidbClient)(nil) + +type tidbClient struct { + client *tidb.Client +} + +func (f *tidbClient) Fetch(op *fetcher.ClientFetchOptions) ([]byte, error) { + return f.client.WithStatusAPIAddress(op.IP, op.Port).WithStatusAPITimeout(maxProfilingTimeout).SendGetRequest(op.Path) +} + +// pd +var _ fetcher.Client = (*pdClient)(nil) + +type pdClient struct { + client *pd.Client + statusAPIHTTPScheme string +} + +func (f *pdClient) Fetch(op *fetcher.ClientFetchOptions) ([]byte, error) { + baseURL := fmt.Sprintf("%s://%s:%d", f.statusAPIHTTPScheme, op.IP, op.Port) + f.client.WithBeforeRequest(func(req *http.Request) { + req.Header.Add("PD-Allow-follower-handle", "true") + }) + return f.client.WithTimeout(maxProfilingTimeout).WithBaseURL(baseURL).SendGetRequest(op.Path) +} diff --git a/pkg/apiserver/profiling/fetcher/client.go b/pkg/apiserver/profiling/fetcher/client.go index f9a1f3f514..8145aa374c 100644 --- a/pkg/apiserver/profiling/fetcher/client.go +++ b/pkg/apiserver/profiling/fetcher/client.go @@ -13,23 +13,6 @@ package fetcher -import ( - "fmt" - "net/http" - "time" - - "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" - "github.com/pingcap/tidb-dashboard/pkg/config" - "github.com/pingcap/tidb-dashboard/pkg/pd" - "github.com/pingcap/tidb-dashboard/pkg/tidb" - "github.com/pingcap/tidb-dashboard/pkg/tiflash" - "github.com/pingcap/tidb-dashboard/pkg/tikv" -) - -const ( - maxProfilingTimeout = time.Minute * 5 -) - type ClientFetchOptions struct { IP string Port int @@ -39,74 +22,3 @@ type ClientFetchOptions struct { type Client interface { Fetch(op *ClientFetchOptions) ([]byte, error) } - -type ClientMap map[model.NodeKind]Client - -func (fm *ClientMap) Get(kind model.NodeKind) (Client, error) { - f, ok := (*fm)[kind] - if !ok { - return nil, fmt.Errorf("unsupported target %s", kind) - } - return f, nil -} - -func NewClientMap( - tikvHTTPClient *tikv.Client, - tiflashHTTPClient *tiflash.Client, - tidbHTTPClient *tidb.Client, - pdHTTPClient *pd.Client, - config *config.Config, -) *ClientMap { - return &ClientMap{ - model.NodeKindTiKV: &tikvClient{ - client: tikvHTTPClient, - }, - model.NodeKindTiFlash: &tiflashClient{ - client: tiflashHTTPClient, - }, - model.NodeKindTiDB: &tidbClient{ - client: tidbHTTPClient, - }, - model.NodeKindPD: &pdClient{ - client: pdHTTPClient, - statusAPIHTTPScheme: config.GetClusterHTTPScheme(), - }, - } -} - -type tikvClient struct { - client *tikv.Client -} - -func (f *tikvClient) Fetch(op *ClientFetchOptions) ([]byte, error) { - return f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.IP, op.Port, op.Path) -} - -type tiflashClient struct { - client *tiflash.Client -} - -func (f *tiflashClient) Fetch(op *ClientFetchOptions) ([]byte, error) { - return f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.IP, op.Port, op.Path) -} - -type tidbClient struct { - client *tidb.Client -} - -func (f *tidbClient) Fetch(op *ClientFetchOptions) ([]byte, error) { - return f.client.WithStatusAPIAddress(op.IP, op.Port).WithStatusAPITimeout(maxProfilingTimeout).SendGetRequest(op.Path) -} - -type pdClient struct { - client *pd.Client - statusAPIHTTPScheme string -} - -func (f *pdClient) Fetch(op *ClientFetchOptions) ([]byte, error) { - baseURL := fmt.Sprintf("%s://%s:%d", f.statusAPIHTTPScheme, op.IP, op.Port) - f.client.WithBeforeRequest(func(req *http.Request) { - req.Header.Add("PD-Allow-follower-handle", "true") - }) - return f.client.WithTimeout(maxProfilingTimeout).WithBaseURL(baseURL).SendGetRequest(op.Path) -} diff --git a/pkg/apiserver/profiling/model.go b/pkg/apiserver/profiling/model.go index 9e3b787fb7..cdb7065b58 100644 --- a/pkg/apiserver/profiling/model.go +++ b/pkg/apiserver/profiling/model.go @@ -19,7 +19,6 @@ import ( "time" "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" - "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" "github.com/pingcap/tidb-dashboard/pkg/dbstore" ) @@ -73,11 +72,11 @@ type Task struct { ctx context.Context cancel context.CancelFunc taskGroup *TaskGroup - clientMap *fetcher.ClientMap + clientMap *clientMap } // NewTask creates a new profiling task. -func NewTask(ctx context.Context, taskGroup *TaskGroup, target model.RequestTargetNode, cm *fetcher.ClientMap) *Task { +func NewTask(ctx context.Context, taskGroup *TaskGroup, target model.RequestTargetNode, cm *clientMap) *Task { ctx, cancel := context.WithCancel(ctx) return &Task{ TaskModel: &TaskModel{ diff --git a/pkg/apiserver/profiling/module.go b/pkg/apiserver/profiling/module.go index 2b5e8f11ff..046274ad46 100644 --- a/pkg/apiserver/profiling/module.go +++ b/pkg/apiserver/profiling/module.go @@ -15,13 +15,11 @@ package profiling import ( "go.uber.org/fx" - - "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" ) var Module = fx.Options( fx.Provide( - fetcher.NewClientMap, + newClientMap, newService, ), fx.Invoke(registerRouter), diff --git a/pkg/apiserver/profiling/profile.go b/pkg/apiserver/profiling/profile.go index bb6845d74f..9f31d73960 100644 --- a/pkg/apiserver/profiling/profile.go +++ b/pkg/apiserver/profiling/profile.go @@ -24,7 +24,7 @@ import ( "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" ) -func profileAndWriteSVG(ctx context.Context, cm *fetcher.ClientMap, target *model.RequestTargetNode, fileNameWithoutExt string, profileDurationSecs uint) (string, error) { +func profileAndWriteSVG(ctx context.Context, cm *clientMap, target *model.RequestTargetNode, fileNameWithoutExt string, profileDurationSecs uint) (string, error) { c, err := cm.Get(target.Kind) if err != nil { return "", err diff --git a/pkg/apiserver/profiling/service.go b/pkg/apiserver/profiling/service.go index 68808ea4da..bde274f9fc 100644 --- a/pkg/apiserver/profiling/service.go +++ b/pkg/apiserver/profiling/service.go @@ -24,7 +24,6 @@ import ( "go.uber.org/zap" "github.com/pingcap/tidb-dashboard/pkg/apiserver/model" - "github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling/fetcher" "github.com/pingcap/tidb-dashboard/pkg/config" "github.com/pingcap/tidb-dashboard/pkg/dbstore" ) @@ -63,10 +62,10 @@ type Service struct { sessionCh chan *StartRequestSession lastTaskGroup *TaskGroup tasks sync.Map - clientMap *fetcher.ClientMap + clientMap *clientMap } -func newService(lc fx.Lifecycle, p ServiceParams, cm *fetcher.ClientMap) (*Service, error) { +func newService(lc fx.Lifecycle, p ServiceParams, cm *clientMap) (*Service, error) { if err := autoMigrate(p.LocalStore); err != nil { return nil, err }