// 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, less, margin time.Duration ) func init() { re = regexp.MustCompile(`^goroutine [0-9]+ \[[^,]+(, ([0-9]+) minutes)?\]:$`) flag.DurationVar(&less, "less", 0, "goroutine waiting less than the specified time") flag.DurationVar(&goTime, "time", 0, "goroutine waiting for exactly the specified time") 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 less != 0 && t >= less { continue } if goTime == 0 || math.Abs(float64(t)-float64(goTime)) <= float64(margin) { for _, stack := range stacks { fmt.Println(stack) } } } } }