1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
|
// Package profile provides simple profiling for Go applications.
package profile
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/signal"
"strings"
)
// Profile represents a profiling session.
type Profile struct {
methods []method
log func(string, ...interface{})
noshutdownhook bool
envvar string
running []method
}
// New creates a new profiling session configured with the given options.
func New(options ...func(*Profile)) *Profile {
p := &Profile{
log: log.Printf,
}
p.Configure(options...)
return p
}
// Start a new profiling session with the given options.
func Start(options ...func(*Profile)) *Profile {
return New(options...).Start()
}
// Configure applies the given options to this profiling session.
func (p *Profile) Configure(options ...func(*Profile)) {
for _, option := range options {
option(p)
}
}
// WithLogger configures informational messages to be logged to the given
// logger. Defaults to the standard library global logger.
func WithLogger(l *log.Logger) func(p *Profile) {
return func(p *Profile) { p.log = l.Printf }
}
// Quiet suppresses logging.
func Quiet(p *Profile) {
p.Configure(WithLogger(log.New(ioutil.Discard, "", 0)))
}
// NoShutdownHook controls whether the profiling session should shutdown on
// interrupt. Programs with more sophisticated signal handling should use this
// option to disable the default shutdown handler, and ensure the profile Stop()
// method is called during shutdown.
func NoShutdownHook(p *Profile) { p.noshutdownhook = true }
// ConfigEnvVar specifies an environment variable to configure profiles from.
func ConfigEnvVar(key string) func(*Profile) {
return func(p *Profile) { p.envvar = key }
}
func (p *Profile) addmethod(m method) {
p.methods = append(p.methods, m)
}
func (p *Profile) setdefaults() {
if len(p.methods) == 0 {
p.Configure(CPUProfile)
}
}
// SetFlags registers flags to configure this profiling session. This should be
// called after all options have been applied.
func (p *Profile) SetFlags(f *flag.FlagSet) {
p.setdefaults()
for _, m := range p.methods {
m.SetFlags(f)
}
}
// config configures profiles based on a GODEBUG-like configuration string.
func (p *Profile) config(cfg string) {
// Convert config string into equivalent command-line arguments and parse them.
args := []string{}
for _, arg := range strings.Split(cfg, ",") {
args = append(args, "-"+arg)
}
// Register flags on a custom flagset. Register custom usage function that
// will output flags in a format closer to the expected format of the
// configuration string.
f := flag.NewFlagSet("", flag.ExitOnError)
p.SetFlags(f)
f.Usage = func() {
f.VisitAll(func(opt *flag.Flag) {
value, usage := flag.UnquoteUsage(opt)
fmt.Fprintf(f.Output(), "%s=%s\n\t%s\n", opt.Name, value, usage)
})
}
// Parse. Discard error because ExitOnError ensures it's handled internally.
_ = f.Parse(args)
}
// Start profiling.
func (p *Profile) Start() *Profile {
// Set defaults.
p.setdefaults()
// Optionally configure via environment variable.
if p.envvar != "" {
p.config(os.Getenv(p.envvar))
}
// Start methods.
for _, m := range p.methods {
if !m.Enabled() {
continue
}
if err := m.Start(); err != nil {
p.log("%s profile: error starting: %v", m.Name(), err)
continue
}
p.log("%s profile: started", m.Name())
p.running = append(p.running, m)
}
// Shutdown hook.
if !p.noshutdownhook {
go func() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
s := <-c
p.log("caught %v: stopping profiles", s)
p.Stop()
os.Exit(0)
}()
}
return p
}
// Stop profiling.
func (p *Profile) Stop() {
for _, m := range p.running {
if err := m.Stop(); err != nil {
p.log("%s profile: error stopping: %v", m.Name(), err)
} else {
p.log("%s profile: stopped", m.Name())
}
}
p.running = nil
}
|