Repository: google/gops
Branch: master
Commit: a2d8f7790eac
Files: 29
Total size: 54.5 KB
Directory structure:
gitextract_m6aexidg/
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── agent/
│ ├── agent.go
│ ├── agent_test.go
│ ├── sockopt_reuseport.go
│ └── sockopt_unsupported.go
├── cmd.go
├── examples/
│ └── hello/
│ └── main.go
├── go.mod
├── go.sum
├── goprocess/
│ ├── goprocess.go
│ ├── goprocess_1.18.go
│ ├── goprocess_lt1.18.go
│ └── goprocess_test.go
├── internal/
│ ├── cmd/
│ │ ├── process.go
│ │ ├── process_test.go
│ │ ├── root.go
│ │ ├── root_test.go
│ │ ├── shared.go
│ │ ├── shared_test.go
│ │ └── tree.go
│ ├── internal.go
│ └── internal_test.go
├── main.go
├── main_test.go
└── signal/
└── signal.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: gomod
directory: /
schedule:
interval: daily
open-pull-requests-limit: 1
rebase-strategy: disabled
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
open-pull-requests-limit: 5
rebase-strategy: disabled
================================================
FILE: .github/workflows/test.yml
================================================
name: Tests
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
test:
strategy:
matrix:
# Minimum supported version (1.18) and the latest two
go-version: ['1.18', '1.22', '1.23']
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v4
- name: Check formatting
if: matrix.go-version == '1.23' && matrix.platform == 'ubuntu-latest'
run: diff -u <(echo -n) <(go fmt $(go list ./...))
- name: Run unit tests
run: go test -race -v ./...
================================================
FILE: .gitignore
================================================
gops
================================================
FILE: LICENSE
================================================
Copyright (c) 2016 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# gops
[](https://github.com/google/gops/actions?query=workflow%3ATests)
[](https://godoc.org/github.com/google/gops)
gops is a command to list and diagnose Go processes currently running on your system.
```sh
$ gops
983 980 uplink-soecks go1.9 /usr/local/bin/uplink-soecks
52697 52695 gops go1.10 /Users/jbd/bin/gops
4132 4130 foops * go1.9 /Users/jbd/bin/foops
51130 51128 gocode go1.9.2 /Users/jbd/bin/gocode
```
## Installation
To install the latest version of gops:
```sh
$ go get github.com/google/gops
```
or
```sh
$ go install github.com/google/gops@latest
```
To install a specific gops version, for example v0.3.19:
```sh
$ go install github.com/google/gops@v0.3.19
```
## Diagnostics
For processes that start the diagnostics agent, gops can report
additional information such as the current stack trace, Go version, memory
stats, etc.
In order to start the diagnostics agent, see the [hello example](https://github.com/google/gops/blob/master/examples/hello/main.go).
``` go
package main
import (
"log"
"time"
"github.com/google/gops/agent"
)
func main() {
if err := agent.Listen(agent.Options{}); err != nil {
log.Fatal(err)
}
time.Sleep(time.Hour)
}
```
Otherwise, you could set `GOPS_CONFIG_DIR` environment variables to assign your config dir.
Default, gops will use the current user's home directory(AppData on windows).
### Manual
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.
#### Listing all processes running locally
To print all go processes, run `gops` without arguments:
```sh
$ gops
983 980 uplink-soecks go1.9 /usr/local/bin/uplink-soecks
52697 52695 gops go1.10 /Users/jbd/bin/gops
4132 4130 foops * go1.9 /Users/jbd/bin/foops
51130 51128 gocode go1.9.2 /Users/jbd/bin/gocode
```
The output displays:
* PID
* PPID
* Name of the program
* Go version used to build the program
* Location of the associated program
Note that processes running the agent are marked with `*` next to the PID (e.g. `4132*`).
#### $ gops \<pid\> [duration]
To report more information about a process, run `gops` followed by a PID:
```sh
$ gops <pid>
parent PID: 5985
threads: 27
memory usage: 0.199%
cpu usage: 0.139%
username: jbd
cmd+args: /Applications/Splice.app/Contents/Resources/Splice Helper.app/Contents/MacOS/Splice Helper -pid 5985
local/remote: 127.0.0.1:56765 <-> :0 (LISTEN)
local/remote: 127.0.0.1:56765 <-> 127.0.0.1:50955 (ESTABLISHED)
local/remote: 100.76.175.164:52353 <-> 54.241.191.232:443 (ESTABLISHED)
```
If an optional duration is specified in the format as expected by
[`time.ParseDuration`](https://golang.org/pkg/time/#ParseDuration), the CPU
usage for the given time period is reported in addition:
```sh
$ gops <pid> 2s
parent PID: 5985
threads: 27
memory usage: 0.199%
cpu usage: 0.139%
cpu usage (2s): 0.271%
username: jbd
cmd+args: /Applications/Splice.app/Contents/Resources/Splice Helper.app/Contents/MacOS/Splice Helper -pid 5985
local/remote: 127.0.0.1:56765 <-> :0 (LISTEN)
local/remote: 127.0.0.1:56765 <-> 127.0.0.1:50955 (ESTABLISHED)
local/remote: 100.76.175.164:52353 <-> 54.241.191.232:443 (ESTABLISHED)
```
#### $ gops tree
To display a process tree with all the running Go processes, run the following command:
```sh
$ gops tree
...
├── 1
│ └── 13962 [gocode] {go1.9}
├── 557
│ └── 635 [com.docker.supervisor] {go1.9.2}
│ └── 638 [com.docker.driver.amd64-linux] {go1.9.2}
└── 13744
└── 67243 [gops] {go1.10}
```
#### $ gops stack (\<pid\>|\<addr\>)
In order to print the current stack trace from a target program, run the following command:
```sh
$ gops stack (<pid>|<addr>)
gops stack 85709
goroutine 8 [running]:
runtime/pprof.writeGoroutineStacks(0x13c7bc0, 0xc42000e008, 0xc420ec8520, 0xc420ec8520)
/Users/jbd/go/src/runtime/pprof/pprof.go:603 +0x79
runtime/pprof.writeGoroutine(0x13c7bc0, 0xc42000e008, 0x2, 0xc428f1c048, 0xc420ec8608)
/Users/jbd/go/src/runtime/pprof/pprof.go:592 +0x44
runtime/pprof.(*Profile).WriteTo(0x13eeda0, 0x13c7bc0, 0xc42000e008, 0x2, 0xc42000e008, 0x0)
/Users/jbd/go/src/runtime/pprof/pprof.go:302 +0x3b5
github.com/google/gops/agent.handle(0x13cd560, 0xc42000e008, 0xc420186000, 0x1, 0x1, 0x0, 0x0)
/Users/jbd/src/github.com/google/gops/agent/agent.go:150 +0x1b3
github.com/google/gops/agent.listen()
/Users/jbd/src/github.com/google/gops/agent/agent.go:113 +0x2b2
created by github.com/google/gops/agent.Listen
/Users/jbd/src/github.com/google/gops/agent/agent.go:94 +0x480
# ...
```
#### $ gops memstats (\<pid\>|\<addr\>)
To print the current memory stats, run the following command:
```sh
$ gops memstats (<pid>|<addr>)
```
#### $ gops gc (\<pid\>|\<addr\>)
If you want to force run garbage collection on the target program, run `gc`.
It will block until the GC is completed.
#### $ gops setgc (\<pid\>|\<addr\>) <perc>
Sets the garbage collection target to a certain percentage.
The following command sets it to 10%:
``` sh
$ gops setgc (<pid>|<addr>) 10
```
The following command turns off the garbage collector:
```sh
$ gops setgc (<pid>|<addr>) off
```
#### $ gops version (\<pid\>|\<addr\>)
gops reports the Go version the target program is built with, if you run the following:
```sh
$ gops version (<pid>|<addr>)
devel +6a3c6c0 Sat Jan 14 05:57:07 2017 +0000
```
#### $ gops stats (\<pid\>|\<addr\>)
To print the runtime statistics such as number of goroutines and `GOMAXPROCS`.
#### Profiling
##### Pprof
gops supports CPU and heap pprof profiles. After reading either heap or CPU profile,
it shells out to the `go tool pprof` and let you interactively examine the profiles.
To enter the CPU profile, run:
```sh
$ gops pprof-cpu (<pid>|<addr>)
```
To enter the heap profile, run:
```sh
$ gops pprof-heap (<pid>|<addr>)
```
##### Execution trace
gops allows you to start the runtime tracer for 5 seconds and examine the results.
```sh
$ gops trace (<pid>|<addr>)
```
================================================
FILE: agent/agent.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 (
"bufio"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"os"
gosignal "os/signal"
"path/filepath"
"runtime"
"runtime/debug"
"runtime/pprof"
"runtime/trace"
"strconv"
"sync"
"syscall"
"time"
"github.com/google/gops/internal"
"github.com/google/gops/signal"
)
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
// ConfigDir is the directory to store the configuration file,
// PID of the gops process, filename, port as well as content.
// Optional.
ConfigDir string
// ShutdownCleanup automatically cleans up resources if the
// running process receives an interrupt. Otherwise, users
// can call Close before shutting down.
// Optional.
ShutdownCleanup bool
// ReuseSocketAddrAndPort determines whether the SO_REUSEADDR and
// SO_REUSEPORT socket options should be set on the listening socket of
// the agent. This option is only effective on unix-like OSes and if
// Addr is set to a fixed host:port.
// Optional.
ReuseSocketAddrAndPort 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 listener != nil {
return fmt.Errorf("gops: agent already listening at: %v", listener.Addr())
}
addr := opts.Addr
if addr == "" {
addr = defaultAddr
}
var lc net.ListenConfig
if opts.ReuseSocketAddrAndPort {
lc.Control = setReuseAddrAndPortSockopts
}
var err error
listener, err = lc.Listen(context.Background(), "tcp", addr)
if err != nil {
return err
}
port := listener.Addr().(*net.TCPAddr).Port
err = saveConfig(opts, port)
if err != nil {
// ignore and work in remote mode only
if !errors.Is(err, syscall.EROFS) && !errors.Is(err, syscall.EPERM) {
return err
}
}
if opts.ShutdownCleanup {
gracefulShutdown()
}
go listen(listener)
return nil
}
func listen(l net.Listener) {
buf := make([]byte, 1)
for {
fd, err := l.Accept()
if err != nil {
if !errors.Is(err, net.ErrClosed) {
fmt.Fprintf(os.Stderr, "gops: %v\n", 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\n", err)
continue
}
if err := handle(fd, buf); err != nil {
fmt.Fprintf(os.Stderr, "gops: %v\n", err)
continue
}
fd.Close()
}
}
func saveConfig(opts Options, port int) error {
gopsdir := opts.ConfigDir
if gopsdir == "" {
cfgDir, err := internal.ConfigDir()
if err != nil {
return err
}
gopsdir = cfgDir
}
err := os.MkdirAll(gopsdir, os.ModePerm)
if err != nil {
return err
}
portfile = filepath.Join(gopsdir, strconv.Itoa(os.Getpid()))
return os.WriteFile(portfile, []byte(strconv.Itoa(port)), os.ModePerm)
}
func gracefulShutdown() {
c := make(chan os.Signal, 1)
gosignal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
go func() {
// cleanup the socket on shutdown.
sig := <-c
Close()
ret := 1
if sig == syscall.SIGTERM {
ret = 0
}
os.Exit(ret)
}()
}
// 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()
listener = nil
}
}
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.ReadWriter, 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, "mspan-in-use: %v\n", formatBytes(s.MSpanInuse))
fmt.Fprintf(conn, "mspan-sys: %v\n", formatBytes(s.MSpanSys))
fmt.Fprintf(conn, "mcache-in-use: %v\n", formatBytes(s.MCacheInuse))
fmt.Fprintf(conn, "mcache-sys: %v\n", formatBytes(s.MCacheSys))
fmt.Fprintf(conn, "buck-hash-sys: %v\n", formatBytes(s.BuckHashSys))
fmt.Fprintf(conn, "other-sys: %v\n", formatBytes(s.OtherSys))
fmt.Fprintf(conn, "gc-sys: %v\n", formatBytes(s.GCSys))
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-total: %v\n", time.Duration(s.PauseTotalNs))
fmt.Fprintf(conn, "gc-pause: %v\n", s.PauseNs[(s.NumGC+255)%256])
fmt.Fprintf(conn, "gc-pause-end: %v\n", s.PauseEnd[(s.NumGC+255)%256])
fmt.Fprintf(conn, "num-gc: %v\n", s.NumGC)
fmt.Fprintf(conn, "num-forced-gc: %v\n", s.NumForcedGC)
fmt.Fprintf(conn, "gc-cpu-fraction: %v\n", s.GCCPUFraction)
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:
return 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 := os.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:
if err := trace.Start(conn); err != nil {
return err
}
time.Sleep(5 * time.Second)
trace.Stop()
case signal.SetGCPercent:
perc, err := binary.ReadVarint(bufio.NewReader(conn))
if err != nil {
return err
}
fmt.Fprintf(conn, "New GC percent set to %v. Previous value was %v.\n", perc, debug.SetGCPercent(int(perc)))
}
return nil
}
================================================
FILE: agent/agent_test.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
import (
"os"
"testing"
)
func TestListen(t *testing.T) {
err := Listen(Options{})
if err != nil {
t.Fatal(err)
}
Close()
}
func TestAgentClose(t *testing.T) {
err := Listen(Options{})
if err != nil {
t.Fatal(err)
}
Close()
_, err = os.Stat(portfile)
if !os.IsNotExist(err) {
t.Fatalf("portfile = %q doesn't exist; err = %v", portfile, err)
}
if portfile != "" {
t.Fatalf("got = %q; want empty portfile", portfile)
}
}
func TestUseCustomConfigDir(t *testing.T) {
err := Listen(Options{
ConfigDir: os.TempDir(),
ShutdownCleanup: true,
})
if err != nil {
t.Fatal(err)
}
Close()
}
func TestAgentListenMultipleClose(t *testing.T) {
err := Listen(Options{})
if err != nil {
t.Fatal(err)
}
Close()
Close()
Close()
Close()
}
func TestAgentListenReuseAddrAndPort(t *testing.T) {
err := Listen(Options{
Addr: "127.0.0.1:50000",
ReuseSocketAddrAndPort: true,
})
if err != nil {
t.Fatal(err)
}
Close()
}
func TestFormatBytes(t *testing.T) {
tests := []struct {
val uint64
want string
}{
{1023, "1023 bytes"},
{1024, "1.00KB (1024 bytes)"},
{1024*1024 - 100, "1023.90KB (1048476 bytes)"},
{1024 * 1024, "1.00MB (1048576 bytes)"},
{1024 * 1025, "1.00MB (1049600 bytes)"},
{1024 * 1024 * 1024, "1.00GB (1073741824 bytes)"},
{1024*1024*1024 + 430*1024*1024, "1.42GB (1524629504 bytes)"},
{1024 * 1024 * 1024 * 1024 * 1024, "1.00PB (1125899906842624 bytes)"},
{1024 * 1024 * 1024 * 1024 * 1024 * 1024, "1024.00PB (1152921504606846976 bytes)"},
}
for _, tt := range tests {
result := formatBytes(tt.val)
if result != tt.want {
t.Errorf("formatBytes(%v) = %q; want %q", tt.val, result, tt.want)
}
}
}
================================================
FILE: agent/sockopt_reuseport.go
================================================
// Copyright 2020 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.
//go:build !js && !plan9 && !solaris && !windows
// +build !js,!plan9,!solaris,!windows
package agent
import (
"syscall"
"golang.org/x/sys/unix"
)
// setReuseAddrAndPortSockopts sets the SO_REUSEADDR and SO_REUSEPORT socket
// options on c's underlying socket in order to increase the chance to re-bind()
// to the same address and port upon agent restart.
func setReuseAddrAndPortSockopts(network, address string, c syscall.RawConn) error {
var soerr error
if err := c.Control(func(su uintptr) {
sock := int(su)
// Allow reuse of recently-used addresses. This socket option is
// set by default on listeners in Go's net package, see
// net.setDefaultSockopts.
soerr = unix.SetsockoptInt(sock, unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
if soerr != nil {
return
}
// Allow reuse of recently-used ports. This gives the agent a
// better chance to re-bind upon restarts.
soerr = unix.SetsockoptInt(sock, unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
}); err != nil {
return err
}
return soerr
}
================================================
FILE: agent/sockopt_unsupported.go
================================================
// Copyright 2020 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.
//go:build (js && wasm) || plan9 || solaris || windows
// +build js,wasm plan9 solaris windows
package agent
import "syscall"
func setReuseAddrAndPortSockopts(network, address string, c syscall.RawConn) error {
return nil
}
================================================
FILE: cmd.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
================================================
FILE: examples/hello/main.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 main
import (
"log"
"time"
"github.com/google/gops/agent"
)
func main() {
if err := agent.Listen(agent.Options{
ShutdownCleanup: true, // automatically closes on os.Interrupt
}); err != nil {
log.Fatal(err)
}
time.Sleep(time.Hour)
}
================================================
FILE: go.mod
================================================
module github.com/google/gops
go 1.18
require (
github.com/shirou/gopsutil/v3 v3.24.5
github.com/spf13/cobra v1.9.1
github.com/xlab/treeprint v1.2.0
golang.org/x/sys v0.30.0
rsc.io/goversion v1.2.0
)
require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
)
================================================
FILE: go.sum
================================================
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w=
rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=
================================================
FILE: goprocess/goprocess.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 goprocess reports the Go processes running on a host.
package goprocess
import (
"os"
"sync"
"github.com/google/gops/internal"
"github.com/shirou/gopsutil/v3/process"
)
// P represents a Go process.
type P struct {
PID int
PPID int
Exec string
Path string
BuildVersion string
Agent bool
}
// FindAll returns all the Go processes currently running on this host.
func FindAll() []P {
const concurrencyLimit = 10 // max number of concurrent workers
pss, err := process.Processes()
if err != nil {
return nil
}
return findAll(pss, isGo, concurrencyLimit)
}
// Allows to inject isGo for testing.
type isGoFunc func(*process.Process) (path, version string, agent, ok bool, err error)
func findAll(pss []*process.Process, isGo isGoFunc, concurrencyLimit int) []P {
input := make(chan *process.Process, len(pss))
output := make(chan P, len(pss))
for _, ps := range pss {
input <- ps
}
close(input)
var wg sync.WaitGroup
wg.Add(concurrencyLimit) // used to wait for workers to be finished
// Run concurrencyLimit of workers until there
// is no more processes to be checked in the input channel.
for i := 0; i < concurrencyLimit; i++ {
go func() {
defer wg.Done()
for pr := range input {
path, version, agent, ok, err := isGo(pr)
if err != nil {
// TODO(jbd): Return a list of errors.
continue
}
if !ok {
continue
}
ppid, err := pr.Ppid()
if err != nil {
continue
}
name, err := pr.Name()
if err != nil {
continue
}
output <- P{
PID: int(pr.Pid),
PPID: int(ppid),
Exec: name,
Path: path,
BuildVersion: version,
Agent: agent,
}
}
}()
}
wg.Wait() // wait until all workers are finished
close(output) // no more results to be waited for
var results []P
for p := range output {
results = append(results, p)
}
return results
}
// Find finds info about the process identified with the given PID.
func Find(pid int) (P, bool, error) {
pr, err := process.NewProcess(int32(pid))
if err != nil {
return P{}, false, err
}
path, version, agent, ok, err := isGo(pr)
if !ok || err != nil {
return P{}, false, nil
}
ppid, err := pr.Ppid()
if err != nil {
return P{}, false, err
}
name, err := pr.Name()
if err != nil {
return P{}, false, err
}
return P{
PID: int(pr.Pid),
PPID: int(ppid),
Exec: name,
Path: path,
BuildVersion: version,
Agent: agent,
}, true, nil
}
// isGo looks up the runtime.buildVersion symbol
// in the process' binary and determines if the process
// if a Go process or not. If the process is a Go process,
// it reports PID, binary name and full path of the binary.
func isGo(pr *process.Process) (path, version string, agent, ok bool, err error) {
if pr.Pid == 0 {
// ignore system process
return
}
path, err = pr.Exe()
if err != nil {
return
}
version, err = goVersion(path)
if err != nil {
return
}
ok = true
pidfile, err := internal.PIDFile(int(pr.Pid))
if err == nil {
_, err := os.Stat(pidfile)
agent = err == nil
}
return path, version, agent, ok, nil
}
================================================
FILE: goprocess/goprocess_1.18.go
================================================
// Copyright 2022 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.
//go:build go1.18
// +build go1.18
package goprocess
import "debug/buildinfo"
func goVersion(path string) (string, error) {
info, err := buildinfo.ReadFile(path)
if err != nil {
return "", err
}
return info.GoVersion, nil
}
================================================
FILE: goprocess/goprocess_lt1.18.go
================================================
// Copyright 2022 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.
//go:build !go1.18
// +build !go1.18
package goprocess
import goversion "rsc.io/goversion/version"
func goVersion(path string) (string, error) {
versionInfo, err := goversion.ReadExe(path)
if err != nil {
return "", err
}
return versionInfo.Release, nil
}
================================================
FILE: goprocess/goprocess_test.go
================================================
package goprocess
import (
"os"
"path/filepath"
"reflect"
"runtime"
"sort"
"strconv"
"testing"
"github.com/shirou/gopsutil/v3/process"
)
func BenchmarkFindAll(b *testing.B) {
for ii := 0; ii < b.N; ii++ {
_ = FindAll()
}
}
// TestFindAll tests findAll implementation function.
func TestFindAll(t *testing.T) {
testProcess, err := process.NewProcess(int32(os.Getpid()))
if err != nil {
t.Errorf("failed to get current process: %v", err)
}
testPpid, _ := testProcess.Ppid()
testExec, _ := testProcess.Name()
wantProcess := P{PID: int(testProcess.Pid), PPID: int(testPpid), Exec: testExec}
for _, tc := range []struct {
name string
concurrencyLimit int
input []*process.Process
goPIDs []int
want []P
mock bool
}{{
name: "no processes",
concurrencyLimit: 10,
input: nil,
want: nil,
}, {
name: "non-Go process",
concurrencyLimit: 10,
input: []*process.Process{testProcess},
want: nil,
}, {
name: "Go process",
concurrencyLimit: 10,
input: []*process.Process{testProcess},
goPIDs: []int{int(testProcess.Pid)},
want: []P{wantProcess},
}, {
name: "filters Go processes",
concurrencyLimit: 10,
input: fakeProcessesWithPIDs(1, 2, 3, 4, 5, 6, 7),
goPIDs: []int{1, 3, 5, 7},
want: []P{{PID: 1}, {PID: 3}, {PID: 5}, {PID: 7}},
mock: true,
}, {
name: "Go processes above max concurrency (issue #123)",
concurrencyLimit: 2,
input: fakeProcessesWithPIDs(1, 2, 3, 4, 5, 6, 7),
goPIDs: []int{1, 3, 5, 7},
want: []P{{PID: 1}, {PID: 3}, {PID: 5}, {PID: 7}},
mock: true,
}} {
t.Run(tc.name, func(t *testing.T) {
if tc.mock {
if runtime.GOOS != "linux" {
t.Skip()
}
tempDir, err := os.MkdirTemp("", "")
if err != nil {
t.Errorf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
for _, p := range tc.input {
os.Mkdir(filepath.Join(tempDir, strconv.Itoa(int(p.Pid))), 0o755)
os.WriteFile(filepath.Join(tempDir, strconv.Itoa(int(p.Pid)), "stat"), []byte(
`1440024 () R 0 1440024 0 34821 1440024 4194304 134 0 0 0 0 0 0 0 20 0 1 0 95120609 6746112 274 18446744073709551615 94467689938944 94467690036601 140724224197808 0 0 0 0 0 0 0 0 0 17 11 0 0 0 0 0 94467690068048 94467690071296 94467715629056 140724224199226 140724224199259 140724224199259 140724224204780 0`,
), 0o644)
os.WriteFile(filepath.Join(tempDir, strconv.Itoa(int(p.Pid)), "status"), []byte(
`Name:
Umask: 0022
State: R (running)
Tgid: 1440366
Ngid: 0
Pid: 1440366
PPid: 0
`,
), 0o644)
}
os.Setenv("HOST_PROC", tempDir)
}
actual := findAll(tc.input, fakeIsGo(tc.goPIDs), tc.concurrencyLimit)
sort.Slice(actual, func(i, j int) bool { return actual[i].PID < actual[j].PID })
if !reflect.DeepEqual(actual, tc.want) {
t.Errorf("findAll(concurrency=%v)\ngot %v\nwant %v",
tc.concurrencyLimit, actual, tc.want)
}
})
}
}
func fakeIsGo(goPIDs []int) isGoFunc {
return func(pr *process.Process) (path, version string, agent, ok bool, err error) {
for _, p := range goPIDs {
if p == int(pr.Pid) {
ok = true
return
}
}
return
}
}
func fakeProcessesWithPIDs(pids ...int) []*process.Process {
p := make([]*process.Process, 0, len(pids))
for _, pid := range pids {
p = append(p, &process.Process{Pid: int32(pid)})
}
return p
}
================================================
FILE: internal/cmd/process.go
================================================
// Copyright 2022 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 cmd
import (
"fmt"
"log"
"math"
"strconv"
"strings"
"time"
"github.com/shirou/gopsutil/v3/process"
"github.com/spf13/cobra"
)
// ProcessCommand displays information about a Go process.
func ProcessCommand() *cobra.Command {
return &cobra.Command{
Use: "process <pid> [period]",
Aliases: []string{"pid", "proc"},
Short: "Prints information about a Go process.",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return ProcessInfo(args)
},
}
}
// ProcessInfo takes arguments starting with pid|:addr and grabs all kinds of
// useful Go process information.
func ProcessInfo(args []string) error {
pid, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("error parsing the first argument: %w", err)
}
var period time.Duration
if len(args) >= 2 {
period, err = time.ParseDuration(args[1])
if err != nil {
secs, err := strconv.Atoi(args[1])
if err != nil {
return fmt.Errorf("error parsing the second argument: %w", err)
}
period = time.Duration(secs) * time.Second
}
}
processInfo(pid, period)
return nil
}
func processInfo(pid int, period time.Duration) {
if period < 0 {
log.Fatalf("Cannot determine CPU usage for negative duration %v", period)
}
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 period > 0 {
if v, err := cpuPercentWithinTime(p, period); err == nil {
fmt.Printf("cpu usage (%v):\t%.3f%%\n", period, 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)
}
}
}
}
// cpuPercentWithinTime return how many percent of the CPU time this process uses within given time duration
func cpuPercentWithinTime(p *process.Process, t time.Duration) (float64, error) {
cput, err := p.Times()
if err != nil {
return 0, err
}
time.Sleep(t)
cput2, err := p.Times()
if err != nil {
return 0, err
}
return 100 * (cput2.Total() - cput.Total()) / t.Seconds(), nil
}
// 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
}
// 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()
}
================================================
FILE: internal/cmd/process_test.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 cmd
import (
"testing"
"time"
)
func Test_fmtEtimeDuration(t *testing.T) {
tests := []struct {
d time.Duration
want string
}{
{
want: "00:00",
},
{
d: 2*time.Minute + 5*time.Second + 400*time.Millisecond,
want: "02:05",
},
{
d: 1*time.Second + 500*time.Millisecond,
want: "00:02",
},
{
d: 2*time.Hour + 42*time.Minute + 12*time.Second,
want: "02:42:12",
},
{
d: 24 * time.Hour,
want: "01-00:00:00",
},
{
d: 24*time.Hour + 59*time.Minute + 59*time.Second,
want: "01-00:59:59",
},
}
for _, tt := range tests {
t.Run(tt.d.String(), func(t *testing.T) {
if got := fmtEtimeDuration(tt.d); got != tt.want {
t.Errorf("fmtEtimeDuration() = %v, want %v", got, tt.want)
}
})
}
}
================================================
FILE: internal/cmd/root.go
================================================
// Copyright 2022 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 cmd
import (
"bytes"
"fmt"
"os"
"regexp"
"strconv"
"strings"
"github.com/google/gops/goprocess"
"github.com/spf13/cobra"
)
// NewRoot command.
func NewRoot() *cobra.Command {
return &cobra.Command{
Use: "gops",
Short: "gops is a tool to list and diagnose Go processes.",
Example: ` gops <cmd> <pid|addr> ...
gops <pid> # displays process info
gops help # displays this help message`,
// TODO(jbd): add link that explains the use of agent.
Run: func(cmd *cobra.Command, args []string) {
processes()
},
}
}
var develRe = regexp.MustCompile(`devel\s+\+\w+`)
func processes() {
ps := goprocess.FindAll()
var maxPID, maxPPID, maxExec, maxVersion int
for i, p := range ps {
ps[i].BuildVersion = shortenVersion(p.BuildVersion)
maxPID = max(maxPID, len(strconv.Itoa(p.PID)))
maxPPID = max(maxPPID, len(strconv.Itoa(p.PPID)))
maxExec = max(maxExec, len(p.Exec))
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))
fmt.Fprint(buf, " ")
ppid := strconv.Itoa(p.PPID)
fmt.Fprint(buf, pad(ppid, maxPPID))
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)
}
}
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]
}
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
}
================================================
FILE: internal/cmd/root_test.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 cmd
import "testing"
func Test_shortenVersion(t *testing.T) {
tests := []struct {
version string
want string
}{
{
version: "go1.8.1.typealias",
want: "go1.8.1.typealias",
},
{
version: "go1.9",
want: "go1.9",
},
{
version: "go1.9rc",
want: "go1.9rc",
},
{
version: "devel +990dac2723 Fri Jun 30 18:24:58 2017 +0000",
want: "devel +990dac2723",
},
}
for _, tt := range tests {
t.Run(tt.version, func(t *testing.T) {
if got := shortenVersion(tt.version); got != tt.want {
t.Errorf("shortenVersion() = %v, want %v", got, tt.want)
}
})
}
}
================================================
FILE: internal/cmd/shared.go
================================================
// Copyright 2022 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 cmd
import (
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"os"
"os/exec"
"strconv"
"strings"
"github.com/google/gops/internal"
"github.com/google/gops/signal"
"github.com/spf13/cobra"
)
// AgentCommands is a bridge between the legacy multiplexing to commands, and
// full migration to Cobra for each command.
//
// The code is already nicely structured with one function per command so it
// seemed cleaner to combine them all together here and "generate" cobra
// commands as just thin wrappers, rather through individual constructors.
func AgentCommands() []*cobra.Command {
var res []*cobra.Command
var cmds = []legacyCommand{
{
name: "stack",
short: "Prints the stack trace.",
fn: stackTrace,
},
{
name: "gc",
short: "Runs the garbage collector and blocks until successful.",
fn: gc,
},
{
name: "setgc",
short: "Sets the garbage collection target percentage. To completely stop GC, set to 'off'",
fn: setGC,
},
{
name: "memstats",
short: "Prints the allocation and garbage collection stats.",
fn: memStats,
},
{
name: "stats",
short: "Prints runtime stats.",
fn: stats,
},
{
name: "trace",
short: "Runs the runtime tracer for 5 secs and launches \"go tool trace\".",
fn: trace,
},
{
name: "pprof-heap",
short: "Reads the heap profile and launches \"go tool pprof\".",
fn: pprofHeap,
},
{
name: "pprof-cpu",
short: "Reads the CPU profile and launches \"go tool pprof\".",
fn: pprofCPU,
},
{
name: "version",
short: "Prints the Go version used to build the program.",
fn: version,
},
}
for _, c := range cmds {
c := c
res = append(res, &cobra.Command{
Use: fmt.Sprintf("%s <pid|addr>", c.name),
Short: c.short,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("missing PID or address")
}
addr, err := targetToAddr(args[0])
if err != nil {
return fmt.Errorf(
"couldn't resolve addr or pid %v to TCPAddress: %v\n", args[0], err,
)
}
var params []string
if len(args) > 1 {
params = append(params, args[1:]...)
}
if err := c.fn(*addr, params); err != nil {
return err
}
return nil
},
// errors get double printed otherwise
SilenceUsage: true,
SilenceErrors: true,
})
}
return res
}
type legacyCommand struct {
name string
short string
fn func(addr net.TCPAddr, params []string) error
}
func setGC(addr net.TCPAddr, params []string) error {
if len(params) != 1 {
return errors.New("missing gc percentage")
}
var (
perc int64
err error
)
if strings.ToLower(params[0]) == "off" {
perc = -1
} else {
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 := os.CreateTemp("", "trace")
if err != nil {
return err
}
if err := os.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 := os.CreateTemp("", 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 := os.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 := os.CreateTemp("", "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 := os.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 := io.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
}
================================================
FILE: internal/cmd/shared_test.go
================================================
// Copyright 2022 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 cmd
import (
"bytes"
"strings"
"testing"
"github.com/spf13/cobra"
)
func TestCommandPresence(t *testing.T) {
cmd := &cobra.Command{Use: "gops"}
cmd.AddCommand(AgentCommands()...)
var out bytes.Buffer
cmd.SetOut(&out)
cmd.SetArgs([]string{"--help"})
if err := cmd.Execute(); err != nil {
t.Error(err)
}
// basic check to make sure all the legacy commands have been ported over
// it doesn't test they are correctly _implemented_, just that they are not
// missing.
wants := []string{
"completion", "gc", "memstats", "pprof-cpu", "pprof-heap", "setgc",
"stack", "stats", "trace", "version",
}
outs := out.String()
for _, want := range wants {
if !strings.Contains(outs, want) {
t.Errorf("%q command not found in help", want)
}
}
}
================================================
FILE: internal/cmd/tree.go
================================================
// Copyright 2022 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 cmd
import (
"fmt"
"sort"
"strconv"
"github.com/google/gops/goprocess"
"github.com/spf13/cobra"
"github.com/xlab/treeprint"
)
// TreeCommand displays a process tree.
func TreeCommand() *cobra.Command {
return &cobra.Command{
Use: "tree",
Short: "Display parent-child tree for Go processes.",
Run: func(cmd *cobra.Command, args []string) {
displayProcessTree()
},
}
}
// displayProcessTree displays a tree of all the running Go processes.
func displayProcessTree() {
ps := goprocess.FindAll()
sort.Slice(ps, func(i, j int) bool {
return ps[i].PPID < ps[j].PPID
})
pstree := make(map[int][]goprocess.P, len(ps))
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, pstree, seen, tree)
}
fmt.Println(tree.String())
}
// constructProcessTree constructs the process tree in a depth-first fashion.
func constructProcessTree(ppid int, process goprocess.P, pstree map[int][]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, pstree, seen, tree)
}
}
================================================
FILE: internal/internal.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 internal
import (
"errors"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
)
const gopsConfigDirEnvKey = "GOPS_CONFIG_DIR"
func ConfigDir() (string, error) {
if configDir := os.Getenv(gopsConfigDirEnvKey); configDir != "" {
return configDir, nil
}
if userConfigDir, err := os.UserConfigDir(); err == nil {
return filepath.Join(userConfigDir, "gops"), nil
}
homeDir := guessUnixHomeDir()
if homeDir == "" {
return "", errors.New("unable to get current user home directory: os/user lookup failed; $HOME is empty")
}
return filepath.Join(homeDir, ".config", "gops"), nil
}
func guessUnixHomeDir() string {
usr, err := user.Current()
if err == nil {
return usr.HomeDir
}
return os.Getenv("HOME")
}
func PIDFile(pid int) (string, error) {
gopsdir, err := ConfigDir()
if err != nil {
return "", err
}
return filepath.Join(gopsdir, strconv.Itoa(pid)), nil
}
func GetPort(pid int) (string, error) {
portfile, err := PIDFile(pid)
if err != nil {
return "", err
}
b, err := os.ReadFile(portfile)
if err != nil {
return "", err
}
port := strings.TrimSpace(string(b))
return port, nil
}
================================================
FILE: internal/internal_test.go
================================================
package internal
import (
"os"
"path/filepath"
"testing"
)
func TestConfigDir(t *testing.T) {
configDir, err := ConfigDir()
if err != nil {
t.Fatal(err)
}
if g, w := filepath.Base(configDir), "gops"; g != w {
t.Errorf("ConfigDir: got base directory %q, want %q", g, w)
}
key := gopsConfigDirEnvKey
oldDir := os.Getenv(key)
defer os.Setenv(key, oldDir)
newDir := "foo-bar"
os.Setenv(key, newDir)
configDir, err = ConfigDir()
if err != nil {
t.Fatal(err)
}
if g, w := configDir, newDir; g != w {
t.Errorf("ConfigDir: got=%v want=%v", g, w)
}
}
================================================
FILE: main.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.
// Program gops is a tool to list currently running Go processes.
package main
import (
"log"
"os"
"strconv"
"github.com/google/gops/internal/cmd"
)
func main() {
var root = cmd.NewRoot()
root.AddCommand(cmd.ProcessCommand())
root.AddCommand(cmd.TreeCommand())
root.AddCommand(cmd.AgentCommands()...)
// Legacy support for `gops <pid>` command.
//
// When the second argument is provided as int as opposed to a sub-command
// (like proc, version, etc), gops command effectively shortcuts that
// to `gops process <pid>`.
if len(os.Args) > 1 {
// See second argument appears to be a pid rather than a subcommand
_, err := strconv.Atoi(os.Args[1])
if err == nil {
cmd.ProcessInfo(os.Args[1:]) // shift off the command name
return
}
}
if err := root.Execute(); err != nil {
log.Fatal(err)
}
}
================================================
FILE: main_test.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
================================================
FILE: signal/signal.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 signal contains signals used to communicate to the gops agents.
package signal
const (
// StackTrace represents a command to print stack trace.
StackTrace = byte(0x1)
// GC runs the garbage collector.
GC = byte(0x2)
// MemStats reports memory stats.
MemStats = byte(0x3)
// Version prints the Go version.
Version = byte(0x4)
// HeapProfile starts `go tool pprof` with the current memory profile.
HeapProfile = byte(0x5)
// CPUProfile starts `go tool pprof` with the current CPU profile
CPUProfile = byte(0x6)
// Stats returns Go runtime statistics such as number of goroutines, GOMAXPROCS, and NumCPU.
Stats = byte(0x7)
// Trace starts the Go execution tracer, waits 5 seconds and launches the trace tool.
Trace = byte(0x8)
// BinaryDump returns running binary file.
BinaryDump = byte(0x9)
// SetGCPercent sets the garbage collection target percentage.
SetGCPercent = byte(0x10)
)
gitextract_m6aexidg/
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── agent/
│ ├── agent.go
│ ├── agent_test.go
│ ├── sockopt_reuseport.go
│ └── sockopt_unsupported.go
├── cmd.go
├── examples/
│ └── hello/
│ └── main.go
├── go.mod
├── go.sum
├── goprocess/
│ ├── goprocess.go
│ ├── goprocess_1.18.go
│ ├── goprocess_lt1.18.go
│ └── goprocess_test.go
├── internal/
│ ├── cmd/
│ │ ├── process.go
│ │ ├── process_test.go
│ │ ├── root.go
│ │ ├── root_test.go
│ │ ├── shared.go
│ │ ├── shared_test.go
│ │ └── tree.go
│ ├── internal.go
│ └── internal_test.go
├── main.go
├── main_test.go
└── signal/
└── signal.go
SYMBOL INDEX (80 symbols across 20 files)
FILE: agent/agent.go
constant defaultAddr (line 33) | defaultAddr = "127.0.0.1:0"
type Options (line 44) | type Options struct
function Listen (line 77) | func Listen(opts Options) error {
function listen (line 117) | func listen(l net.Listener) {
function saveConfig (line 142) | func saveConfig(opts Options, port int) error {
function gracefulShutdown (line 161) | func gracefulShutdown() {
function Close (line 178) | func Close() {
function formatBytes (line 192) | func formatBytes(val uint64) string {
function handle (line 207) | func handle(conn io.ReadWriter, msg []byte) error {
FILE: agent/agent_test.go
function TestListen (line 12) | func TestListen(t *testing.T) {
function TestAgentClose (line 20) | func TestAgentClose(t *testing.T) {
function TestUseCustomConfigDir (line 35) | func TestUseCustomConfigDir(t *testing.T) {
function TestAgentListenMultipleClose (line 46) | func TestAgentListenMultipleClose(t *testing.T) {
function TestAgentListenReuseAddrAndPort (line 57) | func TestAgentListenReuseAddrAndPort(t *testing.T) {
function TestFormatBytes (line 68) | func TestFormatBytes(t *testing.T) {
FILE: agent/sockopt_reuseport.go
function setReuseAddrAndPortSockopts (line 19) | func setReuseAddrAndPortSockopts(network, address string, c syscall.RawC...
FILE: agent/sockopt_unsupported.go
function setReuseAddrAndPortSockopts (line 12) | func setReuseAddrAndPortSockopts(network, address string, c syscall.RawC...
FILE: examples/hello/main.go
function main (line 14) | func main() {
FILE: goprocess/goprocess.go
type P (line 17) | type P struct
function FindAll (line 27) | func FindAll() []P {
type isGoFunc (line 37) | type isGoFunc
function findAll (line 39) | func findAll(pss []*process.Process, isGo isGoFunc, concurrencyLimit int...
function Find (line 97) | func Find(pid int) (P, bool, error) {
function isGo (line 128) | func isGo(pr *process.Process) (path, version string, agent, ok bool, er...
FILE: goprocess/goprocess_1.18.go
function goVersion (line 12) | func goVersion(path string) (string, error) {
FILE: goprocess/goprocess_lt1.18.go
function goVersion (line 12) | func goVersion(path string) (string, error) {
FILE: goprocess/goprocess_test.go
function BenchmarkFindAll (line 15) | func BenchmarkFindAll(b *testing.B) {
function TestFindAll (line 22) | func TestFindAll(t *testing.T) {
function fakeIsGo (line 107) | func fakeIsGo(goPIDs []int) isGoFunc {
function fakeProcessesWithPIDs (line 119) | func fakeProcessesWithPIDs(pids ...int) []*process.Process {
FILE: internal/cmd/process.go
function ProcessCommand (line 20) | func ProcessCommand() *cobra.Command {
function ProcessInfo (line 34) | func ProcessInfo(args []string) error {
function processInfo (line 56) | func processInfo(pid int, period time.Duration) {
function cpuPercentWithinTime (line 101) | func cpuPercentWithinTime(p *process.Process, t time.Duration) (float64,...
function elapsedTime (line 116) | func elapsedTime(p *process.Process) (string, error) {
function fmtEtimeDuration (line 128) | func fmtEtimeDuration(d time.Duration) string {
FILE: internal/cmd/process_test.go
function Test_fmtEtimeDuration (line 12) | func Test_fmtEtimeDuration(t *testing.T) {
FILE: internal/cmd/root.go
function NewRoot (line 20) | func NewRoot() *cobra.Command {
function processes (line 36) | func processes() {
function shortenVersion (line 72) | func shortenVersion(v string) string {
function pad (line 83) | func pad(s string, total int) string {
function max (line 90) | func max(i, j int) int {
FILE: internal/cmd/root_test.go
function Test_shortenVersion (line 9) | func Test_shortenVersion(t *testing.T) {
FILE: internal/cmd/shared.go
function AgentCommands (line 29) | func AgentCommands() []*cobra.Command {
type legacyCommand (line 119) | type legacyCommand struct
function setGC (line 125) | func setGC(addr net.TCPAddr, params []string) error {
function stackTrace (line 146) | func stackTrace(addr net.TCPAddr, _ []string) error {
function gc (line 150) | func gc(addr net.TCPAddr, _ []string) error {
function memStats (line 155) | func memStats(addr net.TCPAddr, _ []string) error {
function version (line 159) | func version(addr net.TCPAddr, _ []string) error {
function pprofHeap (line 163) | func pprofHeap(addr net.TCPAddr, _ []string) error {
function pprofCPU (line 167) | func pprofCPU(addr net.TCPAddr, _ []string) error {
function trace (line 172) | func trace(addr net.TCPAddr, _ []string) error {
function pprof (line 202) | func pprof(addr net.TCPAddr, p byte, prefix string) error {
function stats (line 252) | func stats(addr net.TCPAddr, _ []string) error {
function cmdWithPrint (line 256) | func cmdWithPrint(addr net.TCPAddr, c byte, params ...byte) error {
function targetToAddr (line 267) | func targetToAddr(target string) (*net.TCPAddr, error) {
function cmd (line 290) | func cmd(addr net.TCPAddr, c byte, params ...byte) ([]byte, error) {
function cmdLazy (line 303) | func cmdLazy(addr net.TCPAddr, c byte, params ...byte) (io.Reader, error) {
FILE: internal/cmd/shared_test.go
function TestCommandPresence (line 15) | func TestCommandPresence(t *testing.T) {
FILE: internal/cmd/tree.go
function TreeCommand (line 18) | func TreeCommand() *cobra.Command {
function displayProcessTree (line 29) | func displayProcessTree() {
function constructProcessTree (line 48) | func constructProcessTree(ppid int, process goprocess.P, pstree map[int]...
FILE: internal/internal.go
constant gopsConfigDirEnvKey (line 16) | gopsConfigDirEnvKey = "GOPS_CONFIG_DIR"
function ConfigDir (line 18) | func ConfigDir() (string, error) {
function guessUnixHomeDir (line 34) | func guessUnixHomeDir() string {
function PIDFile (line 42) | func PIDFile(pid int) (string, error) {
function GetPort (line 50) | func GetPort(pid int) (string, error) {
FILE: internal/internal_test.go
function TestConfigDir (line 9) | func TestConfigDir(t *testing.T) {
FILE: main.go
function main (line 16) | func main() {
FILE: signal/signal.go
constant StackTrace (line 10) | StackTrace = byte(0x1)
constant GC (line 13) | GC = byte(0x2)
constant MemStats (line 16) | MemStats = byte(0x3)
constant Version (line 19) | Version = byte(0x4)
constant HeapProfile (line 22) | HeapProfile = byte(0x5)
constant CPUProfile (line 25) | CPUProfile = byte(0x6)
constant Stats (line 28) | Stats = byte(0x7)
constant Trace (line 31) | Trace = byte(0x8)
constant BinaryDump (line 34) | BinaryDump = byte(0x9)
constant SetGCPercent (line 37) | SetGCPercent = byte(0x10)
Condensed preview — 29 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (62K chars).
[
{
"path": ".github/dependabot.yml",
"chars": 318,
"preview": "version: 2\nupdates:\n - package-ecosystem: gomod\n directory: /\n schedule:\n interval: daily\n open-pull-requ"
},
{
"path": ".github/workflows/test.yml",
"chars": 753,
"preview": "name: Tests\n\non:\n push:\n branches:\n - master\n pull_request:\n branches:\n - master\n\njobs:\n test:\n strate"
},
{
"path": ".gitignore",
"chars": 5,
"preview": "gops\n"
},
{
"path": "LICENSE",
"chars": 1479,
"preview": "Copyright (c) 2016 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or with"
},
{
"path": "README.md",
"chars": 6353,
"preview": "# gops\n\n[](https://github.com/google/go"
},
{
"path": "agent/agent.go",
"chars": 8078,
"preview": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "agent/agent_test.go",
"chars": 1879,
"preview": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "agent/sockopt_reuseport.go",
"chars": 1178,
"preview": "// Copyright 2020 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "agent/sockopt_unsupported.go",
"chars": 388,
"preview": "// Copyright 2020 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "cmd.go",
"chars": 173,
"preview": "// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "examples/hello/main.go",
"chars": 415,
"preview": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "go.mod",
"chars": 718,
"preview": "module github.com/google/gops\n\ngo 1.18\n\nrequire (\n\tgithub.com/shirou/gopsutil/v3 v3.24.5\n\tgithub.com/spf13/cobra v1.9.1\n"
},
{
"path": "go.sum",
"chars": 4400,
"preview": "github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/davecgh/go-spe"
},
{
"path": "goprocess/goprocess.go",
"chars": 3386,
"preview": "// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "goprocess/goprocess_1.18.go",
"chars": 393,
"preview": "// Copyright 2022 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "goprocess/goprocess_lt1.18.go",
"chars": 425,
"preview": "// Copyright 2022 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "goprocess/goprocess_test.go",
"chars": 3607,
"preview": "package goprocess\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"sort\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/shi"
},
{
"path": "internal/cmd/process.go",
"chars": 3832,
"preview": "// Copyright 2022 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "internal/cmd/process_test.go",
"chars": 940,
"preview": "// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "internal/cmd/root.go",
"chars": 2035,
"preview": "// Copyright 2022 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "internal/cmd/root_test.go",
"chars": 784,
"preview": "// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "internal/cmd/shared.go",
"chars": 7500,
"preview": "// Copyright 2022 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "internal/cmd/shared_test.go",
"chars": 934,
"preview": "// Copyright 2022 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "internal/cmd/tree.go",
"chars": 1734,
"preview": "// Copyright 2022 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "internal/internal.go",
"chars": 1298,
"preview": "// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "internal/internal_test.go",
"chars": 574,
"preview": "package internal\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestConfigDir(t *testing.T) {\n\tconfigDir, err := Co"
},
{
"path": "main.go",
"chars": 988,
"preview": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "main_test.go",
"chars": 173,
"preview": "// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "signal/signal.go",
"chars": 1082,
"preview": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
}
]
About this extraction
This page contains the full source code of the google/gops GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 29 files (54.5 KB), approximately 18.5k tokens, and a symbol index with 80 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.