2016-11-04 07:43:04 +02:00
|
|
|
// Copyright 2016 The Go Authors. All rights reserved.
|
2016-11-04 01:56:19 +02:00
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
// Program gops is a tool to list currently running Go processes.
|
2016-11-03 23:01:55 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2016-11-04 07:32:46 +02:00
|
|
|
"flag"
|
2016-11-03 23:01:55 +02:00
|
|
|
"fmt"
|
2016-11-04 07:32:46 +02:00
|
|
|
"io/ioutil"
|
2016-11-03 23:01:55 +02:00
|
|
|
"log"
|
2016-11-04 07:32:46 +02:00
|
|
|
"net"
|
|
|
|
"os"
|
2016-11-03 23:01:55 +02:00
|
|
|
|
2016-11-04 07:32:46 +02:00
|
|
|
"hello/gops/agent/signal"
|
2016-11-03 23:01:55 +02:00
|
|
|
"hello/gops/internal/objfile"
|
|
|
|
|
|
|
|
ps "github.com/keybase/go-ps"
|
|
|
|
)
|
|
|
|
|
2016-11-04 07:32:46 +02:00
|
|
|
const helpText = `Usage: gops is a tool to list and diagnose Go processes.
|
2016-11-04 07:48:44 +02:00
|
|
|
gops Lists all Go processes currently running.
|
|
|
|
gops [options...] See the section below.
|
2016-11-04 07:32:46 +02:00
|
|
|
|
2016-11-04 07:48:44 +02:00
|
|
|
Options: (All requires the agent and the -p=<pid> flag.)
|
|
|
|
-stack Prints the stack trace.
|
|
|
|
-gc Runs the garbage collector and blocks until successful.
|
|
|
|
-gcstats Prints the garbage collection stats.
|
2016-11-04 07:53:07 +02:00
|
|
|
-version Prints the Go version used to build the program.
|
2016-11-04 07:32:46 +02:00
|
|
|
`
|
|
|
|
|
|
|
|
var (
|
|
|
|
pid = flag.Int("p", -1, "")
|
|
|
|
stack = flag.Bool("stack", false, "")
|
|
|
|
gc = flag.Bool("gc", false, "")
|
|
|
|
gcstats = flag.Bool("gcstats", false, "")
|
2016-11-04 07:53:07 +02:00
|
|
|
version = flag.Bool("version", false, "")
|
2016-11-04 07:32:46 +02:00
|
|
|
help = flag.Bool("help", false, "")
|
|
|
|
)
|
|
|
|
|
2016-11-03 23:01:55 +02:00
|
|
|
func main() {
|
2016-11-04 07:32:46 +02:00
|
|
|
flag.Usage = usage
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
if len(os.Args) < 2 {
|
|
|
|
list()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if *pid == -1 {
|
|
|
|
usage()
|
|
|
|
}
|
|
|
|
if *help {
|
|
|
|
usage()
|
|
|
|
}
|
|
|
|
if *stack {
|
|
|
|
out, err := cmd(signal.Stack)
|
|
|
|
exit(err)
|
|
|
|
fmt.Println(out)
|
|
|
|
}
|
|
|
|
if *gc {
|
|
|
|
_, err := cmd(signal.GC)
|
|
|
|
exit(err)
|
|
|
|
}
|
|
|
|
if *gcstats {
|
2016-11-04 07:40:49 +02:00
|
|
|
out, err := cmd(signal.GCStats)
|
2016-11-04 07:32:46 +02:00
|
|
|
exit(err)
|
2016-11-04 07:40:49 +02:00
|
|
|
fmt.Printf(out)
|
2016-11-04 07:32:46 +02:00
|
|
|
}
|
2016-11-04 07:53:07 +02:00
|
|
|
if *version {
|
|
|
|
out, err := cmd(signal.Version)
|
|
|
|
exit(err)
|
|
|
|
fmt.Printf(out)
|
|
|
|
}
|
2016-11-04 07:32:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func cmd(c byte) (string, error) {
|
|
|
|
sock := fmt.Sprintf("/tmp/gops%d.sock", *pid)
|
|
|
|
conn, err := net.Dial("unix", sock)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if _, err := conn.Write([]byte{c}); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
all, err := ioutil.ReadAll(conn)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return string(all), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func list() {
|
2016-11-03 23:01:55 +02:00
|
|
|
pss, err := ps.Processes()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
var undetermined int
|
|
|
|
for _, pr := range pss {
|
|
|
|
name, err := pr.Path()
|
|
|
|
if err != nil {
|
|
|
|
undetermined++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
ok, err := isGo(name)
|
|
|
|
if err != nil {
|
2016-11-04 04:33:43 +02:00
|
|
|
// TODO(jbd): worth to report the number?
|
2016-11-03 23:01:55 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if ok {
|
2016-11-04 04:33:43 +02:00
|
|
|
fmt.Printf("%d\t%v\t(%v)\n", pr.Pid(), pr.Executable(), name)
|
2016-11-03 23:01:55 +02:00
|
|
|
}
|
|
|
|
}
|
2016-11-04 01:56:19 +02:00
|
|
|
if undetermined > 0 {
|
2016-11-04 02:52:29 +02:00
|
|
|
fmt.Printf("\n%d processes left undetermined\n", undetermined)
|
2016-11-04 01:56:19 +02:00
|
|
|
}
|
2016-11-03 23:01:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func isGo(filename string) (ok bool, err error) {
|
|
|
|
obj, err := objfile.Open(filename)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
defer obj.Close()
|
|
|
|
|
|
|
|
symbols, err := obj.Symbols()
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(jbd): find a faster way to determine Go programs
|
2016-11-04 01:56:19 +02:00
|
|
|
// looping over the symbols is a joke.
|
2016-11-03 23:01:55 +02:00
|
|
|
for _, s := range symbols {
|
|
|
|
if s.Name == "runtime.buildVersion" {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false, nil
|
|
|
|
}
|
2016-11-04 07:32:46 +02:00
|
|
|
|
|
|
|
func usage() {
|
|
|
|
fmt.Fprintf(os.Stderr, "%v\n", helpText)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func exit(err error) {
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|