github.com/lingyao2333/mo-zero@v1.4.1/core/proc/profile.go (about)

     1  //go:build linux || darwin
     2  // +build linux darwin
     3  
     4  package proc
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"os/signal"
    10  	"path"
    11  	"runtime"
    12  	"runtime/pprof"
    13  	"runtime/trace"
    14  	"sync/atomic"
    15  	"syscall"
    16  	"time"
    17  
    18  	"github.com/lingyao2333/mo-zero/core/logx"
    19  )
    20  
    21  // DefaultMemProfileRate is the default memory profiling rate.
    22  // See also http://golang.org/pkg/runtime/#pkg-variables
    23  const DefaultMemProfileRate = 4096
    24  
    25  // started is non zero if a profile is running.
    26  var started uint32
    27  
    28  // Profile represents an active profiling session.
    29  type Profile struct {
    30  	// closers holds cleanup functions that run after each profile
    31  	closers []func()
    32  
    33  	// stopped records if a call to profile.Stop has been made
    34  	stopped uint32
    35  }
    36  
    37  func (p *Profile) close() {
    38  	for _, closer := range p.closers {
    39  		closer()
    40  	}
    41  }
    42  
    43  func (p *Profile) startBlockProfile() {
    44  	fn := createDumpFile("block")
    45  	f, err := os.Create(fn)
    46  	if err != nil {
    47  		logx.Errorf("profile: could not create block profile %q: %v", fn, err)
    48  		return
    49  	}
    50  
    51  	runtime.SetBlockProfileRate(1)
    52  	logx.Infof("profile: block profiling enabled, %s", fn)
    53  	p.closers = append(p.closers, func() {
    54  		pprof.Lookup("block").WriteTo(f, 0)
    55  		f.Close()
    56  		runtime.SetBlockProfileRate(0)
    57  		logx.Infof("profile: block profiling disabled, %s", fn)
    58  	})
    59  }
    60  
    61  func (p *Profile) startCpuProfile() {
    62  	fn := createDumpFile("cpu")
    63  	f, err := os.Create(fn)
    64  	if err != nil {
    65  		logx.Errorf("profile: could not create cpu profile %q: %v", fn, err)
    66  		return
    67  	}
    68  
    69  	logx.Infof("profile: cpu profiling enabled, %s", fn)
    70  	pprof.StartCPUProfile(f)
    71  	p.closers = append(p.closers, func() {
    72  		pprof.StopCPUProfile()
    73  		f.Close()
    74  		logx.Infof("profile: cpu profiling disabled, %s", fn)
    75  	})
    76  }
    77  
    78  func (p *Profile) startMemProfile() {
    79  	fn := createDumpFile("mem")
    80  	f, err := os.Create(fn)
    81  	if err != nil {
    82  		logx.Errorf("profile: could not create memory profile %q: %v", fn, err)
    83  		return
    84  	}
    85  
    86  	old := runtime.MemProfileRate
    87  	runtime.MemProfileRate = DefaultMemProfileRate
    88  	logx.Infof("profile: memory profiling enabled (rate %d), %s", runtime.MemProfileRate, fn)
    89  	p.closers = append(p.closers, func() {
    90  		pprof.Lookup("heap").WriteTo(f, 0)
    91  		f.Close()
    92  		runtime.MemProfileRate = old
    93  		logx.Infof("profile: memory profiling disabled, %s", fn)
    94  	})
    95  }
    96  
    97  func (p *Profile) startMutexProfile() {
    98  	fn := createDumpFile("mutex")
    99  	f, err := os.Create(fn)
   100  	if err != nil {
   101  		logx.Errorf("profile: could not create mutex profile %q: %v", fn, err)
   102  		return
   103  	}
   104  
   105  	runtime.SetMutexProfileFraction(1)
   106  	logx.Infof("profile: mutex profiling enabled, %s", fn)
   107  	p.closers = append(p.closers, func() {
   108  		if mp := pprof.Lookup("mutex"); mp != nil {
   109  			mp.WriteTo(f, 0)
   110  		}
   111  		f.Close()
   112  		runtime.SetMutexProfileFraction(0)
   113  		logx.Infof("profile: mutex profiling disabled, %s", fn)
   114  	})
   115  }
   116  
   117  func (p *Profile) startThreadCreateProfile() {
   118  	fn := createDumpFile("threadcreate")
   119  	f, err := os.Create(fn)
   120  	if err != nil {
   121  		logx.Errorf("profile: could not create threadcreate profile %q: %v", fn, err)
   122  		return
   123  	}
   124  
   125  	logx.Infof("profile: threadcreate profiling enabled, %s", fn)
   126  	p.closers = append(p.closers, func() {
   127  		if mp := pprof.Lookup("threadcreate"); mp != nil {
   128  			mp.WriteTo(f, 0)
   129  		}
   130  		f.Close()
   131  		logx.Infof("profile: threadcreate profiling disabled, %s", fn)
   132  	})
   133  }
   134  
   135  func (p *Profile) startTraceProfile() {
   136  	fn := createDumpFile("trace")
   137  	f, err := os.Create(fn)
   138  	if err != nil {
   139  		logx.Errorf("profile: could not create trace output file %q: %v", fn, err)
   140  		return
   141  	}
   142  
   143  	if err := trace.Start(f); err != nil {
   144  		logx.Errorf("profile: could not start trace: %v", err)
   145  		return
   146  	}
   147  
   148  	logx.Infof("profile: trace enabled, %s", fn)
   149  	p.closers = append(p.closers, func() {
   150  		trace.Stop()
   151  		logx.Infof("profile: trace disabled, %s", fn)
   152  	})
   153  }
   154  
   155  // Stop stops the profile and flushes any unwritten data.
   156  func (p *Profile) Stop() {
   157  	if !atomic.CompareAndSwapUint32(&p.stopped, 0, 1) {
   158  		// someone has already called close
   159  		return
   160  	}
   161  	p.close()
   162  	atomic.StoreUint32(&started, 0)
   163  }
   164  
   165  // StartProfile starts a new profiling session.
   166  // The caller should call the Stop method on the value returned
   167  // to cleanly stop profiling.
   168  func StartProfile() Stopper {
   169  	if !atomic.CompareAndSwapUint32(&started, 0, 1) {
   170  		logx.Error("profile: Start() already called")
   171  		return noopStopper
   172  	}
   173  
   174  	var prof Profile
   175  	prof.startCpuProfile()
   176  	prof.startMemProfile()
   177  	prof.startMutexProfile()
   178  	prof.startBlockProfile()
   179  	prof.startTraceProfile()
   180  	prof.startThreadCreateProfile()
   181  
   182  	go func() {
   183  		c := make(chan os.Signal, 1)
   184  		signal.Notify(c, syscall.SIGINT)
   185  		<-c
   186  
   187  		logx.Info("profile: caught interrupt, stopping profiles")
   188  		prof.Stop()
   189  
   190  		signal.Reset()
   191  		syscall.Kill(os.Getpid(), syscall.SIGINT)
   192  	}()
   193  
   194  	return &prof
   195  }
   196  
   197  func createDumpFile(kind string) string {
   198  	command := path.Base(os.Args[0])
   199  	pid := syscall.Getpid()
   200  	return path.Join(os.TempDir(), fmt.Sprintf("%s-%d-%s-%s.pprof",
   201  		command, pid, kind, time.Now().Format(timeFormat)))
   202  }