vitess.io/vitess@v0.16.2/go/vt/servenv/pprof.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package servenv
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"os/signal"
    24  	"path/filepath"
    25  	"runtime"
    26  	"runtime/pprof"
    27  	"runtime/trace"
    28  	"strconv"
    29  	"strings"
    30  	"sync/atomic"
    31  	"syscall"
    32  
    33  	"github.com/spf13/pflag"
    34  
    35  	"vitess.io/vitess/go/vt/log"
    36  )
    37  
    38  var (
    39  	pprofFlag []string
    40  )
    41  
    42  type profmode string
    43  
    44  const (
    45  	profileCPU       profmode = "cpu"
    46  	profileMemHeap   profmode = "mem_heap"
    47  	profileMemAllocs profmode = "mem_allocs"
    48  	profileMutex     profmode = "mutex"
    49  	profileBlock     profmode = "block"
    50  	profileTrace     profmode = "trace"
    51  	profileThreads   profmode = "threads"
    52  	profileGoroutine profmode = "goroutine"
    53  )
    54  
    55  func (p profmode) filename() string {
    56  	return fmt.Sprintf("%s.pprof", string(p))
    57  }
    58  
    59  type profile struct {
    60  	mode    profmode
    61  	rate    int
    62  	path    string
    63  	quiet   bool
    64  	waitSig bool
    65  }
    66  
    67  func parseProfileFlag(pf []string) (*profile, error) {
    68  	if len(pf) == 0 {
    69  		return nil, nil
    70  	}
    71  
    72  	var p profile
    73  
    74  	switch pf[0] {
    75  	case "cpu":
    76  		p.mode = profileCPU
    77  	case "mem", "mem=heap":
    78  		p.mode = profileMemHeap
    79  		p.rate = 4096
    80  	case "mem=allocs":
    81  		p.mode = profileMemAllocs
    82  		p.rate = 4096
    83  	case "mutex":
    84  		p.mode = profileMutex
    85  		p.rate = 1
    86  	case "block":
    87  		p.mode = profileBlock
    88  		p.rate = 1
    89  	case "trace":
    90  		p.mode = profileTrace
    91  	case "threads":
    92  		p.mode = profileThreads
    93  	case "goroutine":
    94  		p.mode = profileGoroutine
    95  	default:
    96  		return nil, fmt.Errorf("unknown profile mode: %q", pf[0])
    97  	}
    98  
    99  	for _, kv := range pf[1:] {
   100  		var err error
   101  		fields := strings.SplitN(kv, "=", 2)
   102  
   103  		switch fields[0] {
   104  		case "rate":
   105  			if len(fields) == 1 {
   106  				return nil, fmt.Errorf("missing value for 'rate'")
   107  			}
   108  			p.rate, err = strconv.Atoi(fields[1])
   109  			if err != nil {
   110  				return nil, fmt.Errorf("invalid profile rate %q: %v", fields[1], err)
   111  			}
   112  
   113  		case "path":
   114  			if len(fields) == 1 {
   115  				return nil, fmt.Errorf("missing value for 'path'")
   116  			}
   117  			p.path = fields[1]
   118  
   119  		case "quiet":
   120  			if len(fields) == 1 {
   121  				p.quiet = true
   122  				continue
   123  			}
   124  
   125  			p.quiet, err = strconv.ParseBool(fields[1])
   126  			if err != nil {
   127  				return nil, fmt.Errorf("invalid quiet flag %q: %v", fields[1], err)
   128  			}
   129  		case "waitSig":
   130  			if len(fields) == 1 {
   131  				p.waitSig = true
   132  				continue
   133  			}
   134  			p.waitSig, err = strconv.ParseBool(fields[1])
   135  			if err != nil {
   136  				return nil, fmt.Errorf("invalid waitSig flag %q: %v", fields[1], err)
   137  			}
   138  		default:
   139  			return nil, fmt.Errorf("unknown flag: %q", fields[0])
   140  		}
   141  	}
   142  
   143  	return &p, nil
   144  }
   145  
   146  var profileStarted uint32
   147  
   148  func startCallback(start func()) func() {
   149  	return func() {
   150  		if atomic.CompareAndSwapUint32(&profileStarted, 0, 1) {
   151  			start()
   152  		} else {
   153  			log.Fatal("profile: Start() already called")
   154  		}
   155  	}
   156  }
   157  
   158  func stopCallback(stop func()) func() {
   159  	return func() {
   160  		if atomic.CompareAndSwapUint32(&profileStarted, 1, 0) {
   161  			stop()
   162  		}
   163  	}
   164  }
   165  
   166  func (prof *profile) mkprofile() io.WriteCloser {
   167  	var (
   168  		path string
   169  		err  error
   170  		logf = func(format string, args ...any) {}
   171  	)
   172  
   173  	if prof.path != "" {
   174  		path = prof.path
   175  		err = os.MkdirAll(path, 0777)
   176  	} else {
   177  		path, err = os.MkdirTemp("", "profile")
   178  	}
   179  	if err != nil {
   180  		log.Fatalf("pprof: could not create initial output directory: %v", err)
   181  	}
   182  
   183  	if !prof.quiet {
   184  		logf = log.Infof
   185  	}
   186  
   187  	fn := filepath.Join(path, prof.mode.filename())
   188  	f, err := os.Create(fn)
   189  	if err != nil {
   190  		log.Fatalf("pprof: could not create profile %q: %v", fn, err)
   191  	}
   192  	logf("pprof: %s profiling enabled, %s", string(prof.mode), fn)
   193  
   194  	return f
   195  }
   196  
   197  // init returns a start function that begins the configured profiling process and
   198  // returns a cleanup function that must be executed before process termination to
   199  // flush the profile to disk.
   200  // Based on the profiling code in github.com/pkg/profile
   201  func (prof *profile) init() (start func(), stop func()) {
   202  	var pf io.WriteCloser
   203  
   204  	switch prof.mode {
   205  	case profileCPU:
   206  		start = startCallback(func() {
   207  			pf = prof.mkprofile()
   208  			pprof.StartCPUProfile(pf)
   209  		})
   210  		stop = stopCallback(func() {
   211  			pprof.StopCPUProfile()
   212  			pf.Close()
   213  		})
   214  		return start, stop
   215  
   216  	case profileMemHeap, profileMemAllocs:
   217  		old := runtime.MemProfileRate
   218  		start = startCallback(func() {
   219  			pf = prof.mkprofile()
   220  			runtime.MemProfileRate = prof.rate
   221  		})
   222  		stop = stopCallback(func() {
   223  			tt := "heap"
   224  			if prof.mode == profileMemAllocs {
   225  				tt = "allocs"
   226  			}
   227  			pprof.Lookup(tt).WriteTo(pf, 0)
   228  			pf.Close()
   229  			runtime.MemProfileRate = old
   230  		})
   231  		return start, stop
   232  
   233  	case profileMutex:
   234  		start = startCallback(func() {
   235  			pf = prof.mkprofile()
   236  			runtime.SetMutexProfileFraction(prof.rate)
   237  		})
   238  		stop = stopCallback(func() {
   239  			if mp := pprof.Lookup("mutex"); mp != nil {
   240  				mp.WriteTo(pf, 0)
   241  			}
   242  			pf.Close()
   243  			runtime.SetMutexProfileFraction(0)
   244  		})
   245  		return start, stop
   246  
   247  	case profileBlock:
   248  		start = startCallback(func() {
   249  			pf = prof.mkprofile()
   250  			runtime.SetBlockProfileRate(prof.rate)
   251  		})
   252  		stop = stopCallback(func() {
   253  			pprof.Lookup("block").WriteTo(pf, 0)
   254  			pf.Close()
   255  			runtime.SetBlockProfileRate(0)
   256  		})
   257  		return start, stop
   258  
   259  	case profileThreads:
   260  		start = startCallback(func() {
   261  			pf = prof.mkprofile()
   262  		})
   263  		stop = stopCallback(func() {
   264  			if mp := pprof.Lookup("threadcreate"); mp != nil {
   265  				mp.WriteTo(pf, 0)
   266  			}
   267  			pf.Close()
   268  		})
   269  		return start, stop
   270  
   271  	case profileTrace:
   272  		start = startCallback(func() {
   273  			pf = prof.mkprofile()
   274  			if err := trace.Start(pf); err != nil {
   275  				log.Fatalf("pprof: could not start trace: %v", err)
   276  			}
   277  		})
   278  		stop = stopCallback(func() {
   279  			trace.Stop()
   280  			pf.Close()
   281  		})
   282  		return start, stop
   283  
   284  	case profileGoroutine:
   285  		start = startCallback(func() {
   286  			pf = prof.mkprofile()
   287  		})
   288  		stop = stopCallback(func() {
   289  			if mp := pprof.Lookup("goroutine"); mp != nil {
   290  				mp.WriteTo(pf, 0)
   291  			}
   292  			pf.Close()
   293  		})
   294  		return start, stop
   295  
   296  	default:
   297  		panic("unsupported profile mode")
   298  	}
   299  }
   300  
   301  func pprofInit() {
   302  	prof, err := parseProfileFlag(pprofFlag)
   303  	if err != nil {
   304  		log.Fatal(err)
   305  	}
   306  	if prof != nil {
   307  		start, stop := prof.init()
   308  		startSignal := make(chan os.Signal, 1)
   309  		stopSignal := make(chan os.Signal, 1)
   310  
   311  		if prof.waitSig {
   312  			signal.Notify(startSignal, syscall.SIGUSR1)
   313  		} else {
   314  			start()
   315  			signal.Notify(stopSignal, syscall.SIGUSR1)
   316  		}
   317  
   318  		go func() {
   319  			for {
   320  				<-startSignal
   321  				start()
   322  				signal.Reset(syscall.SIGUSR1)
   323  				signal.Notify(stopSignal, syscall.SIGUSR1)
   324  			}
   325  		}()
   326  
   327  		go func() {
   328  			for {
   329  				<-stopSignal
   330  				stop()
   331  				signal.Reset(syscall.SIGUSR1)
   332  				signal.Notify(startSignal, syscall.SIGUSR1)
   333  			}
   334  		}()
   335  
   336  		OnTerm(stop)
   337  	}
   338  }
   339  
   340  func init() {
   341  	OnParse(func(fs *pflag.FlagSet) {
   342  		fs.StringSliceVar(&pprofFlag, "pprof", pprofFlag, "enable profiling")
   343  	})
   344  	OnInit(pprofInit)
   345  }