github.com/grailbio/base@v0.0.11/pprof/pprof.go (about)

     1  // Copyright 2018 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache-2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  package pprof
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"net"
    11  	"net/http"
    12  	"os"
    13  	"runtime"
    14  	rtpprof "runtime/pprof"
    15  	"sync"
    16  	"sync/atomic"
    17  	"time"
    18  
    19  	"github.com/grailbio/base/grail/go/net/http/pprof"
    20  	"v.io/x/lib/vlog"
    21  )
    22  
    23  // Profiling provides access to the various profiling options provided
    24  // by runtime/pprof.
    25  type profiling struct {
    26  	started int32 // # calls to Start().
    27  
    28  	cpuName    string
    29  	heapName   string
    30  	threadName string
    31  	blockName  string
    32  	mutexName  string
    33  
    34  	blockRate       int
    35  	mutexRate       int
    36  	profileInterval float64
    37  
    38  	mu *sync.Mutex
    39  	// Following fields are guarded by mu.
    40  	cpuFile    *os.File
    41  	generation int // guarded by mu
    42  
    43  	// HTTP Listen address, in form ":12345"
    44  	httpAddr string
    45  	// Actual listen address of the debug HTTP server.
    46  	pprofAddr net.Addr
    47  }
    48  
    49  func newProfiling() *profiling {
    50  	pr := &profiling{
    51  		mu: &sync.Mutex{},
    52  	}
    53  	for _, p := range []struct {
    54  		fl interface{}
    55  		n  string
    56  		dv interface{}
    57  		d  string
    58  	}{
    59  		{&pr.httpAddr, "pprof", "", "address for pprof server"},
    60  		{&pr.cpuName, "cpu-profile", "", "filename for cpu profile"},
    61  		{&pr.heapName, "heap-profile", "", "filename prefix for heap profiles"},
    62  		{&pr.threadName, "thread-create-profile", "", "filename prefix for thread create profiles"},
    63  		{&pr.blockName, "block-profile", "", "filename prefix for block profiles"},
    64  		{&pr.mutexName, "mutex-profile", "", "filename prefix for mutex profiles"},
    65  		{&pr.mutexRate, "mutex-profile-rate", 200, "rate for runtime.SetMutexProfileFraction"},
    66  		{&pr.blockRate, "block-profile-rate", 200, "rate for runtime.SetBlockProfileRate"},
    67  		{&pr.profileInterval, "profile-interval-s", 0.0, "If >0, output new profiles at this interval (seconds). If <=0, profiles are written only when Write() is called"},
    68  	} {
    69  		fn := p.n
    70  		switch flt := p.fl.(type) {
    71  		case *string:
    72  			flag.StringVar(flt, fn, p.dv.(string), p.d)
    73  		case *int:
    74  			flag.IntVar(flt, fn, p.dv.(int), p.d)
    75  		case *float64:
    76  			flag.Float64Var(flt, fn, p.dv.(float64), p.d)
    77  		}
    78  	}
    79  	return pr
    80  }
    81  
    82  func generationSuffix(generation int) string {
    83  	return fmt.Sprintf("-%05v.pprof", generation)
    84  }
    85  
    86  // Write writes the profile information to new files. Each call results
    87  // in a new file name of the for <command-line-prefix>-<number> where
    88  // number is incremented each time Write is called. All of the profiles
    89  // enabled on the command line are written.
    90  func (p *profiling) Write(debug int) {
    91  	p.mu.Lock()
    92  	defer p.mu.Unlock()
    93  	suffix := generationSuffix(p.generation)
    94  	p.generation++
    95  	for _, n := range []struct {
    96  		fl string
    97  		pn string
    98  	}{
    99  		{p.cpuName, "cpu"},
   100  		{p.heapName, "heap"},
   101  		{p.threadName, "threadcreate"},
   102  		{p.blockName, "block"},
   103  		{p.mutexName, "mutex"},
   104  	} {
   105  		if len(n.fl) == 0 {
   106  			continue
   107  		}
   108  		if n.fl == p.cpuName {
   109  			p.stopCPU()
   110  			p.startCPU()
   111  		} else {
   112  			pr := rtpprof.Lookup(n.pn)
   113  			if pr == nil {
   114  				vlog.Errorf("failed to lookup profile: %v", n.pn)
   115  			}
   116  			fn := n.fl + suffix
   117  			f, err := os.Create(fn)
   118  			if err != nil {
   119  				vlog.Errorf("failed to create %v for %v profile", fn, n.pn)
   120  				continue
   121  			}
   122  			defer f.Close()
   123  			if err := pr.WriteTo(f, debug); err != nil {
   124  				vlog.Errorf("failed to write Profiling for %v to %v: %v", n.pn, fn, err)
   125  			}
   126  			vlog.VI(1).Infof("%v: Wrote profile", f.Name())
   127  		}
   128  	}
   129  
   130  }
   131  
   132  // Start starts CPU and all other profiling that has been specified on the
   133  // command line.  Calling this method multiple times is a noop.
   134  func (p *profiling) Start() {
   135  	if atomic.AddInt32(&p.started, 1) > 1 {
   136  		return
   137  	}
   138  	if len(p.blockName) > 0 || len(p.httpAddr) > 0 {
   139  		runtime.SetBlockProfileRate(p.blockRate)
   140  	}
   141  	if len(p.mutexName) > 0 || len(p.httpAddr) > 0 {
   142  		runtime.SetMutexProfileFraction(p.mutexRate)
   143  	}
   144  	if len(p.cpuName) > 0 {
   145  		p.startCPU()
   146  	}
   147  	if p.profileInterval > 0 {
   148  		go func() {
   149  			for {
   150  				time.Sleep(time.Duration(p.profileInterval * float64(time.Second)))
   151  				p.Write(1)
   152  			}
   153  		}()
   154  	}
   155  	if len(p.httpAddr) > 0 {
   156  		mux := http.NewServeMux()
   157  		mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   158  			w.Header().Set("Content-Type", "text/html; charset=utf-8")
   159  			w.Write([]byte("<ul><li> <a href=\"/debug/pprof/goroutine?debug=1\">threadz</a></ul>"))
   160  		}))
   161  		mux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
   162  		mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
   163  		mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
   164  		mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
   165  		mux.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace))
   166  
   167  		l, err := net.Listen("tcp", p.httpAddr)
   168  		if err != nil {
   169  			vlog.Error(err)
   170  			return
   171  		}
   172  
   173  		p.pprofAddr = l.Addr()
   174  		vlog.Infof("Pprof server listening on %s", p.pprofAddr.String())
   175  		go func() {
   176  			if err := http.Serve(l, mux); err != nil {
   177  				vlog.Error(err)
   178  			}
   179  		}()
   180  	}
   181  
   182  }
   183  
   184  // startCPU starts CPU profiling. The CPU profiler must not be already active.
   185  func (p *profiling) startCPU() {
   186  	f, err := os.Create(p.cpuName + generationSuffix(p.generation))
   187  	if err != nil {
   188  		vlog.Fatal("could not create CPU profile: ", err)
   189  	}
   190  	p.cpuFile = f
   191  	if err := rtpprof.StartCPUProfile(f); err != nil {
   192  		f.Close()
   193  		vlog.Fatal("could not start CPU profile: ", err)
   194  	}
   195  }
   196  
   197  // stopCPU stops cpu profiling and must be called to ensure that the CPU
   198  // Profiling information is written to its output file.
   199  func (p *profiling) stopCPU() {
   200  	rtpprof.StopCPUProfile()
   201  	p.cpuFile.Close()
   202  	p.cpuFile = nil
   203  }
   204  
   205  var singleton *profiling
   206  
   207  func init() {
   208  	singleton = newProfiling()
   209  }
   210  
   211  // Start starts the cpu, memory, and other profilers specified in the
   212  // commandline. This function has no effect if none of the following flags is
   213  // set.
   214  //
   215  // -cpu-profile
   216  // -heap-profile
   217  // -thread-create-profile
   218  // -block-profile
   219  // -mutex-profile
   220  // -profile-interval-s
   221  // -pprof
   222  //
   223  // This function should be called at the start of a process. Calling it multiple
   224  // times a noop.
   225  func Start() {
   226  	singleton.Start()
   227  }
   228  
   229  // PprofAddr returns the listen address of the HTTP httpserver. It is useful
   230  // when the process was started with flag -pprof=:0.
   231  func HTTPAddr() net.Addr {
   232  	return singleton.pprofAddr
   233  }
   234  
   235  // WritePprof writes the profile information to new files. Each call results in
   236  // a new file name of the for <command-line-prefix>-<number> where number is
   237  // incremented each time Write is called. All of the profiles enabled on the
   238  // command line are written.
   239  func Write(debug int) {
   240  	singleton.Write(debug)
   241  }