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  }