forked from lug/matterbridge
		
	
		
			
				
	
	
		
			238 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			238 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// 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
 | 
						|
}
 |