github.com/matrixorigin/matrixone@v1.2.0/cmd/mo-debug/goroutine.go (about) 1 // Copyright 2023 Matrix Origin 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 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "bufio" 19 "bytes" 20 "fmt" 21 "io" 22 "math" 23 "net/http" 24 "os" 25 "strings" 26 27 "github.com/fagongzi/util/format" 28 "github.com/matrixorigin/matrixone/pkg/util/debug/goroutine" 29 "github.com/spf13/cobra" 30 ) 31 32 var ( 33 parsed []goroutine.Goroutine 34 targets []goroutine.Goroutine 35 groupRes goroutine.AnalyzeResult 36 analyzer = goroutine.GetAnalyzer() 37 ) 38 39 var ( 40 goroutineCMD = &cobra.Command{ 41 Use: "goroutine", 42 Short: "goroutine tool", 43 Long: "MO debug tool. Helps to analyze MO problems like Goroutine, Txn, etc.", 44 SuggestFor: []string{"goroutine"}, 45 Run: handleGoroutineCommand, 46 } 47 ) 48 49 func handleGoroutineCommand( 50 cmd *cobra.Command, 51 args []string) { 52 53 scanner := bufio.NewScanner(os.Stdin) 54 for { 55 fmt.Printf(">>> ") 56 if scanner.Scan() { 57 line := strings.ToLower(strings.TrimSpace(scanner.Text())) 58 if strings.HasPrefix(line, "parse") { 59 handleParse(line) 60 } else if strings.HasPrefix(line, "filter") { 61 handleFilter(line) 62 } else if strings.HasPrefix(line, "top") { 63 handleTop(line) 64 } else if strings.HasPrefix(line, "dump") { 65 handleDump(line) 66 } else { 67 switch line { 68 case "count": 69 fmt.Printf("%d\n", len(targets)) 70 case "reset": 71 resetTargets() 72 case "group": 73 handleGroup() 74 case "help": 75 handleHelp() 76 case "exit": 77 return 78 default: 79 handleHelp() 80 } 81 } 82 continue 83 } 84 return 85 } 86 } 87 88 func handleHelp() { 89 fmt.Println("command list") 90 fmt.Println("\t1. parse [file|url], parse goroutines from file or url") 91 fmt.Println("\t2. filter [!]str, filter goroutines which contains str, ! means not, str can be any string in goroutine's stack") 92 fmt.Println("\t3. reset, drop all filter conditions") 93 fmt.Println("\t4. count, show current goroutines count") 94 fmt.Println("\t5. group, group goroutines") 95 fmt.Println("\t6. top n [groupIndex subIndex], show top n(0 means print all groups) of group result, groupIndex and subIndex are used to control print full goroutine stack") 96 fmt.Println("\t7. dump file, dump group result to file") 97 fmt.Println("\t8. exit, exit.") 98 } 99 100 func handleParse(line string) { 101 from := strings.TrimSpace(line[6:]) 102 fn := getFromFile 103 if strings.HasPrefix(from, "http") { 104 fn = getFromURL 105 } 106 107 data, err := fn(from) 108 if err != nil { 109 fmt.Println(err.Error()) 110 return 111 } 112 parsed = analyzer.Parse(data) 113 fmt.Printf("parse %d goroutines from %s\n", 114 len(parsed), 115 strings.TrimSpace(line[6:])) 116 117 resetTargets() 118 } 119 120 func handleFilter(line string) { 121 condition := strings.TrimSpace(line[7:]) 122 not := condition[0] == '!' 123 if not { 124 condition = condition[1:] 125 } 126 127 newTargets := targets[:0] 128 for _, g := range targets { 129 has := g.Has(condition) 130 if not { 131 has = !has 132 } 133 if has { 134 newTargets = append(newTargets, g) 135 } 136 } 137 fmt.Printf("%d -> %d, filter by (%s)\n", len(targets), len(newTargets), line[7:]) 138 targets = newTargets 139 } 140 141 func handleTop(line string) { 142 if groupRes.GroupCount() == 0 { 143 fmt.Println("no group result") 144 return 145 } 146 147 var fields []string 148 var err error 149 top := 0 150 if len(line) > 4 { 151 fields = strings.Split(strings.TrimSpace(line[4:]), " ") 152 if len(fields) != 1 && len(fields) != 3 { 153 fmt.Println("invalid top command.") 154 return 155 } 156 157 top, err = format.ParseStringInt(fields[0]) 158 if err != nil { 159 fmt.Println(err.Error()) 160 return 161 } 162 } 163 164 if top == 0 || 165 top >= groupRes.GroupCount() { 166 top = groupRes.GroupCount() 167 } 168 169 groupIdx, subIndex := -1, -1 170 if len(fields) == 3 { 171 groupIdx, err = format.ParseStringInt(fields[1]) 172 if err != nil { 173 fmt.Println(err.Error()) 174 return 175 } 176 177 subIndex, err = format.ParseStringInt(fields[2]) 178 if err != nil { 179 fmt.Println(err.Error()) 180 return 181 } 182 } 183 184 fmt.Println(groupRes.Display(top, 185 func(i, j int) (bool, bool) { 186 return false, groupIdx == i && subIndex == j 187 })) 188 } 189 190 func handleGroup() { 191 groupRes = analyzer.GroupAnalyze(targets) 192 fmt.Printf("%d groups\n", groupRes.GroupCount()) 193 } 194 195 func handleDump(line string) { 196 file, err := os.OpenFile( 197 strings.TrimSpace(line[4:]), 198 os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 199 0644) 200 if err != nil { 201 fmt.Println(err.Error()) 202 return 203 } 204 defer file.Close() 205 206 _, err = file.WriteString( 207 groupRes.Display( 208 math.MaxInt, 209 func(i, j int) (bool, bool) { 210 return false, true 211 }), 212 ) 213 if err != nil { 214 fmt.Println(err.Error()) 215 } 216 } 217 218 func resetTargets() { 219 targets = append(targets[:0], parsed...) 220 } 221 222 func getFromFile(file string) ([]byte, error) { 223 return os.ReadFile(file) 224 } 225 226 func getFromURL(url string) ([]byte, error) { 227 resp, err := http.Get(url) 228 if err != nil { 229 return nil, err 230 } 231 defer resp.Body.Close() 232 if resp.StatusCode != http.StatusOK { 233 return nil, 234 fmt.Errorf("get goroutine from url failed, status code: %d", resp.StatusCode) 235 } 236 237 var buf bytes.Buffer 238 _, err = io.CopyBuffer(&buf, resp.Body, make([]byte, 1024*64)) 239 if err != nil { 240 return nil, err 241 } 242 return buf.Bytes(), nil 243 }