github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/profile/profile.go (about)

     1  package profile
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"runtime"
     8  	"runtime/pprof"
     9  	"runtime/trace"
    10  	"sync/atomic"
    11  	"time"
    12  
    13  	"github.com/bingoohuang/gg/pkg/ss"
    14  )
    15  
    16  const (
    17  	cpuMode = 1 << iota
    18  	heapMode
    19  	allocsMode
    20  	mutexMode
    21  	blockMode
    22  	traceMode
    23  	threadCreateMode
    24  	goroutineMode
    25  )
    26  
    27  // Profile represents an active profiling session.
    28  type Profile struct {
    29  	// mode holds the type of profiling that will be made
    30  	mode int
    31  
    32  	// memProfileRate holds the rate for the memory profile.
    33  	memProfileRate int
    34  
    35  	// closer holds a cleanup function that run after each profile
    36  	closer []func()
    37  
    38  	// stopped records if a call to profile.Stop has been made
    39  	stopped uint32
    40  
    41  	// duration set the profile to stop after the specified duration.
    42  	duration time.Duration
    43  }
    44  
    45  // Specs specifies the profile settings by specifies expression.
    46  // like cpu,heap,allocs,mutex,block,trace,threadcreate,goroutine,d:5m,rate:4096.
    47  func Specs(specs string) func(*Profile) {
    48  	return func(p *Profile) {
    49  		for _, spec := range ss.Split(specs, ss.WithIgnoreEmpty(true), ss.WithCase(ss.CaseLower), ss.WithSeps(", ")) {
    50  			switch spec {
    51  			case "cpu":
    52  				p.mode |= cpuMode
    53  			case "heap":
    54  				p.mode |= heapMode
    55  			case "allocs":
    56  				p.mode |= allocsMode
    57  			case "mutex":
    58  				p.mode |= mutexMode
    59  			case "block":
    60  				p.mode |= blockMode
    61  			case "trace":
    62  				p.mode |= traceMode
    63  			case "threadcreate":
    64  				p.mode |= threadCreateMode
    65  			case "goroutine":
    66  				p.mode |= goroutineMode
    67  			default:
    68  				switch {
    69  				case ss.HasPrefix(spec, "d:"):
    70  					if d, err := time.ParseDuration(spec[2:]); err == nil {
    71  						p.duration = d
    72  						continue
    73  					}
    74  				case ss.HasPrefix(spec, "rate:"):
    75  					if r, err := ss.ParseIntE(spec[5:]); err == nil {
    76  						p.memProfileRate = r
    77  						continue
    78  					}
    79  				}
    80  				log.Printf("W! unknown spec: %s", spec)
    81  			}
    82  		}
    83  	}
    84  }
    85  
    86  // DefaultMemProfileRate is the default memory profiling rate.
    87  // See also http://golang.org/pkg/runtime/#pkg-variables
    88  const DefaultMemProfileRate = 4096
    89  
    90  // Stop stops the profile and flushes any unwritten data.
    91  func (p *Profile) Stop() {
    92  	if !atomic.CompareAndSwapUint32(&p.stopped, 0, 1) {
    93  		// someone has already called close
    94  		return
    95  	}
    96  	for _, closer := range p.closer {
    97  		closer()
    98  	}
    99  	atomic.StoreUint32(&started, 0)
   100  }
   101  
   102  // started is non zero if a profile is running.
   103  var started uint32
   104  
   105  // Start starts a new profiling session.
   106  // The caller should call the Stop method on the value returned
   107  // to cleanly stop profiling.
   108  func Start(options ...func(*Profile)) (interface{ Stop() }, error) {
   109  	if !atomic.CompareAndSwapUint32(&started, 0, 1) {
   110  		log.Fatal("profile: Start() already called")
   111  	}
   112  
   113  	prof := Profile{memProfileRate: DefaultMemProfileRate, duration: 15 * time.Second}
   114  	for _, option := range options {
   115  		option(&prof)
   116  	}
   117  
   118  	if prof.mode&cpuMode > 0 {
   119  		f, fcloser, err := createFile("cpu.pprof")
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  		pprof.StartCPUProfile(f)
   124  		prof.closer = append(prof.closer, func() {
   125  			pprof.StopCPUProfile()
   126  			fcloser()
   127  		})
   128  	}
   129  	if prof.mode&allocsMode > 0 {
   130  		f, fcloser, err := createFile("allocs.pprof")
   131  		if err != nil {
   132  			return nil, err
   133  		}
   134  		old := runtime.MemProfileRate
   135  		runtime.MemProfileRate = prof.memProfileRate
   136  		prof.closer = append(prof.closer, func() {
   137  			pprof.Lookup("allocs").WriteTo(f, 0)
   138  			runtime.MemProfileRate = old
   139  			fcloser()
   140  		})
   141  	}
   142  	if prof.mode&heapMode > 0 {
   143  		f, fcloser, err := createFile("heap.pprof")
   144  		if err != nil {
   145  			return nil, err
   146  		}
   147  		old := runtime.MemProfileRate
   148  		runtime.MemProfileRate = prof.memProfileRate
   149  		prof.closer = append(prof.closer, func() {
   150  			pprof.Lookup("heap").WriteTo(f, 0)
   151  			runtime.MemProfileRate = old
   152  			fcloser()
   153  		})
   154  	}
   155  	if prof.mode&mutexMode > 0 {
   156  		f, fcloser, err := createFile("mutex.pprof")
   157  		if err != nil {
   158  			return nil, err
   159  		}
   160  		runtime.SetMutexProfileFraction(1)
   161  		prof.closer = append(prof.closer, func() {
   162  			if mp := pprof.Lookup("mutex"); mp != nil {
   163  				mp.WriteTo(f, 0)
   164  			}
   165  			runtime.SetMutexProfileFraction(0)
   166  			fcloser()
   167  		})
   168  	}
   169  	if prof.mode&blockMode > 0 {
   170  		f, fcloser, err := createFile("block.pprof")
   171  		if err != nil {
   172  			return nil, err
   173  		}
   174  		runtime.SetBlockProfileRate(1)
   175  		prof.closer = append(prof.closer, func() {
   176  			pprof.Lookup("block").WriteTo(f, 0)
   177  			runtime.SetBlockProfileRate(0)
   178  			fcloser()
   179  		})
   180  	}
   181  	if prof.mode&threadCreateMode > 0 {
   182  		f, fcloser, err := createFile("threadcreation.pprof")
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  		prof.closer = append(prof.closer, func() {
   187  			if mp := pprof.Lookup("threadcreate"); mp != nil {
   188  				mp.WriteTo(f, 0)
   189  			}
   190  			fcloser()
   191  		})
   192  	}
   193  	if prof.mode&traceMode > 0 {
   194  		f, fcloser, err := createFile("trace.out")
   195  		if err != nil {
   196  			return nil, err
   197  		}
   198  		trace.Start(f)
   199  		prof.closer = append(prof.closer, func() {
   200  			trace.Stop()
   201  			fcloser()
   202  		})
   203  	}
   204  	if prof.mode&goroutineMode > 0 {
   205  		f, fcloser, err := createFile("goroutine.pprof")
   206  		if err != nil {
   207  			return nil, err
   208  		}
   209  		prof.closer = append(prof.closer, func() {
   210  			if mp := pprof.Lookup("goroutine"); mp != nil {
   211  				mp.WriteTo(f, 0)
   212  			}
   213  			fcloser()
   214  		})
   215  	}
   216  
   217  	if len(prof.closer) == 0 {
   218  		return nil, fmt.Errorf("no profiles")
   219  	}
   220  
   221  	if prof.duration > 0 {
   222  		time.Sleep(prof.duration)
   223  		log.Printf("profile: after %s, stopping profiles", prof.duration)
   224  		prof.Stop()
   225  		return nil, nil
   226  	}
   227  
   228  	return &prof, nil
   229  }
   230  
   231  func createFile(fn string) (*os.File, func(), error) {
   232  	f, err := os.Create(fn)
   233  	if err != nil {
   234  		log.Printf("E! profile: could not create   %q: %v", fn, err)
   235  		return nil, nil, err
   236  	}
   237  	log.Printf("profile: profiling started, %s", fn)
   238  
   239  	fcloser := func() {
   240  		f.Close()
   241  		log.Printf("profile: profiling ended, %s", fn)
   242  	}
   243  
   244  	return f, fcloser, err
   245  }