You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

140 lines
2.8 KiB

  1. // Copyright (c) 2015-2023 MinIO, Inc.
  2. //
  3. // This file is part of MinIO Object Storage stack
  4. //
  5. // This program is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU Affero General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // This program is distributed in the hope that it will be useful
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU Affero General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU Affero General Public License
  16. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. package main
  18. import (
  19. "bufio"
  20. "bytes"
  21. "flag"
  22. "fmt"
  23. "log"
  24. "math"
  25. "os"
  26. "path"
  27. "regexp"
  28. "strconv"
  29. "strings"
  30. "time"
  31. )
  32. var (
  33. re *regexp.Regexp
  34. goTime, margin time.Duration
  35. )
  36. func init() {
  37. re = regexp.MustCompile(`^goroutine [0-9]+ \[[^,]+(, ([0-9]+) minutes)?\]:$`)
  38. flag.DurationVar(&goTime, "time", 0, "goroutine block age")
  39. flag.DurationVar(&margin, "margin", 0, "margin time")
  40. }
  41. func parseGoroutineType2(path string) (map[time.Duration][]string, error) {
  42. f, err := os.Open(path)
  43. if err != nil {
  44. return nil, err
  45. }
  46. bf := bytes.Buffer{}
  47. save := func(s string) {
  48. bf.WriteString(s + "\n")
  49. }
  50. reset := func() {
  51. bf.Reset()
  52. }
  53. ret := make(map[time.Duration][]string)
  54. s := bufio.NewScanner(f)
  55. s.Split(bufio.ScanLines)
  56. var (
  57. t time.Duration
  58. skip, record bool
  59. )
  60. for s.Scan() {
  61. line := s.Text()
  62. switch {
  63. case skip && line != "":
  64. case skip && line == "":
  65. skip = false
  66. case record && line == "":
  67. record = false
  68. ret[t] = append(ret[t], bf.String())
  69. reset()
  70. case record:
  71. save(line)
  72. default:
  73. z := re.FindStringSubmatch(line)
  74. if len(z) == 3 {
  75. if z[2] != "" {
  76. a, _ := strconv.Atoi(z[2])
  77. t = time.Duration(a) * time.Minute
  78. save(line)
  79. record = true
  80. } else {
  81. skip = true
  82. }
  83. }
  84. }
  85. }
  86. return ret, nil
  87. }
  88. const helpUsage = `
  89. At least one argument is required to run this tool.
  90. EXAMPLE:
  91. ./pprofgoparser --time 50m --margin 1m /path/to/*-goroutines-before,debug=2.txt
  92. `
  93. func main() {
  94. flag.Parse()
  95. if len(flag.Args()) == 0 {
  96. log.Fatal(helpUsage)
  97. }
  98. for _, arg := range flag.Args() {
  99. if !strings.HasSuffix(arg, "-goroutines-before,debug=2.txt") {
  100. continue
  101. }
  102. r, err := parseGoroutineType2(arg)
  103. if err != nil {
  104. log.Fatal(err)
  105. }
  106. profFName := path.Base(arg)
  107. fmt.Println(strings.Repeat("=", len(profFName)))
  108. fmt.Println(profFName)
  109. fmt.Println(strings.Repeat("=", len(profFName)))
  110. fmt.Println("")
  111. for t, stacks := range r {
  112. if goTime == 0 || math.Abs(float64(t)-float64(goTime)) <= float64(margin) {
  113. for _, stack := range stacks {
  114. fmt.Println(stack)
  115. }
  116. }
  117. }
  118. }
  119. }