├── .travis.yml ├── AUTHORS ├── LICENSE ├── README.md ├── example_test.go ├── go.mod ├── profile.go ├── profile_test.go └── trace_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go_import_path: github.com/pkg/profile 3 | go: 4 | - 1.13.x 5 | - 1.14.x 6 | - tip 7 | 8 | script: 9 | - go test -race github.com/pkg/profile 10 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Dave Cheney 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Dave Cheney. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 18 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | profile 2 | ======= 3 | 4 | Simple profiling support package for Go 5 | 6 | [![Build Status](https://travis-ci.org/pkg/profile.svg?branch=master)](https://travis-ci.org/pkg/profile) [![GoDoc](http://godoc.org/github.com/pkg/profile?status.svg)](http://godoc.org/github.com/pkg/profile) 7 | 8 | 9 | installation 10 | ------------ 11 | 12 | go get github.com/pkg/profile 13 | 14 | usage 15 | ----- 16 | 17 | Enabling profiling in your application is as simple as one line at the top of your main function 18 | 19 | ```go 20 | import "github.com/pkg/profile" 21 | 22 | func main() { 23 | defer profile.Start().Stop() 24 | ... 25 | } 26 | ``` 27 | 28 | options 29 | ------- 30 | 31 | What to profile is controlled by config value passed to profile.Start. 32 | By default CPU profiling is enabled. 33 | 34 | ```go 35 | import "github.com/pkg/profile" 36 | 37 | func main() { 38 | // p.Stop() must be called before the program exits to 39 | // ensure profiling information is written to disk. 40 | p := profile.Start(profile.MemProfile, profile.ProfilePath("."), profile.NoShutdownHook) 41 | ... 42 | // You can enable different kinds of memory profiling, either Heap or Allocs where Heap 43 | // profiling is the default with profile.MemProfile. 44 | p := profile.Start(profile.MemProfileAllocs, profile.ProfilePath("."), profile.NoShutdownHook) 45 | } 46 | ``` 47 | 48 | Several convenience package level values are provided for cpu, memory, and block (contention) profiling. 49 | 50 | For more complex options, consult the [documentation](http://godoc.org/github.com/pkg/profile). 51 | 52 | contributing 53 | ------------ 54 | 55 | We welcome pull requests, bug fixes and issue reports. 56 | 57 | Before proposing a change, please discuss it first by raising an issue. 58 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package profile_test 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | 7 | "github.com/pkg/profile" 8 | ) 9 | 10 | func ExampleStart() { 11 | // start a simple CPU profile and register 12 | // a defer to Stop (flush) the profiling data. 13 | defer profile.Start().Stop() 14 | } 15 | 16 | func ExampleCPUProfile() { 17 | // CPU profiling is the default profiling mode, but you can specify it 18 | // explicitly for completeness. 19 | defer profile.Start(profile.CPUProfile).Stop() 20 | } 21 | 22 | func ExampleMemProfile() { 23 | // use memory profiling, rather than the default cpu profiling. 24 | defer profile.Start(profile.MemProfile).Stop() 25 | } 26 | 27 | func ExampleMemProfileRate() { 28 | // use memory profiling with custom rate. 29 | defer profile.Start(profile.MemProfileRate(2048)).Stop() 30 | } 31 | 32 | func ExampleMemProfileHeap() { 33 | // use heap memory profiling. 34 | defer profile.Start(profile.MemProfileHeap).Stop() 35 | } 36 | 37 | func ExampleMemProfileAllocs() { 38 | // use allocs memory profiling. 39 | defer profile.Start(profile.MemProfileAllocs).Stop() 40 | } 41 | 42 | func ExampleProfilePath() { 43 | // set the location that the profile will be written to 44 | defer profile.Start(profile.ProfilePath(os.Getenv("HOME"))).Stop() 45 | } 46 | 47 | func ExampleNoShutdownHook() { 48 | // disable the automatic shutdown hook. 49 | defer profile.Start(profile.NoShutdownHook).Stop() 50 | } 51 | 52 | func ExampleStart_withFlags() { 53 | // use the flags package to selectively enable profiling. 54 | mode := flag.String("profile.mode", "", "enable profiling mode, one of [cpu, mem, mutex, block]") 55 | flag.Parse() 56 | switch *mode { 57 | case "cpu": 58 | defer profile.Start(profile.CPUProfile).Stop() 59 | case "mem": 60 | defer profile.Start(profile.MemProfile).Stop() 61 | case "mutex": 62 | defer profile.Start(profile.MutexProfile).Stop() 63 | case "block": 64 | defer profile.Start(profile.BlockProfile).Stop() 65 | default: 66 | // do nothing 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pkg/profile 2 | 3 | go 1.13 4 | 5 | require github.com/felixge/fgprof v0.9.3 6 | -------------------------------------------------------------------------------- /profile.go: -------------------------------------------------------------------------------- 1 | // Package profile provides a simple way to manage runtime/pprof 2 | // profiling of your Go application. 3 | package profile 4 | 5 | import ( 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "os/signal" 10 | "path/filepath" 11 | "runtime" 12 | "runtime/pprof" 13 | "runtime/trace" 14 | "sync/atomic" 15 | 16 | "github.com/felixge/fgprof" 17 | ) 18 | 19 | const ( 20 | cpuMode = iota 21 | memMode 22 | mutexMode 23 | blockMode 24 | traceMode 25 | threadCreateMode 26 | goroutineMode 27 | clockMode 28 | ) 29 | 30 | // Profile represents an active profiling session. 31 | type Profile struct { 32 | // quiet suppresses informational messages during profiling. 33 | quiet bool 34 | 35 | // noShutdownHook controls whether the profiling package should 36 | // hook SIGINT to write profiles cleanly. 37 | noShutdownHook bool 38 | 39 | // mode holds the type of profiling that will be made 40 | mode int 41 | 42 | // path holds the base path where various profiling files are written. 43 | // If blank, the base path will be generated by ioutil.TempDir. 44 | path string 45 | 46 | // memProfileRate holds the rate for the memory profile. 47 | memProfileRate int 48 | 49 | // memProfileType holds the profile type for memory 50 | // profiles. Allowed values are `heap` and `allocs`. 51 | memProfileType string 52 | 53 | // closer holds a cleanup function that run after each profile 54 | closer func() 55 | 56 | // stopped records if a call to profile.Stop has been made 57 | stopped uint32 58 | } 59 | 60 | // NoShutdownHook controls whether the profiling package should 61 | // hook SIGINT to write profiles cleanly. 62 | // Programs with more sophisticated signal handling should set 63 | // this to true and ensure the Stop() function returned from Start() 64 | // is called during shutdown. 65 | func NoShutdownHook(p *Profile) { p.noShutdownHook = true } 66 | 67 | // Quiet suppresses informational messages during profiling. 68 | func Quiet(p *Profile) { p.quiet = true } 69 | 70 | // CPUProfile enables cpu profiling. 71 | // It disables any previous profiling settings. 72 | func CPUProfile(p *Profile) { p.mode = cpuMode } 73 | 74 | // DefaultMemProfileRate is the default memory profiling rate. 75 | // See also http://golang.org/pkg/runtime/#pkg-variables 76 | const DefaultMemProfileRate = 4096 77 | 78 | // MemProfile enables memory profiling. 79 | // It disables any previous profiling settings. 80 | func MemProfile(p *Profile) { 81 | p.memProfileRate = DefaultMemProfileRate 82 | p.mode = memMode 83 | } 84 | 85 | // MemProfileRate enables memory profiling at the preferred rate. 86 | // It disables any previous profiling settings. 87 | func MemProfileRate(rate int) func(*Profile) { 88 | return func(p *Profile) { 89 | p.memProfileRate = rate 90 | p.mode = memMode 91 | } 92 | } 93 | 94 | // MemProfileHeap changes which type of memory profiling to profile 95 | // the heap. 96 | func MemProfileHeap(p *Profile) { 97 | p.memProfileType = "heap" 98 | p.mode = memMode 99 | } 100 | 101 | // MemProfileAllocs changes which type of memory to profile 102 | // allocations. 103 | func MemProfileAllocs(p *Profile) { 104 | p.memProfileType = "allocs" 105 | p.mode = memMode 106 | } 107 | 108 | // MutexProfile enables mutex profiling. 109 | // It disables any previous profiling settings. 110 | func MutexProfile(p *Profile) { p.mode = mutexMode } 111 | 112 | // BlockProfile enables block (contention) profiling. 113 | // It disables any previous profiling settings. 114 | func BlockProfile(p *Profile) { p.mode = blockMode } 115 | 116 | // Trace profile enables execution tracing. 117 | // It disables any previous profiling settings. 118 | func TraceProfile(p *Profile) { p.mode = traceMode } 119 | 120 | // ThreadcreationProfile enables thread creation profiling.. 121 | // It disables any previous profiling settings. 122 | func ThreadcreationProfile(p *Profile) { p.mode = threadCreateMode } 123 | 124 | // GoroutineProfile enables goroutine profiling. 125 | // It disables any previous profiling settings. 126 | func GoroutineProfile(p *Profile) { p.mode = goroutineMode } 127 | 128 | // ClockProfile enables wall clock (fgprof) profiling. 129 | // It disables any previous profiling settings. 130 | func ClockProfile(p *Profile) { p.mode = clockMode } 131 | 132 | // ProfilePath controls the base path where various profiling 133 | // files are written. If blank, the base path will be generated 134 | // by ioutil.TempDir. 135 | func ProfilePath(path string) func(*Profile) { 136 | return func(p *Profile) { 137 | p.path = path 138 | } 139 | } 140 | 141 | // Stop stops the profile and flushes any unwritten data. 142 | func (p *Profile) Stop() { 143 | if !atomic.CompareAndSwapUint32(&p.stopped, 0, 1) { 144 | // someone has already called close 145 | return 146 | } 147 | p.closer() 148 | atomic.StoreUint32(&started, 0) 149 | } 150 | 151 | // started is non zero if a profile is running. 152 | var started uint32 153 | 154 | // Start starts a new profiling session. 155 | // The caller should call the Stop method on the value returned 156 | // to cleanly stop profiling. 157 | func Start(options ...func(*Profile)) interface { 158 | Stop() 159 | } { 160 | if !atomic.CompareAndSwapUint32(&started, 0, 1) { 161 | log.Fatal("profile: Start() already called") 162 | } 163 | 164 | var prof Profile 165 | for _, option := range options { 166 | option(&prof) 167 | } 168 | 169 | path, err := func() (string, error) { 170 | if p := prof.path; p != "" { 171 | return p, os.MkdirAll(p, 0777) 172 | } 173 | return ioutil.TempDir("", "profile") 174 | }() 175 | 176 | if err != nil { 177 | log.Fatalf("profile: could not create initial output directory: %v", err) 178 | } 179 | 180 | logf := func(format string, args ...interface{}) { 181 | if !prof.quiet { 182 | log.Printf(format, args...) 183 | } 184 | } 185 | 186 | if prof.memProfileType == "" { 187 | prof.memProfileType = "heap" 188 | } 189 | 190 | switch prof.mode { 191 | case cpuMode: 192 | fn := filepath.Join(path, "cpu.pprof") 193 | f, err := os.Create(fn) 194 | if err != nil { 195 | log.Fatalf("profile: could not create cpu profile %q: %v", fn, err) 196 | } 197 | logf("profile: cpu profiling enabled, %s", fn) 198 | pprof.StartCPUProfile(f) 199 | prof.closer = func() { 200 | pprof.StopCPUProfile() 201 | f.Close() 202 | logf("profile: cpu profiling disabled, %s", fn) 203 | } 204 | 205 | case memMode: 206 | fn := filepath.Join(path, "mem.pprof") 207 | f, err := os.Create(fn) 208 | if err != nil { 209 | log.Fatalf("profile: could not create memory profile %q: %v", fn, err) 210 | } 211 | old := runtime.MemProfileRate 212 | runtime.MemProfileRate = prof.memProfileRate 213 | logf("profile: memory profiling enabled (rate %d), %s", runtime.MemProfileRate, fn) 214 | prof.closer = func() { 215 | pprof.Lookup(prof.memProfileType).WriteTo(f, 0) 216 | f.Close() 217 | runtime.MemProfileRate = old 218 | logf("profile: memory profiling disabled, %s", fn) 219 | } 220 | 221 | case mutexMode: 222 | fn := filepath.Join(path, "mutex.pprof") 223 | f, err := os.Create(fn) 224 | if err != nil { 225 | log.Fatalf("profile: could not create mutex profile %q: %v", fn, err) 226 | } 227 | runtime.SetMutexProfileFraction(1) 228 | logf("profile: mutex profiling enabled, %s", fn) 229 | prof.closer = func() { 230 | if mp := pprof.Lookup("mutex"); mp != nil { 231 | mp.WriteTo(f, 0) 232 | } 233 | f.Close() 234 | runtime.SetMutexProfileFraction(0) 235 | logf("profile: mutex profiling disabled, %s", fn) 236 | } 237 | 238 | case blockMode: 239 | fn := filepath.Join(path, "block.pprof") 240 | f, err := os.Create(fn) 241 | if err != nil { 242 | log.Fatalf("profile: could not create block profile %q: %v", fn, err) 243 | } 244 | runtime.SetBlockProfileRate(1) 245 | logf("profile: block profiling enabled, %s", fn) 246 | prof.closer = func() { 247 | pprof.Lookup("block").WriteTo(f, 0) 248 | f.Close() 249 | runtime.SetBlockProfileRate(0) 250 | logf("profile: block profiling disabled, %s", fn) 251 | } 252 | 253 | case threadCreateMode: 254 | fn := filepath.Join(path, "threadcreation.pprof") 255 | f, err := os.Create(fn) 256 | if err != nil { 257 | log.Fatalf("profile: could not create thread creation profile %q: %v", fn, err) 258 | } 259 | logf("profile: thread creation profiling enabled, %s", fn) 260 | prof.closer = func() { 261 | if mp := pprof.Lookup("threadcreate"); mp != nil { 262 | mp.WriteTo(f, 0) 263 | } 264 | f.Close() 265 | logf("profile: thread creation profiling disabled, %s", fn) 266 | } 267 | 268 | case traceMode: 269 | fn := filepath.Join(path, "trace.out") 270 | f, err := os.Create(fn) 271 | if err != nil { 272 | log.Fatalf("profile: could not create trace output file %q: %v", fn, err) 273 | } 274 | if err := trace.Start(f); err != nil { 275 | log.Fatalf("profile: could not start trace: %v", err) 276 | } 277 | logf("profile: trace enabled, %s", fn) 278 | prof.closer = func() { 279 | trace.Stop() 280 | logf("profile: trace disabled, %s", fn) 281 | } 282 | 283 | case goroutineMode: 284 | fn := filepath.Join(path, "goroutine.pprof") 285 | f, err := os.Create(fn) 286 | if err != nil { 287 | log.Fatalf("profile: could not create goroutine profile %q: %v", fn, err) 288 | } 289 | logf("profile: goroutine profiling enabled, %s", fn) 290 | prof.closer = func() { 291 | if mp := pprof.Lookup("goroutine"); mp != nil { 292 | mp.WriteTo(f, 0) 293 | } 294 | f.Close() 295 | logf("profile: goroutine profiling disabled, %s", fn) 296 | } 297 | 298 | case clockMode: 299 | fn := filepath.Join(path, "clock.pprof") 300 | f, err := os.Create(fn) 301 | if err != nil { 302 | log.Fatalf("profile: could not create clock profile %q: %v", fn, err) 303 | } 304 | logf("profile: clock profiling enabled, %s", fn) 305 | stop := fgprof.Start(f, fgprof.FormatPprof) 306 | prof.closer = func() { 307 | stop() 308 | f.Close() 309 | logf("profile: clock profiling disabled, %s", fn) 310 | } 311 | } 312 | 313 | if !prof.noShutdownHook { 314 | go func() { 315 | c := make(chan os.Signal, 1) 316 | signal.Notify(c, os.Interrupt) 317 | <-c 318 | 319 | log.Println("profile: caught interrupt, stopping profiles") 320 | prof.Stop() 321 | 322 | os.Exit(0) 323 | }() 324 | } 325 | 326 | return &prof 327 | } 328 | -------------------------------------------------------------------------------- /profile_test.go: -------------------------------------------------------------------------------- 1 | package profile 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | "strings" 12 | "testing" 13 | ) 14 | 15 | type checkFn func(t *testing.T, stdout, stderr []byte, err error) 16 | 17 | func TestProfile(t *testing.T) { 18 | f, err := ioutil.TempFile("", "profile_test") 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | defer os.Remove(f.Name()) 23 | 24 | var profileTests = []struct { 25 | name string 26 | code string 27 | checks []checkFn 28 | }{{ 29 | name: "default profile (cpu)", 30 | code: ` 31 | package main 32 | 33 | import "github.com/pkg/profile" 34 | 35 | func main() { 36 | defer profile.Start().Stop() 37 | } 38 | `, 39 | checks: []checkFn{ 40 | NoStdout, 41 | Stderr("profile: cpu profiling enabled"), 42 | NoErr, 43 | }, 44 | }, { 45 | name: "memory profile", 46 | code: ` 47 | package main 48 | 49 | import "github.com/pkg/profile" 50 | 51 | func main() { 52 | defer profile.Start(profile.MemProfile).Stop() 53 | } 54 | `, 55 | checks: []checkFn{ 56 | NoStdout, 57 | Stderr("profile: memory profiling enabled"), 58 | NoErr, 59 | }, 60 | }, { 61 | name: "memory profile (rate 2048)", 62 | code: ` 63 | package main 64 | 65 | import "github.com/pkg/profile" 66 | 67 | func main() { 68 | defer profile.Start(profile.MemProfileRate(2048)).Stop() 69 | } 70 | `, 71 | checks: []checkFn{ 72 | NoStdout, 73 | Stderr("profile: memory profiling enabled (rate 2048)"), 74 | NoErr, 75 | }, 76 | }, { 77 | name: "double start", 78 | code: ` 79 | package main 80 | 81 | import "github.com/pkg/profile" 82 | 83 | func main() { 84 | profile.Start() 85 | profile.Start() 86 | } 87 | `, 88 | checks: []checkFn{ 89 | NoStdout, 90 | Stderr("cpu profiling enabled", "profile: Start() already called"), 91 | Err, 92 | }, 93 | }, { 94 | name: "block profile", 95 | code: ` 96 | package main 97 | 98 | import "github.com/pkg/profile" 99 | 100 | func main() { 101 | defer profile.Start(profile.BlockProfile).Stop() 102 | } 103 | `, 104 | checks: []checkFn{ 105 | NoStdout, 106 | Stderr("profile: block profiling enabled"), 107 | NoErr, 108 | }, 109 | }, { 110 | name: "mutex profile", 111 | code: ` 112 | package main 113 | 114 | import "github.com/pkg/profile" 115 | 116 | func main() { 117 | defer profile.Start(profile.MutexProfile).Stop() 118 | } 119 | `, 120 | checks: []checkFn{ 121 | NoStdout, 122 | Stderr("profile: mutex profiling enabled"), 123 | NoErr, 124 | }, 125 | }, { 126 | name: "clock profile", 127 | code: ` 128 | package main 129 | 130 | import "github.com/pkg/profile" 131 | 132 | func main() { 133 | defer profile.Start(profile.ClockProfile).Stop() 134 | } 135 | `, 136 | checks: []checkFn{ 137 | NoStdout, 138 | Stderr("profile: clock profiling enabled"), 139 | NoErr, 140 | }, 141 | }, { 142 | name: "profile path", 143 | code: ` 144 | package main 145 | 146 | import "github.com/pkg/profile" 147 | 148 | func main() { 149 | defer profile.Start(profile.ProfilePath(".")).Stop() 150 | } 151 | `, 152 | checks: []checkFn{ 153 | NoStdout, 154 | Stderr("profile: cpu profiling enabled, cpu.pprof"), 155 | NoErr, 156 | }, 157 | }, { 158 | name: "profile path error", 159 | code: ` 160 | package main 161 | 162 | import "github.com/pkg/profile" 163 | 164 | func main() { 165 | defer profile.Start(profile.ProfilePath("` + f.Name() + `")).Stop() 166 | } 167 | `, 168 | checks: []checkFn{ 169 | NoStdout, 170 | Stderr("could not create initial output"), 171 | Err, 172 | }, 173 | }, { 174 | name: "multiple profile sessions", 175 | code: ` 176 | package main 177 | 178 | import "github.com/pkg/profile" 179 | 180 | func main() { 181 | profile.Start(profile.CPUProfile).Stop() 182 | profile.Start(profile.MemProfile).Stop() 183 | profile.Start(profile.BlockProfile).Stop() 184 | profile.Start(profile.CPUProfile).Stop() 185 | profile.Start(profile.MutexProfile).Stop() 186 | } 187 | `, 188 | checks: []checkFn{ 189 | NoStdout, 190 | Stderr("profile: cpu profiling enabled", 191 | "profile: cpu profiling disabled", 192 | "profile: memory profiling enabled", 193 | "profile: memory profiling disabled", 194 | "profile: block profiling enabled", 195 | "profile: block profiling disabled", 196 | "profile: cpu profiling enabled", 197 | "profile: cpu profiling disabled", 198 | "profile: mutex profiling enabled", 199 | "profile: mutex profiling disabled"), 200 | NoErr, 201 | }, 202 | }, { 203 | name: "profile quiet", 204 | code: ` 205 | package main 206 | 207 | import "github.com/pkg/profile" 208 | 209 | func main() { 210 | defer profile.Start(profile.Quiet).Stop() 211 | } 212 | `, 213 | checks: []checkFn{NoStdout, NoStderr, NoErr}, 214 | }} 215 | for _, tt := range profileTests { 216 | t.Log(tt.name) 217 | stdout, stderr, err := runTest(t, tt.code) 218 | for _, f := range tt.checks { 219 | f(t, stdout, stderr, err) 220 | } 221 | } 222 | } 223 | 224 | // NoStdout checks that stdout was blank. 225 | func NoStdout(t *testing.T, stdout, _ []byte, _ error) { 226 | if len := len(stdout); len > 0 { 227 | t.Errorf("stdout: wanted 0 bytes, got %d", len) 228 | } 229 | } 230 | 231 | // Stderr verifies that the given lines match the output from stderr 232 | func Stderr(lines ...string) checkFn { 233 | return func(t *testing.T, _, stderr []byte, _ error) { 234 | r := bytes.NewReader(stderr) 235 | if !validateOutput(r, lines) { 236 | t.Errorf("stderr: wanted '%s', got '%s'", lines, stderr) 237 | } 238 | } 239 | } 240 | 241 | // NoStderr checks that stderr was blank. 242 | func NoStderr(t *testing.T, _, stderr []byte, _ error) { 243 | if len := len(stderr); len > 0 { 244 | t.Errorf("stderr: wanted 0 bytes, got %d", len) 245 | } 246 | } 247 | 248 | // Err checks that there was an error returned 249 | func Err(t *testing.T, _, _ []byte, err error) { 250 | if err == nil { 251 | t.Errorf("expected error") 252 | } 253 | } 254 | 255 | // NoErr checks that err was nil 256 | func NoErr(t *testing.T, _, _ []byte, err error) { 257 | if err != nil { 258 | t.Errorf("error: expected nil, got %v", err) 259 | } 260 | } 261 | 262 | // validatedOutput validates the given slice of lines against data from the given reader. 263 | func validateOutput(r io.Reader, want []string) bool { 264 | s := bufio.NewScanner(r) 265 | for _, line := range want { 266 | if !s.Scan() || !strings.Contains(s.Text(), line) { 267 | return false 268 | } 269 | } 270 | return true 271 | } 272 | 273 | var validateOutputTests = []struct { 274 | input string 275 | lines []string 276 | want bool 277 | }{{ 278 | input: "", 279 | want: true, 280 | }, { 281 | input: `profile: yes 282 | `, 283 | want: true, 284 | }, { 285 | input: `profile: yes 286 | `, 287 | lines: []string{"profile: yes"}, 288 | want: true, 289 | }, { 290 | input: `profile: yes 291 | profile: no 292 | `, 293 | lines: []string{"profile: yes"}, 294 | want: true, 295 | }, { 296 | input: `profile: yes 297 | profile: no 298 | `, 299 | lines: []string{"profile: yes", "profile: no"}, 300 | want: true, 301 | }, { 302 | input: `profile: yes 303 | profile: no 304 | `, 305 | lines: []string{"profile: no"}, 306 | want: false, 307 | }} 308 | 309 | func TestValidateOutput(t *testing.T) { 310 | for _, tt := range validateOutputTests { 311 | r := strings.NewReader(tt.input) 312 | got := validateOutput(r, tt.lines) 313 | if tt.want != got { 314 | t.Errorf("validateOutput(%q, %q), want %v, got %v", tt.input, tt.lines, tt.want, got) 315 | } 316 | } 317 | } 318 | 319 | // runTest executes the go program supplied and returns the contents of stdout, 320 | // stderr, and an error which may contain status information about the result 321 | // of the program. 322 | func runTest(t *testing.T, code string) ([]byte, []byte, error) { 323 | chk := func(err error) { 324 | if err != nil { 325 | t.Fatal(err) 326 | } 327 | } 328 | gopath, err := ioutil.TempDir("", "profile-gopath") 329 | chk(err) 330 | defer os.RemoveAll(gopath) 331 | 332 | srcdir := filepath.Join(gopath, "src") 333 | err = os.Mkdir(srcdir, 0755) 334 | chk(err) 335 | src := filepath.Join(srcdir, "main.go") 336 | err = ioutil.WriteFile(src, []byte(code), 0644) 337 | chk(err) 338 | 339 | cmd := exec.Command("go", "run", src) 340 | 341 | var stdout, stderr bytes.Buffer 342 | cmd.Stdout = &stdout 343 | cmd.Stderr = &stderr 344 | err = cmd.Run() 345 | return stdout.Bytes(), stderr.Bytes(), err 346 | } 347 | -------------------------------------------------------------------------------- /trace_test.go: -------------------------------------------------------------------------------- 1 | package profile_test 2 | 3 | import "github.com/pkg/profile" 4 | 5 | func ExampleTraceProfile() { 6 | // use execution tracing, rather than the default cpu profiling. 7 | defer profile.Start(profile.TraceProfile).Stop() 8 | } 9 | --------------------------------------------------------------------------------