[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: gomod\n    directory: /\n    schedule:\n      interval: daily\n    open-pull-requests-limit: 1\n    rebase-strategy: disabled\n\n  - package-ecosystem: github-actions\n    directory: /\n    schedule:\n      interval: daily\n    open-pull-requests-limit: 5\n    rebase-strategy: disabled\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Tests\n\non:\n  push:\n    branches:\n    - master\n  pull_request:\n    branches:\n    - master\n\njobs:\n  test:\n    strategy:\n      matrix:\n        # Minimum supported version (1.18) and the latest two\n        go-version: ['1.18', '1.22', '1.23']\n        platform: [ubuntu-latest, macos-latest, windows-latest]\n    runs-on: ${{ matrix.platform }}\n\n    steps:\n    - name: Install Go\n      uses: actions/setup-go@v5\n      with:\n        go-version: ${{ matrix.go-version }}\n\n    - name: Checkout code\n      uses: actions/checkout@v4\n\n    - name: Check formatting\n      if: matrix.go-version == '1.23' && matrix.platform == 'ubuntu-latest'\n      run: diff -u <(echo -n) <(go fmt $(go list ./...))\n\n    - name: Run unit tests\n      run: go test -race -v ./...\n"
  },
  {
    "path": ".gitignore",
    "content": "gops\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2016 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n   * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n   * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n   * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# gops\n\n[![GitHub Action Status](https://github.com/google/gops/workflows/Tests/badge.svg)](https://github.com/google/gops/actions?query=workflow%3ATests)\n[![GoDoc](https://godoc.org/github.com/google/gops?status.svg)](https://godoc.org/github.com/google/gops)\n\ngops is a command to list and diagnose Go processes currently running on your system.\n\n```sh\n$ gops\n983   980    uplink-soecks  go1.9   /usr/local/bin/uplink-soecks\n52697 52695  gops           go1.10  /Users/jbd/bin/gops\n4132  4130   foops        * go1.9   /Users/jbd/bin/foops\n51130 51128  gocode         go1.9.2 /Users/jbd/bin/gocode\n```\n\n## Installation\n\nTo install the latest version of gops:\n\n```sh\n$ go get github.com/google/gops\n```\n\nor\n\n```sh\n$ go install github.com/google/gops@latest\n```\n\nTo install a specific gops version, for example v0.3.19:\n\n```sh\n$ go install github.com/google/gops@v0.3.19\n```\n\n## Diagnostics\n\nFor processes that start the diagnostics agent, gops can report\nadditional information such as the current stack trace, Go version, memory\nstats, etc.\n\nIn order to start the diagnostics agent, see the [hello example](https://github.com/google/gops/blob/master/examples/hello/main.go).\n\n``` go\npackage main\n\nimport (\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/google/gops/agent\"\n)\n\nfunc main() {\n\tif err := agent.Listen(agent.Options{}); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\ttime.Sleep(time.Hour)\n}\n```\n\nOtherwise, you could set `GOPS_CONFIG_DIR` environment variables to assign your config dir.\nDefault, gops will use the current user's home directory(AppData on windows).\n\n### Manual\n\nIt is possible to use gops tool both in local and remote mode.\n\nLocal mode requires that you start the target binary as the same user that runs gops binary.\nTo use gops in a remote mode you need to know target's agent address.\n\nIn Local mode use process's PID as a target; in Remote mode target is a `host:port` combination.\n\n#### Listing all processes running locally\n\nTo print all go processes, run `gops` without arguments:\n\n```sh\n$ gops\n983   980    uplink-soecks  go1.9   /usr/local/bin/uplink-soecks\n52697 52695  gops           go1.10  /Users/jbd/bin/gops\n4132  4130   foops        * go1.9   /Users/jbd/bin/foops\n51130 51128  gocode         go1.9.2 /Users/jbd/bin/gocode\n```\n\nThe output displays:\n* PID\n* PPID\n* Name of the program\n* Go version used to build the program\n* Location of the associated program\n\nNote that processes running the agent are marked with `*` next to the PID (e.g. `4132*`).\n\n#### $ gops \\<pid\\> [duration]\n\nTo report more information about a process, run `gops` followed by a PID:\n\n```sh\n$ gops <pid>\nparent PID:\t5985\nthreads:\t27\nmemory usage:\t0.199%\ncpu usage:\t0.139%\nusername:\tjbd\ncmd+args:\t/Applications/Splice.app/Contents/Resources/Splice Helper.app/Contents/MacOS/Splice Helper -pid 5985\nlocal/remote:\t127.0.0.1:56765 <-> :0 (LISTEN)\nlocal/remote:\t127.0.0.1:56765 <-> 127.0.0.1:50955 (ESTABLISHED)\nlocal/remote:\t100.76.175.164:52353 <-> 54.241.191.232:443 (ESTABLISHED)\n```\n\nIf an optional duration is specified in the format as expected by\n[`time.ParseDuration`](https://golang.org/pkg/time/#ParseDuration), the CPU\nusage for the given time period is reported in addition:\n\n```sh\n$ gops <pid> 2s\nparent PID:\t5985\nthreads:\t27\nmemory usage:\t0.199%\ncpu usage:\t0.139%\ncpu usage (2s):\t0.271%\nusername:\tjbd\ncmd+args:\t/Applications/Splice.app/Contents/Resources/Splice Helper.app/Contents/MacOS/Splice Helper -pid 5985\nlocal/remote:\t127.0.0.1:56765 <-> :0 (LISTEN)\nlocal/remote:\t127.0.0.1:56765 <-> 127.0.0.1:50955 (ESTABLISHED)\nlocal/remote:\t100.76.175.164:52353 <-> 54.241.191.232:443 (ESTABLISHED)\n```\n\n#### $ gops tree\n\nTo display a process tree with all the running Go processes, run the following command:\n\n```sh\n$ gops tree\n\n...\n├── 1\n│   └── 13962 [gocode] {go1.9}\n├── 557\n│   └── 635 [com.docker.supervisor] {go1.9.2}\n│       └── 638 [com.docker.driver.amd64-linux] {go1.9.2}\n└── 13744\n    └── 67243 [gops] {go1.10}\n```\n\n#### $ gops stack (\\<pid\\>|\\<addr\\>)\n\nIn order to print the current stack trace from a target program, run the following command:\n\n\n```sh\n$ gops stack (<pid>|<addr>)\ngops stack 85709\ngoroutine 8 [running]:\nruntime/pprof.writeGoroutineStacks(0x13c7bc0, 0xc42000e008, 0xc420ec8520, 0xc420ec8520)\n\t/Users/jbd/go/src/runtime/pprof/pprof.go:603 +0x79\nruntime/pprof.writeGoroutine(0x13c7bc0, 0xc42000e008, 0x2, 0xc428f1c048, 0xc420ec8608)\n\t/Users/jbd/go/src/runtime/pprof/pprof.go:592 +0x44\nruntime/pprof.(*Profile).WriteTo(0x13eeda0, 0x13c7bc0, 0xc42000e008, 0x2, 0xc42000e008, 0x0)\n\t/Users/jbd/go/src/runtime/pprof/pprof.go:302 +0x3b5\ngithub.com/google/gops/agent.handle(0x13cd560, 0xc42000e008, 0xc420186000, 0x1, 0x1, 0x0, 0x0)\n\t/Users/jbd/src/github.com/google/gops/agent/agent.go:150 +0x1b3\ngithub.com/google/gops/agent.listen()\n\t/Users/jbd/src/github.com/google/gops/agent/agent.go:113 +0x2b2\ncreated by github.com/google/gops/agent.Listen\n\t/Users/jbd/src/github.com/google/gops/agent/agent.go:94 +0x480\n# ...\n```\n\n#### $ gops memstats (\\<pid\\>|\\<addr\\>)\n\nTo print the current memory stats, run the following command:\n\n```sh\n$ gops memstats (<pid>|<addr>)\n```\n\n#### $ gops gc (\\<pid\\>|\\<addr\\>)\n\nIf you want to force run garbage collection on the target program, run `gc`.\nIt will block until the GC is completed.\n\n#### $ gops setgc (\\<pid\\>|\\<addr\\>) <perc>\n\nSets the garbage collection target to a certain percentage.\nThe following command sets it to 10%:\n\n``` sh\n$ gops setgc (<pid>|<addr>) 10\n```\nThe following command turns off the garbage collector:\n\n```sh\n$ gops setgc (<pid>|<addr>) off\n```\n\n#### $ gops version (\\<pid\\>|\\<addr\\>)\n\ngops reports the Go version the target program is built with, if you run the following:\n\n```sh\n$ gops version (<pid>|<addr>)\ndevel +6a3c6c0 Sat Jan 14 05:57:07 2017 +0000\n```\n\n#### $ gops stats (\\<pid\\>|\\<addr\\>)\n\nTo print the runtime statistics such as number of goroutines and `GOMAXPROCS`.\n\n#### Profiling\n\n\n##### Pprof\n\ngops supports CPU and heap pprof profiles. After reading either heap or CPU profile,\nit shells out to the `go tool pprof` and let you interactively examine the profiles.\n\nTo enter the CPU profile, run:\n\n```sh\n$ gops pprof-cpu (<pid>|<addr>)\n```\n\nTo enter the heap profile, run:\n\n```sh\n$ gops pprof-heap (<pid>|<addr>)\n```\n\n##### Execution trace\n\ngops allows you to start the runtime tracer for 5 seconds and examine the results.\n\n```sh\n$ gops trace (<pid>|<addr>)\n```\n"
  },
  {
    "path": "agent/agent.go",
    "content": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// Package agent provides hooks programs can register to retrieve\n// diagnostics data by using gops.\npackage agent\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\tgosignal \"os/signal\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"runtime/debug\"\n\t\"runtime/pprof\"\n\t\"runtime/trace\"\n\t\"strconv\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/google/gops/internal\"\n\t\"github.com/google/gops/signal\"\n)\n\nconst defaultAddr = \"127.0.0.1:0\"\n\nvar (\n\tmu       sync.Mutex\n\tportfile string\n\tlistener net.Listener\n\n\tunits = []string{\" bytes\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\"}\n)\n\n// Options allows configuring the started agent.\ntype Options struct {\n\t// Addr is the host:port the agent will be listening at.\n\t// Optional.\n\tAddr string\n\n\t// ConfigDir is the directory to store the configuration file,\n\t// PID of the gops process, filename, port as well as content.\n\t// Optional.\n\tConfigDir string\n\n\t// ShutdownCleanup automatically cleans up resources if the\n\t// running process receives an interrupt. Otherwise, users\n\t// can call Close before shutting down.\n\t// Optional.\n\tShutdownCleanup bool\n\n\t// ReuseSocketAddrAndPort determines whether the SO_REUSEADDR and\n\t// SO_REUSEPORT socket options should be set on the listening socket of\n\t// the agent. This option is only effective on unix-like OSes and if\n\t// Addr is set to a fixed host:port.\n\t// Optional.\n\tReuseSocketAddrAndPort bool\n}\n\n// Listen starts the gops agent on a host process. Once agent started, users\n// can use the advanced gops features. The agent will listen to Interrupt\n// signals and exit the process, if you need to perform further work on the\n// Interrupt signal use the options parameter to configure the agent\n// accordingly.\n//\n// Note: The agent exposes an endpoint via a TCP connection that can be used by\n// any program on the system. Review your security requirements before starting\n// the agent.\nfunc Listen(opts Options) error {\n\tmu.Lock()\n\tdefer mu.Unlock()\n\n\tif listener != nil {\n\t\treturn fmt.Errorf(\"gops: agent already listening at: %v\", listener.Addr())\n\t}\n\n\taddr := opts.Addr\n\tif addr == \"\" {\n\t\taddr = defaultAddr\n\t}\n\n\tvar lc net.ListenConfig\n\tif opts.ReuseSocketAddrAndPort {\n\t\tlc.Control = setReuseAddrAndPortSockopts\n\t}\n\n\tvar err error\n\tlistener, err = lc.Listen(context.Background(), \"tcp\", addr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tport := listener.Addr().(*net.TCPAddr).Port\n\terr = saveConfig(opts, port)\n\tif err != nil {\n\t\t// ignore and work in remote mode only\n\t\tif !errors.Is(err, syscall.EROFS) && !errors.Is(err, syscall.EPERM) {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif opts.ShutdownCleanup {\n\t\tgracefulShutdown()\n\t}\n\tgo listen(listener)\n\treturn nil\n}\n\nfunc listen(l net.Listener) {\n\tbuf := make([]byte, 1)\n\tfor {\n\t\tfd, err := l.Accept()\n\t\tif err != nil {\n\t\t\tif !errors.Is(err, net.ErrClosed) {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"gops: %v\\n\", err)\n\t\t\t}\n\t\t\tif netErr, ok := err.(net.Error); ok && !netErr.Temporary() {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif _, err := fd.Read(buf); err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"gops: %v\\n\", err)\n\t\t\tcontinue\n\t\t}\n\t\tif err := handle(fd, buf); err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"gops: %v\\n\", err)\n\t\t\tcontinue\n\t\t}\n\t\tfd.Close()\n\t}\n}\n\nfunc saveConfig(opts Options, port int) error {\n\tgopsdir := opts.ConfigDir\n\tif gopsdir == \"\" {\n\t\tcfgDir, err := internal.ConfigDir()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tgopsdir = cfgDir\n\t}\n\n\terr := os.MkdirAll(gopsdir, os.ModePerm)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tportfile = filepath.Join(gopsdir, strconv.Itoa(os.Getpid()))\n\treturn os.WriteFile(portfile, []byte(strconv.Itoa(port)), os.ModePerm)\n}\n\nfunc gracefulShutdown() {\n\tc := make(chan os.Signal, 1)\n\tgosignal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)\n\tgo func() {\n\t\t// cleanup the socket on shutdown.\n\t\tsig := <-c\n\t\tClose()\n\t\tret := 1\n\t\tif sig == syscall.SIGTERM {\n\t\t\tret = 0\n\t\t}\n\t\tos.Exit(ret)\n\t}()\n}\n\n// Close closes the agent, removing temporary files and closing the TCP listener.\n// If no agent is listening, Close does nothing.\nfunc Close() {\n\tmu.Lock()\n\tdefer mu.Unlock()\n\n\tif portfile != \"\" {\n\t\tos.Remove(portfile)\n\t\tportfile = \"\"\n\t}\n\tif listener != nil {\n\t\tlistener.Close()\n\t\tlistener = nil\n\t}\n}\n\nfunc formatBytes(val uint64) string {\n\tvar i int\n\tvar target uint64\n\tfor i = range units {\n\t\ttarget = 1 << uint(10*(i+1))\n\t\tif val < target {\n\t\t\tbreak\n\t\t}\n\t}\n\tif i > 0 {\n\t\treturn fmt.Sprintf(\"%0.2f%s (%d bytes)\", float64(val)/(float64(target)/1024), units[i], val)\n\t}\n\treturn fmt.Sprintf(\"%d bytes\", val)\n}\n\nfunc handle(conn io.ReadWriter, msg []byte) error {\n\tswitch msg[0] {\n\tcase signal.StackTrace:\n\t\treturn pprof.Lookup(\"goroutine\").WriteTo(conn, 2)\n\tcase signal.GC:\n\t\truntime.GC()\n\t\t_, err := conn.Write([]byte(\"ok\"))\n\t\treturn err\n\tcase signal.MemStats:\n\t\tvar s runtime.MemStats\n\t\truntime.ReadMemStats(&s)\n\t\tfmt.Fprintf(conn, \"alloc: %v\\n\", formatBytes(s.Alloc))\n\t\tfmt.Fprintf(conn, \"total-alloc: %v\\n\", formatBytes(s.TotalAlloc))\n\t\tfmt.Fprintf(conn, \"sys: %v\\n\", formatBytes(s.Sys))\n\t\tfmt.Fprintf(conn, \"lookups: %v\\n\", s.Lookups)\n\t\tfmt.Fprintf(conn, \"mallocs: %v\\n\", s.Mallocs)\n\t\tfmt.Fprintf(conn, \"frees: %v\\n\", s.Frees)\n\t\tfmt.Fprintf(conn, \"heap-alloc: %v\\n\", formatBytes(s.HeapAlloc))\n\t\tfmt.Fprintf(conn, \"heap-sys: %v\\n\", formatBytes(s.HeapSys))\n\t\tfmt.Fprintf(conn, \"heap-idle: %v\\n\", formatBytes(s.HeapIdle))\n\t\tfmt.Fprintf(conn, \"heap-in-use: %v\\n\", formatBytes(s.HeapInuse))\n\t\tfmt.Fprintf(conn, \"heap-released: %v\\n\", formatBytes(s.HeapReleased))\n\t\tfmt.Fprintf(conn, \"heap-objects: %v\\n\", s.HeapObjects)\n\t\tfmt.Fprintf(conn, \"stack-in-use: %v\\n\", formatBytes(s.StackInuse))\n\t\tfmt.Fprintf(conn, \"stack-sys: %v\\n\", formatBytes(s.StackSys))\n\t\tfmt.Fprintf(conn, \"mspan-in-use: %v\\n\", formatBytes(s.MSpanInuse))\n\t\tfmt.Fprintf(conn, \"mspan-sys: %v\\n\", formatBytes(s.MSpanSys))\n\t\tfmt.Fprintf(conn, \"mcache-in-use: %v\\n\", formatBytes(s.MCacheInuse))\n\t\tfmt.Fprintf(conn, \"mcache-sys: %v\\n\", formatBytes(s.MCacheSys))\n\t\tfmt.Fprintf(conn, \"buck-hash-sys: %v\\n\", formatBytes(s.BuckHashSys))\n\t\tfmt.Fprintf(conn, \"other-sys: %v\\n\", formatBytes(s.OtherSys))\n\t\tfmt.Fprintf(conn, \"gc-sys: %v\\n\", formatBytes(s.GCSys))\n\t\tfmt.Fprintf(conn, \"next-gc: when heap-alloc >= %v\\n\", formatBytes(s.NextGC))\n\t\tlastGC := \"-\"\n\t\tif s.LastGC != 0 {\n\t\t\tlastGC = fmt.Sprint(time.Unix(0, int64(s.LastGC)))\n\t\t}\n\t\tfmt.Fprintf(conn, \"last-gc: %v\\n\", lastGC)\n\t\tfmt.Fprintf(conn, \"gc-pause-total: %v\\n\", time.Duration(s.PauseTotalNs))\n\t\tfmt.Fprintf(conn, \"gc-pause: %v\\n\", s.PauseNs[(s.NumGC+255)%256])\n\t\tfmt.Fprintf(conn, \"gc-pause-end: %v\\n\", s.PauseEnd[(s.NumGC+255)%256])\n\t\tfmt.Fprintf(conn, \"num-gc: %v\\n\", s.NumGC)\n\t\tfmt.Fprintf(conn, \"num-forced-gc: %v\\n\", s.NumForcedGC)\n\t\tfmt.Fprintf(conn, \"gc-cpu-fraction: %v\\n\", s.GCCPUFraction)\n\t\tfmt.Fprintf(conn, \"enable-gc: %v\\n\", s.EnableGC)\n\t\tfmt.Fprintf(conn, \"debug-gc: %v\\n\", s.DebugGC)\n\tcase signal.Version:\n\t\tfmt.Fprintf(conn, \"%v\\n\", runtime.Version())\n\tcase signal.HeapProfile:\n\t\treturn pprof.WriteHeapProfile(conn)\n\tcase signal.CPUProfile:\n\t\tif err := pprof.StartCPUProfile(conn); err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttime.Sleep(30 * time.Second)\n\t\tpprof.StopCPUProfile()\n\tcase signal.Stats:\n\t\tfmt.Fprintf(conn, \"goroutines: %v\\n\", runtime.NumGoroutine())\n\t\tfmt.Fprintf(conn, \"OS threads: %v\\n\", pprof.Lookup(\"threadcreate\").Count())\n\t\tfmt.Fprintf(conn, \"GOMAXPROCS: %v\\n\", runtime.GOMAXPROCS(0))\n\t\tfmt.Fprintf(conn, \"num CPU: %v\\n\", runtime.NumCPU())\n\tcase signal.BinaryDump:\n\t\tpath, err := os.Executable()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tf, err := os.Open(path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer f.Close()\n\n\t\t_, err = bufio.NewReader(f).WriteTo(conn)\n\t\treturn err\n\tcase signal.Trace:\n\t\tif err := trace.Start(conn); err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttime.Sleep(5 * time.Second)\n\t\ttrace.Stop()\n\tcase signal.SetGCPercent:\n\t\tperc, err := binary.ReadVarint(bufio.NewReader(conn))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfmt.Fprintf(conn, \"New GC percent set to %v. Previous value was %v.\\n\", perc, debug.SetGCPercent(int(perc)))\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "agent/agent_test.go",
    "content": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage agent\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestListen(t *testing.T) {\n\terr := Listen(Options{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tClose()\n}\n\nfunc TestAgentClose(t *testing.T) {\n\terr := Listen(Options{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tClose()\n\t_, err = os.Stat(portfile)\n\tif !os.IsNotExist(err) {\n\t\tt.Fatalf(\"portfile = %q doesn't exist; err = %v\", portfile, err)\n\t}\n\tif portfile != \"\" {\n\t\tt.Fatalf(\"got = %q; want empty portfile\", portfile)\n\t}\n}\n\nfunc TestUseCustomConfigDir(t *testing.T) {\n\terr := Listen(Options{\n\t\tConfigDir:       os.TempDir(),\n\t\tShutdownCleanup: true,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tClose()\n}\n\nfunc TestAgentListenMultipleClose(t *testing.T) {\n\terr := Listen(Options{})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tClose()\n\tClose()\n\tClose()\n\tClose()\n}\n\nfunc TestAgentListenReuseAddrAndPort(t *testing.T) {\n\terr := Listen(Options{\n\t\tAddr:                   \"127.0.0.1:50000\",\n\t\tReuseSocketAddrAndPort: true,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tClose()\n}\n\nfunc TestFormatBytes(t *testing.T) {\n\ttests := []struct {\n\t\tval  uint64\n\t\twant string\n\t}{\n\t\t{1023, \"1023 bytes\"},\n\t\t{1024, \"1.00KB (1024 bytes)\"},\n\t\t{1024*1024 - 100, \"1023.90KB (1048476 bytes)\"},\n\t\t{1024 * 1024, \"1.00MB (1048576 bytes)\"},\n\t\t{1024 * 1025, \"1.00MB (1049600 bytes)\"},\n\t\t{1024 * 1024 * 1024, \"1.00GB (1073741824 bytes)\"},\n\t\t{1024*1024*1024 + 430*1024*1024, \"1.42GB (1524629504 bytes)\"},\n\t\t{1024 * 1024 * 1024 * 1024 * 1024, \"1.00PB (1125899906842624 bytes)\"},\n\t\t{1024 * 1024 * 1024 * 1024 * 1024 * 1024, \"1024.00PB (1152921504606846976 bytes)\"},\n\t}\n\tfor _, tt := range tests {\n\t\tresult := formatBytes(tt.val)\n\t\tif result != tt.want {\n\t\t\tt.Errorf(\"formatBytes(%v) = %q; want %q\", tt.val, result, tt.want)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "agent/sockopt_reuseport.go",
    "content": "// Copyright 2020 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build !js && !plan9 && !solaris && !windows\n// +build !js,!plan9,!solaris,!windows\n\npackage agent\n\nimport (\n\t\"syscall\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\n// setReuseAddrAndPortSockopts sets the SO_REUSEADDR and SO_REUSEPORT socket\n// options on c's underlying socket in order to increase the chance to re-bind()\n// to the same address and port upon agent restart.\nfunc setReuseAddrAndPortSockopts(network, address string, c syscall.RawConn) error {\n\tvar soerr error\n\tif err := c.Control(func(su uintptr) {\n\t\tsock := int(su)\n\t\t// Allow reuse of recently-used addresses. This socket option is\n\t\t// set by default on listeners in Go's net package, see\n\t\t// net.setDefaultSockopts.\n\t\tsoerr = unix.SetsockoptInt(sock, unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)\n\t\tif soerr != nil {\n\t\t\treturn\n\t\t}\n\t\t// Allow reuse of recently-used ports. This gives the agent a\n\t\t// better chance to re-bind upon restarts.\n\t\tsoerr = unix.SetsockoptInt(sock, unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)\n\t}); err != nil {\n\t\treturn err\n\t}\n\treturn soerr\n}\n"
  },
  {
    "path": "agent/sockopt_unsupported.go",
    "content": "// Copyright 2020 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build (js && wasm) || plan9 || solaris || windows\n// +build js,wasm plan9 solaris windows\n\npackage agent\n\nimport \"syscall\"\n\nfunc setReuseAddrAndPortSockopts(network, address string, c syscall.RawConn) error {\n\treturn nil\n}\n"
  },
  {
    "path": "cmd.go",
    "content": "// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage main\n"
  },
  {
    "path": "examples/hello/main.go",
    "content": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage main\n\nimport (\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/google/gops/agent\"\n)\n\nfunc main() {\n\tif err := agent.Listen(agent.Options{\n\t\tShutdownCleanup: true, // automatically closes on os.Interrupt\n\t}); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\ttime.Sleep(time.Hour)\n}\n"
  },
  {
    "path": "go.mod",
    "content": "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\tgithub.com/xlab/treeprint v1.2.0\n\tgolang.org/x/sys v0.30.0\n\trsc.io/goversion v1.2.0\n)\n\nrequire (\n\tgithub.com/go-ole/go-ole v1.2.6 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect\n\tgithub.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect\n\tgithub.com/shoenig/go-m1cpu v0.1.6 // indirect\n\tgithub.com/spf13/pflag v1.0.6 // indirect\n\tgithub.com/tklauser/go-sysconf v0.3.12 // indirect\n\tgithub.com/tklauser/numcpus v0.6.1 // indirect\n\tgithub.com/yusufpapurcu/wmi v1.2.4 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=\ngithub.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=\ngithub.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=\ngithub.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=\ngithub.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=\ngithub.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=\ngithub.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=\ngithub.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=\ngithub.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=\ngithub.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=\ngithub.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=\ngithub.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=\ngithub.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=\ngithub.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=\ngithub.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=\ngithub.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=\ngithub.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=\ngithub.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=\ngithub.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=\ngithub.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=\ngithub.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=\ngolang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=\ngolang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nrsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w=\nrsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=\n"
  },
  {
    "path": "goprocess/goprocess.go",
    "content": "// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// Package goprocess reports the Go processes running on a host.\npackage goprocess\n\nimport (\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/google/gops/internal\"\n\t\"github.com/shirou/gopsutil/v3/process\"\n)\n\n// P represents a Go process.\ntype P struct {\n\tPID          int\n\tPPID         int\n\tExec         string\n\tPath         string\n\tBuildVersion string\n\tAgent        bool\n}\n\n// FindAll returns all the Go processes currently running on this host.\nfunc FindAll() []P {\n\tconst concurrencyLimit = 10 // max number of concurrent workers\n\tpss, err := process.Processes()\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn findAll(pss, isGo, concurrencyLimit)\n}\n\n// Allows to inject isGo for testing.\ntype isGoFunc func(*process.Process) (path, version string, agent, ok bool, err error)\n\nfunc findAll(pss []*process.Process, isGo isGoFunc, concurrencyLimit int) []P {\n\tinput := make(chan *process.Process, len(pss))\n\toutput := make(chan P, len(pss))\n\n\tfor _, ps := range pss {\n\t\tinput <- ps\n\t}\n\tclose(input)\n\n\tvar wg sync.WaitGroup\n\twg.Add(concurrencyLimit) // used to wait for workers to be finished\n\n\t// Run concurrencyLimit of workers until there\n\t// is no more processes to be checked in the input channel.\n\tfor i := 0; i < concurrencyLimit; i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\tfor pr := range input {\n\t\t\t\tpath, version, agent, ok, err := isGo(pr)\n\t\t\t\tif err != nil {\n\t\t\t\t\t// TODO(jbd): Return a list of errors.\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif !ok {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tppid, err := pr.Ppid()\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tname, err := pr.Name()\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\toutput <- P{\n\t\t\t\t\tPID:          int(pr.Pid),\n\t\t\t\t\tPPID:         int(ppid),\n\t\t\t\t\tExec:         name,\n\t\t\t\t\tPath:         path,\n\t\t\t\t\tBuildVersion: version,\n\t\t\t\t\tAgent:        agent,\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()     // wait until all workers are finished\n\tclose(output) // no more results to be waited for\n\n\tvar results []P\n\tfor p := range output {\n\t\tresults = append(results, p)\n\t}\n\treturn results\n}\n\n// Find finds info about the process identified with the given PID.\nfunc Find(pid int) (P, bool, error) {\n\tpr, err := process.NewProcess(int32(pid))\n\tif err != nil {\n\t\treturn P{}, false, err\n\t}\n\tpath, version, agent, ok, err := isGo(pr)\n\tif !ok || err != nil {\n\t\treturn P{}, false, nil\n\t}\n\tppid, err := pr.Ppid()\n\tif err != nil {\n\t\treturn P{}, false, err\n\t}\n\tname, err := pr.Name()\n\tif err != nil {\n\t\treturn P{}, false, err\n\t}\n\treturn P{\n\t\tPID:          int(pr.Pid),\n\t\tPPID:         int(ppid),\n\t\tExec:         name,\n\t\tPath:         path,\n\t\tBuildVersion: version,\n\t\tAgent:        agent,\n\t}, true, nil\n}\n\n// isGo looks up the runtime.buildVersion symbol\n// in the process' binary and determines if the process\n// if a Go process or not. If the process is a Go process,\n// it reports PID, binary name and full path of the binary.\nfunc isGo(pr *process.Process) (path, version string, agent, ok bool, err error) {\n\tif pr.Pid == 0 {\n\t\t// ignore system process\n\t\treturn\n\t}\n\tpath, err = pr.Exe()\n\tif err != nil {\n\t\treturn\n\t}\n\tversion, err = goVersion(path)\n\tif err != nil {\n\t\treturn\n\t}\n\tok = true\n\tpidfile, err := internal.PIDFile(int(pr.Pid))\n\tif err == nil {\n\t\t_, err := os.Stat(pidfile)\n\t\tagent = err == nil\n\t}\n\treturn path, version, agent, ok, nil\n}\n"
  },
  {
    "path": "goprocess/goprocess_1.18.go",
    "content": "// Copyright 2022 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build go1.18\n// +build go1.18\n\npackage goprocess\n\nimport \"debug/buildinfo\"\n\nfunc goVersion(path string) (string, error) {\n\tinfo, err := buildinfo.ReadFile(path)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn info.GoVersion, nil\n}\n"
  },
  {
    "path": "goprocess/goprocess_lt1.18.go",
    "content": "// Copyright 2022 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build !go1.18\n// +build !go1.18\n\npackage goprocess\n\nimport goversion \"rsc.io/goversion/version\"\n\nfunc goVersion(path string) (string, error) {\n\tversionInfo, err := goversion.ReadExe(path)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn versionInfo.Release, nil\n}\n"
  },
  {
    "path": "goprocess/goprocess_test.go",
    "content": "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/shirou/gopsutil/v3/process\"\n)\n\nfunc BenchmarkFindAll(b *testing.B) {\n\tfor ii := 0; ii < b.N; ii++ {\n\t\t_ = FindAll()\n\t}\n}\n\n// TestFindAll tests findAll implementation function.\nfunc TestFindAll(t *testing.T) {\n\ttestProcess, err := process.NewProcess(int32(os.Getpid()))\n\tif err != nil {\n\t\tt.Errorf(\"failed to get current process: %v\", err)\n\t}\n\ttestPpid, _ := testProcess.Ppid()\n\ttestExec, _ := testProcess.Name()\n\twantProcess := P{PID: int(testProcess.Pid), PPID: int(testPpid), Exec: testExec}\n\n\tfor _, tc := range []struct {\n\t\tname             string\n\t\tconcurrencyLimit int\n\t\tinput            []*process.Process\n\t\tgoPIDs           []int\n\t\twant             []P\n\t\tmock             bool\n\t}{{\n\t\tname:             \"no processes\",\n\t\tconcurrencyLimit: 10,\n\t\tinput:            nil,\n\t\twant:             nil,\n\t}, {\n\t\tname:             \"non-Go process\",\n\t\tconcurrencyLimit: 10,\n\t\tinput:            []*process.Process{testProcess},\n\t\twant:             nil,\n\t}, {\n\t\tname:             \"Go process\",\n\t\tconcurrencyLimit: 10,\n\t\tinput:            []*process.Process{testProcess},\n\t\tgoPIDs:           []int{int(testProcess.Pid)},\n\t\twant:             []P{wantProcess},\n\t}, {\n\t\tname:             \"filters Go processes\",\n\t\tconcurrencyLimit: 10,\n\t\tinput:            fakeProcessesWithPIDs(1, 2, 3, 4, 5, 6, 7),\n\t\tgoPIDs:           []int{1, 3, 5, 7},\n\t\twant:             []P{{PID: 1}, {PID: 3}, {PID: 5}, {PID: 7}},\n\t\tmock:             true,\n\t}, {\n\t\tname:             \"Go processes above max concurrency (issue #123)\",\n\t\tconcurrencyLimit: 2,\n\t\tinput:            fakeProcessesWithPIDs(1, 2, 3, 4, 5, 6, 7),\n\t\tgoPIDs:           []int{1, 3, 5, 7},\n\t\twant:             []P{{PID: 1}, {PID: 3}, {PID: 5}, {PID: 7}},\n\t\tmock:             true,\n\t}} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif tc.mock {\n\t\t\t\tif runtime.GOOS != \"linux\" {\n\t\t\t\t\tt.Skip()\n\t\t\t\t}\n\t\t\t\ttempDir, err := os.MkdirTemp(\"\", \"\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"failed to create temp dir: %v\", err)\n\t\t\t\t}\n\t\t\t\tdefer os.RemoveAll(tempDir)\n\t\t\t\tfor _, p := range tc.input {\n\t\t\t\t\tos.Mkdir(filepath.Join(tempDir, strconv.Itoa(int(p.Pid))), 0o755)\n\t\t\t\t\tos.WriteFile(filepath.Join(tempDir, strconv.Itoa(int(p.Pid)), \"stat\"), []byte(\n\t\t\t\t\t\t`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`,\n\t\t\t\t\t), 0o644)\n\t\t\t\t\tos.WriteFile(filepath.Join(tempDir, strconv.Itoa(int(p.Pid)), \"status\"), []byte(\n\t\t\t\t\t\t`Name:\nUmask:  0022\nState:  R (running)\nTgid:   1440366\nNgid:   0\nPid:    1440366\nPPid:   0\n`,\n\t\t\t\t\t), 0o644)\n\t\t\t\t}\n\t\t\t\tos.Setenv(\"HOST_PROC\", tempDir)\n\t\t\t}\n\t\t\tactual := findAll(tc.input, fakeIsGo(tc.goPIDs), tc.concurrencyLimit)\n\t\t\tsort.Slice(actual, func(i, j int) bool { return actual[i].PID < actual[j].PID })\n\t\t\tif !reflect.DeepEqual(actual, tc.want) {\n\t\t\t\tt.Errorf(\"findAll(concurrency=%v)\\ngot  %v\\nwant %v\",\n\t\t\t\t\ttc.concurrencyLimit, actual, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc fakeIsGo(goPIDs []int) isGoFunc {\n\treturn func(pr *process.Process) (path, version string, agent, ok bool, err error) {\n\t\tfor _, p := range goPIDs {\n\t\t\tif p == int(pr.Pid) {\n\t\t\t\tok = true\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n}\n\nfunc fakeProcessesWithPIDs(pids ...int) []*process.Process {\n\tp := make([]*process.Process, 0, len(pids))\n\tfor _, pid := range pids {\n\t\tp = append(p, &process.Process{Pid: int32(pid)})\n\t}\n\treturn p\n}\n"
  },
  {
    "path": "internal/cmd/process.go",
    "content": "// Copyright 2022 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage cmd\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/shirou/gopsutil/v3/process\"\n\t\"github.com/spf13/cobra\"\n)\n\n// ProcessCommand displays information about a Go process.\nfunc ProcessCommand() *cobra.Command {\n\treturn &cobra.Command{\n\t\tUse:     \"process <pid> [period]\",\n\t\tAliases: []string{\"pid\", \"proc\"},\n\t\tShort:   \"Prints information about a Go process.\",\n\t\tArgs:    cobra.MinimumNArgs(1),\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn ProcessInfo(args)\n\t\t},\n\t}\n}\n\n// ProcessInfo takes arguments starting with pid|:addr and grabs all kinds of\n// useful Go process information.\nfunc ProcessInfo(args []string) error {\n\tpid, err := strconv.Atoi(args[0])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error parsing the first argument: %w\", err)\n\t}\n\n\tvar period time.Duration\n\tif len(args) >= 2 {\n\t\tperiod, err = time.ParseDuration(args[1])\n\t\tif err != nil {\n\t\t\tsecs, err := strconv.Atoi(args[1])\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error parsing the second argument: %w\", err)\n\t\t\t}\n\t\t\tperiod = time.Duration(secs) * time.Second\n\t\t}\n\t}\n\n\tprocessInfo(pid, period)\n\treturn nil\n}\n\nfunc processInfo(pid int, period time.Duration) {\n\tif period < 0 {\n\t\tlog.Fatalf(\"Cannot determine CPU usage for negative duration %v\", period)\n\t}\n\tp, err := process.NewProcess(int32(pid))\n\tif err != nil {\n\t\tlog.Fatalf(\"Cannot read process info: %v\", err)\n\t}\n\tif v, err := p.Parent(); err == nil {\n\t\tfmt.Printf(\"parent PID:\\t%v\\n\", v.Pid)\n\t}\n\tif v, err := p.NumThreads(); err == nil {\n\t\tfmt.Printf(\"threads:\\t%v\\n\", v)\n\t}\n\tif v, err := p.MemoryPercent(); err == nil {\n\t\tfmt.Printf(\"memory usage:\\t%.3f%%\\n\", v)\n\t}\n\tif v, err := p.CPUPercent(); err == nil {\n\t\tfmt.Printf(\"cpu usage:\\t%.3f%%\\n\", v)\n\t}\n\tif period > 0 {\n\t\tif v, err := cpuPercentWithinTime(p, period); err == nil {\n\t\t\tfmt.Printf(\"cpu usage (%v):\\t%.3f%%\\n\", period, v)\n\t\t}\n\t}\n\tif v, err := p.Username(); err == nil {\n\t\tfmt.Printf(\"username:\\t%v\\n\", v)\n\t}\n\tif v, err := p.Cmdline(); err == nil {\n\t\tfmt.Printf(\"cmd+args:\\t%v\\n\", v)\n\t}\n\tif v, err := elapsedTime(p); err == nil {\n\t\tfmt.Printf(\"elapsed time:\\t%v\\n\", v)\n\t}\n\tif v, err := p.Connections(); err == nil {\n\t\tif len(v) > 0 {\n\t\t\tfor _, conn := range v {\n\t\t\t\tfmt.Printf(\"local/remote:\\t%v:%v <-> %v:%v (%v)\\n\",\n\t\t\t\t\tconn.Laddr.IP, conn.Laddr.Port, conn.Raddr.IP, conn.Raddr.Port, conn.Status)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// cpuPercentWithinTime return how many percent of the CPU time this process uses within given time duration\nfunc cpuPercentWithinTime(p *process.Process, t time.Duration) (float64, error) {\n\tcput, err := p.Times()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\ttime.Sleep(t)\n\tcput2, err := p.Times()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn 100 * (cput2.Total() - cput.Total()) / t.Seconds(), nil\n}\n\n// elapsedTime shows the elapsed time of the process indicating how long the\n// process has been running for.\nfunc elapsedTime(p *process.Process) (string, error) {\n\tcrtTime, err := p.CreateTime()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tetime := time.Since(time.Unix(crtTime/1000, 0))\n\treturn fmtEtimeDuration(etime), nil\n}\n\n// fmtEtimeDuration formats etime's duration based on ps' format:\n// [[DD-]hh:]mm:ss\n// format specification: http://linuxcommand.org/lc3_man_pages/ps1.html\nfunc fmtEtimeDuration(d time.Duration) string {\n\tdays := d / (24 * time.Hour)\n\thours := d % (24 * time.Hour)\n\tminutes := hours % time.Hour\n\tseconds := math.Mod(minutes.Seconds(), 60)\n\tvar b strings.Builder\n\tif days > 0 {\n\t\tfmt.Fprintf(&b, \"%02d-\", days)\n\t}\n\tif days > 0 || hours/time.Hour > 0 {\n\t\tfmt.Fprintf(&b, \"%02d:\", hours/time.Hour)\n\t}\n\tfmt.Fprintf(&b, \"%02d:\", minutes/time.Minute)\n\tfmt.Fprintf(&b, \"%02.0f\", seconds)\n\treturn b.String()\n}\n"
  },
  {
    "path": "internal/cmd/process_test.go",
    "content": "// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage cmd\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc Test_fmtEtimeDuration(t *testing.T) {\n\ttests := []struct {\n\t\td    time.Duration\n\t\twant string\n\t}{\n\t\t{\n\t\t\twant: \"00:00\",\n\t\t},\n\n\t\t{\n\t\t\td:    2*time.Minute + 5*time.Second + 400*time.Millisecond,\n\t\t\twant: \"02:05\",\n\t\t},\n\t\t{\n\t\t\td:    1*time.Second + 500*time.Millisecond,\n\t\t\twant: \"00:02\",\n\t\t},\n\t\t{\n\t\t\td:    2*time.Hour + 42*time.Minute + 12*time.Second,\n\t\t\twant: \"02:42:12\",\n\t\t},\n\t\t{\n\t\t\td:    24 * time.Hour,\n\t\t\twant: \"01-00:00:00\",\n\t\t},\n\t\t{\n\t\t\td:    24*time.Hour + 59*time.Minute + 59*time.Second,\n\t\t\twant: \"01-00:59:59\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.d.String(), func(t *testing.T) {\n\t\t\tif got := fmtEtimeDuration(tt.d); got != tt.want {\n\t\t\t\tt.Errorf(\"fmtEtimeDuration() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/cmd/root.go",
    "content": "// Copyright 2022 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage cmd\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/google/gops/goprocess\"\n\t\"github.com/spf13/cobra\"\n)\n\n// NewRoot command.\nfunc NewRoot() *cobra.Command {\n\treturn &cobra.Command{\n\t\tUse:   \"gops\",\n\t\tShort: \"gops is a tool to list and diagnose Go processes.\",\n\t\tExample: `  gops <cmd> <pid|addr> ...\n  gops <pid> # displays process info\n  gops help  # displays this help message`,\n\t\t// TODO(jbd): add link that explains the use of agent.\n\t\tRun: func(cmd *cobra.Command, args []string) {\n\t\t\tprocesses()\n\t\t},\n\t}\n}\n\nvar develRe = regexp.MustCompile(`devel\\s+\\+\\w+`)\n\nfunc processes() {\n\tps := goprocess.FindAll()\n\n\tvar maxPID, maxPPID, maxExec, maxVersion int\n\tfor i, p := range ps {\n\t\tps[i].BuildVersion = shortenVersion(p.BuildVersion)\n\t\tmaxPID = max(maxPID, len(strconv.Itoa(p.PID)))\n\t\tmaxPPID = max(maxPPID, len(strconv.Itoa(p.PPID)))\n\t\tmaxExec = max(maxExec, len(p.Exec))\n\t\tmaxVersion = max(maxVersion, len(ps[i].BuildVersion))\n\n\t}\n\n\tfor _, p := range ps {\n\t\tbuf := bytes.NewBuffer(nil)\n\t\tpid := strconv.Itoa(p.PID)\n\t\tfmt.Fprint(buf, pad(pid, maxPID))\n\t\tfmt.Fprint(buf, \" \")\n\t\tppid := strconv.Itoa(p.PPID)\n\t\tfmt.Fprint(buf, pad(ppid, maxPPID))\n\t\tfmt.Fprint(buf, \" \")\n\t\tfmt.Fprint(buf, pad(p.Exec, maxExec))\n\t\tif p.Agent {\n\t\t\tfmt.Fprint(buf, \"*\")\n\t\t} else {\n\t\t\tfmt.Fprint(buf, \" \")\n\t\t}\n\t\tfmt.Fprint(buf, \" \")\n\t\tfmt.Fprint(buf, pad(p.BuildVersion, maxVersion))\n\t\tfmt.Fprint(buf, \" \")\n\t\tfmt.Fprint(buf, p.Path)\n\t\tfmt.Fprintln(buf)\n\t\tbuf.WriteTo(os.Stdout)\n\t}\n}\n\nfunc shortenVersion(v string) string {\n\tif !strings.HasPrefix(v, \"devel\") {\n\t\treturn v\n\t}\n\tresults := develRe.FindAllString(v, 1)\n\tif len(results) == 0 {\n\t\treturn v\n\t}\n\treturn results[0]\n}\n\nfunc pad(s string, total int) string {\n\tif len(s) >= total {\n\t\treturn s\n\t}\n\treturn s + strings.Repeat(\" \", total-len(s))\n}\n\nfunc max(i, j int) int {\n\tif i > j {\n\t\treturn i\n\t}\n\treturn j\n}\n"
  },
  {
    "path": "internal/cmd/root_test.go",
    "content": "// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage cmd\n\nimport \"testing\"\n\nfunc Test_shortenVersion(t *testing.T) {\n\ttests := []struct {\n\t\tversion string\n\t\twant    string\n\t}{\n\t\t{\n\t\t\tversion: \"go1.8.1.typealias\",\n\t\t\twant:    \"go1.8.1.typealias\",\n\t\t},\n\t\t{\n\t\t\tversion: \"go1.9\",\n\t\t\twant:    \"go1.9\",\n\t\t},\n\t\t{\n\t\t\tversion: \"go1.9rc\",\n\t\t\twant:    \"go1.9rc\",\n\t\t},\n\t\t{\n\t\t\tversion: \"devel +990dac2723 Fri Jun 30 18:24:58 2017 +0000\",\n\t\t\twant:    \"devel +990dac2723\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.version, func(t *testing.T) {\n\t\t\tif got := shortenVersion(tt.version); got != tt.want {\n\t\t\t\tt.Errorf(\"shortenVersion() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/cmd/shared.go",
    "content": "// Copyright 2022 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage cmd\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/google/gops/internal\"\n\t\"github.com/google/gops/signal\"\n\t\"github.com/spf13/cobra\"\n)\n\n// AgentCommands is a bridge between the legacy multiplexing to commands, and\n// full migration to Cobra for each command.\n//\n// The code is already nicely structured with one function per command so it\n// seemed cleaner to combine them all together here and \"generate\" cobra\n// commands as just thin wrappers, rather through individual constructors.\nfunc AgentCommands() []*cobra.Command {\n\tvar res []*cobra.Command\n\n\tvar cmds = []legacyCommand{\n\t\t{\n\t\t\tname:  \"stack\",\n\t\t\tshort: \"Prints the stack trace.\",\n\t\t\tfn:    stackTrace,\n\t\t},\n\t\t{\n\t\t\tname:  \"gc\",\n\t\t\tshort: \"Runs the garbage collector and blocks until successful.\",\n\t\t\tfn:    gc,\n\t\t},\n\t\t{\n\t\t\tname:  \"setgc\",\n\t\t\tshort: \"Sets the garbage collection target percentage. To completely stop GC, set to 'off'\",\n\t\t\tfn:    setGC,\n\t\t},\n\t\t{\n\t\t\tname:  \"memstats\",\n\t\t\tshort: \"Prints the allocation and garbage collection stats.\",\n\t\t\tfn:    memStats,\n\t\t},\n\t\t{\n\t\t\tname:  \"stats\",\n\t\t\tshort: \"Prints runtime stats.\",\n\t\t\tfn:    stats,\n\t\t},\n\t\t{\n\t\t\tname:  \"trace\",\n\t\t\tshort: \"Runs the runtime tracer for 5 secs and launches \\\"go tool trace\\\".\",\n\t\t\tfn:    trace,\n\t\t},\n\t\t{\n\t\t\tname:  \"pprof-heap\",\n\t\t\tshort: \"Reads the heap profile and launches \\\"go tool pprof\\\".\",\n\t\t\tfn:    pprofHeap,\n\t\t},\n\t\t{\n\t\t\tname:  \"pprof-cpu\",\n\t\t\tshort: \"Reads the CPU profile and launches \\\"go tool pprof\\\".\",\n\t\t\tfn:    pprofCPU,\n\t\t},\n\t\t{\n\t\t\tname:  \"version\",\n\t\t\tshort: \"Prints the Go version used to build the program.\",\n\t\t\tfn:    version,\n\t\t},\n\t}\n\n\tfor _, c := range cmds {\n\t\tc := c\n\t\tres = append(res, &cobra.Command{\n\t\t\tUse:   fmt.Sprintf(\"%s <pid|addr>\", c.name),\n\t\t\tShort: c.short,\n\n\t\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\t\tif len(args) < 1 {\n\t\t\t\t\treturn fmt.Errorf(\"missing PID or address\")\n\t\t\t\t}\n\n\t\t\t\taddr, err := targetToAddr(args[0])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\n\t\t\t\t\t\t\"couldn't resolve addr or pid %v to TCPAddress: %v\\n\", args[0], err,\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tvar params []string\n\t\t\t\tif len(args) > 1 {\n\t\t\t\t\tparams = append(params, args[1:]...)\n\t\t\t\t}\n\n\t\t\t\tif err := c.fn(*addr, params); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\treturn nil\n\t\t\t},\n\n\t\t\t// errors get double printed otherwise\n\t\t\tSilenceUsage:  true,\n\t\t\tSilenceErrors: true,\n\t\t})\n\t}\n\n\treturn res\n}\n\ntype legacyCommand struct {\n\tname  string\n\tshort string\n\tfn    func(addr net.TCPAddr, params []string) error\n}\n\nfunc setGC(addr net.TCPAddr, params []string) error {\n\tif len(params) != 1 {\n\t\treturn errors.New(\"missing gc percentage\")\n\t}\n\tvar (\n\t\tperc int64\n\t\terr  error\n\t)\n\tif strings.ToLower(params[0]) == \"off\" {\n\t\tperc = -1\n\t} else {\n\t\tperc, err = strconv.ParseInt(params[0], 10, strconv.IntSize)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tbuf := make([]byte, binary.MaxVarintLen64)\n\tbinary.PutVarint(buf, perc)\n\treturn cmdWithPrint(addr, signal.SetGCPercent, buf...)\n}\n\nfunc stackTrace(addr net.TCPAddr, _ []string) error {\n\treturn cmdWithPrint(addr, signal.StackTrace)\n}\n\nfunc gc(addr net.TCPAddr, _ []string) error {\n\t_, err := cmd(addr, signal.GC)\n\treturn err\n}\n\nfunc memStats(addr net.TCPAddr, _ []string) error {\n\treturn cmdWithPrint(addr, signal.MemStats)\n}\n\nfunc version(addr net.TCPAddr, _ []string) error {\n\treturn cmdWithPrint(addr, signal.Version)\n}\n\nfunc pprofHeap(addr net.TCPAddr, _ []string) error {\n\treturn pprof(addr, signal.HeapProfile, \"heap\")\n}\n\nfunc pprofCPU(addr net.TCPAddr, _ []string) error {\n\tfmt.Println(\"Profiling CPU now, will take 30 secs...\")\n\treturn pprof(addr, signal.CPUProfile, \"cpu\")\n}\n\nfunc trace(addr net.TCPAddr, _ []string) error {\n\tfmt.Println(\"Tracing now, will take 5 secs...\")\n\tout, err := cmd(addr, signal.Trace)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(out) == 0 {\n\t\treturn errors.New(\"nothing has traced\")\n\t}\n\ttmpfile, err := os.CreateTemp(\"\", \"trace\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := os.WriteFile(tmpfile.Name(), out, 0); err != nil {\n\t\treturn err\n\t}\n\tfmt.Printf(\"Trace dump saved to: %s\\n\", tmpfile.Name())\n\t// If go tool chain not found, stopping here and keep trace file.\n\tif _, err := exec.LookPath(\"go\"); err != nil {\n\t\treturn nil\n\t}\n\tdefer os.Remove(tmpfile.Name())\n\tcmd := exec.Command(\"go\", \"tool\", \"trace\", tmpfile.Name())\n\tcmd.Env = os.Environ()\n\tcmd.Stdin = os.Stdin\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\treturn cmd.Run()\n}\n\nfunc pprof(addr net.TCPAddr, p byte, prefix string) error {\n\ttmpDumpFile, err := os.CreateTemp(\"\", prefix+\"_profile\")\n\tif err != nil {\n\t\treturn err\n\t}\n\t{\n\t\tout, err := cmd(addr, p)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(out) == 0 {\n\t\t\treturn errors.New(\"failed to read the profile\")\n\t\t}\n\t\tif err := os.WriteFile(tmpDumpFile.Name(), out, 0); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfmt.Printf(\"Profile dump saved to: %s\\n\", tmpDumpFile.Name())\n\t\t// If go tool chain not found, stopping here and keep dump file.\n\t\tif _, err := exec.LookPath(\"go\"); err != nil {\n\t\t\treturn nil\n\t\t}\n\t\tdefer os.Remove(tmpDumpFile.Name())\n\t}\n\t// Download running binary\n\ttmpBinFile, err := os.CreateTemp(\"\", \"binary\")\n\tif err != nil {\n\t\treturn err\n\t}\n\t{\n\t\tout, err := cmd(addr, signal.BinaryDump)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to read the binary: %v\", err)\n\t\t}\n\t\tif len(out) == 0 {\n\t\t\treturn errors.New(\"failed to read the binary\")\n\t\t}\n\t\tdefer os.Remove(tmpBinFile.Name())\n\t\tif err := os.WriteFile(tmpBinFile.Name(), out, 0); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tfmt.Printf(\"Binary file saved to: %s\\n\", tmpBinFile.Name())\n\tcmd := exec.Command(\"go\", \"tool\", \"pprof\", tmpBinFile.Name(), tmpDumpFile.Name())\n\tcmd.Env = os.Environ()\n\tcmd.Stdin = os.Stdin\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\treturn cmd.Run()\n}\n\nfunc stats(addr net.TCPAddr, _ []string) error {\n\treturn cmdWithPrint(addr, signal.Stats)\n}\n\nfunc cmdWithPrint(addr net.TCPAddr, c byte, params ...byte) error {\n\tout, err := cmd(addr, c, params...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Printf(\"%s\", out)\n\treturn nil\n}\n\n// targetToAddr tries to parse the target string, be it remote host:port\n// or local process's PID.\nfunc targetToAddr(target string) (*net.TCPAddr, error) {\n\tif strings.Contains(target, \":\") {\n\t\t// addr host:port passed\n\t\tvar err error\n\t\taddr, err := net.ResolveTCPAddr(\"tcp\", target)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"couldn't parse dst address: %v\", err)\n\t\t}\n\t\treturn addr, nil\n\t}\n\t// try to find port by pid then, connect to local\n\tpid, err := strconv.Atoi(target)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"couldn't parse PID: %v\", err)\n\t}\n\tport, err := internal.GetPort(pid)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"couldn't get port for PID %v: %v\", pid, err)\n\t}\n\taddr, _ := net.ResolveTCPAddr(\"tcp\", \"127.0.0.1:\"+port)\n\treturn addr, nil\n}\n\nfunc cmd(addr net.TCPAddr, c byte, params ...byte) ([]byte, error) {\n\tconn, err := cmdLazy(addr, c, params...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"couldn't get port by PID: %v\", err)\n\t}\n\n\tall, err := io.ReadAll(conn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn all, nil\n}\n\nfunc cmdLazy(addr net.TCPAddr, c byte, params ...byte) (io.Reader, error) {\n\tconn, err := net.DialTCP(\"tcp\", nil, &addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbuf := []byte{c}\n\tbuf = append(buf, params...)\n\tif _, err := conn.Write(buf); err != nil {\n\t\treturn nil, err\n\t}\n\treturn conn, nil\n}\n"
  },
  {
    "path": "internal/cmd/shared_test.go",
    "content": "// Copyright 2022 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage cmd\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nfunc TestCommandPresence(t *testing.T) {\n\tcmd := &cobra.Command{Use: \"gops\"}\n\tcmd.AddCommand(AgentCommands()...)\n\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"--help\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\t// basic check to make sure all the legacy commands have been ported over\n\t// it doesn't test they are correctly _implemented_, just that they are not\n\t// missing.\n\twants := []string{\n\t\t\"completion\", \"gc\", \"memstats\", \"pprof-cpu\", \"pprof-heap\", \"setgc\",\n\t\t\"stack\", \"stats\", \"trace\", \"version\",\n\t}\n\touts := out.String()\n\tfor _, want := range wants {\n\t\tif !strings.Contains(outs, want) {\n\t\t\tt.Errorf(\"%q command not found in help\", want)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/cmd/tree.go",
    "content": "// Copyright 2022 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage cmd\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strconv\"\n\n\t\"github.com/google/gops/goprocess\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/xlab/treeprint\"\n)\n\n// TreeCommand displays a process tree.\nfunc TreeCommand() *cobra.Command {\n\treturn &cobra.Command{\n\t\tUse:   \"tree\",\n\t\tShort: \"Display parent-child tree for Go processes.\",\n\t\tRun: func(cmd *cobra.Command, args []string) {\n\t\t\tdisplayProcessTree()\n\t\t},\n\t}\n}\n\n// displayProcessTree displays a tree of all the running Go processes.\nfunc displayProcessTree() {\n\tps := goprocess.FindAll()\n\tsort.Slice(ps, func(i, j int) bool {\n\t\treturn ps[i].PPID < ps[j].PPID\n\t})\n\tpstree := make(map[int][]goprocess.P, len(ps))\n\tfor _, p := range ps {\n\t\tpstree[p.PPID] = append(pstree[p.PPID], p)\n\t}\n\ttree := treeprint.New()\n\ttree.SetValue(\"...\")\n\tseen := map[int]bool{}\n\tfor _, p := range ps {\n\t\tconstructProcessTree(p.PPID, p, pstree, seen, tree)\n\t}\n\tfmt.Println(tree.String())\n}\n\n// constructProcessTree constructs the process tree in a depth-first fashion.\nfunc constructProcessTree(ppid int, process goprocess.P, pstree map[int][]goprocess.P, seen map[int]bool, tree treeprint.Tree) {\n\n\tif seen[ppid] {\n\t\treturn\n\t}\n\tseen[ppid] = true\n\tif ppid != process.PPID {\n\t\toutput := strconv.Itoa(ppid) + \" (\" + process.Exec + \")\" + \" {\" + process.BuildVersion + \"}\"\n\t\tif process.Agent {\n\t\t\ttree = tree.AddMetaBranch(\"*\", output)\n\t\t} else {\n\t\t\ttree = tree.AddBranch(output)\n\t\t}\n\t} else {\n\t\ttree = tree.AddBranch(ppid)\n\t}\n\tfor index := range pstree[ppid] {\n\t\tprocess := pstree[ppid][index]\n\t\tconstructProcessTree(process.PID, process, pstree, seen, tree)\n\t}\n}\n"
  },
  {
    "path": "internal/internal.go",
    "content": "// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage internal\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"os/user\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nconst gopsConfigDirEnvKey = \"GOPS_CONFIG_DIR\"\n\nfunc ConfigDir() (string, error) {\n\tif configDir := os.Getenv(gopsConfigDirEnvKey); configDir != \"\" {\n\t\treturn configDir, nil\n\t}\n\n\tif userConfigDir, err := os.UserConfigDir(); err == nil {\n\t\treturn filepath.Join(userConfigDir, \"gops\"), nil\n\t}\n\n\thomeDir := guessUnixHomeDir()\n\tif homeDir == \"\" {\n\t\treturn \"\", errors.New(\"unable to get current user home directory: os/user lookup failed; $HOME is empty\")\n\t}\n\treturn filepath.Join(homeDir, \".config\", \"gops\"), nil\n}\n\nfunc guessUnixHomeDir() string {\n\tusr, err := user.Current()\n\tif err == nil {\n\t\treturn usr.HomeDir\n\t}\n\treturn os.Getenv(\"HOME\")\n}\n\nfunc PIDFile(pid int) (string, error) {\n\tgopsdir, err := ConfigDir()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn filepath.Join(gopsdir, strconv.Itoa(pid)), nil\n}\n\nfunc GetPort(pid int) (string, error) {\n\tportfile, err := PIDFile(pid)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tb, err := os.ReadFile(portfile)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tport := strings.TrimSpace(string(b))\n\treturn port, nil\n}\n"
  },
  {
    "path": "internal/internal_test.go",
    "content": "package internal\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestConfigDir(t *testing.T) {\n\tconfigDir, err := ConfigDir()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif g, w := filepath.Base(configDir), \"gops\"; g != w {\n\t\tt.Errorf(\"ConfigDir: got base directory %q, want %q\", g, w)\n\t}\n\n\tkey := gopsConfigDirEnvKey\n\toldDir := os.Getenv(key)\n\tdefer os.Setenv(key, oldDir)\n\n\tnewDir := \"foo-bar\"\n\tos.Setenv(key, newDir)\n\tconfigDir, err = ConfigDir()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif g, w := configDir, newDir; g != w {\n\t\tt.Errorf(\"ConfigDir: got=%v want=%v\", g, w)\n\t}\n}\n"
  },
  {
    "path": "main.go",
    "content": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// Program gops is a tool to list currently running Go processes.\npackage main\n\nimport (\n\t\"log\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/google/gops/internal/cmd\"\n)\n\nfunc main() {\n\tvar root = cmd.NewRoot()\n\troot.AddCommand(cmd.ProcessCommand())\n\troot.AddCommand(cmd.TreeCommand())\n\troot.AddCommand(cmd.AgentCommands()...)\n\n\t// Legacy support for `gops <pid>` command.\n\t//\n\t// When the second argument is provided as int as opposed to a sub-command\n\t// (like proc, version, etc), gops command effectively shortcuts that\n\t// to `gops process <pid>`.\n\tif len(os.Args) > 1 {\n\t\t// See second argument appears to be a pid rather than a subcommand\n\t\t_, err := strconv.Atoi(os.Args[1])\n\t\tif err == nil {\n\t\t\tcmd.ProcessInfo(os.Args[1:]) // shift off the command name\n\t\t\treturn\n\t\t}\n\t}\n\n\tif err := root.Execute(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "main_test.go",
    "content": "// Copyright 2017 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage main\n"
  },
  {
    "path": "signal/signal.go",
    "content": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// Package signal contains signals used to communicate to the gops agents.\npackage signal\n\nconst (\n\t// StackTrace represents a command to print stack trace.\n\tStackTrace = byte(0x1)\n\n\t// GC runs the garbage collector.\n\tGC = byte(0x2)\n\n\t// MemStats reports memory stats.\n\tMemStats = byte(0x3)\n\n\t// Version prints the Go version.\n\tVersion = byte(0x4)\n\n\t// HeapProfile starts `go tool pprof` with the current memory profile.\n\tHeapProfile = byte(0x5)\n\n\t// CPUProfile starts `go tool pprof` with the current CPU profile\n\tCPUProfile = byte(0x6)\n\n\t// Stats returns Go runtime statistics such as number of goroutines, GOMAXPROCS, and NumCPU.\n\tStats = byte(0x7)\n\n\t// Trace starts the Go execution tracer, waits 5 seconds and launches the trace tool.\n\tTrace = byte(0x8)\n\n\t// BinaryDump returns running binary file.\n\tBinaryDump = byte(0x9)\n\n\t// SetGCPercent sets the garbage collection target percentage.\n\tSetGCPercent = byte(0x10)\n)\n"
  }
]