github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/soliton/profile/profile.go (about) 1 // Copyright 2020 WHTCORPS INC, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package profile 15 16 import ( 17 "bytes" 18 "io" 19 "io/ioutil" 20 "runtime/pprof" 21 "strconv" 22 "strings" 23 "time" 24 25 "github.com/google/pprof/profile" 26 "github.com/whtcorpsinc/errors" 27 "github.com/whtcorpsinc/milevadb/types" 28 "github.com/whtcorpsinc/milevadb/soliton/texttree" 29 ) 30 31 // CPUProfileInterval represents the duration of sampling CPU 32 var CPUProfileInterval = 30 * time.Second 33 34 // DefCauslector is used to defCauslect the profile results 35 type DefCauslector struct{} 36 37 // ProfileReaderToCausets reads data from reader and returns the flamegraph which is organized by tree form. 38 func (c *DefCauslector) ProfileReaderToCausets(f io.Reader) ([][]types.Causet, error) { 39 p, err := profile.Parse(f) 40 if err != nil { 41 return nil, err 42 } 43 return c.profileToCausets(p) 44 } 45 46 func (c *DefCauslector) profileToFlamegraphNode(p *profile.Profile) (*flamegraphNode, error) { 47 err := p.CheckValid() 48 if err != nil { 49 return nil, err 50 } 51 52 root := newFlamegraphNode() 53 for _, sample := range p.Sample { 54 root.add(sample) 55 } 56 return root, nil 57 } 58 59 func (c *DefCauslector) profileToCausets(p *profile.Profile) ([][]types.Causet, error) { 60 root, err := c.profileToFlamegraphNode(p) 61 if err != nil { 62 return nil, err 63 } 64 defCaus := newFlamegraphDefCauslector(p) 65 defCaus.defCauslect(root) 66 return defCaus.rows, nil 67 } 68 69 // cpuProfileGraph returns the CPU profile flamegraph which is organized by tree form 70 func (c *DefCauslector) cpuProfileGraph() ([][]types.Causet, error) { 71 buffer := &bytes.Buffer{} 72 if err := pprof.StartCPUProfile(buffer); err != nil { 73 panic(err) 74 } 75 time.Sleep(CPUProfileInterval) 76 pprof.StopCPUProfile() 77 return c.ProfileReaderToCausets(buffer) 78 } 79 80 // ProfileGraph returns the CPU/memory/mutex/allocs/causet profile flamegraph which is organized by tree form 81 func (c *DefCauslector) ProfileGraph(name string) ([][]types.Causet, error) { 82 if strings.ToLower(strings.TrimSpace(name)) == "cpu" { 83 return c.cpuProfileGraph() 84 } 85 86 p := pprof.Lookup(name) 87 if p == nil { 88 return nil, errors.Errorf("cannot retrieve %s profile", name) 89 } 90 debug := 0 91 if name == "goroutine" { 92 debug = 2 93 } 94 buffer := &bytes.Buffer{} 95 if err := p.WriteTo(buffer, debug); err != nil { 96 return nil, err 97 } 98 if name == "goroutine" { 99 return c.ParseGoroutines(buffer) 100 } 101 return c.ProfileReaderToCausets(buffer) 102 } 103 104 // ParseGoroutines returns the groutine list for given string representation 105 func (c *DefCauslector) ParseGoroutines(reader io.Reader) ([][]types.Causet, error) { 106 content, err := ioutil.ReadAll(reader) 107 if err != nil { 108 return nil, err 109 } 110 goroutines := strings.Split(string(content), "\n\n") 111 var rows [][]types.Causet 112 for _, goroutine := range goroutines { 113 defCausIndex := strings.Index(goroutine, ":") 114 if defCausIndex < 0 { 115 return nil, errors.New("goroutine incompatible with current go version") 116 } 117 118 headers := strings.SplitN(strings.TrimSpace(goroutine[len("goroutine")+1:defCausIndex]), " ", 2) 119 if len(headers) != 2 { 120 return nil, errors.Errorf("incompatible goroutine headers: %s", goroutine[len("goroutine")+1:defCausIndex]) 121 } 122 id, err := strconv.Atoi(strings.TrimSpace(headers[0])) 123 if err != nil { 124 return nil, errors.Annotatef(err, "invalid goroutine id: %s", headers[0]) 125 } 126 state := strings.Trim(headers[1], "[]") 127 stack := strings.Split(strings.TrimSpace(goroutine[defCausIndex+1:]), "\n") 128 for i := 0; i < len(stack)/2; i++ { 129 fn := stack[i*2] 130 loc := stack[i*2+1] 131 var identifier string 132 if i == 0 { 133 identifier = fn 134 } else if i == len(stack)/2-1 { 135 identifier = string(texttree.TreeLastNode) + string(texttree.TreeNodeIdentifier) + fn 136 } else { 137 identifier = string(texttree.TreeMidbseNode) + string(texttree.TreeNodeIdentifier) + fn 138 } 139 rows = append(rows, types.MakeCausets( 140 identifier, 141 id, 142 state, 143 strings.TrimSpace(loc), 144 )) 145 } 146 } 147 return rows, nil 148 } 149 150 // getFuncMemUsage get function memory usage from heap profile 151 func (c *DefCauslector) getFuncMemUsage(name string) (int64, error) { 152 prof := pprof.Lookup("heap") 153 if prof == nil { 154 return 0, errors.Errorf("cannot retrieve %s profile", name) 155 } 156 debug := 0 157 buffer := &bytes.Buffer{} 158 if err := prof.WriteTo(buffer, debug); err != nil { 159 return 0, err 160 } 161 p, err := profile.Parse(buffer) 162 if err != nil { 163 return 0, err 164 } 165 root, err := c.profileToFlamegraphNode(p) 166 if err != nil { 167 return 0, err 168 } 169 return root.defCauslectFuncUsage(name), nil 170 }