1
0
mirror of https://github.com/google/gops.git synced 2024-11-30 08:36:53 +02:00
gops/cmd.go
Tobias Klauser f1b8f2b15c
Add prefix to profile dump file names (#101)
When getting profile dumps for further processing, it's hard to distinguish
CPU and heap profiles from the file names alone. Make this easier by
adding a prefix (cpu or heap) to the file name depending on the type of
profile.
2020-04-29 10:44:32 -07:00

217 lines
5.2 KiB
Go

// Copyright 2017 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 main
import (
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"os/exec"
"strconv"
"strings"
"github.com/google/gops/internal"
"github.com/google/gops/signal"
)
var cmds = map[string](func(addr net.TCPAddr, params []string) error){
"stack": stackTrace,
"gc": gc,
"memstats": memStats,
"version": version,
"pprof-heap": pprofHeap,
"pprof-cpu": pprofCPU,
"stats": stats,
"trace": trace,
"setgc": setGC,
}
func setGC(addr net.TCPAddr, params []string) error {
if len(params) != 1 {
return errors.New("missing gc percentage")
}
perc, err := strconv.ParseInt(params[0], 10, strconv.IntSize)
if err != nil {
return err
}
buf := make([]byte, binary.MaxVarintLen64)
binary.PutVarint(buf, perc)
return cmdWithPrint(addr, signal.SetGCPercent, buf...)
}
func stackTrace(addr net.TCPAddr, _ []string) error {
return cmdWithPrint(addr, signal.StackTrace)
}
func gc(addr net.TCPAddr, _ []string) error {
_, err := cmd(addr, signal.GC)
return err
}
func memStats(addr net.TCPAddr, _ []string) error {
return cmdWithPrint(addr, signal.MemStats)
}
func version(addr net.TCPAddr, _ []string) error {
return cmdWithPrint(addr, signal.Version)
}
func pprofHeap(addr net.TCPAddr, _ []string) error {
return pprof(addr, signal.HeapProfile, "heap")
}
func pprofCPU(addr net.TCPAddr, _ []string) error {
fmt.Println("Profiling CPU now, will take 30 secs...")
return pprof(addr, signal.CPUProfile, "cpu")
}
func trace(addr net.TCPAddr, _ []string) error {
fmt.Println("Tracing now, will take 5 secs...")
out, err := cmd(addr, signal.Trace)
if err != nil {
return err
}
if len(out) == 0 {
return errors.New("nothing has traced")
}
tmpfile, err := ioutil.TempFile("", "trace")
if err != nil {
return err
}
if err := ioutil.WriteFile(tmpfile.Name(), out, 0); err != nil {
return err
}
fmt.Printf("Trace dump saved to: %s\n", tmpfile.Name())
// If go tool chain not found, stopping here and keep trace file.
if _, err := exec.LookPath("go"); err != nil {
return nil
}
defer os.Remove(tmpfile.Name())
cmd := exec.Command("go", "tool", "trace", tmpfile.Name())
cmd.Env = os.Environ()
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func pprof(addr net.TCPAddr, p byte, prefix string) error {
tmpDumpFile, err := ioutil.TempFile("", prefix+"_profile")
if err != nil {
return err
}
{
out, err := cmd(addr, p)
if err != nil {
return err
}
if len(out) == 0 {
return errors.New("failed to read the profile")
}
if err := ioutil.WriteFile(tmpDumpFile.Name(), out, 0); err != nil {
return err
}
fmt.Printf("Profile dump saved to: %s\n", tmpDumpFile.Name())
// If go tool chain not found, stopping here and keep dump file.
if _, err := exec.LookPath("go"); err != nil {
return nil
}
defer os.Remove(tmpDumpFile.Name())
}
// Download running binary
tmpBinFile, err := ioutil.TempFile("", "binary")
if err != nil {
return err
}
{
out, err := cmd(addr, signal.BinaryDump)
if err != nil {
return fmt.Errorf("failed to read the binary: %v", err)
}
if len(out) == 0 {
return errors.New("failed to read the binary")
}
defer os.Remove(tmpBinFile.Name())
if err := ioutil.WriteFile(tmpBinFile.Name(), out, 0); err != nil {
return err
}
}
fmt.Printf("Binary file saved to: %s\n", tmpBinFile.Name())
cmd := exec.Command("go", "tool", "pprof", tmpBinFile.Name(), tmpDumpFile.Name())
cmd.Env = os.Environ()
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func stats(addr net.TCPAddr, _ []string) error {
return cmdWithPrint(addr, signal.Stats)
}
func cmdWithPrint(addr net.TCPAddr, c byte, params ...byte) error {
out, err := cmd(addr, c, params...)
if err != nil {
return err
}
fmt.Printf("%s", out)
return nil
}
// targetToAddr tries to parse the target string, be it remote host:port
// or local process's PID.
func targetToAddr(target string) (*net.TCPAddr, error) {
if strings.Contains(target, ":") {
// addr host:port passed
var err error
addr, err := net.ResolveTCPAddr("tcp", target)
if err != nil {
return nil, fmt.Errorf("couldn't parse dst address: %v", err)
}
return addr, nil
}
// try to find port by pid then, connect to local
pid, err := strconv.Atoi(target)
if err != nil {
return nil, fmt.Errorf("couldn't parse PID: %v", err)
}
port, err := internal.GetPort(pid)
if err != nil {
return nil, fmt.Errorf("couldn't get port for PID %v: %v", pid, err)
}
addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:"+port)
return addr, nil
}
func cmd(addr net.TCPAddr, c byte, params ...byte) ([]byte, error) {
conn, err := cmdLazy(addr, c, params...)
if err != nil {
return nil, fmt.Errorf("couldn't get port by PID: %v", err)
}
all, err := ioutil.ReadAll(conn)
if err != nil {
return nil, err
}
return all, nil
}
func cmdLazy(addr net.TCPAddr, c byte, params ...byte) (io.Reader, error) {
conn, err := net.DialTCP("tcp", nil, &addr)
if err != nil {
return nil, err
}
buf := []byte{c}
buf = append(buf, params...)
if _, err := conn.Write(buf); err != nil {
return nil, err
}
return conn, nil
}