1
0
mirror of https://github.com/google/gops.git synced 2024-11-24 08:22:25 +02:00
gops/main.go

276 lines
6.6 KiB
Go
Raw Normal View History

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 (
"bytes"
2016-11-03 23:01:55 +02:00
"fmt"
"log"
"math"
2016-11-04 07:32:46 +02:00
"os"
2017-09-05 05:08:19 +02:00
"regexp"
"strconv"
"strings"
"time"
2016-11-03 23:01:55 +02:00
"github.com/google/gops/goprocess"
"github.com/shirou/gopsutil/v3/process"
"github.com/xlab/treeprint"
2016-11-03 23:01:55 +02:00
)
const helpText = `gops is a tool to list and diagnose Go processes.
2016-11-04 08:05:56 +02:00
Usage:
gops <cmd> <pid|addr> ...
gops <pid> # displays process info
gops help # displays this help message
2016-11-04 07:32:46 +02:00
2016-11-14 07:59:09 +02:00
Commands:
stack Prints the stack trace.
gc Runs the garbage collector and blocks until successful.
setgc Sets the garbage collection target percentage.
memstats Prints the allocation and garbage collection stats.
version Prints the Go version used to build the program.
stats Prints runtime stats.
trace Runs the runtime tracer for 5 secs and launches "go tool trace".
pprof-heap Reads the heap profile and launches "go tool pprof".
pprof-cpu Reads the CPU profile and launches "go tool pprof".
2016-11-04 08:05:56 +02:00
2016-11-08 03:29:14 +02:00
All commands require the agent running on the Go process.
"*" indicates the process is running the agent.`
2016-11-04 07:32:46 +02:00
2016-11-04 08:05:56 +02:00
// TODO(jbd): add link that explains the use of agent.
2016-11-03 23:01:55 +02:00
func main() {
2016-11-04 07:32:46 +02:00
if len(os.Args) < 2 {
2016-11-08 03:29:14 +02:00
processes()
2016-11-04 07:32:46 +02:00
return
}
2016-11-08 03:29:14 +02:00
cmd := os.Args[1]
// See if it is a PID.
pid, err := strconv.Atoi(cmd)
if err == nil {
processInfo(pid)
return
}
2016-11-12 05:55:16 +02:00
if cmd == "help" {
usage("")
}
if cmd == "tree" {
displayProcessTree()
return
}
2016-11-08 03:29:14 +02:00
fn, ok := cmds[cmd]
if !ok {
usage("unknown subcommand")
2016-11-04 07:32:46 +02:00
}
2018-07-11 21:14:45 +02:00
if len(os.Args) < 3 {
usage("Missing PID or address.")
os.Exit(1)
}
2017-03-02 21:51:14 +02:00
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)
}
var params []string
if len(os.Args) > 3 {
params = append(params, os.Args[3:]...)
}
if err := fn(*addr, params); err != nil {
2016-11-08 03:29:14 +02:00
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
2016-11-04 07:32:46 +02:00
}
}
2016-11-08 03:29:14 +02:00
func processes() {
ps := goprocess.FindAll()
2017-09-05 03:49:17 +02:00
var maxPID, maxPPID, maxExec, maxVersion int
2017-09-05 05:08:19 +02:00
for i, p := range ps {
ps[i].BuildVersion = shortenVersion(p.BuildVersion)
maxPID = max(maxPID, len(strconv.Itoa(p.PID)))
2017-09-05 03:49:17 +02:00
maxPPID = max(maxPPID, len(strconv.Itoa(p.PPID)))
maxExec = max(maxExec, len(p.Exec))
2017-09-05 05:08:19 +02:00
maxVersion = max(maxVersion, len(ps[i].BuildVersion))
}
for _, p := range ps {
buf := bytes.NewBuffer(nil)
pid := strconv.Itoa(p.PID)
fmt.Fprint(buf, pad(pid, maxPID))
2017-09-05 03:49:17 +02:00
fmt.Fprint(buf, " ")
ppid := strconv.Itoa(p.PPID)
fmt.Fprint(buf, pad(ppid, maxPPID))
2017-09-05 08:05:17 +02:00
fmt.Fprint(buf, " ")
fmt.Fprint(buf, pad(p.Exec, maxExec))
if p.Agent {
fmt.Fprint(buf, "*")
} else {
fmt.Fprint(buf, " ")
}
fmt.Fprint(buf, " ")
fmt.Fprint(buf, pad(p.BuildVersion, maxVersion))
fmt.Fprint(buf, " ")
fmt.Fprint(buf, p.Path)
fmt.Fprintln(buf)
buf.WriteTo(os.Stdout)
}
2016-11-03 23:01:55 +02:00
}
2016-11-04 07:32:46 +02:00
func processInfo(pid int) {
p, err := process.NewProcess(int32(pid))
if err != nil {
log.Fatalf("Cannot read process info: %v", err)
}
if v, err := p.Parent(); err == nil {
fmt.Printf("parent PID:\t%v\n", v.Pid)
}
if v, err := p.NumThreads(); err == nil {
fmt.Printf("threads:\t%v\n", v)
}
if v, err := p.MemoryPercent(); err == nil {
fmt.Printf("memory usage:\t%.3f%%\n", v)
}
if v, err := p.CPUPercent(); err == nil {
fmt.Printf("cpu usage:\t%.3f%%\n", v)
}
if v, err := p.Username(); err == nil {
fmt.Printf("username:\t%v\n", v)
}
if v, err := p.Cmdline(); err == nil {
fmt.Printf("cmd+args:\t%v\n", v)
}
if v, err := elapsedTime(p); err == nil {
fmt.Printf("elapsed time:\t%v\n", v)
}
if v, err := p.Connections(); err == nil {
if len(v) > 0 {
for _, conn := range v {
fmt.Printf("local/remote:\t%v:%v <-> %v:%v (%v)\n",
conn.Laddr.IP, conn.Laddr.Port, conn.Raddr.IP, conn.Raddr.Port, conn.Status)
}
}
}
}
// elapsedTime shows the elapsed time of the process indicating how long the
// process has been running for.
func elapsedTime(p *process.Process) (string, error) {
crtTime, err := p.CreateTime()
if err != nil {
return "", err
}
etime := time.Since(time.Unix(crtTime/1000, 0))
return fmtEtimeDuration(etime), nil
}
// pstree contains a mapping between the PPIDs and the child processes.
var pstree map[int][]goprocess.P
// displayProcessTree displays a tree of all the running Go processes.
func displayProcessTree() {
ps := goprocess.FindAll()
pstree = make(map[int][]goprocess.P)
for _, p := range ps {
pstree[p.PPID] = append(pstree[p.PPID], p)
}
tree := treeprint.New()
tree.SetValue("...")
seen := map[int]bool{}
for _, p := range ps {
constructProcessTree(p.PPID, p, seen, tree)
}
fmt.Println(tree.String())
}
// constructProcessTree constructs the process tree in a depth-first fashion.
func constructProcessTree(ppid int, process goprocess.P, seen map[int]bool, tree treeprint.Tree) {
if seen[ppid] {
return
}
seen[ppid] = true
if ppid != process.PPID {
output := strconv.Itoa(ppid) + " (" + process.Exec + ")" + " {" + process.BuildVersion + "}"
if process.Agent {
tree = tree.AddMetaBranch("*", output)
} else {
tree = tree.AddBranch(output)
}
} else {
tree = tree.AddBranch(ppid)
}
for index := range pstree[ppid] {
process := pstree[ppid][index]
constructProcessTree(process.PID, process, seen, tree)
}
}
2017-09-05 05:08:19 +02:00
var develRe = regexp.MustCompile(`devel\s+\+\w+`)
func shortenVersion(v string) string {
if !strings.HasPrefix(v, "devel") {
return v
}
results := develRe.FindAllString(v, 1)
if len(results) == 0 {
return v
}
return results[0]
}
2016-11-08 03:29:14 +02:00
func usage(msg string) {
// default exit code for the statement
exitCode := 0
2016-11-08 03:29:14 +02:00
if msg != "" {
// founded an unexpected command
2016-11-08 03:29:14 +02:00
fmt.Printf("gops: %v\n", msg)
exitCode = 1
2016-11-04 07:32:46 +02:00
}
2016-11-08 03:29:14 +02:00
fmt.Fprintf(os.Stderr, "%v\n", helpText)
os.Exit(exitCode)
2016-11-04 07:32:46 +02:00
}
func pad(s string, total int) string {
if len(s) >= total {
return s
}
return s + strings.Repeat(" ", total-len(s))
}
func max(i, j int) int {
if i > j {
return i
}
return j
}
// fmtEtimeDuration formats etime's duration based on ps' format:
// [[DD-]hh:]mm:ss
// format specification: http://linuxcommand.org/lc3_man_pages/ps1.html
func fmtEtimeDuration(d time.Duration) string {
days := d / (24 * time.Hour)
hours := d % (24 * time.Hour)
minutes := hours % time.Hour
seconds := math.Mod(minutes.Seconds(), 60)
var b strings.Builder
if days > 0 {
fmt.Fprintf(&b, "%02d-", days)
}
if days > 0 || hours/time.Hour > 0 {
fmt.Fprintf(&b, "%02d:", hours/time.Hour)
}
fmt.Fprintf(&b, "%02d:", minutes/time.Minute)
fmt.Fprintf(&b, "%02.0f", seconds)
return b.String()
}