diff --git a/docs/debugging/pprofgoparser/main.go b/docs/debugging/pprofgoparser/main.go new file mode 100644 index 000000000..becb1d740 --- /dev/null +++ b/docs/debugging/pprofgoparser/main.go @@ -0,0 +1,140 @@ +// Copyright (c) 2015-2023 MinIO, Inc. +// +// This file is part of MinIO Object Storage stack +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package main + +import ( + "bufio" + "bytes" + "flag" + "fmt" + "log" + "math" + "os" + "path" + "regexp" + "strconv" + "strings" + "time" +) + +var ( + re *regexp.Regexp + goTime, margin time.Duration +) + +func init() { + re = regexp.MustCompile(`^goroutine [0-9]+ \[[^,]+(, ([0-9]+) minutes)?\]:$`) + + flag.DurationVar(&goTime, "time", 0, "goroutine block age") + flag.DurationVar(&margin, "margin", 0, "margin time") +} + +func parseGoroutineType2(path string) (map[time.Duration][]string, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + + bf := bytes.Buffer{} + + save := func(s string) { + bf.WriteString(s + "\n") + } + reset := func() { + bf.Reset() + } + + ret := make(map[time.Duration][]string) + + s := bufio.NewScanner(f) + s.Split(bufio.ScanLines) + + var ( + t time.Duration + skip, record bool + ) + + for s.Scan() { + line := s.Text() + switch { + case skip && line != "": + case skip && line == "": + skip = false + case record && line == "": + record = false + ret[t] = append(ret[t], bf.String()) + reset() + case record: + save(line) + default: + z := re.FindStringSubmatch(line) + if len(z) == 3 { + if z[2] != "" { + a, _ := strconv.Atoi(z[2]) + t = time.Duration(a) * time.Minute + save(line) + record = true + } else { + skip = true + } + } + } + } + + return ret, nil +} + +const helpUsage = ` + +At least one argument is required to run this tool. + +EXAMPLE: + ./pprofgoparser --time 50m --margin 1m /path/to/*-goroutines-before,debug=2.txt +` + +func main() { + flag.Parse() + + if len(flag.Args()) == 0 { + log.Fatal(helpUsage) + } + + for _, arg := range flag.Args() { + if !strings.HasSuffix(arg, "-goroutines-before,debug=2.txt") { + continue + } + r, err := parseGoroutineType2(arg) + if err != nil { + log.Fatal(err) + } + + profFName := path.Base(arg) + fmt.Println(strings.Repeat("=", len(profFName))) + fmt.Println(profFName) + fmt.Println(strings.Repeat("=", len(profFName))) + fmt.Println("") + + for t, stacks := range r { + if goTime == 0 || math.Abs(float64(t)-float64(goTime)) <= float64(margin) { + for _, stack := range stacks { + fmt.Println(stack) + } + } + } + } +}