Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(72)

Unified Diff: common/runtime/profiling/profiler.go

Issue 2548253004: Add profiling, integrate into Butler/Annotee. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | logdog/client/cmd/logdog_annotee/main.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: common/runtime/profiling/profiler.go
diff --git a/common/runtime/profiling/profiler.go b/common/runtime/profiling/profiler.go
new file mode 100644
index 0000000000000000000000000000000000000000..e599c84a033728667dbf437b8ff28e943c2c3f8f
--- /dev/null
+++ b/common/runtime/profiling/profiler.go
@@ -0,0 +1,160 @@
+// Copyright 2016 The LUCI Authors. All rights reserved.
+// Use of this source code is governed under the Apache License, Version 2.0
+// that can be found in the LICENSE file.
+
+package profiling
+
+import (
+ "flag"
+ "fmt"
+ "net"
+ "net/http"
+ httpProf "net/http/pprof"
+ "os"
+ "path/filepath"
+ "runtime"
+ "runtime/pprof"
+ "sync/atomic"
+
+ "github.com/julienschmidt/httprouter"
+ "github.com/luci/luci-go/common/clock"
+ "github.com/luci/luci-go/common/errors"
+ "github.com/luci/luci-go/common/logging"
+)
+
+// Profiler helps setup and manage profiling
+type Profiler struct {
+ // BindHTTP, if not empty, is the HTTP address to bind to.
+ //
+ // Can also be configured with "-profile-bind-http" flag.
+ BindHTTP string
+
+ // Dir, if set, is the path where profiling data will be written to.
+ //
+ // Can also be configured with "-profile-output-dir" flag.
+ Dir string
+
+ // Logger, if not nil, will be used to log events and errors. If nil, no
+ // logging will be used.
+ Logger logging.Logger
+ // Clock is the clock instance to use. If nil, the system clock will be used.
+ Clock clock.Clock
+
+ // listener is the active listener instance. It is set when Start is called.
+ listener net.Listener
+
+ // pathCounter is an atomic counter used to ensure non-conflicting paths.
+ pathCounter uint32
+}
+
+// AddFlags adds command line flags to common Profiler fields.
+func (p *Profiler) AddFlags(fs *flag.FlagSet) {
+ fs.StringVar(&p.BindHTTP, "profile-bind-http", "",
+ "If specified, run a runtime profiler HTTP server bound to this [address][:port].")
+ fs.StringVar(&p.Dir, "profile-output-dir", "",
+ "If specified, allow generation of profiling artifacts, which will be written here.")
+}
+
+// Start starts the Profiler's configured operations. On success, returns a
+// function that can be called to shutdown the profiling server.
+//
+// Calling Stop is not necessary, but will enable end-of-operation profiling
+// to be gathered.
+func (p *Profiler) Start() error {
+ // If we have an output directory, start our CPU profiling.
+ if p.BindHTTP != "" {
+ if err := p.startHTTP(); err != nil {
+ return errors.Annotate(err).Reason("failed to start HTTP server").Err()
+ }
+ }
+ return nil
+}
+
+func (p *Profiler) startHTTP() error {
+ // Register paths: https://golang.org/src/net/http/pprof/pprof.go
+ router := httprouter.New()
+ router.HandlerFunc("GET", "/debug/pprof/", httpProf.Index)
+ router.HandlerFunc("GET", "/debug/pprof/cmdline", httpProf.Cmdline)
+ router.HandlerFunc("GET", "/debug/pprof/profile", httpProf.Profile)
+ router.HandlerFunc("GET", "/debug/pprof/symbol", httpProf.Symbol)
+ router.HandlerFunc("GET", "/debug/pprof/trace", httpProf.Trace)
+ for _, p := range pprof.Profiles() {
+ name := p.Name()
+ router.Handler("GET", fmt.Sprintf("/debug/pprof/%s", name), httpProf.Handler(name))
+ }
+
+ // Bind to our profiling port.
+ l, err := net.Listen("tcp4", p.BindHTTP)
+ if err != nil {
+ return errors.Annotate(err).Reason("failed to bind to TCP4 address: %(addr)q").
+ D("addr", p.BindHTTP).Err()
+ }
+
+ server := http.Server{
+ Handler: http.HandlerFunc(router.ServeHTTP),
+ }
+ go func() {
+ if err := server.Serve(l); err != nil {
+ p.getLogger().Errorf("Error serving profile HTTP: %s", err)
+ }
+ }()
+ return nil
+}
+
+// Stop stops the Profiler's operations.
+func (p *Profiler) Stop() {
+ if p.listener != nil {
+ if err := p.listener.Close(); err != nil {
+ p.getLogger().Warningf("Failed to stop profile HTTP server: %s", err)
+ }
+ p.listener = nil
+ }
+
+ // Take one final snapshot.
+ p.DumpSnapshot()
+}
+
+// DumpSnapshot dumps a profile snapshot to the configured output directory. If
+// no output directory is configured, nothing wil happen.
+func (p *Profiler) DumpSnapshot() error {
+ if p.Dir == "" {
+ return nil
+ }
+
+ if err := p.dumpHeapProfile(); err != nil {
+ return errors.Annotate(err).Reason("failed to dump heap profile").Err()
+ }
+ return nil
+}
+
+func (p *Profiler) dumpHeapProfile() error {
+ fd, err := os.Create(p.generateOutPath("memory"))
+ if err != nil {
+ return errors.Annotate(err).Reason("failed to create output file").Err()
+ }
+ defer fd.Close()
+
+ // Get up-to-date statistics.
+ runtime.GC()
+ if err := pprof.WriteHeapProfile(fd); err != nil {
+ return errors.Annotate(err).Reason("failed to write heap profile").Err()
+ }
+ return nil
+}
+
+func (p *Profiler) generateOutPath(base string) string {
+ clk := p.Clock
+ if clk == nil {
+ clk = clock.GetSystemClock()
+ }
+ now := clk.Now()
+ counter := atomic.AddUint32(&p.pathCounter, 1) - 1
+ return filepath.Join(p.Dir, fmt.Sprintf("%s_%d_%d.prof", base, now.Unix(), counter))
+}
+
+func (p *Profiler) getLogger() logging.Logger {
+ if p.Logger != nil {
+ return p.Logger
+ }
+ return logging.Null
+}
« no previous file with comments | « no previous file | logdog/client/cmd/logdog_annotee/main.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698