git-vendor-name: profile git-vendor-dir: vendor/github.com/pkg/profile git-vendor-repository: git://github.com/pkg/profile git-vendor-ref: v1.0.0master
| @ -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 | |||