pprof.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. // Copyright 2019 Yunion
  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. package misc
  15. import (
  16. "fmt"
  17. "io"
  18. "io/ioutil"
  19. "os"
  20. "strings"
  21. "syscall"
  22. "yunion.io/x/jsonutils"
  23. "yunion.io/x/pkg/errors"
  24. "yunion.io/x/pkg/util/httputils"
  25. "yunion.io/x/pkg/util/netutils"
  26. "yunion.io/x/pkg/util/signalutils"
  27. "yunion.io/x/onecloud/pkg/mcclient"
  28. "yunion.io/x/onecloud/pkg/mcclient/modules"
  29. "yunion.io/x/onecloud/pkg/util/procutils"
  30. )
  31. func init() {
  32. type TraceOptions struct {
  33. Second int `help:"pprof seconds" short-token:"s" default:"15"`
  34. Service string `help:"Service type"`
  35. Address string `help:"Service listen address"`
  36. Gc bool `help:"run GC before taking the heap sample"`
  37. Output string `help:"Save pprof file to specified path instead of opening with go tool" short-token:"o"`
  38. }
  39. writeReaderToFile := func(input io.Reader, file *os.File) error {
  40. if _, err := io.Copy(file, input); err != nil {
  41. return err
  42. }
  43. return nil
  44. }
  45. downloadToFile := func(input io.Reader, filepath string) error {
  46. file, err := os.Create(filepath)
  47. if err != nil {
  48. return err
  49. }
  50. defer file.Close()
  51. return writeReaderToFile(input, file)
  52. }
  53. downloadToTemp := func(input io.Reader, pattern string) (string, error) {
  54. tmpfile, err := ioutil.TempFile("", pattern)
  55. if err != nil {
  56. return "", err
  57. }
  58. defer tmpfile.Close()
  59. if err := writeReaderToFile(input, tmpfile); err != nil {
  60. return "", err
  61. }
  62. return tmpfile.Name(), nil
  63. }
  64. pprofRun := func(s *mcclient.ClientSession, opts *TraceOptions, pType string, args ...string) error {
  65. var (
  66. src io.Reader
  67. err error
  68. svcUrl string
  69. )
  70. if len(opts.Service) > 0 {
  71. svcUrl, err = s.GetServiceURL(opts.Service, "", httputils.GET)
  72. if err != nil {
  73. return errors.Wrapf(err, "get service %s url", opts.Service)
  74. }
  75. } else if len(opts.Address) > 0 {
  76. svcUrl = opts.Address
  77. } else {
  78. return fmt.Errorf("no service address provide")
  79. }
  80. if opts.Service == "identity" {
  81. svcUrl = strings.TrimSuffix(svcUrl, "/v3")
  82. }
  83. params := jsonutils.NewDict()
  84. params.Add(jsonutils.NewInt(int64(opts.Second)), "seconds")
  85. if pType == "heap" && opts.Gc {
  86. params.Add(jsonutils.JSONTrue, "gc")
  87. }
  88. src, err = modules.GetNamedAddressPProfByType(s, svcUrl, pType, params)
  89. if err != nil {
  90. return err
  91. }
  92. // If output path is specified, save file and return
  93. if len(opts.Output) > 0 {
  94. if err := downloadToFile(src, opts.Output); err != nil {
  95. return errors.Wrapf(err, "save pprof file to %s", opts.Output)
  96. }
  97. fmt.Printf("pprof file saved to: %s\n", opts.Output)
  98. return nil
  99. }
  100. tempfile, err := downloadToTemp(src, pType)
  101. if err != nil {
  102. return err
  103. }
  104. defer func() { os.Remove(tempfile) }()
  105. signalutils.RegisterSignal(func() {
  106. os.Remove(tempfile)
  107. os.Exit(0)
  108. }, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
  109. signalutils.StartTrap()
  110. argv := []string{"tool"}
  111. argv = append(argv, args...)
  112. argv = append(argv, tempfile)
  113. cmd := procutils.NewCommand("go", argv...)
  114. if err := cmd.Run(); err != nil {
  115. return err
  116. }
  117. return nil
  118. }
  119. R(&TraceOptions{}, "pprof-trace", "pprof trace of backend service", func(s *mcclient.ClientSession, args *TraceOptions) error {
  120. // A trace of execution of the current program
  121. return pprofRun(s, args, "trace", "trace")
  122. })
  123. for _, kind := range []string{
  124. // A sampling of all past memory allocations
  125. "allocs",
  126. // Stack traces that led to blocking on synchronization primitives
  127. "block",
  128. // The command line invocation of the current program
  129. "cmdline",
  130. // Stack traces of all current goroutines
  131. "goroutine",
  132. // A sampling of memory allocations of live objects
  133. "heap",
  134. // Stack straces of holders of contended mutexes
  135. "mutex",
  136. // CPU profile
  137. "profile",
  138. // Stack traces that led to the creation of new OS threads
  139. "threadcreate",
  140. } {
  141. pType := kind
  142. R(&TraceOptions{}, fmt.Sprintf("pprof-%s", pType), fmt.Sprintf("pprof %s of backend service", pType), func(s *mcclient.ClientSession, args *TraceOptions) error {
  143. port, err := netutils.GetFreePort()
  144. if err != nil {
  145. return err
  146. }
  147. return pprofRun(s, args, pType, "pprof", fmt.Sprintf("-http=:%d", port))
  148. })
  149. }
  150. }