| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. |
| 4 |
| 5 package profiling |
| 6 |
| 7 import ( |
| 8 "flag" |
| 9 "fmt" |
| 10 "net" |
| 11 "net/http" |
| 12 httpProf "net/http/pprof" |
| 13 "os" |
| 14 "path/filepath" |
| 15 "runtime" |
| 16 "runtime/pprof" |
| 17 "sync/atomic" |
| 18 |
| 19 "github.com/julienschmidt/httprouter" |
| 20 "github.com/luci/luci-go/common/clock" |
| 21 "github.com/luci/luci-go/common/errors" |
| 22 "github.com/luci/luci-go/common/logging" |
| 23 ) |
| 24 |
| 25 // Profiler helps setup and manage profiling |
| 26 type Profiler struct { |
| 27 // BindHTTP, if not empty, is the HTTP address to bind to. |
| 28 // |
| 29 // Can also be configured with "-profile-bind-http" flag. |
| 30 BindHTTP string |
| 31 |
| 32 // Dir, if set, is the path where profiling data will be written to. |
| 33 // |
| 34 // Can also be configured with "-profile-output-dir" flag. |
| 35 Dir string |
| 36 |
| 37 // Logger, if not nil, will be used to log events and errors. If nil, no |
| 38 // logging will be used. |
| 39 Logger logging.Logger |
| 40 // Clock is the clock instance to use. If nil, the system clock will be
used. |
| 41 Clock clock.Clock |
| 42 |
| 43 // listener is the active listener instance. It is set when Start is cal
led. |
| 44 listener net.Listener |
| 45 |
| 46 // pathCounter is an atomic counter used to ensure non-conflicting paths
. |
| 47 pathCounter uint32 |
| 48 } |
| 49 |
| 50 // AddFlags adds command line flags to common Profiler fields. |
| 51 func (p *Profiler) AddFlags(fs *flag.FlagSet) { |
| 52 fs.StringVar(&p.BindHTTP, "profile-bind-http", "", |
| 53 "If specified, run a runtime profiler HTTP server bound to this
[address][:port].") |
| 54 fs.StringVar(&p.Dir, "profile-output-dir", "", |
| 55 "If specified, allow generation of profiling artifacts, which wi
ll be written here.") |
| 56 } |
| 57 |
| 58 // Start starts the Profiler's configured operations. On success, returns a |
| 59 // function that can be called to shutdown the profiling server. |
| 60 // |
| 61 // Calling Stop is not necessary, but will enable end-of-operation profiling |
| 62 // to be gathered. |
| 63 func (p *Profiler) Start() error { |
| 64 // If we have an output directory, start our CPU profiling. |
| 65 if p.BindHTTP != "" { |
| 66 if err := p.startHTTP(); err != nil { |
| 67 return errors.Annotate(err).Reason("failed to start HTTP
server").Err() |
| 68 } |
| 69 } |
| 70 return nil |
| 71 } |
| 72 |
| 73 func (p *Profiler) startHTTP() error { |
| 74 // Register paths: https://golang.org/src/net/http/pprof/pprof.go |
| 75 router := httprouter.New() |
| 76 router.HandlerFunc("GET", "/debug/pprof/", httpProf.Index) |
| 77 router.HandlerFunc("GET", "/debug/pprof/cmdline", httpProf.Cmdline) |
| 78 router.HandlerFunc("GET", "/debug/pprof/profile", httpProf.Profile) |
| 79 router.HandlerFunc("GET", "/debug/pprof/symbol", httpProf.Symbol) |
| 80 router.HandlerFunc("GET", "/debug/pprof/trace", httpProf.Trace) |
| 81 for _, p := range pprof.Profiles() { |
| 82 name := p.Name() |
| 83 router.Handler("GET", fmt.Sprintf("/debug/pprof/%s", name), http
Prof.Handler(name)) |
| 84 } |
| 85 |
| 86 // Bind to our profiling port. |
| 87 l, err := net.Listen("tcp4", p.BindHTTP) |
| 88 if err != nil { |
| 89 return errors.Annotate(err).Reason("failed to bind to TCP4 addre
ss: %(addr)q"). |
| 90 D("addr", p.BindHTTP).Err() |
| 91 } |
| 92 |
| 93 server := http.Server{ |
| 94 Handler: http.HandlerFunc(router.ServeHTTP), |
| 95 } |
| 96 go func() { |
| 97 if err := server.Serve(l); err != nil { |
| 98 p.getLogger().Errorf("Error serving profile HTTP: %s", e
rr) |
| 99 } |
| 100 }() |
| 101 return nil |
| 102 } |
| 103 |
| 104 // Stop stops the Profiler's operations. |
| 105 func (p *Profiler) Stop() { |
| 106 if p.listener != nil { |
| 107 if err := p.listener.Close(); err != nil { |
| 108 p.getLogger().Warningf("Failed to stop profile HTTP serv
er: %s", err) |
| 109 } |
| 110 p.listener = nil |
| 111 } |
| 112 |
| 113 // Take one final snapshot. |
| 114 p.DumpSnapshot() |
| 115 } |
| 116 |
| 117 // DumpSnapshot dumps a profile snapshot to the configured output directory. If |
| 118 // no output directory is configured, nothing wil happen. |
| 119 func (p *Profiler) DumpSnapshot() error { |
| 120 if p.Dir == "" { |
| 121 return nil |
| 122 } |
| 123 |
| 124 if err := p.dumpHeapProfile(); err != nil { |
| 125 return errors.Annotate(err).Reason("failed to dump heap profile"
).Err() |
| 126 } |
| 127 return nil |
| 128 } |
| 129 |
| 130 func (p *Profiler) dumpHeapProfile() error { |
| 131 fd, err := os.Create(p.generateOutPath("memory")) |
| 132 if err != nil { |
| 133 return errors.Annotate(err).Reason("failed to create output file
").Err() |
| 134 } |
| 135 defer fd.Close() |
| 136 |
| 137 // Get up-to-date statistics. |
| 138 runtime.GC() |
| 139 if err := pprof.WriteHeapProfile(fd); err != nil { |
| 140 return errors.Annotate(err).Reason("failed to write heap profile
").Err() |
| 141 } |
| 142 return nil |
| 143 } |
| 144 |
| 145 func (p *Profiler) generateOutPath(base string) string { |
| 146 clk := p.Clock |
| 147 if clk == nil { |
| 148 clk = clock.GetSystemClock() |
| 149 } |
| 150 now := clk.Now() |
| 151 counter := atomic.AddUint32(&p.pathCounter, 1) - 1 |
| 152 return filepath.Join(p.Dir, fmt.Sprintf("%s_%d_%d.prof", base, now.Unix(
), counter)) |
| 153 } |
| 154 |
| 155 func (p *Profiler) getLogger() logging.Logger { |
| 156 if p.Logger != nil { |
| 157 return p.Logger |
| 158 } |
| 159 return logging.Null |
| 160 } |
| OLD | NEW |