mirror of
https://github.com/google/gops.git
synced 2025-04-17 11:26:22 +02:00
add remote mode support (#30)
This commit is contained in:
parent
c8ccacaea5
commit
288e543fee
31
README.md
31
README.md
@ -44,7 +44,14 @@ func main() {
|
|||||||
|
|
||||||
### Manual
|
### Manual
|
||||||
|
|
||||||
#### Listing all processes
|
It is possible to use gops tool both in local and remote mode.
|
||||||
|
|
||||||
|
Local mode requires that you start the target binary as the same user that runs gops binary.
|
||||||
|
To use gops in a remote mode you need to know target's agent address.
|
||||||
|
|
||||||
|
In Local mode use process's PID as a target; in Remote mode target is a `host:port` combination.
|
||||||
|
|
||||||
|
#### 0. Listing all processes running locally
|
||||||
|
|
||||||
To print all go processes, run `gops` without arguments:
|
To print all go processes, run `gops` without arguments:
|
||||||
|
|
||||||
@ -58,13 +65,13 @@ $ gops
|
|||||||
|
|
||||||
Note that processes running the agent are marked with `*` next to the PID (e.g. `4132*`).
|
Note that processes running the agent are marked with `*` next to the PID (e.g. `4132*`).
|
||||||
|
|
||||||
#### $ gops stack \<pid\>
|
#### $ gops stack (\<pid\>|\<addr\>)
|
||||||
|
|
||||||
In order to print the current stack trace from a target program, run the following command:
|
In order to print the current stack trace from a target program, run the following command:
|
||||||
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ gops stack <pid>
|
$ gops stack (<pid>|<addr>)
|
||||||
gops stack 85709
|
gops stack 85709
|
||||||
goroutine 8 [running]:
|
goroutine 8 [running]:
|
||||||
runtime/pprof.writeGoroutineStacks(0x13c7bc0, 0xc42000e008, 0xc420ec8520, 0xc420ec8520)
|
runtime/pprof.writeGoroutineStacks(0x13c7bc0, 0xc42000e008, 0xc420ec8520, 0xc420ec8520)
|
||||||
@ -82,31 +89,31 @@ created by github.com/google/gops/agent.Listen
|
|||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
#### $ gops memstats \<pid\>
|
#### $ gops memstats (\<pid\>|\<addr\>)
|
||||||
|
|
||||||
To print the current memory stats, run the following command:
|
To print the current memory stats, run the following command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ gops memstats <pid>
|
$ gops memstats (<pid>|<addr>)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### $ gops gc \<pid\>
|
#### $ gops gc (\<pid\>|\<addr\>)
|
||||||
|
|
||||||
If you want to force run garbage collection on the target program, run `gc`.
|
If you want to force run garbage collection on the target program, run `gc`.
|
||||||
It will block until the GC is completed.
|
It will block until the GC is completed.
|
||||||
|
|
||||||
|
|
||||||
#### $ gops version \<pid\>
|
#### $ gops version (\<pid\>|\<addr\>)
|
||||||
|
|
||||||
gops reports the Go version the target program is built with, if you run the following:
|
gops reports the Go version the target program is built with, if you run the following:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ gops version <pid>
|
$ gops version (<pid>|<addr>)
|
||||||
devel +6a3c6c0 Sat Jan 14 05:57:07 2017 +0000
|
devel +6a3c6c0 Sat Jan 14 05:57:07 2017 +0000
|
||||||
```
|
```
|
||||||
|
|
||||||
#### $ gops stats \<pid\>
|
#### $ gops stats (\<pid\>|\<addr\>)
|
||||||
|
|
||||||
To print the runtime statistics such as number of goroutines and `GOMAXPROCS`.
|
To print the runtime statistics such as number of goroutines and `GOMAXPROCS`.
|
||||||
|
|
||||||
@ -121,13 +128,13 @@ it shells out to the `go tool pprof` and let you interatively examine the profil
|
|||||||
To enter the CPU profile, run:
|
To enter the CPU profile, run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ gops pprof-cpu <pid>
|
$ gops pprof-cpu (<pid>|<addr>)
|
||||||
```
|
```
|
||||||
|
|
||||||
To enter the heap profile, run:
|
To enter the heap profile, run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ gops pprof-heap <pid>
|
$ gops pprof-heap (<pid>|<addr>)
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Go execution trace
|
##### Go execution trace
|
||||||
@ -135,6 +142,6 @@ $ gops pprof-heap <pid>
|
|||||||
gops allows you to start the runtime tracer for 5 seconds and examine the results.
|
gops allows you to start the runtime tracer for 5 seconds and examine the results.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ gops trace <pid>
|
$ gops trace (<pid>|<addr>)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -20,8 +20,11 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"bufio"
|
||||||
|
|
||||||
"github.com/google/gops/internal"
|
"github.com/google/gops/internal"
|
||||||
"github.com/google/gops/signal"
|
"github.com/google/gops/signal"
|
||||||
|
"github.com/kardianos/osext"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultAddr = "127.0.0.1:0"
|
const defaultAddr = "127.0.0.1:0"
|
||||||
@ -212,6 +215,19 @@ func handle(conn io.Writer, msg []byte) error {
|
|||||||
fmt.Fprintf(conn, "OS threads: %v\n", pprof.Lookup("threadcreate").Count())
|
fmt.Fprintf(conn, "OS threads: %v\n", pprof.Lookup("threadcreate").Count())
|
||||||
fmt.Fprintf(conn, "GOMAXPROCS: %v\n", runtime.GOMAXPROCS(0))
|
fmt.Fprintf(conn, "GOMAXPROCS: %v\n", runtime.GOMAXPROCS(0))
|
||||||
fmt.Fprintf(conn, "num CPU: %v\n", runtime.NumCPU())
|
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:
|
case signal.Trace:
|
||||||
trace.Start(conn)
|
trace.Start(conn)
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
131
cmd.go
131
cmd.go
@ -1,20 +1,21 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/google/gops/internal"
|
"github.com/google/gops/internal"
|
||||||
"github.com/google/gops/signal"
|
"github.com/google/gops/signal"
|
||||||
ps "github.com/keybase/go-ps"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmds = map[string](func(pid int) error){
|
var cmds = map[string](func(addr net.TCPAddr) error){
|
||||||
"stack": stackTrace,
|
"stack": stackTrace,
|
||||||
"gc": gc,
|
"gc": gc,
|
||||||
"memstats": memStats,
|
"memstats": memStats,
|
||||||
@ -25,35 +26,35 @@ var cmds = map[string](func(pid int) error){
|
|||||||
"trace": trace,
|
"trace": trace,
|
||||||
}
|
}
|
||||||
|
|
||||||
func stackTrace(pid int) error {
|
func stackTrace(addr net.TCPAddr) error {
|
||||||
return cmdWithPrint(pid, signal.StackTrace)
|
return cmdWithPrint(addr, signal.StackTrace)
|
||||||
}
|
}
|
||||||
|
|
||||||
func gc(pid int) error {
|
func gc(addr net.TCPAddr) error {
|
||||||
_, err := cmd(pid, signal.GC)
|
_, err := cmd(addr, signal.GC)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func memStats(pid int) error {
|
func memStats(addr net.TCPAddr) error {
|
||||||
return cmdWithPrint(pid, signal.MemStats)
|
return cmdWithPrint(addr, signal.MemStats)
|
||||||
}
|
}
|
||||||
|
|
||||||
func version(pid int) error {
|
func version(addr net.TCPAddr) error {
|
||||||
return cmdWithPrint(pid, signal.Version)
|
return cmdWithPrint(addr, signal.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pprofHeap(pid int) error {
|
func pprofHeap(addr net.TCPAddr) error {
|
||||||
return pprof(pid, signal.HeapProfile)
|
return pprof(addr, signal.HeapProfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pprofCPU(pid int) error {
|
func pprofCPU(addr net.TCPAddr) error {
|
||||||
fmt.Println("Profiling CPU now, will take 30 secs...")
|
fmt.Println("Profiling CPU now, will take 30 secs...")
|
||||||
return pprof(pid, signal.CPUProfile)
|
return pprof(addr, signal.CPUProfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func trace(pid int) error {
|
func trace(addr net.TCPAddr) error {
|
||||||
fmt.Println("Tracing now, will take 5 secs...")
|
fmt.Println("Tracing now, will take 5 secs...")
|
||||||
out, err := cmd(pid, signal.Trace)
|
out, err := cmd(addr, signal.Trace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -76,32 +77,45 @@ func trace(pid int) error {
|
|||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func pprof(pid int, p byte) error {
|
func pprof(addr net.TCPAddr, p byte) error {
|
||||||
out, err := cmd(pid, p)
|
|
||||||
|
tmpDumpFile, err := ioutil.TempFile("", "profile")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(out) == 0 {
|
{
|
||||||
return errors.New("failed to read the profile")
|
out, err := cmd(addr, p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(out) == 0 {
|
||||||
|
return errors.New("failed to read the profile")
|
||||||
|
}
|
||||||
|
defer os.Remove(tmpDumpFile.Name())
|
||||||
|
if err := ioutil.WriteFile(tmpDumpFile.Name(), out, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tmpfile, err := ioutil.TempFile("", "profile")
|
// Download running binary
|
||||||
|
tmpBinFile, err := ioutil.TempFile("", "binary")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer os.Remove(tmpfile.Name())
|
{
|
||||||
if err := ioutil.WriteFile(tmpfile.Name(), out, 0); err != nil {
|
|
||||||
return err
|
out, err := cmd(addr, signal.BinaryDump)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("couldn't retrieve running binary's dump")
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
process, err := ps.FindProcess(pid)
|
cmd := exec.Command("go", "tool", "pprof", tmpBinFile.Name(), tmpDumpFile.Name())
|
||||||
if err != nil {
|
|
||||||
// TODO(jbd): add context to the error
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
binary, err := process.Path()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot the binary for the PID: %v", err)
|
|
||||||
}
|
|
||||||
cmd := exec.Command("go", "tool", "pprof", binary, tmpfile.Name())
|
|
||||||
cmd.Env = os.Environ()
|
cmd.Env = os.Environ()
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
@ -109,12 +123,12 @@ func pprof(pid int, p byte) error {
|
|||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func stats(pid int) error {
|
func stats(addr net.TCPAddr) error {
|
||||||
return cmdWithPrint(pid, signal.Stats)
|
return cmdWithPrint(addr, signal.Stats)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdWithPrint(pid int, c byte) error {
|
func cmdWithPrint(addr net.TCPAddr, c byte) error {
|
||||||
out, err := cmd(pid, c)
|
out, err := cmd(addr, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -122,11 +136,34 @@ func cmdWithPrint(pid int, c byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmd(pid int, c byte) ([]byte, error) {
|
// targetToAddr tries to parse the target string, be it remote host:port
|
||||||
conn, err := cmdLazy(pid, c)
|
// or local process's PID.
|
||||||
if err != nil {
|
func targetToAddr(target string) (*net.TCPAddr, error) {
|
||||||
return nil, err
|
if strings.Index(target, ":") != -1 {
|
||||||
|
// addr host:port passed
|
||||||
|
var err error
|
||||||
|
addr, err := net.ResolveTCPAddr("tcp", target)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "couldn't parse dst address")
|
||||||
|
}
|
||||||
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
// try to find port by pid then, connect to local
|
||||||
|
pid, err := strconv.Atoi(target)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "couldn't parse PID")
|
||||||
|
}
|
||||||
|
port, err := internal.GetPort(pid)
|
||||||
|
addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:"+port)
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmd(addr net.TCPAddr, c byte) ([]byte, error) {
|
||||||
|
conn, err := cmdLazy(addr, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "couldn't get port by PID")
|
||||||
|
}
|
||||||
|
|
||||||
all, err := ioutil.ReadAll(conn)
|
all, err := ioutil.ReadAll(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -134,12 +171,8 @@ func cmd(pid int, c byte) ([]byte, error) {
|
|||||||
return all, nil
|
return all, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdLazy(pid int, c byte) (io.Reader, error) {
|
func cmdLazy(addr net.TCPAddr, c byte) (io.Reader, error) {
|
||||||
port, err := internal.GetPort(pid)
|
conn, err := net.DialTCP("tcp", nil, &addr)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn, err := net.Dial("tcp", "127.0.0.1:"+port)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := agent.Listen(nil); err != nil {
|
if err := agent.Listen(&agent.Options{
|
||||||
|
Addr: "127.0.0.1:4321",
|
||||||
|
}); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
time.Sleep(time.Hour)
|
time.Sleep(time.Hour)
|
||||||
|
16
main.go
16
main.go
@ -10,7 +10,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/google/gops/internal"
|
"github.com/google/gops/internal"
|
||||||
@ -20,8 +19,6 @@ import (
|
|||||||
|
|
||||||
const helpText = `Usage: gops is a tool to list and diagnose Go processes.
|
const helpText = `Usage: gops is a tool to list and diagnose Go processes.
|
||||||
|
|
||||||
gops Lists all Go processes currently running.
|
|
||||||
gops cmd <pid> See the commands below.
|
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
stack Prints the stack trace.
|
stack Prints the stack trace.
|
||||||
@ -53,17 +50,18 @@ func main() {
|
|||||||
usage("")
|
usage("")
|
||||||
}
|
}
|
||||||
if len(os.Args) < 3 {
|
if len(os.Args) < 3 {
|
||||||
usage("missing PID")
|
usage("missing PID or address")
|
||||||
}
|
|
||||||
pid, err := strconv.Atoi(os.Args[2])
|
|
||||||
if err != nil {
|
|
||||||
usage("PID should be numeric")
|
|
||||||
}
|
}
|
||||||
fn, ok := cmds[cmd]
|
fn, ok := cmds[cmd]
|
||||||
if !ok {
|
if !ok {
|
||||||
usage("unknown subcommand")
|
usage("unknown subcommand")
|
||||||
}
|
}
|
||||||
if err := fn(pid); err != nil {
|
addr, err := targetToAddr(os.Args[2])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Couldn't resolve addr or pid %v to TCPAddress: %v\n", os.Args[2], err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := fn(*addr); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
@ -29,4 +29,7 @@ const (
|
|||||||
|
|
||||||
// Trace starts the Go execution tracer, waits 5 seconds and launches the trace tool.
|
// Trace starts the Go execution tracer, waits 5 seconds and launches the trace tool.
|
||||||
Trace = byte(0x8)
|
Trace = byte(0x8)
|
||||||
|
|
||||||
|
// BinaryDump returns running binary file.
|
||||||
|
BinaryDump = byte(0x9)
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user