From 5c410b0931895c8ab44cf4cf0465ae35347d2182 Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Thu, 6 Apr 2017 15:54:33 -0700 Subject: [PATCH] add gops support --- gops.go | 13 + vendor-log | 4 + vendor/github.com/google/gops/agent/agent.go | 237 ++++++++++++++++++ .../google/gops/internal/internal.go | 52 ++++ .../github.com/google/gops/signal/signal.go | 35 +++ vendor/github.com/kardianos/osext/osext.go | 33 +++ .../github.com/kardianos/osext/osext_plan9.go | 20 ++ .../kardianos/osext/osext_procfs.go | 36 +++ .../kardianos/osext/osext_sysctl.go | 126 ++++++++++ .../kardianos/osext/osext_windows.go | 34 +++ 10 files changed, 590 insertions(+) create mode 100644 gops.go create mode 100644 vendor/github.com/google/gops/agent/agent.go create mode 100644 vendor/github.com/google/gops/internal/internal.go create mode 100644 vendor/github.com/google/gops/signal/signal.go create mode 100644 vendor/github.com/kardianos/osext/osext.go create mode 100644 vendor/github.com/kardianos/osext/osext_plan9.go create mode 100644 vendor/github.com/kardianos/osext/osext_procfs.go create mode 100644 vendor/github.com/kardianos/osext/osext_sysctl.go create mode 100644 vendor/github.com/kardianos/osext/osext_windows.go diff --git a/gops.go b/gops.go new file mode 100644 index 0000000..184b656 --- /dev/null +++ b/gops.go @@ -0,0 +1,13 @@ +package main + +import ( + "log" + + "github.com/google/gops/agent" +) + +func init() { + if err := agent.Listen(nil); err != nil { + log.Fatal(err) + } +} diff --git a/vendor-log b/vendor-log index d813ae1..9e85e2b 100644 --- a/vendor-log +++ b/vendor-log @@ -102,3 +102,7 @@ a00a8beb369cafd88bb7b32f31fc4ff3219c3565 github.com/Xe/gopreload ff09b135c25aae272398c51a07235b90a75aa4f0 github.com/pkg/errors f759b797c0ff6b2c514202198fe5e8ba90094c14 github.com/Xe/ln ff09b135c25aae272398c51a07235b90a75aa4f0 github.com/pkg/errors +62f833fc9f6c4d3223bdb37bd0c2f8951bed8596 github.com/google/gops/agent +62f833fc9f6c4d3223bdb37bd0c2f8951bed8596 github.com/google/gops/internal +62f833fc9f6c4d3223bdb37bd0c2f8951bed8596 github.com/google/gops/signal +c2c54e542fb797ad986b31721e1baedf214ca413 github.com/kardianos/osext diff --git a/vendor/github.com/google/gops/agent/agent.go b/vendor/github.com/google/gops/agent/agent.go new file mode 100644 index 0000000..5708391 --- /dev/null +++ b/vendor/github.com/google/gops/agent/agent.go @@ -0,0 +1,237 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package agent provides hooks programs can register to retrieve +// diagnostics data by using gops. +package agent + +import ( + "fmt" + "io" + "io/ioutil" + "net" + "os" + gosignal "os/signal" + "runtime" + "runtime/pprof" + "runtime/trace" + "strconv" + "sync" + "time" + + "bufio" + + "github.com/google/gops/internal" + "github.com/google/gops/signal" + "github.com/kardianos/osext" +) + +const defaultAddr = "127.0.0.1:0" + +var ( + mu sync.Mutex + portfile string + listener net.Listener + + units = []string{" bytes", "KB", "MB", "GB", "TB", "PB"} +) + +// Options allows configuring the started agent. +type Options struct { + // Addr is the host:port the agent will be listening at. + // Optional. + Addr string + + // NoShutdownCleanup tells the agent not to automatically cleanup + // resources if the running process receives an interrupt. + // Optional. + NoShutdownCleanup bool +} + +// Listen starts the gops agent on a host process. Once agent started, users +// can use the advanced gops features. The agent will listen to Interrupt +// signals and exit the process, if you need to perform further work on the +// Interrupt signal use the options parameter to configure the agent +// accordingly. +// +// Note: The agent exposes an endpoint via a TCP connection that can be used by +// any program on the system. Review your security requirements before starting +// the agent. +func Listen(opts *Options) error { + mu.Lock() + defer mu.Unlock() + + if opts == nil { + opts = &Options{} + } + if portfile != "" { + return fmt.Errorf("gops: agent already listening at: %v", listener.Addr()) + } + + gopsdir, err := internal.ConfigDir() + if err != nil { + return err + } + err = os.MkdirAll(gopsdir, os.ModePerm) + if err != nil { + return err + } + if !opts.NoShutdownCleanup { + gracefulShutdown() + } + + addr := opts.Addr + if addr == "" { + addr = defaultAddr + } + ln, err := net.Listen("tcp", addr) + if err != nil { + return err + } + listener = ln + port := listener.Addr().(*net.TCPAddr).Port + portfile = fmt.Sprintf("%s/%d", gopsdir, os.Getpid()) + err = ioutil.WriteFile(portfile, []byte(strconv.Itoa(port)), os.ModePerm) + if err != nil { + return err + } + + go listen() + return nil +} + +func listen() { + buf := make([]byte, 1) + for { + fd, err := listener.Accept() + if err != nil { + fmt.Fprintf(os.Stderr, "gops: %v", err) + if netErr, ok := err.(net.Error); ok && !netErr.Temporary() { + break + } + continue + } + if _, err := fd.Read(buf); err != nil { + fmt.Fprintf(os.Stderr, "gops: %v", err) + continue + } + if err := handle(fd, buf); err != nil { + fmt.Fprintf(os.Stderr, "gops: %v", err) + continue + } + fd.Close() + } +} + +func gracefulShutdown() { + c := make(chan os.Signal, 1) + gosignal.Notify(c, os.Interrupt) + go func() { + // cleanup the socket on shutdown. + <-c + Close() + os.Exit(1) + }() +} + +// Close closes the agent, removing temporary files and closing the TCP listener. +// If no agent is listening, Close does nothing. +func Close() { + mu.Lock() + defer mu.Unlock() + + if portfile != "" { + os.Remove(portfile) + portfile = "" + } + if listener != nil { + listener.Close() + } +} + +func formatBytes(val uint64) string { + var i int + var target uint64 + for i = range units { + target = 1 << uint(10*(i+1)) + if val < target { + break + } + } + if i > 0 { + return fmt.Sprintf("%0.2f%s (%d bytes)", float64(val)/(float64(target)/1024), units[i], val) + } + return fmt.Sprintf("%d bytes", val) +} + +func handle(conn io.Writer, msg []byte) error { + switch msg[0] { + case signal.StackTrace: + return pprof.Lookup("goroutine").WriteTo(conn, 2) + case signal.GC: + runtime.GC() + _, err := conn.Write([]byte("ok")) + return err + case signal.MemStats: + var s runtime.MemStats + runtime.ReadMemStats(&s) + fmt.Fprintf(conn, "alloc: %v\n", formatBytes(s.Alloc)) + fmt.Fprintf(conn, "total-alloc: %v\n", formatBytes(s.TotalAlloc)) + fmt.Fprintf(conn, "sys: %v\n", formatBytes(s.Sys)) + fmt.Fprintf(conn, "lookups: %v\n", s.Lookups) + fmt.Fprintf(conn, "mallocs: %v\n", s.Mallocs) + fmt.Fprintf(conn, "frees: %v\n", s.Frees) + fmt.Fprintf(conn, "heap-alloc: %v\n", formatBytes(s.HeapAlloc)) + fmt.Fprintf(conn, "heap-sys: %v\n", formatBytes(s.HeapSys)) + fmt.Fprintf(conn, "heap-idle: %v\n", formatBytes(s.HeapIdle)) + fmt.Fprintf(conn, "heap-in-use: %v\n", formatBytes(s.HeapInuse)) + fmt.Fprintf(conn, "heap-released: %v\n", formatBytes(s.HeapReleased)) + fmt.Fprintf(conn, "heap-objects: %v\n", s.HeapObjects) + fmt.Fprintf(conn, "stack-in-use: %v\n", formatBytes(s.StackInuse)) + fmt.Fprintf(conn, "stack-sys: %v\n", formatBytes(s.StackSys)) + fmt.Fprintf(conn, "next-gc: when heap-alloc >= %v\n", formatBytes(s.NextGC)) + lastGC := "-" + if s.LastGC != 0 { + lastGC = fmt.Sprint(time.Unix(0, int64(s.LastGC))) + } + fmt.Fprintf(conn, "last-gc: %v\n", lastGC) + fmt.Fprintf(conn, "gc-pause: %v\n", time.Duration(s.PauseTotalNs)) + fmt.Fprintf(conn, "num-gc: %v\n", s.NumGC) + fmt.Fprintf(conn, "enable-gc: %v\n", s.EnableGC) + fmt.Fprintf(conn, "debug-gc: %v\n", s.DebugGC) + case signal.Version: + fmt.Fprintf(conn, "%v\n", runtime.Version()) + case signal.HeapProfile: + pprof.WriteHeapProfile(conn) + case signal.CPUProfile: + if err := pprof.StartCPUProfile(conn); err != nil { + return err + } + time.Sleep(30 * time.Second) + pprof.StopCPUProfile() + case signal.Stats: + fmt.Fprintf(conn, "goroutines: %v\n", runtime.NumGoroutine()) + fmt.Fprintf(conn, "OS threads: %v\n", pprof.Lookup("threadcreate").Count()) + fmt.Fprintf(conn, "GOMAXPROCS: %v\n", runtime.GOMAXPROCS(0)) + fmt.Fprintf(conn, "num CPU: %v\n", runtime.NumCPU()) + case signal.BinaryDump: + path, err := osext.Executable() + if err != nil { + return err + } + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + + _, err = bufio.NewReader(f).WriteTo(conn) + return err + case signal.Trace: + trace.Start(conn) + time.Sleep(5 * time.Second) + trace.Stop() + } + return nil +} diff --git a/vendor/github.com/google/gops/internal/internal.go b/vendor/github.com/google/gops/internal/internal.go new file mode 100644 index 0000000..1382822 --- /dev/null +++ b/vendor/github.com/google/gops/internal/internal.go @@ -0,0 +1,52 @@ +package internal + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "os/user" + "path/filepath" + "runtime" + "strings" +) + +func ConfigDir() (string, error) { + if runtime.GOOS == "windows" { + return filepath.Join(os.Getenv("APPDATA"), "gops"), nil + } + homeDir := guessUnixHomeDir() + if homeDir == "" { + return "", errors.New("unable to get current user home directory: os/user lookup failed; $HOME is empty") + } + return filepath.Join(homeDir, ".config", "gops"), nil +} + +func guessUnixHomeDir() string { + usr, err := user.Current() + if err == nil { + return usr.HomeDir + } + return os.Getenv("HOME") +} + +func PIDFile(pid int) (string, error) { + gopsdir, err := ConfigDir() + if err != nil { + return "", err + } + return fmt.Sprintf("%s/%d", gopsdir, pid), nil +} + +func GetPort(pid int) (string, error) { + portfile, err := PIDFile(pid) + if err != nil { + return "", err + } + b, err := ioutil.ReadFile(portfile) + if err != nil { + return "", err + } + port := strings.TrimSpace(string(b)) + return port, nil +} diff --git a/vendor/github.com/google/gops/signal/signal.go b/vendor/github.com/google/gops/signal/signal.go new file mode 100644 index 0000000..b2bfbe1 --- /dev/null +++ b/vendor/github.com/google/gops/signal/signal.go @@ -0,0 +1,35 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package signal contains signals used to communicate to the gops agents. +package signal + +const ( + // StackTrace represents a command to print stack trace. + StackTrace = byte(0x1) + + // GC runs the garbage collector. + GC = byte(0x2) + + // MemStats reports memory stats. + MemStats = byte(0x3) + + // Version prints the Go version. + Version = byte(0x4) + + // HeapProfile starts `go tool pprof` with the current memory profile. + HeapProfile = byte(0x5) + + // CPUProfile starts `go tool pprof` with the current CPU profile + CPUProfile = byte(0x6) + + // Stats returns Go runtime statistics such as number of goroutines, GOMAXPROCS, and NumCPU. + Stats = byte(0x7) + + // Trace starts the Go execution tracer, waits 5 seconds and launches the trace tool. + Trace = byte(0x8) + + // BinaryDump returns running binary file. + BinaryDump = byte(0x9) +) diff --git a/vendor/github.com/kardianos/osext/osext.go b/vendor/github.com/kardianos/osext/osext.go new file mode 100644 index 0000000..17f380f --- /dev/null +++ b/vendor/github.com/kardianos/osext/osext.go @@ -0,0 +1,33 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Extensions to the standard "os" package. +package osext // import "github.com/kardianos/osext" + +import "path/filepath" + +var cx, ce = executableClean() + +func executableClean() (string, error) { + p, err := executable() + return filepath.Clean(p), err +} + +// Executable returns an absolute path that can be used to +// re-invoke the current program. +// It may not be valid after the current program exits. +func Executable() (string, error) { + return cx, ce +} + +// Returns same path as Executable, returns just the folder +// path. Excludes the executable name and any trailing slash. +func ExecutableFolder() (string, error) { + p, err := Executable() + if err != nil { + return "", err + } + + return filepath.Dir(p), nil +} diff --git a/vendor/github.com/kardianos/osext/osext_plan9.go b/vendor/github.com/kardianos/osext/osext_plan9.go new file mode 100644 index 0000000..655750c --- /dev/null +++ b/vendor/github.com/kardianos/osext/osext_plan9.go @@ -0,0 +1,20 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package osext + +import ( + "os" + "strconv" + "syscall" +) + +func executable() (string, error) { + f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text") + if err != nil { + return "", err + } + defer f.Close() + return syscall.Fd2path(int(f.Fd())) +} diff --git a/vendor/github.com/kardianos/osext/osext_procfs.go b/vendor/github.com/kardianos/osext/osext_procfs.go new file mode 100644 index 0000000..d59847e --- /dev/null +++ b/vendor/github.com/kardianos/osext/osext_procfs.go @@ -0,0 +1,36 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux netbsd solaris dragonfly + +package osext + +import ( + "errors" + "fmt" + "os" + "runtime" + "strings" +) + +func executable() (string, error) { + switch runtime.GOOS { + case "linux": + const deletedTag = " (deleted)" + execpath, err := os.Readlink("/proc/self/exe") + if err != nil { + return execpath, err + } + execpath = strings.TrimSuffix(execpath, deletedTag) + execpath = strings.TrimPrefix(execpath, deletedTag) + return execpath, nil + case "netbsd": + return os.Readlink("/proc/curproc/exe") + case "dragonfly": + return os.Readlink("/proc/curproc/file") + case "solaris": + return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid())) + } + return "", errors.New("ExecPath not implemented for " + runtime.GOOS) +} diff --git a/vendor/github.com/kardianos/osext/osext_sysctl.go b/vendor/github.com/kardianos/osext/osext_sysctl.go new file mode 100644 index 0000000..66da0bc --- /dev/null +++ b/vendor/github.com/kardianos/osext/osext_sysctl.go @@ -0,0 +1,126 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin freebsd openbsd + +package osext + +import ( + "os" + "os/exec" + "path/filepath" + "runtime" + "syscall" + "unsafe" +) + +var initCwd, initCwdErr = os.Getwd() + +func executable() (string, error) { + var mib [4]int32 + switch runtime.GOOS { + case "freebsd": + mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1} + case "darwin": + mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1} + case "openbsd": + mib = [4]int32{1 /* CTL_KERN */, 55 /* KERN_PROC_ARGS */, int32(os.Getpid()), 1 /* KERN_PROC_ARGV */} + } + + n := uintptr(0) + // Get length. + _, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0) + if errNum != 0 { + return "", errNum + } + if n == 0 { // This shouldn't happen. + return "", nil + } + buf := make([]byte, n) + _, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0) + if errNum != 0 { + return "", errNum + } + if n == 0 { // This shouldn't happen. + return "", nil + } + + var execPath string + switch runtime.GOOS { + case "openbsd": + // buf now contains **argv, with pointers to each of the C-style + // NULL terminated arguments. + var args []string + argv := uintptr(unsafe.Pointer(&buf[0])) + Loop: + for { + argp := *(**[1 << 20]byte)(unsafe.Pointer(argv)) + if argp == nil { + break + } + for i := 0; uintptr(i) < n; i++ { + // we don't want the full arguments list + if string(argp[i]) == " " { + break Loop + } + if argp[i] != 0 { + continue + } + args = append(args, string(argp[:i])) + n -= uintptr(i) + break + } + if n < unsafe.Sizeof(argv) { + break + } + argv += unsafe.Sizeof(argv) + n -= unsafe.Sizeof(argv) + } + execPath = args[0] + // There is no canonical way to get an executable path on + // OpenBSD, so check PATH in case we are called directly + if execPath[0] != '/' && execPath[0] != '.' { + execIsInPath, err := exec.LookPath(execPath) + if err == nil { + execPath = execIsInPath + } + } + default: + for i, v := range buf { + if v == 0 { + buf = buf[:i] + break + } + } + execPath = string(buf) + } + + var err error + // execPath will not be empty due to above checks. + // Try to get the absolute path if the execPath is not rooted. + if execPath[0] != '/' { + execPath, err = getAbs(execPath) + if err != nil { + return execPath, err + } + } + // For darwin KERN_PROCARGS may return the path to a symlink rather than the + // actual executable. + if runtime.GOOS == "darwin" { + if execPath, err = filepath.EvalSymlinks(execPath); err != nil { + return execPath, err + } + } + return execPath, nil +} + +func getAbs(execPath string) (string, error) { + if initCwdErr != nil { + return execPath, initCwdErr + } + // The execPath may begin with a "../" or a "./" so clean it first. + // Join the two paths, trailing and starting slashes undetermined, so use + // the generic Join function. + return filepath.Join(initCwd, filepath.Clean(execPath)), nil +} diff --git a/vendor/github.com/kardianos/osext/osext_windows.go b/vendor/github.com/kardianos/osext/osext_windows.go new file mode 100644 index 0000000..72d282c --- /dev/null +++ b/vendor/github.com/kardianos/osext/osext_windows.go @@ -0,0 +1,34 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package osext + +import ( + "syscall" + "unicode/utf16" + "unsafe" +) + +var ( + kernel = syscall.MustLoadDLL("kernel32.dll") + getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW") +) + +// GetModuleFileName() with hModule = NULL +func executable() (exePath string, err error) { + return getModuleFileName() +} + +func getModuleFileName() (string, error) { + var n uint32 + b := make([]uint16, syscall.MAX_PATH) + size := uint32(len(b)) + + r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size)) + n = uint32(r0) + if n == 0 { + return "", e1 + } + return string(utf16.Decode(b[0:n])), nil +}