git-subtree-dir: vendor/github.com/pkg/profile
git-subtree-split: 7c085900f7
master
| @ -0,0 +1 @@ | |||||
| Dave Cheney <dave@cheney.net> | |||||
| @ -0,0 +1,24 @@ | |||||
| Copyright (c) 2013 Dave Cheney. All rights reserved. | |||||
| Redistribution and use in source and binary forms, with or without | |||||
| modification, are permitted provided that the following conditions are | |||||
| met: | |||||
| * Redistributions of source code must retain the above copyright | |||||
| notice, this list of conditions and the following disclaimer. | |||||
| * Redistributions in binary form must reproduce the above | |||||
| copyright notice, this list of conditions and the following disclaimer | |||||
| in the documentation and/or other materials provided with the | |||||
| distribution. | |||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |||||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
| @ -0,0 +1,44 @@ | |||||
| profile | |||||
| ======= | |||||
| Simple profiling support package for Go | |||||
| installation | |||||
| ------------ | |||||
| go get github.com/pkg/profile | |||||
| usage | |||||
| ----- | |||||
| Enabling profiling in your application is as simple as one line at the top of your main function | |||||
| ```go | |||||
| import "github.com/pkg/profile" | |||||
| func main() { | |||||
| defer profile.Start().Stop() | |||||
| ... | |||||
| } | |||||
| ``` | |||||
| options | |||||
| ------- | |||||
| What to profile is controlled by config value passed to profile.Start. | |||||
| By default CPU profiling is enabled. | |||||
| ```go | |||||
| import "github.com/pkg/profile" | |||||
| func main() { | |||||
| // p.Stop() must be called before the program exits to | |||||
| // ensure profiling information is written to disk. | |||||
| p := profile.Start(profile.MemProfile, profile.ProfilePath("."), profile.NoShutdownHook) | |||||
| ... | |||||
| } | |||||
| ``` | |||||
| Several convenience package level values are provided for cpu, memory, and block (contention) profiling. | |||||
| For more complex options, consult the [documentation](http://godoc.org/github.com/pkg/profile). | |||||
| @ -0,0 +1,56 @@ | |||||
| package profile_test | |||||
| import ( | |||||
| "flag" | |||||
| "os" | |||||
| "github.com/pkg/profile" | |||||
| ) | |||||
| func ExampleStart() { | |||||
| // start a simple CPU profile and register | |||||
| // a defer to Stop (flush) the profiling data. | |||||
| defer profile.Start().Stop() | |||||
| } | |||||
| func ExampleCPUProfile() { | |||||
| // CPU profiling is the default profiling mode, but you can specify it | |||||
| // explicitly for completeness. | |||||
| defer profile.Start(profile.CPUProfile).Stop() | |||||
| } | |||||
| func ExampleMemProfile() { | |||||
| // use memory profiling, rather than the default cpu profiling. | |||||
| defer profile.Start(profile.MemProfile).Stop() | |||||
| } | |||||
| func ExampleMemProfileRate() { | |||||
| // use memory profiling with custom rate. | |||||
| defer profile.Start(profile.MemProfileRate(2048)).Stop() | |||||
| } | |||||
| func ExampleProfilePath() { | |||||
| // set the location that the profile will be written to | |||||
| defer profile.Start(profile.ProfilePath(os.Getenv("HOME"))) | |||||
| } | |||||
| func ExampleNoShutdownHook() { | |||||
| // disable the automatic shutdown hook. | |||||
| defer profile.Start(profile.NoShutdownHook).Stop() | |||||
| } | |||||
| func ExampleStart_withFlags() { | |||||
| // use the flags package to selectively enable profiling. | |||||
| mode := flag.String("profile.mode", "", "enable profiling mode, one of [cpu, mem, block]") | |||||
| flag.Parse() | |||||
| switch *mode { | |||||
| case "cpu": | |||||
| defer profile.Start(profile.CPUProfile).Stop() | |||||
| case "mem": | |||||
| defer profile.Start(profile.MemProfile).Stop() | |||||
| case "block": | |||||
| defer profile.Start(profile.BlockProfile).Stop() | |||||
| default: | |||||
| // do nothing | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,192 @@ | |||||
| // Package profile provides a simple way to manage runtime/pprof | |||||
| // profiling of your Go application. | |||||
| package profile | |||||
| import ( | |||||
| "io/ioutil" | |||||
| "log" | |||||
| "os" | |||||
| "os/signal" | |||||
| "path/filepath" | |||||
| "runtime" | |||||
| "runtime/pprof" | |||||
| "sync/atomic" | |||||
| ) | |||||
| // started counts the number of times Start has been called | |||||
| var started uint32 | |||||
| const ( | |||||
| cpuMode = iota | |||||
| memMode | |||||
| blockMode | |||||
| ) | |||||
| type profile struct { | |||||
| // quiet suppresses informational messages during profiling. | |||||
| quiet bool | |||||
| // noShutdownHook controls whether the profiling package should | |||||
| // hook SIGINT to write profiles cleanly. | |||||
| noShutdownHook bool | |||||
| // mode holds the type of profiling that will be made | |||||
| mode int | |||||
| // path holds the base path where various profiling files are written. | |||||
| // If blank, the base path will be generated by ioutil.TempDir. | |||||
| path string | |||||
| // memProfileRate holds the rate for the memory profile. | |||||
| memProfileRate int | |||||
| // closers holds the cleanup functions that run after each profile | |||||
| closers []func() | |||||
| } | |||||
| // NoShutdownHook controls whether the profiling package should | |||||
| // hook SIGINT to write profiles cleanly. | |||||
| // Programs with more sophisticated signal handling should set | |||||
| // this to true and ensure the Stop() function returned from Start() | |||||
| // is called during shutdown. | |||||
| func NoShutdownHook(p *profile) { p.noShutdownHook = true } | |||||
| // Quiet suppresses informational messages during profiling. | |||||
| func Quiet(p *profile) { p.quiet = true } | |||||
| // CPUProfile controls if cpu profiling will be enabled. It disables any previous profiling settings. | |||||
| func CPUProfile(p *profile) { p.mode = cpuMode } | |||||
| // DefaultMemProfileRate is the default memory profiling rate. | |||||
| // See also http://golang.org/pkg/runtime/#pkg-variables | |||||
| const DefaultMemProfileRate = 4096 | |||||
| // MemProfile controls if memory profiling will be enabled. It disables any previous profiling settings. | |||||
| func MemProfile(p *profile) { | |||||
| p.memProfileRate = DefaultMemProfileRate | |||||
| p.mode = memMode | |||||
| } | |||||
| // MemProfileRate controls if memory profiling will be enabled. Additionally, it takes a parameter which | |||||
| // allows the setting of the memory profile rate. | |||||
| func MemProfileRate(rate int) func(*profile) { | |||||
| return func(p *profile) { | |||||
| p.memProfileRate = rate | |||||
| p.mode = memMode | |||||
| } | |||||
| } | |||||
| // BlockProfile controls if block (contention) profiling will be enabled. It disables any previous profiling settings. | |||||
| func BlockProfile(p *profile) { p.mode = blockMode } | |||||
| // ProfilePath controls the base path where various profiling | |||||
| // files are written. If blank, the base path will be generated | |||||
| // by ioutil.TempDir. | |||||
| func ProfilePath(path string) func(*profile) { | |||||
| return func(p *profile) { | |||||
| p.path = path | |||||
| } | |||||
| } | |||||
| // Stop stops the profile and flushes any unwritten data. | |||||
| func (p *profile) Stop() { | |||||
| for _, c := range p.closers { | |||||
| c() | |||||
| } | |||||
| } | |||||
| // Start starts a new profiling session. | |||||
| // The caller should call the Stop method on the value returned | |||||
| // to cleanly stop profiling. | |||||
| func Start(options ...func(*profile)) interface { | |||||
| Stop() | |||||
| } { | |||||
| if !atomic.CompareAndSwapUint32(&started, 0, 1) { | |||||
| log.Fatal("profile: Start() already called") | |||||
| } | |||||
| var prof profile | |||||
| for _, option := range options { | |||||
| option(&prof) | |||||
| } | |||||
| path, err := func() (string, error) { | |||||
| if p := prof.path; p != "" { | |||||
| return p, os.MkdirAll(p, 0777) | |||||
| } | |||||
| return ioutil.TempDir("", "profile") | |||||
| }() | |||||
| if err != nil { | |||||
| log.Fatalf("profile: could not create initial output directory: %v", err) | |||||
| } | |||||
| switch prof.mode { | |||||
| case cpuMode: | |||||
| fn := filepath.Join(path, "cpu.pprof") | |||||
| f, err := os.Create(fn) | |||||
| if err != nil { | |||||
| log.Fatalf("profile: could not create cpu profile %q: %v", fn, err) | |||||
| } | |||||
| if !prof.quiet { | |||||
| log.Printf("profile: cpu profiling enabled, %s", fn) | |||||
| } | |||||
| pprof.StartCPUProfile(f) | |||||
| prof.closers = append(prof.closers, func() { | |||||
| pprof.StopCPUProfile() | |||||
| f.Close() | |||||
| }) | |||||
| case memMode: | |||||
| fn := filepath.Join(path, "mem.pprof") | |||||
| f, err := os.Create(fn) | |||||
| if err != nil { | |||||
| log.Fatalf("profile: could not create memory profile %q: %v", fn, err) | |||||
| } | |||||
| old := runtime.MemProfileRate | |||||
| runtime.MemProfileRate = prof.memProfileRate | |||||
| if !prof.quiet { | |||||
| log.Printf("profile: memory profiling enabled (rate %d), %s", runtime.MemProfileRate, fn) | |||||
| } | |||||
| prof.closers = append(prof.closers, func() { | |||||
| pprof.Lookup("heap").WriteTo(f, 0) | |||||
| f.Close() | |||||
| runtime.MemProfileRate = old | |||||
| }) | |||||
| case blockMode: | |||||
| fn := filepath.Join(path, "block.pprof") | |||||
| f, err := os.Create(fn) | |||||
| if err != nil { | |||||
| log.Fatalf("profile: could not create block profile %q: %v", fn, err) | |||||
| } | |||||
| runtime.SetBlockProfileRate(1) | |||||
| if !prof.quiet { | |||||
| log.Printf("profile: block profiling enabled, %s", fn) | |||||
| } | |||||
| prof.closers = append(prof.closers, func() { | |||||
| pprof.Lookup("block").WriteTo(f, 0) | |||||
| f.Close() | |||||
| runtime.SetBlockProfileRate(0) | |||||
| }) | |||||
| } | |||||
| if !prof.noShutdownHook { | |||||
| go func() { | |||||
| c := make(chan os.Signal, 1) | |||||
| signal.Notify(c, os.Interrupt) | |||||
| <-c | |||||
| log.Println("profile: caught interrupt, stopping profiles") | |||||
| prof.Stop() | |||||
| os.Exit(0) | |||||
| }() | |||||
| } | |||||
| prof.closers = append(prof.closers, func() { | |||||
| atomic.SwapUint32(&started, 0) | |||||
| }) | |||||
| return &prof | |||||
| } | |||||
| @ -0,0 +1,280 @@ | |||||
| package profile | |||||
| import ( | |||||
| "bufio" | |||||
| "bytes" | |||||
| "io" | |||||
| "io/ioutil" | |||||
| "os" | |||||
| "os/exec" | |||||
| "path/filepath" | |||||
| "strings" | |||||
| "testing" | |||||
| ) | |||||
| type checkFn func(t *testing.T, stdout, stderr []byte, err error) | |||||
| var profileTests = []struct { | |||||
| name string | |||||
| code string | |||||
| checks []checkFn | |||||
| }{{ | |||||
| name: "default profile (cpu)", | |||||
| code: ` | |||||
| package main | |||||
| import "github.com/pkg/profile" | |||||
| func main() { | |||||
| defer profile.Start().Stop() | |||||
| } | |||||
| `, | |||||
| checks: []checkFn{ | |||||
| NoStdout, | |||||
| Stderr("profile: cpu profiling enabled"), | |||||
| NoErr, | |||||
| }, | |||||
| }, { | |||||
| name: "memory profile", | |||||
| code: ` | |||||
| package main | |||||
| import "github.com/pkg/profile" | |||||
| func main() { | |||||
| defer profile.Start(profile.MemProfile).Stop() | |||||
| } | |||||
| `, | |||||
| checks: []checkFn{ | |||||
| NoStdout, | |||||
| Stderr("profile: memory profiling enabled"), | |||||
| NoErr, | |||||
| }, | |||||
| }, { | |||||
| name: "memory profile (rate 2048)", | |||||
| code: ` | |||||
| package main | |||||
| import "github.com/pkg/profile" | |||||
| func main() { | |||||
| defer profile.Start(profile.MemProfileRate(2048)).Stop() | |||||
| } | |||||
| `, | |||||
| checks: []checkFn{ | |||||
| NoStdout, | |||||
| Stderr("profile: memory profiling enabled (rate 2048)"), | |||||
| NoErr, | |||||
| }, | |||||
| }, { | |||||
| name: "double start", | |||||
| code: ` | |||||
| package main | |||||
| import "github.com/pkg/profile" | |||||
| func main() { | |||||
| defer profile.Start().Stop() | |||||
| defer profile.Start().Stop() | |||||
| } | |||||
| `, | |||||
| checks: []checkFn{ | |||||
| NoStdout, | |||||
| Stderr("cpu profiling enabled", "profile: Start() already called"), | |||||
| Err, | |||||
| }, | |||||
| }, { | |||||
| name: "block profile", | |||||
| code: ` | |||||
| package main | |||||
| import "github.com/pkg/profile" | |||||
| func main() { | |||||
| defer profile.Start(profile.BlockProfile).Stop() | |||||
| } | |||||
| `, | |||||
| checks: []checkFn{ | |||||
| NoStdout, | |||||
| Stderr("profile: block profiling enabled"), | |||||
| NoErr, | |||||
| }, | |||||
| }, { | |||||
| name: "profile path", | |||||
| code: ` | |||||
| package main | |||||
| import "github.com/pkg/profile" | |||||
| func main() { | |||||
| defer profile.Start(profile.ProfilePath(".")).Stop() | |||||
| } | |||||
| `, | |||||
| checks: []checkFn{ | |||||
| NoStdout, | |||||
| Stderr("profile: cpu profiling enabled, cpu.pprof"), | |||||
| NoErr, | |||||
| }, | |||||
| }, { | |||||
| name: "profile path error", | |||||
| code: ` | |||||
| package main | |||||
| import "github.com/pkg/profile" | |||||
| func main() { | |||||
| defer profile.Start(profile.ProfilePath("README.md")).Stop() | |||||
| } | |||||
| `, | |||||
| checks: []checkFn{ | |||||
| NoStdout, | |||||
| Stderr("could not create initial output"), | |||||
| Err, | |||||
| }, | |||||
| }, { | |||||
| name: "profile quiet", | |||||
| code: ` | |||||
| package main | |||||
| import "github.com/pkg/profile" | |||||
| func main() { | |||||
| defer profile.Start(profile.Quiet).Stop() | |||||
| } | |||||
| `, | |||||
| checks: []checkFn{NoStdout, NoStderr, NoErr}, | |||||
| }} | |||||
| func TestProfile(t *testing.T) { | |||||
| for _, tt := range profileTests { | |||||
| t.Log(tt.name) | |||||
| stdout, stderr, err := runTest(t, tt.code) | |||||
| for _, f := range tt.checks { | |||||
| f(t, stdout, stderr, err) | |||||
| } | |||||
| } | |||||
| } | |||||
| // NoStdout checks that stdout was blank. | |||||
| func NoStdout(t *testing.T, stdout, _ []byte, _ error) { | |||||
| if len := len(stdout); len > 0 { | |||||
| t.Errorf("stdout: wanted 0 bytes, got %d", len) | |||||
| } | |||||
| } | |||||
| // Stderr verifies that the given lines match the output from stderr | |||||
| func Stderr(lines ...string) checkFn { | |||||
| return func(t *testing.T, _, stderr []byte, _ error) { | |||||
| r := bytes.NewReader(stderr) | |||||
| if !validateOutput(r, lines) { | |||||
| t.Errorf("stderr: wanted '%s', got '%s'", lines, stderr) | |||||
| } | |||||
| } | |||||
| } | |||||
| // NoStderr checks that stderr was blank. | |||||
| func NoStderr(t *testing.T, _, stderr []byte, _ error) { | |||||
| if len := len(stderr); len > 0 { | |||||
| t.Errorf("stderr: wanted 0 bytes, got %d", len) | |||||
| } | |||||
| } | |||||
| // Err checks that there was an error returned | |||||
| func Err(t *testing.T, _, _ []byte, err error) { | |||||
| if err == nil { | |||||
| t.Errorf("expected error") | |||||
| } | |||||
| } | |||||
| // NoErr checks that err was nil | |||||
| func NoErr(t *testing.T, _, _ []byte, err error) { | |||||
| if err != nil { | |||||
| t.Errorf("error: expected nil, got %v", err) | |||||
| } | |||||
| } | |||||
| // validatedOutput validates the given slice of lines against data from the given reader. | |||||
| func validateOutput(r io.Reader, want []string) bool { | |||||
| s := bufio.NewScanner(r) | |||||
| for _, line := range want { | |||||
| if !s.Scan() || !strings.Contains(s.Text(), line) { | |||||
| return false | |||||
| } | |||||
| } | |||||
| return true | |||||
| } | |||||
| var validateOutputTests = []struct { | |||||
| input string | |||||
| lines []string | |||||
| want bool | |||||
| }{{ | |||||
| input: "", | |||||
| want: true, | |||||
| }, { | |||||
| input: `profile: yes | |||||
| `, | |||||
| want: true, | |||||
| }, { | |||||
| input: `profile: yes | |||||
| `, | |||||
| lines: []string{"profile: yes"}, | |||||
| want: true, | |||||
| }, { | |||||
| input: `profile: yes | |||||
| profile: no | |||||
| `, | |||||
| lines: []string{"profile: yes"}, | |||||
| want: true, | |||||
| }, { | |||||
| input: `profile: yes | |||||
| profile: no | |||||
| `, | |||||
| lines: []string{"profile: yes", "profile: no"}, | |||||
| want: true, | |||||
| }, { | |||||
| input: `profile: yes | |||||
| profile: no | |||||
| `, | |||||
| lines: []string{"profile: no"}, | |||||
| want: false, | |||||
| }} | |||||
| func TestValidateOutput(t *testing.T) { | |||||
| for _, tt := range validateOutputTests { | |||||
| r := strings.NewReader(tt.input) | |||||
| got := validateOutput(r, tt.lines) | |||||
| if tt.want != got { | |||||
| t.Errorf("validateOutput(%q, %q), want %v, got %v", tt.input, tt.lines, tt.want, got) | |||||
| } | |||||
| } | |||||
| } | |||||
| // runTest executes the go program supplied and returns the contents of stdout, | |||||
| // stderr, and an error which may contain status information about the result | |||||
| // of the program. | |||||
| func runTest(t *testing.T, code string) ([]byte, []byte, error) { | |||||
| chk := func(err error) { | |||||
| if err != nil { | |||||
| t.Fatal(err) | |||||
| } | |||||
| } | |||||
| gopath, err := ioutil.TempDir("", "profile-gopath") | |||||
| chk(err) | |||||
| defer os.RemoveAll(gopath) | |||||
| srcdir := filepath.Join(gopath, "src") | |||||
| err = os.Mkdir(srcdir, 0755) | |||||
| chk(err) | |||||
| src := filepath.Join(srcdir, "main.go") | |||||
| err = ioutil.WriteFile(src, []byte(code), 0644) | |||||
| chk(err) | |||||
| cmd := exec.Command("go", "run", src) | |||||
| var stdout, stderr bytes.Buffer | |||||
| cmd.Stdout = &stdout | |||||
| cmd.Stderr = &stderr | |||||
| err = cmd.Run() | |||||
| return stdout.Bytes(), stderr.Bytes(), err | |||||
| } | |||||
| @ -0,0 +1 @@ | |||||
| box: wercker/golang | |||||